def display(self, workspace, figure): orig_pixels = workspace.display_data.orig_pixels output_pixels = workspace.display_data.output_pixels figure.set_subplots((2, 2)) figure.subplot_imshow_grayscale(0, 0, orig_pixels, "Original: %s" % self.image_name.value) if self.method == M_CANNY: # Canny is binary figure.subplot_imshow_bw(0, 1, output_pixels, self.output_image_name.value, sharexy=figure.subplot(0, 0)) else: figure.subplot_imshow_grayscale(0, 1, output_pixels, self.output_image_name.value, sharexy=figure.subplot(0, 0)) color_image = np.zeros( (output_pixels.shape[0], output_pixels.shape[1], 3)) color_image[:, :, 0] = stretch(orig_pixels) color_image[:, :, 1] = stretch(output_pixels) figure.subplot_imshow(1, 0, color_image, "Composite image", sharexy=figure.subplot(0, 0))
def test_01_01_rescale(self): np.random.seed(0) image = np.random.uniform(-2, 2, size=(10, 10)) image[0, 0] = -2 image[9, 9] = 2 expected = (image + 2.0) / 4.0 result = F.stretch(image) self.assertTrue(np.all(result == expected))
def test_01_02_rescale_plus_mask(self): np.random.seed(0) image = np.random.uniform(-2, 2, size=(10, 10)) mask = np.zeros((10, 10), bool) mask[1:9, 1:9] = True image[0, 0] = -4 image[9, 9] = 4 image[1, 1] = -2 image[8, 8] = 2 expected = (image[1:9, 1:9] + 2.0) / 4.0 result = F.stretch(image, mask) self.assertTrue(np.all(result[1:9, 1:9] == expected))
def entropy2(x, y): """Joint entropy of paired samples X and Y""" # # Bin each image into 256 gray levels # x = (stretch(x) * 255).astype(int) y = (stretch(y) * 255).astype(int) # # create an image where each pixel with the same X & Y gets # the same value # xy = 256 * x + y xy = xy.flatten() sparse = scipy.sparse.coo_matrix((np.ones(xy.shape), (xy, np.zeros(xy.shape)))) histogram = sparse.toarray() n = np.sum(histogram) if n > 0 and np.max(histogram) > 0: histogram = histogram[histogram > 0] return np.log2(n) - np.sum(histogram * np.log2(histogram)) / n else: return 0
def entropy2(x, y): '''Joint entropy of paired samples X and Y''' # # Bin each image into 256 gray levels # x = (stretch(x) * 255).astype(int) y = (stretch(y) * 255).astype(int) # # create an image where each pixel with the same X & Y gets # the same value # xy = 256 * x + y xy = xy.flatten() sparse = scipy.sparse.coo_matrix( (np.ones(xy.shape), (xy, np.zeros(xy.shape)))) histogram = sparse.toarray() n = np.sum(histogram) if n > 0 and np.max(histogram) > 0: histogram = histogram[histogram > 0] return np.log2(n) - np.sum(histogram * np.log2(histogram)) / n else: return 0
def display(self, workspace, figure): orig_pixels = workspace.display_data.orig_pixels output_pixels = workspace.display_data.output_pixels figure.set_subplots((2, 2)) figure.subplot_imshow_grayscale(0, 0, orig_pixels, "Original: %s" % self.image_name.value) if self.method == M_CANNY: # Canny is binary figure.subplot_imshow_bw(0, 1, output_pixels, self.output_image_name.value, sharexy=figure.subplot(0, 0)) else: figure.subplot_imshow_grayscale(0, 1, output_pixels, self.output_image_name.value, sharexy=figure.subplot(0, 0)) color_image = np.zeros((output_pixels.shape[0], output_pixels.shape[1], 3)) color_image[:, :, 0] = stretch(orig_pixels) color_image[:, :, 1] = stretch(output_pixels) figure.subplot_imshow(1, 0, color_image, "Composite image", sharexy=figure.subplot(0, 0))
def run_image_gabor(self, image_name, scale, workspace): image = workspace.image_set.get_image(image_name, must_be_grayscale=True) pixel_data = image.pixel_data labels = np.ones(pixel_data.shape, int) if image.has_mask: labels[~image.mask] = 0 pixel_data = stretch(pixel_data, labels > 0) best_score = 0 for angle in range(self.gabor_angles.value): theta = np.pi * angle / self.gabor_angles.value g = gabor(pixel_data, labels, scale, theta) score_r = np.sum(g.real) score_i = np.sum(g.imag) score = np.sqrt(score_r**2 + score_i**2) best_score = max(best_score, score) statistics = self.record_image_measurement(workspace, image_name, scale, F_GABOR, best_score) return statistics
def test_05_02_otsu_entropy(self): '''Test the entropy version of Otsu''' np.random.seed(0) image = np.hstack((np.random.exponential(1.5,size=600), np.random.poisson(15,size=300))) image.shape=(30,30) image = stretch(image) limage, d = T.log_transform(image) threshold = entropy(limage) threshold = T.inverse_log_transform(threshold, d) expected = image > threshold workspace, module = self.make_workspace(image) module.binary.value = A.BINARY module.threshold_scope.value = I.TS_GLOBAL module.threshold_method.value = T.TM_OTSU module.use_weighted_variance.value = I.O_ENTROPY module.two_class_otsu.value = I.O_TWO_CLASS module.run(workspace) output = workspace.image_set.get_image(OUTPUT_IMAGE_NAME) self.assertTrue(np.all(output.pixel_data == expected))
def test_05_02_otsu_entropy(self): '''Test the entropy version of Otsu''' np.random.seed(0) image = np.hstack((np.random.exponential(1.5, size=600), np.random.poisson(15, size=300))) image.shape = (30, 30) image = stretch(image) limage, d = T.log_transform(image) threshold = entropy(limage) threshold = T.inverse_log_transform(threshold, d) expected = image > threshold workspace, module = self.make_workspace(image) module.binary.value = A.BINARY module.threshold_scope.value = I.TS_GLOBAL module.threshold_method.value = T.TM_OTSU module.use_weighted_variance.value = I.O_ENTROPY module.two_class_otsu.value = I.O_TWO_CLASS module.run(workspace) output = workspace.image_set.get_image(OUTPUT_IMAGE_NAME) self.assertTrue(np.all(output.pixel_data == expected))
def log_transform(image): '''Renormalize image intensities to log space Returns a tuple of transformed image and a dictionary to be passed into inverse_log_transform. The minimum and maximum from the dictionary can be applied to an image by the inverse_log_transform to convert it back to its former intensity values. ''' orig_min, orig_max = scipy.ndimage.extrema(image)[:2] # # We add 1/2 bit noise to an 8 bit image to give the log a bottom # limage = image.copy() noise_min = orig_min + (orig_max-orig_min)/256.0+np.finfo(image.dtype).eps limage[limage < noise_min] = noise_min d = { "noise_min":noise_min} limage = np.log(limage) log_min, log_max = scipy.ndimage.extrema(limage)[:2] d["log_min"] = log_min d["log_max"] = log_max return stretch(limage), d
def test_05_04_otsu3_wv_high(self): '''Test the three-class otsu, weighted variance middle = foreground''' np.random.seed(0) image = np.hstack((np.random.exponential(1.5, size=300), np.random.poisson(15, size=300), np.random.poisson(30, size=300))) image.shape = (30, 30) image = stretch(image) limage, d = T.log_transform(image) t1, t2 = otsu3(limage) threshold = T.inverse_log_transform(t1, d) workspace, module = self.make_workspace(image) module.binary.value = A.BINARY module.threshold_scope.value = I.TS_GLOBAL module.threshold_method.value = T.TM_OTSU module.use_weighted_variance.value = I.O_WEIGHTED_VARIANCE module.two_class_otsu.value = I.O_THREE_CLASS module.assign_middle_to_foreground.value = I.O_FOREGROUND module.run(workspace) m = workspace.measurements m_threshold = m[cpmeas.IMAGE, I.FF_ORIG_THRESHOLD % module.get_measurement_objects_name()] self.assertAlmostEqual(m_threshold, threshold)
def test_05_06_otsu3_entropy_high(self): '''Test the three-class otsu, entropy, middle = background''' np.random.seed(0) image = np.hstack( (np.random.exponential(1.5, size=300), np.random.poisson(15, size=300), np.random.poisson(30, size=300))) image.shape = (30, 30) image = stretch(image) limage, d = T.log_transform(image) t1, t2 = entropy3(limage) threshold = T.inverse_log_transform(t1, d) expected = image > threshold workspace, module = self.make_workspace(image) module.binary.value = A.BINARY module.threshold_scope.value = I.TS_GLOBAL module.threshold_method.value = T.TM_OTSU module.use_weighted_variance.value = I.O_ENTROPY module.two_class_otsu.value = I.O_THREE_CLASS module.assign_middle_to_foreground.value = I.O_FOREGROUND module.run(workspace) output = workspace.image_set.get_image(OUTPUT_IMAGE_NAME) self.assertTrue(np.all(output.pixel_data == expected))
def test_05_04_otsu3_wv_high(self): '''Test the three-class otsu, weighted variance middle = foreground''' np.random.seed(0) image = np.hstack((np.random.exponential(1.5,size=300), np.random.poisson(15,size=300), np.random.poisson(30,size=300))) image.shape=(30,30) image = stretch(image) limage, d = T.log_transform(image) t1,t2 = otsu3(limage) threshold = T.inverse_log_transform(t1, d) workspace, module = self.make_workspace(image) module.binary.value = A.BINARY module.threshold_scope.value = I.TS_GLOBAL module.threshold_method.value = T.TM_OTSU module.use_weighted_variance.value = I.O_WEIGHTED_VARIANCE module.two_class_otsu.value = I.O_THREE_CLASS module.assign_middle_to_foreground.value = I.O_FOREGROUND module.run(workspace) m = workspace.measurements m_threshold = m[cpmeas.IMAGE, I.FF_ORIG_THRESHOLD % module.get_measurement_objects_name()] self.assertAlmostEqual(m_threshold, threshold)
def test_05_06_otsu3_entropy_high(self): '''Test the three-class otsu, entropy, middle = background''' np.random.seed(0) image = np.hstack((np.random.exponential(1.5, size=300), np.random.poisson(15, size=300), np.random.poisson(30, size=300))) image.shape = (30, 30) image = stretch(image) limage, d = T.log_transform(image) t1, t2 = entropy3(limage) threshold = T.inverse_log_transform(t1, d) expected = image > threshold workspace, module = self.make_workspace(image) module.binary.value = A.BINARY module.threshold_scope.value = I.TS_GLOBAL module.threshold_method.value = T.TM_OTSU module.use_weighted_variance.value = I.O_ENTROPY module.two_class_otsu.value = I.O_THREE_CLASS module.assign_middle_to_foreground.value = I.O_FOREGROUND module.run(workspace) output = workspace.image_set.get_image(OUTPUT_IMAGE_NAME) self.assertTrue(np.all(output.pixel_data == expected))
def run_image_gabor(self, image_name, scale, workspace): image = workspace.image_set.get_image(image_name, must_be_grayscale=True) pixel_data = image.pixel_data labels = np.ones(pixel_data.shape, int) if image.has_mask: labels[~image.mask] = 0 pixel_data = stretch(pixel_data, labels > 0) best_score = 0 for angle in range(self.gabor_angles.value): theta = np.pi * angle / self.gabor_angles.value g = gabor(pixel_data, labels, scale, theta) score_r = np.sum(g.real) score_i = np.sum(g.imag) score = np.sqrt(score_r ** 2 + score_i ** 2) best_score = max(best_score, score) statistics = self.record_image_measurement(workspace, image_name, scale, F_GABOR, best_score) return statistics
def test_05_05_otsu3_entropy_low(self): '''Test the three-class otsu, entropy, middle = background''' np.random.seed(0) image = np.hstack((np.random.exponential(1.5, size=300), np.random.poisson(15, size=300), np.random.poisson(30, size=300))) image.shape = (30, 30) image = stretch(image) limage, d = T.log_transform(image) t1, t2 = entropy3(limage) threshold = T.inverse_log_transform(t2, d) workspace, module = self.make_workspace(image) module.binary.value = A.BINARY module.threshold_scope.value = I.TS_GLOBAL module.threshold_method.value = T.TM_OTSU module.use_weighted_variance.value = I.O_ENTROPY module.two_class_otsu.value = I.O_THREE_CLASS module.assign_middle_to_foreground.value = I.O_BACKGROUND module.run(workspace) output = workspace.image_set.get_image(OUTPUT_IMAGE_NAME) m = workspace.measurements m_threshold = m[cpmeas.IMAGE, I.FF_ORIG_THRESHOLD % module.get_measurement_objects_name()] self.assertAlmostEqual(m_threshold, threshold)
def test_05_05_otsu3_entropy_low(self): '''Test the three-class otsu, entropy, middle = background''' np.random.seed(0) image = np.hstack((np.random.exponential(1.5,size=300), np.random.poisson(15,size=300), np.random.poisson(30,size=300))) image.shape=(30,30) image = stretch(image) limage, d = T.log_transform(image) t1,t2 = entropy3(limage) threshold = T.inverse_log_transform(t2, d) workspace, module = self.make_workspace(image) module.binary.value = A.BINARY module.threshold_scope.value = I.TS_GLOBAL module.threshold_method.value = T.TM_OTSU module.use_weighted_variance.value = I.O_ENTROPY module.two_class_otsu.value = I.O_THREE_CLASS module.assign_middle_to_foreground.value = I.O_BACKGROUND module.run(workspace) output = workspace.image_set.get_image(OUTPUT_IMAGE_NAME) m = workspace.measurements m_threshold = m[cpmeas.IMAGE, I.FF_ORIG_THRESHOLD % module.get_measurement_objects_name()] self.assertAlmostEqual(m_threshold, threshold)
def handle_interaction(self, current_shape, orig_image): '''Show the cropping user interface''' import matplotlib as M import matplotlib.cm import wx from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg pixel_data = stretch(orig_image) # # Create the UI - a dialog with a figure inside # style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER dialog_box = wx.Dialog(wx.GetApp().TopWindow, -1, "Select the cropping region", size=(640,480), style = style) sizer = wx.BoxSizer(wx.VERTICAL) figure = matplotlib.figure.Figure() panel = FigureCanvasWxAgg(dialog_box, -1, figure) sizer.Add(panel, 1, wx.EXPAND) btn_sizer = wx.StdDialogButtonSizer() btn_sizer.AddButton(wx.Button(dialog_box, wx.ID_OK)) btn_sizer.AddButton(wx.Button(dialog_box, wx.ID_CANCEL)) btn_sizer.Realize() sizer.Add(btn_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 5) dialog_box.SetSizer(sizer) dialog_box.Size = dialog_box.BestSize dialog_box.Layout() axes = figure.add_subplot(1,1,1) assert isinstance(axes, matplotlib.axes.Axes) if pixel_data.ndim == 2: axes.imshow(pixel_data, matplotlib.cm.Greys_r, origin="upper") else: axes.imshow(pixel_data, origin="upper") #t = axes.transData.inverted() current_handle = [ None ] def data_xy(mouse_event): '''Return the mouse event's x & y converted into data-relative coords''' x = mouse_event.xdata y = mouse_event.ydata return (x,y) class handle(M.patches.Rectangle): dm = max((10,min(pixel_data.shape)/50)) height, width = (dm,dm) def __init__(self, x, y, on_move): x = max(0, min(x, pixel_data.shape[1])) y = max(0, min(y, pixel_data.shape[0])) self.__selected = False self.__color = cpprefs.get_primary_outline_color() self.__color = np.hstack((self.__color,[255])).astype(float) / 255.0 self.__on_move = on_move super(handle, self).__init__((x-self.width/2, y-self.height/2), self.width, self.height, edgecolor = self.__color, facecolor = "none") self.set_picker(True) def move(self, x, y): self.set_xy((x-self.width/2, y-self.height/2)) self.__on_move(x, y) def select(self, on): self.__selected = on if on: current_handle[0] = self self.set_facecolor(self.__color) else: self.set_facecolor("none") if current_handle[0] == self: current_handle[0] = None figure.canvas.draw() dialog_box.Update() @property def is_selected(self): return self.__selected @property def center_x(self): '''The handle's notion of its x coordinate''' return self.get_x() + self.get_width() / 2 @property def center_y(self): '''The handle's notion of its y coordinate''' return self.get_y() + self.get_height() / 2 def handle_pick(self, event): mouse_event = event.mouseevent x, y = data_xy(mouse_event) if mouse_event.button == 1: self.select(True) self.orig_x = self.center_x self.orig_y = self.center_y self.first_x = x self.first_y = y def handle_mouse_move_event(self, event): x,y = data_xy(event) if x is None or y is None: return x = x - self.first_x + self.orig_x y = y - self.first_y + self.orig_y if x < 0: x = 0 if x >= pixel_data.shape[1]: x = pixel_data.shape[1] -1 if y < 0: y = 0 if y >= pixel_data.shape[0]: y = pixel_data.shape[0] -1 self.move(x, y) class crop_rectangle(object): def __init__(self, top_left, bottom_right): self.__left, self.__top = top_left self.__right, self.__bottom = bottom_right color = cpprefs.get_primary_outline_color() color = np.hstack((color,[255])).astype(float) / 255.0 self.rectangle = M.patches.Rectangle( (min(self.__left, self.__right), min(self.__bottom, self.__top)), abs(self.__right - self.__left), abs(self.__top - self.__bottom), edgecolor = color, facecolor = "none" ) self.top_left_handle = handle(top_left[0], top_left[1], self.handle_top_left) self.bottom_right_handle = handle(bottom_right[0], bottom_right[1], self.handle_bottom_right) def handle_top_left(self, x, y): self.__left = x self.__top = y self.__reshape() def handle_bottom_right(self, x, y): self.__right = x self.__bottom = y self.__reshape() def __reshape(self): self.rectangle.set_xy((min(self.__left, self.__right), min(self.__bottom,self.__top))) self.rectangle.set_width(abs(self.__right - self.__left)) self.rectangle.set_height(abs(self.__bottom - self.__top)) self.rectangle.figure.canvas.draw() dialog_box.Update() @property def patches(self): return [self.rectangle, self.top_left_handle, self.bottom_right_handle] @property def handles(self): return [self.top_left_handle, self.bottom_right_handle] @property def left(self): return min(self.__left, self.__right) @property def right(self): return max(self.__left, self.__right) @property def top(self): return min(self.__top, self.__bottom) @property def bottom(self): return max(self.__top, self.__bottom) class crop_ellipse(object): def __init__(self, center, radius): '''Draw an ellipse with control points at the ellipse center and a given x and y radius''' self.center_x, self.center_y = center self.radius_x = self.center_x + radius[0] / 2 self.radius_y = self.center_y + radius[1] / 2 color = cpprefs.get_primary_outline_color() color = np.hstack((color,[255])).astype(float) / 255.0 self.ellipse = M.patches.Ellipse(center, self.width, self.height, edgecolor = color, facecolor = "none") self.center_handle = handle(self.center_x, self.center_y, self.move_center) self.radius_handle = handle(self.radius_x, self.radius_y, self.move_radius) def move_center(self, x, y): self.center_x = x self.center_y = y self.redraw() def move_radius(self, x, y): self.radius_x = x self.radius_y = y self.redraw() @property def width(self): return abs(self.center_x - self.radius_x) * 4 @property def height(self): return abs(self.center_y - self.radius_y) * 4 def redraw(self): self.ellipse.center = (self.center_x, self.center_y) self.ellipse.width = self.width self.ellipse.height = self.height self.ellipse.figure.canvas.draw() dialog_box.Update() @property def patches(self): return [self.ellipse, self.center_handle, self.radius_handle] @property def handles(self): return [self.center_handle, self.radius_handle] if self.shape == SH_ELLIPSE: if current_shape is None: current_shape = { EL_XCENTER: pixel_data.shape[1] / 2, EL_YCENTER: pixel_data.shape[0] / 2, EL_XRADIUS: pixel_data.shape[1] / 2, EL_YRADIUS: pixel_data.shape[0] / 2 } ellipse = current_shape shape = crop_ellipse((ellipse[EL_XCENTER], ellipse[EL_YCENTER]), (ellipse[EL_XRADIUS], ellipse[EL_YRADIUS])) else: if current_shape is None: current_shape = { RE_LEFT: pixel_data.shape[1] / 4, RE_TOP: pixel_data.shape[0] / 4, RE_RIGHT: pixel_data.shape[1] * 3 / 4, RE_BOTTOM: pixel_data.shape[0] * 3 / 4 } rectangle = current_shape shape = crop_rectangle((rectangle[RE_LEFT], rectangle[RE_TOP]), (rectangle[RE_RIGHT], rectangle[RE_BOTTOM])) for patch in shape.patches: axes.add_artist(patch) def on_mouse_down_event(event): axes.pick(event) def on_mouse_move_event(event): if current_handle[0] is not None: current_handle[0].handle_mouse_move_event(event) def on_mouse_up_event(event): if current_handle[0] is not None: current_handle[0].select(False) def on_pick_event(event): for h in shape.handles: if id(h) == id(event.artist): h.handle_pick(event) figure.canvas.mpl_connect('button_press_event', on_mouse_down_event) figure.canvas.mpl_connect('button_release_event', on_mouse_up_event) figure.canvas.mpl_connect('motion_notify_event', on_mouse_move_event) figure.canvas.mpl_connect('pick_event', on_pick_event) try: if dialog_box.ShowModal() != wx.ID_OK: raise ValueError("Cancelled by user") finally: dialog_box.Destroy() if self.shape == SH_RECTANGLE: return { RE_LEFT: shape.left, RE_TOP: shape.top, RE_RIGHT: shape.right, RE_BOTTOM: shape.bottom } else: return { EL_XCENTER: shape.center_x, EL_YCENTER: shape.center_y, EL_XRADIUS: shape.width / 2, EL_YRADIUS: shape.height / 2 }
def stretch(self, input_image): '''Stretch the input image to the range 0:1''' if input_image.has_mask: return stretch(input_image.pixel_data, input_image.mask) else: return stretch(input_image.pixel_data)
def test_00_04_half(self): result = F.stretch(np.ones((10, 10)) * .5) self.assertTrue(np.all(result == .5))
def test_00_05_half_plus_mask(self): result = F.stretch(np.ones((10, 10)) * .5, np.ones((10, 10), bool)) self.assertTrue(np.all(result == .5))
def test_00_03_zeros_plus_mask(self): result = F.stretch(np.zeros((10, 10)), np.ones((10, 10), bool)) self.assertTrue(np.all(result == 0))
def test_00_02_zeros(self): result = F.stretch(np.zeros((10, 10))) self.assertTrue(np.all(result == 0))
def test_00_01_empty_plus_mask(self): result = F.stretch(np.zeros((0, )), np.zeros((0, ), bool)) self.assertEqual(len(result), 0)
def test_00_00_empty(self): result = F.stretch(np.zeros((0, ))) self.assertEqual(len(result), 0)