From 0692cd7cdcb1fc3566b7f5a7989935a63e80c2f7 Mon Sep 17 00:00:00 2001 From: alexgobbo Date: Tue, 17 Sep 2024 18:10:45 +0200 Subject: [PATCH] --- config/plugins.properties | 1 + config/settings.properties | 2 +- config/tasks.properties | 1 + devices/cover_detection.properties | 3 +- plugins/Image_Correlator.java | 220 ++++++++++++++++++++++++++ script/devices/CoverDetection.py | 75 ++++++++- script/devices/VmbCamera.py | 17 +- script/imgproc/NewCoverCorrelation.py | 99 ++++++++++++ script/local.py | 13 +- script/test/TestCorrelation.py | 129 +++++++++++++++ script/test/TestCorrelationDevices.py | 40 +++++ 11 files changed, 575 insertions(+), 25 deletions(-) create mode 100644 plugins/Image_Correlator.java create mode 100644 script/imgproc/NewCoverCorrelation.py create mode 100644 script/test/TestCorrelation.py create mode 100644 script/test/TestCorrelationDevices.py diff --git a/config/plugins.properties b/config/plugins.properties index ea7acd2..618a8b9 100644 --- a/config/plugins.properties +++ b/config/plugins.properties @@ -1,3 +1,4 @@ +Image_Correlator.java=enabled MXSC-1.14.0.jar=disabled MXSC-1.15.0.jar=disabled Commands.java=disabled diff --git a/config/settings.properties b/config/settings.properties index d4fe438..13da492 100644 --- a/config/settings.properties +++ b/config/settings.properties @@ -1,4 +1,4 @@ -#Fri Sep 13 09:52:22 CEST 2024 +#Tue Sep 17 18:09:54 CEST 2024 barcode_reader_scan_pucks=false beamline_status_enabled=false cold_position_timeout=3600 diff --git a/config/tasks.properties b/config/tasks.properties index 7662dfd..562a82a 100644 --- a/config/tasks.properties +++ b/config/tasks.properties @@ -1,3 +1,4 @@ tasks/LedMonitoring=120.0;120.0 tasks/ColdPositionTimeout=300.0;300.0 #tasks/Reinit=30.0;30.0 +#test/TestCorrelation=1.0;1.0 diff --git a/devices/cover_detection.properties b/devices/cover_detection.properties index 5f635d6..532b194 100644 --- a/devices/cover_detection.properties +++ b/devices/cover_detection.properties @@ -1,4 +1,4 @@ -#Fri Sep 13 10:02:48 CEST 2024 +#Tue Sep 17 18:09:58 CEST 2024 center_x=820.0 center_y=570.0 continuous=false @@ -6,3 +6,4 @@ modulo=3 number_images=2 scale_x=0.3 scale_y=0.3 +threshold=0.6 diff --git a/plugins/Image_Correlator.java b/plugins/Image_Correlator.java new file mode 100644 index 0000000..27f26c6 --- /dev/null +++ b/plugins/Image_Correlator.java @@ -0,0 +1,220 @@ +import java.awt.*; +import java.io.*; +import ij.*; +import ij.gui.*; +import ij.process.*; +import ij.text.*; +import ij.plugin.PlugIn; + +//This plugin correlates two 8-bit images or stacks. The resultant correlation plot is a +//stack with the same number of slices as the stack with the fewer number. +//Many correlation plots need to be recontrasted. Pressing Apple/Shift/C will +//bring up the Brightness/Contrast menu. + +public class Image_Correlator implements PlugIn { + + private static int index1; + private static int index2; + private static boolean displayCounts, collate; + private ImageStack img1, img2; + private int smallest; + private int z1, z2, count; + + public void run(String arg) { + if (showDialog()) + correlate(img1, img2); + } + + public boolean showDialog() { + int[] wList = WindowManager.getIDList(); + if (wList==null) { + IJ.noImage(); + return false; + } + String[] titles = new String[wList.length]; + for (int i=0; i=titles.length)index1 = 0; + if (index2>=titles.length)index2 = 0; + GenericDialog gd = new GenericDialog("Image Correlator"); + gd.addChoice("Image1: ", titles, titles[index1]); + gd.addChoice("Image2: ", titles, titles[index2]); + gd.addCheckbox("Display Counts: ", displayCounts); + gd.addCheckbox("Collate Z-Data (Stacks Only):", collate); + gd.showDialog(); + if (gd.wasCanceled()) + return false; + index1 = gd.getNextChoiceIndex(); + index2 = gd.getNextChoiceIndex(); + displayCounts = gd.getNextBoolean(); + collate = gd.getNextBoolean(); + String title1 = titles[index1]; + String title2 = titles[index2]; + //Test to find out if both images are 8-bit grayscale. + ImagePlus sliceimg1 = WindowManager.getImage(wList[index1]); + ImagePlus sliceimg2 = WindowManager.getImage(wList[index2]); + if (sliceimg1.getType()!=sliceimg1.GRAY8 || sliceimg2.getType()!=sliceimg1.GRAY8) { + IJ.showMessage("Image Correlator", "Both stacks must be 8-bit grayscale."); + return false; + } + img1 = sliceimg1.getStack(); + img2 = sliceimg2.getStack(); + return true; + } + + public Object[] getCorrelation(ImagePlus im1, ImagePlus im2){ + int width = im1.getWidth(); + int height = im1.getHeight(); + float[] v1, v2; + ip1 = im1.getProcessor(); + ip2 = im2.getProcessor(); + v1 = new float[width*height]; + v2 = new float[width*height]; + ImageProcessor plot= new FloatProcessor(256, 256); + int index = 0; + for (int y=0; y1) && (i2.getSize()>1)) + return true; + else + return false; + } + + void displayCounts(ImageStack plot) { + StringBuffer sb = new StringBuffer(); + int count; + int slices = plot.getSize(); + if (slices==1) { + ImageProcessor ip = plot.getProcessor(1); + for (int x=0; x<256; x++) + for (int y=255; y>=0; y--) { + count = (int)ip.getPixelValue(x,y); + if (count>0) + sb.append(x+"\t"+(255-y)+"\t"+count+"\n"); + } + new TextWindow("Non-zero Counts" , "X\tY\tCount", sb.toString(), 300, 400); + } else { + for (int slice=1; slice<=slices; slice++) { + ImageProcessor ip = plot.getProcessor(slice); + for (int x=0; x<256; x++) + for (int y=255; y>=0; y--) { + count = (int)ip.getPixelValue(x,y); + if (count>0) + sb.append(x+"\t"+(255-y)+"\t"+(slice-1)+"\t"+count+"\n"); + } + } + new TextWindow("Non-zero Counts" , "X\tY\tZ\tCount", sb.toString(), 300, 400); + } + } +} diff --git a/script/devices/CoverDetection.py b/script/devices/CoverDetection.py index 82d029f..1a26b20 100644 --- a/script/devices/CoverDetection.py +++ b/script/devices/CoverDetection.py @@ -1,4 +1,5 @@ run("imgproc/NewCoverDetection") +run("imgproc/NewCoverCorrelation") class CoverDetectionConfig(RegisterConfig): @@ -10,7 +11,16 @@ class CoverDetectionConfig(RegisterConfig): self.center_y = 570.0 self.scale_x = 1.0 self.scale_y = 1.0 - + self.scale_y = 1.0 + self.threshold = 0.60 + +class CoverCorrelation(ReadonlyAsyncRegisterBase): + def __init__(self, name): + ReadonlyAsyncRegisterBase.__init__(self, name) + def set(self, value): + self.onReadout(value) + + class CoverDetection(ReadonlyAsyncRegisterBase, ReadonlyRegisterArray): def __init__(self, name, image): @@ -32,13 +42,21 @@ class CoverDetection(ReadonlyAsyncRegisterBase, ReadonlyRegisterArray): self.error_overlay=None self.img_counter, self.proc_counter = 0, 0 self.offset_px = self.offset_um = None + self.correlation = CoverCorrelation("cover_correlation") + self.correlation_error = False def doInitialize(self): - self.enable() - self.clear() + try: + self.enable() + self.clear() + #self.grab_ref_image() + except: + traceback.print_exc() + raise - def doClose(self): - self.disable() + def doClose(self): + self.disable() + self.correlation.close() def set_renderer(self, renderer): self.clear() @@ -60,7 +78,8 @@ class CoverDetection(ReadonlyAsyncRegisterBase, ReadonlyRegisterArray): def clear(self): self.set_marker(None) - self.set_error_overlay(None) + self.set_error_overlay(None) + self.set_correlation(None) def append(self, image): self.img_counter += 1 @@ -83,7 +102,20 @@ class CoverDetection(ReadonlyAsyncRegisterBase, ReadonlyRegisterArray): x,y = -1, -1 self.processing_time = time.time()-start self.proc_counter += 1 + + try: + corr = get_correlation(ip.getBufferedImage()) + self.correlation.set(corr) + self.set_correlation(corr) + except Exception as e: + #traceback.print_exc() + if self.error is None: + self.error = e + self.set(x, y, self.processing_time * 1000, self.error) + + #traceback.print_exc() + if not self.config.continuous: self.images=[] @@ -96,7 +128,7 @@ class CoverDetection(ReadonlyAsyncRegisterBase, ReadonlyRegisterArray): offx, offy = x-cover_detection.config.center_x, -(y-cover_detection.config.center_y) self.offset_px = opx, opy = round(offx), round(offy) self.offset_um = omx, omy = self.config.scale_x * offx * 1000.0, self.config.scale_y * offy *1000.0 - value = to_array([x, y, opx, opy, omx, omy, tm], 'i') + value = to_array([x, y, opx, opy, omx, omy, tm], 'i') self.onReadout(value) if self.renderer is not None: if error is None: @@ -122,8 +154,35 @@ class CoverDetection(ReadonlyAsyncRegisterBase, ReadonlyRegisterArray): self.error_overlay = error_overlay else: self.error_overlay = None - + + def grab_ref_image(self): + if self.config.number_images>1: + images = [] + for i in range(self.config.number_images): + ip = load_image(ImagingUtils.grayscale(self.image.output, None)) + images.append(ip) + self.image.waitNext(-1) + ip = integrate(images) + img = ip.getBufferedImage() + else: + img = self.image.output + save_ref_img(img) + + def set_correlation(self, corr): + self.corr = corr + error = corr is not None and corr < self.config.threshold + if error != self.correlation_error: + self.correlation_error = error + if error: + print ("Image anomally detected") + else: + print ("Image ok") + if error: + raise Exception ("Image anomally detected") + + add_device(CoverDetection("cover_detection", image), force = True) +add_device(cover_detection.correlation, force = True) renderer=show_panel(image) cover_detection.set_renderer(renderer) diff --git a/script/devices/VmbCamera.py b/script/devices/VmbCamera.py index 684522e..3851b91 100644 --- a/script/devices/VmbCamera.py +++ b/script/devices/VmbCamera.py @@ -55,16 +55,15 @@ class VmbCamera (CameraBase): fork(init_img) self.setState(State.Disabled) - def add_img_device(self): - def add_dev_task(): + def add_initialized_callback(self, callback): + def wait_init(): try: self.waitStateNot(State.Disabled, -1) if (self.stream.state == State.Busy) and (get_context().state.isActive()): - add_device(self.image, True) - log("Added VmbCamera image device") + callback() except Exception as e: log(str(e)) - fork(add_dev_task) + fork(wait_init) def getInfo(self): try: @@ -282,6 +281,10 @@ fork(start_camera_server) c = VmbCamera("cam", image_name="image") add_device(c, True) add_device(c.stream, True) -c.add_img_device() - +def initialized_callback(): + add_device(c.image, True) + add_device(c.image.contrast, force = True) + log("Added VmbCamera image device") + run( "devices/CoverDetection") +c.add_initialized_callback(initialized_callback) diff --git a/script/imgproc/NewCoverCorrelation.py b/script/imgproc/NewCoverCorrelation.py new file mode 100644 index 0000000..565352b --- /dev/null +++ b/script/imgproc/NewCoverCorrelation.py @@ -0,0 +1,99 @@ +import ch.psi.pshell.imaging.ImageBuffer as ImageBuffer + +offset = 0 +add_mark = False +#offset = 50 +#add_mark = True + + +Image_Correlator = get_context().getClassByName("Image_Correlator") +correlator = Image_Correlator() + +def get_min(ip1): + return ip1.getProcessor().getStatistics().min + +def mean_abs_error(ip1, ip2): + sum = 0.0 + for y in range(ip1.getHeight()): + for x in range(ip1.getWidth()): + p1 = ip1.getPixel(x, y) + p2 = ip2.getPixel(x, y) + sum += abs(p1 - p2) + return sum / (ip1.height * ip1.width ) + +def rms_error(ip1, ip2): + sum = 0.0 + for y in range(ip1.getHeight()): + for x in range(ip1.getWidth()): + p1 = ip1.getPixel(x, y) + p2 = ip2.getPixel(x, y) + sum += math.pow((p1 - p2),2) + return sum / (ip1.height * ip1.width ) + + + +def get_roi_img(img, img_size=1024, radius=512, offset_x=0, offset_y=0): + x,y,w,h = int(cover_detection.config.center_x-img_size/2 + offset_x), \ + int(cover_detection.config.center_y-img_size/2 + offset_y), \ + img_size, img_size + roi = ImagingUtils.copy(img, None, Rectangle(x,y,w,h)) + ip = load_image(roi, title="Img") + p = ip.getProcessor() + mask = p.createProcessor(roi.getWidth(), roi.getHeight()) + mask.setColor(255) + cx, cy = img_size/2 - offset_x, img_size/2 - offset_y + mask.fillOval(int(cx - radius), int(cy - radius), int(2 * radius), int(2 * radius)) # Draw the circular ROI + mask.autoThreshold() # Ensures the mask is binary (if needed) + p.copyBits(mask, 0, 0, Blitter.AND) # Apply the mask using a bitwise AND + #gaussian_blur(ip) + p.blurGaussian(1.0) + return ip.getBufferedImage() + +def get_edge_img(ib): + ed = ImagingUtils.edgeDetect(ib) + ret = load_image(ed, title="Refe") + #auto_threshold(ret, dark_background=True) + #binary_dilate(ret, iterations=1, count=1) + return ret + +def save_ref_img(img): + path = os.path.abspath(expand_path("{images}/ip_ref_fe.png")) + roi=get_roi_img(img, offset_x=0, offset_y=0) + ImageBuffer.saveImage(roi, path, "png") + + #ip_ref = load_image(roi, title="ip_ref") + #save_image(ip_ref, path ,"png") + #$ImagingUtils. + #ip_ref_e =get_edge_img(roi) + #save_image(ip_ref_e, path+"ip_ref_e.png" ,"png") + ##ip_ref_f = image_fft(ip_ref) + #ip_ref_fe = image_fft(ip_ref_e) + #save_image(ip_ref_fe, path+"ip_ref_fe.png" ,"png") + + +def put_mark(ib): + for x in range(450, 470): + for y in range(450, 470): + ib.setRGB(x,y, 0) + +def get_correlation(img): + global ip_ref_fe + if "ip_ref_fe" not in globals() or ip_ref_fe is None: + path = os.path.abspath(expand_path("{images}/ip_ref_fe.png")) + roi = ImagingUtils.newImage(path) + #ip_ref_fe=open_imagroie(path+"ip_ref_fe.png" ,"png") + #ip_ref = load_image(roi, title="ip_ref") + ip_ref_e =get_edge_img(roi) + ip_ref_fe = image_fft(ip_ref_e) + print "Opend reference image" + + roi =get_roi_img(img, offset_x=-offset, offset_y=-offset) + if add_mark: + put_mark(roi) + #ip_cur = load_image(roi, title="Ref") + ip_cur_e =get_edge_img(roi) + #ip_cur_f = image_fft(ip_cur) + ip_cur_fe = image_fft(ip_cur_e) + return correlator.getCorrelation(ip_ref_fe, ip_cur_fe)[0] + + \ No newline at end of file diff --git a/script/local.py b/script/local.py index 9ffa8d1..06c125d 100644 --- a/script/local.py +++ b/script/local.py @@ -110,9 +110,12 @@ set_setting(BEAMLINE_STATUS_ENABLED_PREFERENCE, is_beamline_status_enabled()) ################################################################################################### # Scripted devices and pseudo-devices ################################################################################################### - + + + scripted_devices = ["devices/RobotSC", "devices/Wago", "devices/BarcodeReader", "devices/LaserDistance", - "devices/LedCtrl", "devices/SmartMagnet", "devices/Gonio", "devices/VmbCamera" #"devices/HexiPosi"]" + "devices/LedCtrl", "devices/SmartMagnet", "devices/Gonio", #"devices/HexiPosi"]" + "devices/VmbCamera", ] for script in scripted_devices: @@ -122,12 +125,6 @@ for script in scripted_devices: except: print >> sys.stderr, traceback.format_exc() - - -#if is_imaging_enabled(): -if get_device("img") is not None: - add_device(img.getContrast(), force = True) - add_device(img.getCamera(), force = True) ################################################################################################### diff --git a/script/test/TestCorrelation.py b/script/test/TestCorrelation.py new file mode 100644 index 0000000..b447276 --- /dev/null +++ b/script/test/TestCorrelation.py @@ -0,0 +1,129 @@ +show = False +img_size = 1024 +RADIUS = 512 +FRAMES = 1 +offset = 0 +add_mark = False +#offset = 50 +#add_mark = True + + +Image_Correlator = get_context().getClassByName("Image_Correlator") +correlator = Image_Correlator() + + + +def get_roi_img(radius, offset_x=0, offset_y=0, frames=1): + x,y,w,h = int(cover_detection.config.center_x-img_size/2 + offset_x), \ + int(cover_detection.config.center_y-img_size/2 + offset_y), \ + img_size, img_size + if frames>1: + ip = integrate_frames(samples = frames) + roi = ImagingUtils.copy(ip.getBufferedImage(), None, Rectangle(x,y,w,h)) + else: + roi = ImagingUtils.copy(image.output, None, Rectangle(x,y,w,h)) + ip = load_image(roi, title="Img") + p = ip.getProcessor() + mask = p.createProcessor(roi.getWidth(), roi.getHeight()) + mask.setColor(255) + cx, cy = img_size/2 - offset_x, img_size/2 - offset_y + mask.fillOval(int(cx - radius), int(cy - radius), int(2 * radius), int(2 * radius)) # Draw the circular ROI + mask.autoThreshold() # Ensures the mask is binary (if needed) + p.copyBits(mask, 0, 0, Blitter.AND) # Apply the mask using a bitwise AND + #gaussian_blur(ip) + p.blurGaussian(1.0) + return ip.getBufferedImage() + +def get_edge_img(ib): + ed = ImagingUtils.edgeDetect(ib) + ret = load_image(ed, title="Refe") + #auto_threshold(ret, dark_background=True) + #binary_dilate(ret, iterations=1, count=1) + return ret + +def put_mark(ib): + for x in range(450, 470): + for y in range(450, 470): + ib.setRGB(x,y, 0) + + + + +def load_ref_img(reload = False, frames=FRAMES): + global ip_ref, ip_ref_e, ip_ref_f, ip_ref_fe + if reload or "ip_ref_fe" not in globals(): + print "Loading cover reference image" + roi =get_roi_img(RADIUS, 0, 0, frames) + ip_ref = load_image(roi, title="Ref") + ip_ref_e =get_edge_img(roi) + ip_ref_f = image_fft(ip_ref) + ip_ref_fe = image_fft(ip_ref_e) + +load_ref_img() + +def load_cur_img(frames=FRAMES): + global ip_cur, ip_cur_e, ip_cur_f, ip_cur_fe + roi =get_roi_img(RADIUS, -offset, -offset, frames) + if add_mark: + put_mark(roi) + ip_cur = load_image(roi, title="Ref") + ip_cur_e =get_edge_img(roi) + ip_cur_f = image_fft(ip_cur) + ip_cur_fe = image_fft(ip_cur_e) + +load_cur_img() + + +#show_panel(ip1e.getBufferedImage()) +#show_panel(ip2e.getBufferedImage()) + +coef_img, _ = correlator.getCorrelation(ip_ref, ip_cur) +coef_ed, _ = correlator.getCorrelation(ip_ref_e, ip_cur_e) +coef_fft, _ = correlator.getCorrelation(ip_ref_f, ip_cur_f) +coef_ffte, _ = correlator.getCorrelation(ip_ref_fe, ip_cur_fe) + +#show_panel(sub1) + +#sub = ImagingUtils.sub(BufferedImage, BufferedImage, boolean) + +#sub = load_image(sub, title="sub") +#sube = load_image(sube, title="sube") + +if show: + st=create_stack([ip_ref, ip_cur, ip_ref_e, ip_cur_e, ip_ref_f, ip_cur_f, ip_ref_fe, ip_cur_fe]) + st.show() + + +def get_min(ip1): + return ip1.getProcessor().getStatistics().min + +def mean_abs_error(ip1, ip2): + sum = 0.0 + for y in range(ip1.getHeight()): + for x in range(ip1.getWidth()): + p1 = ip1.getPixel(x, y) + p2 = ip2.getPixel(x, y) + sum += abs(p1 - p2) + return sum / (ip1.height * ip1.width ) + +def rms_error(ip1, ip2): + sum = 0.0 + for y in range(ip1.getHeight()): + for x in range(ip1.getWidth()): + p1 = ip1.getPixel(x, y) + p2 = ip2.getPixel(x, y) + sum += math.pow((p1 - p2),2) + return sum / (ip1.height * ip1.width ) + + +mean_err, mean_erre = mean_abs_error(ip_ref_f.getProcessor(), ip_cur_f.getProcessor()), mean_abs_error(ip_ref_fe.getProcessor(), ip_cur_fe.getProcessor()) +rms_err, rms_erre = rms_error(ip_ref_f.getProcessor(), ip_cur_f.getProcessor()), rms_error(ip_ref_fe.getProcessor(), ip_cur_fe.getProcessor()) + +if show: + #print offset, coef_img, coef_ed, coef_fft, coef_ffte + print coef_fft, coef_ffte + print mean_err, mean_erre + print rms_err, rms_erre + + + \ No newline at end of file diff --git a/script/test/TestCorrelationDevices.py b/script/test/TestCorrelationDevices.py new file mode 100644 index 0000000..b1408c9 --- /dev/null +++ b/script/test/TestCorrelationDevices.py @@ -0,0 +1,40 @@ +class CoefFFT(ReadonlyRegisterBase): + def doRead(self): + return coef_fft +add_device(CoefFFT(), True) +CoefFFT.polling=1000 + +class CoefFFTE(ReadonlyRegisterBase): + def doRead(self): + return coef_ffte +add_device(CoefFFTE(), True) +CoefFFTE.polling=1000 + +class MeanErr(ReadonlyRegisterBase): + def doRead(self): + return mean_err +add_device(MeanErr(), True) +MeanErr.polling=1000 + +class MeanErrE(ReadonlyRegisterBase): + def doRead(self): + return mean_erre +add_device(MeanErrE(), True) +MeanErrE.polling=1000 + +class RmsErr(ReadonlyRegisterBase): + def doRead(self): + return rms_err +add_device(RmsErr(), True) +RmsErr.polling=1000 + +class RmsErrE(ReadonlyRegisterBase): + def doRead(self): + return rms_erre +add_device(RmsErrE(), True) +RmsErrE.polling=1000 + + +#print coef_fft, coef_ffte +#print mean_err, mean_erre +#print rms_err, rms_erre \ No newline at end of file