def run_function(self, function, pixel_data, mask): '''Apply the function once to the image, returning the result''' count = function.repeat_count function_name = function.function.value scale = function.scale.value custom_repeats = function.custom_repeats.value is_binary = pixel_data.dtype.kind == 'b' if function.structuring_element == SE_ARBITRARY: strel = np.array(function.strel.get_matrix()) elif function.structuring_element == SE_DISK: strel = morph.strel_disk(scale / 2.0) elif function.structuring_element == SE_DIAMOND: strel = morph.strel_diamond(scale / 2.0) elif function.structuring_element == SE_LINE: strel = morph.strel_line(scale, function.angle.value) elif function.structuring_element == SE_OCTAGON: strel = morph.strel_octagon(scale / 2.0) elif function.structuring_element == SE_PAIR: strel = morph.strel_pair(function.x_offset.value, function.y_offset.value) elif function.structuring_element == SE_PERIODIC_LINE: xoff = function.x_offset.value yoff = function.y_offset.value n = max(scale / 2.0 / np.sqrt(float(xoff * xoff + yoff * yoff)), 1) strel = morph.strel_periodicline(xoff, yoff, n) elif function.structuring_element == SE_RECTANGLE: strel = morph.strel_rectangle(function.width.value, function.height.value) else: strel = morph.strel_square(scale) if (function_name in (F_BRANCHPOINTS, F_BRIDGE, F_CLEAN, F_DIAG, F_CONVEX_HULL, F_DISTANCE, F_ENDPOINTS, F_FILL, F_FILL_SMALL, F_HBREAK, F_LIFE, F_MAJORITY, F_REMOVE, F_SHRINK, F_SKEL, F_SKELPE, F_SPUR, F_THICKEN, F_THIN, F_VBREAK) and not is_binary): # Apply a very crude threshold to the image for binary algorithms logger.warning("Warning: converting image to binary for %s\n" % function_name) pixel_data = pixel_data != 0 if (function_name in (F_BRANCHPOINTS, F_BRIDGE, F_CLEAN, F_DIAG, F_CONVEX_HULL, F_DISTANCE, F_ENDPOINTS, F_FILL, F_FILL_SMALL, F_HBREAK, F_INVERT, F_LIFE, F_MAJORITY, F_REMOVE, F_SHRINK, F_SKEL, F_SKELPE, F_SPUR, F_THICKEN, F_THIN, F_VBREAK, F_OPENLINES) or (is_binary and function_name in (F_CLOSE, F_DILATE, F_ERODE, F_OPEN))): # All of these have an iterations argument or it makes no # sense to iterate if function_name == F_BRANCHPOINTS: return morph.branchpoints(pixel_data, mask) elif function_name == F_BRIDGE: return morph.bridge(pixel_data, mask, count) elif function_name == F_CLEAN: return morph.clean(pixel_data, mask, count) elif function_name == F_CLOSE: if mask is None: return scind.binary_closing(pixel_data, strel, iterations=count) else: return (scind.binary_closing( pixel_data & mask, strel, iterations=count) | (pixel_data & ~mask)) elif function_name == F_CONVEX_HULL: if mask is None: return morph.convex_hull_image(pixel_data) else: return morph.convex_hull_image(pixel_data & mask) elif function_name == F_DIAG: return morph.diag(pixel_data, mask, count) elif function_name == F_DILATE: return scind.binary_dilation(pixel_data, strel, iterations=count, mask=mask) elif function_name == F_DISTANCE: image = scind.distance_transform_edt(pixel_data) if function.rescale_values.value: image = image / np.max(image) return image elif function_name == F_ENDPOINTS: return morph.endpoints(pixel_data, mask) elif function_name == F_ERODE: return scind.binary_erosion(pixel_data, strel, iterations=count, mask=mask) elif function_name == F_FILL: return morph.fill(pixel_data, mask, count) elif function_name == F_FILL_SMALL: def small_fn(area, foreground): return (not foreground) and (area <= custom_repeats) return morph.fill_labeled_holes(pixel_data, mask, small_fn) elif function_name == F_HBREAK: return morph.hbreak(pixel_data, mask, count) elif function_name == F_INVERT: if is_binary: if mask is None: return ~pixel_data result = pixel_data.copy() result[mask] = ~result[mask] return result elif mask is None: return 1 - pixel_data else: result = pixel_data.copy() result[mask] = 1 - result[mask] return result elif function_name == F_LIFE: return morph.life(pixel_data, count) elif function_name == F_MAJORITY: return morph.majority(pixel_data, mask, count) elif function_name == F_OPEN: if mask is None: return scind.binary_opening(pixel_data, strel, iterations=count) else: return (scind.binary_opening( pixel_data & mask, strel, iterations=count) | (pixel_data & ~mask)) elif function_name == F_OPENLINES: return morph.openlines(pixel_data, linelength=custom_repeats, mask=mask) elif function_name == F_REMOVE: return morph.remove(pixel_data, mask, count) elif function_name == F_SHRINK: return morph.binary_shrink(pixel_data, count) elif function_name == F_SKEL: return morph.skeletonize(pixel_data, mask) elif function_name == F_SKELPE: return morph.skeletonize( pixel_data, mask, scind.distance_transform_edt(pixel_data) * poisson_equation(pixel_data)) elif function_name == F_SPUR: return morph.spur(pixel_data, mask, count) elif function_name == F_THICKEN: return morph.thicken(pixel_data, mask, count) elif function_name == F_THIN: return morph.thin(pixel_data, mask, count) elif function_name == F_VBREAK: return morph.vbreak(pixel_data, mask) else: raise NotImplementedError( "Unimplemented morphological function: %s" % function_name) else: for i in range(count): if function_name == F_BOTHAT: new_pixel_data = morph.black_tophat(pixel_data, mask=mask, footprint=strel) elif function_name == F_CLOSE: new_pixel_data = morph.closing(pixel_data, mask=mask, footprint=strel) elif function_name == F_DILATE: new_pixel_data = morph.grey_dilation(pixel_data, mask=mask, footprint=strel) elif function_name == F_ERODE: new_pixel_data = morph.grey_erosion(pixel_data, mask=mask, footprint=strel) elif function_name == F_OPEN: new_pixel_data = morph.opening(pixel_data, mask=mask, footprint=strel) elif function_name == F_TOPHAT: new_pixel_data = morph.white_tophat(pixel_data, mask=mask, footprint=strel) else: raise NotImplementedError( "Unimplemented morphological function: %s" % function_name) if np.all(new_pixel_data == pixel_data): break pixel_data = new_pixel_data return pixel_data
def run_on_image_setting(self, workspace, image): assert isinstance(workspace, cpw.Workspace) image_set = workspace.image_set measurements = workspace.measurements im = image_set.get_image(image.image_name.value, must_be_grayscale=True) # # Downsample the image and mask # new_shape = np.array(im.pixel_data.shape) if image.subsample_size.value < 1: new_shape = new_shape * image.subsample_size.value i, j = (np.mgrid[0:new_shape[0], 0:new_shape[1]].astype(float) / image.subsample_size.value) pixels = scind.map_coordinates(im.pixel_data, (i, j), order=1) mask = scind.map_coordinates(im.mask.astype(float), (i, j)) > .9 else: pixels = im.pixel_data mask = im.mask # # Remove background pixels using a greyscale tophat filter # if image.image_sample_size.value < 1: back_shape = new_shape * image.image_sample_size.value i, j = (np.mgrid[0:back_shape[0], 0:back_shape[1]].astype(float) / image.image_sample_size.value) back_pixels = scind.map_coordinates(pixels, (i, j), order=1) back_mask = scind.map_coordinates(mask.astype(float), (i, j)) > .9 else: back_pixels = pixels back_mask = mask radius = image.element_size.value back_pixels = morph.grey_erosion(back_pixels, radius, back_mask) back_pixels = morph.grey_dilation(back_pixels, radius, back_mask) if image.image_sample_size.value < 1: i, j = np.mgrid[0:new_shape[0], 0:new_shape[1]].astype(float) # # Make sure the mapping only references the index range of # back_pixels. # i *= float(back_shape[0] - 1) / float(new_shape[0] - 1) j *= float(back_shape[1] - 1) / float(new_shape[1] - 1) back_pixels = scind.map_coordinates(back_pixels, (i, j), order=1) pixels -= back_pixels pixels[pixels < 0] = 0 # # For each object, build a little record # class ObjectRecord(object): def __init__(self, name): self.name = name self.labels = workspace.object_set.get_objects(name).segmented self.nobjects = np.max(self.labels) if self.nobjects != 0: self.range = np.arange(1, np.max(self.labels) + 1) self.labels = self.labels.copy() self.labels[~im.mask] = 0 self.current_mean = fix( scind.mean(im.pixel_data, self.labels, self.range)) self.start_mean = np.maximum(self.current_mean, np.finfo(float).eps) object_records = [ ObjectRecord(ob.objects_name.value) for ob in image.objects ] # # Transcribed from the Matlab module: granspectr function # # CALCULATES GRANULAR SPECTRUM, ALSO KNOWN AS SIZE DISTRIBUTION, # GRANULOMETRY, AND PATTERN SPECTRUM, SEE REF.: # J.Serra, Image Analysis and Mathematical Morphology, Vol. 1. Academic Press, London, 1989 # Maragos,P. "Pattern spectrum and multiscale shape representation", IEEE Transactions on Pattern Analysis and Machine Intelligence, 11, N 7, pp. 701-716, 1989 # L.Vincent "Granulometries and Opening Trees", Fundamenta Informaticae, 41, No. 1-2, pp. 57-90, IOS Press, 2000. # L.Vincent "Morphological Area Opening and Closing for Grayscale Images", Proc. NATO Shape in Picture Workshop, Driebergen, The Netherlands, pp. 197-208, 1992. # I.Ravkin, V.Temov "Bit representation techniques and image processing", Applied Informatics, v.14, pp. 41-90, Finances and Statistics, Moskow, 1988 (in Russian) # THIS IMPLEMENTATION INSTEAD OF OPENING USES EROSION FOLLOWED BY RECONSTRUCTION # ng = image.granular_spectrum_length.value startmean = np.mean(pixels[mask]) ero = pixels.copy() # Mask the test image so that masked pixels will have no effect # during reconstruction # ero[~mask] = 0 currentmean = startmean startmean = max(startmean, np.finfo(float).eps) footprint = np.array([[False, True, False], [True, True, True], [False, True, False]]) statistics = [image.image_name.value] for i in range(1, ng + 1): prevmean = currentmean ero = morph.grey_erosion(ero, mask=mask, footprint=footprint) rec = morph.grey_reconstruction(ero, pixels, footprint) currentmean = np.mean(rec[mask]) gs = (prevmean - currentmean) * 100 / startmean statistics += ["%.2f" % gs] feature = image.granularity_feature(i) measurements.add_image_measurement(feature, gs) # # Restore the reconstructed image to the shape of the # original image so we can match against object labels # orig_shape = im.pixel_data.shape i, j = np.mgrid[0:orig_shape[0], 0:orig_shape[1]].astype(float) # # Make sure the mapping only references the index range of # back_pixels. # i *= float(new_shape[0] - 1) / float(orig_shape[0] - 1) j *= float(new_shape[1] - 1) / float(orig_shape[1] - 1) rec = scind.map_coordinates(rec, (i, j), order=1) # # Calculate the means for the objects # for object_record in object_records: assert isinstance(object_record, ObjectRecord) if object_record.nobjects > 0: new_mean = fix( scind.mean(rec, object_record.labels, object_record.range)) gss = ((object_record.current_mean - new_mean) * 100 / object_record.start_mean) object_record.current_mean = new_mean else: gss = np.zeros((0, )) measurements.add_measurement(object_record.name, feature, gss) return statistics
def run_on_image_setting(self, workspace, image): assert isinstance(workspace, cpw.Workspace) image_set = workspace.image_set measurements = workspace.measurements im = image_set.get_image(image.image_name.value, must_be_grayscale=True) # # Downsample the image and mask # new_shape = np.array(im.pixel_data.shape) if image.subsample_size.value < 1: new_shape = new_shape * image.subsample_size.value i,j = (np.mgrid[0:new_shape[0],0:new_shape[1]].astype(float) / image.subsample_size.value) pixels = scind.map_coordinates(im.pixel_data,(i,j),order=1) mask = scind.map_coordinates(im.mask.astype(float), (i,j)) > .9 else: pixels = im.pixel_data mask = im.mask # # Remove background pixels using a greyscale tophat filter # if image.image_sample_size.value < 1: back_shape = new_shape * image.image_sample_size.value i,j = (np.mgrid[0:back_shape[0],0:back_shape[1]].astype(float) / image.image_sample_size.value) back_pixels = scind.map_coordinates(pixels,(i,j), order=1) back_mask = scind.map_coordinates(mask.astype(float), (i,j)) > .9 else: back_pixels = pixels back_mask = mask radius = image.element_size.value back_pixels = morph.grey_erosion(back_pixels, radius, back_mask) back_pixels = morph.grey_dilation(back_pixels, radius, back_mask) if image.image_sample_size.value < 1: i,j = np.mgrid[0:new_shape[0],0:new_shape[1]].astype(float) # # Make sure the mapping only references the index range of # back_pixels. # i *= float(back_shape[0]-1)/float(new_shape[0]-1) j *= float(back_shape[1]-1)/float(new_shape[1]-1) back_pixels = scind.map_coordinates(back_pixels,(i,j), order=1) pixels -= back_pixels pixels[pixels < 0] = 0 # # For each object, build a little record # class ObjectRecord(object): def __init__(self, name): self.name = name self.labels = workspace.object_set.get_objects(name).segmented self.nobjects = np.max(self.labels) if self.nobjects != 0: self.range = np.arange(1, np.max(self.labels)+1) self.labels = self.labels.copy() self.labels[~ im.mask] = 0 self.current_mean = fix( scind.mean(im.pixel_data, self.labels, self.range)) self.start_mean = np.maximum( self.current_mean, np.finfo(float).eps) object_records = [ObjectRecord(ob.objects_name.value) for ob in image.objects ] # # Transcribed from the Matlab module: granspectr function # # CALCULATES GRANULAR SPECTRUM, ALSO KNOWN AS SIZE DISTRIBUTION, # GRANULOMETRY, AND PATTERN SPECTRUM, SEE REF.: # J.Serra, Image Analysis and Mathematical Morphology, Vol. 1. Academic Press, London, 1989 # Maragos,P. "Pattern spectrum and multiscale shape representation", IEEE Transactions on Pattern Analysis and Machine Intelligence, 11, N 7, pp. 701-716, 1989 # L.Vincent "Granulometries and Opening Trees", Fundamenta Informaticae, 41, No. 1-2, pp. 57-90, IOS Press, 2000. # L.Vincent "Morphological Area Opening and Closing for Grayscale Images", Proc. NATO Shape in Picture Workshop, Driebergen, The Netherlands, pp. 197-208, 1992. # I.Ravkin, V.Temov "Bit representation techniques and image processing", Applied Informatics, v.14, pp. 41-90, Finances and Statistics, Moskow, 1988 (in Russian) # THIS IMPLEMENTATION INSTEAD OF OPENING USES EROSION FOLLOWED BY RECONSTRUCTION # ng = image.granular_spectrum_length.value startmean = np.mean(pixels[mask]) ero = pixels.copy() # Mask the test image so that masked pixels will have no effect # during reconstruction # ero[~mask] = 0 currentmean = startmean startmean = max(startmean, np.finfo(float).eps) footprint = np.array([[False,True,False], [True ,True,True], [False,True,False]]) statistics = [ image.image_name.value] for i in range(1,ng+1): prevmean = currentmean ero = morph.grey_erosion(ero, mask = mask, footprint=footprint) rec = morph.grey_reconstruction(ero, pixels, footprint) currentmean = np.mean(rec[mask]) gs = (prevmean - currentmean) * 100 / startmean statistics += [ "%.2f"%gs] feature = image.granularity_feature(i) measurements.add_image_measurement(feature, gs) # # Restore the reconstructed image to the shape of the # original image so we can match against object labels # orig_shape = im.pixel_data.shape i,j = np.mgrid[0:orig_shape[0],0:orig_shape[1]].astype(float) # # Make sure the mapping only references the index range of # back_pixels. # i *= float(new_shape[0]-1)/float(orig_shape[0]-1) j *= float(new_shape[1]-1)/float(orig_shape[1]-1) rec = scind.map_coordinates(rec,(i,j), order=1) # # Calculate the means for the objects # for object_record in object_records: assert isinstance(object_record, ObjectRecord) if object_record.nobjects > 0: new_mean = fix(scind.mean(rec, object_record.labels, object_record.range)) gss = ((object_record.current_mean - new_mean) * 100 / object_record.start_mean) object_record.current_mean = new_mean else: gss = np.zeros((0,)) measurements.add_measurement(object_record.name, feature, gss) return statistics
def run_function(self, function, pixel_data, mask): '''Apply the function once to the image, returning the result''' count = function.repeat_count function_name = function.function.value scale = function.scale.value custom_repeats = function.custom_repeats.value is_binary = pixel_data.dtype.kind == 'b' if function.structuring_element == SE_ARBITRARY: strel = np.array(function.strel.get_matrix()) elif function.structuring_element == SE_DISK: strel = morph.strel_disk(scale / 2.0) elif function.structuring_element == SE_DIAMOND: strel = morph.strel_diamond(scale / 2.0) elif function.structuring_element == SE_LINE: strel = morph.strel_line(scale, function.angle.value) elif function.structuring_element == SE_OCTAGON: strel = morph.strel_octagon(scale / 2.0) elif function.structuring_element == SE_PAIR: strel = morph.strel_pair(function.x_offset.value, function.y_offset.value) elif function.structuring_element == SE_PERIODIC_LINE: xoff = function.x_offset.value yoff = function.y_offset.value n = max(scale / 2.0 / np.sqrt(float(xoff * xoff + yoff * yoff)), 1) strel = morph.strel_periodicline( xoff, yoff, n) elif function.structuring_element == SE_RECTANGLE: strel = morph.strel_rectangle( function.width.value, function.height.value) else: strel = morph.strel_square(scale) if (function_name in (F_BRANCHPOINTS, F_BRIDGE, F_CLEAN, F_DIAG, F_CONVEX_HULL, F_DISTANCE, F_ENDPOINTS, F_FILL, F_FILL_SMALL, F_HBREAK, F_LIFE, F_MAJORITY, F_REMOVE, F_SHRINK, F_SKEL, F_SKELPE, F_SPUR, F_THICKEN, F_THIN, F_VBREAK) and not is_binary): # Apply a very crude threshold to the image for binary algorithms logger.warning("Warning: converting image to binary for %s\n" % function_name) pixel_data = pixel_data != 0 if (function_name in (F_BRANCHPOINTS, F_BRIDGE, F_CLEAN, F_DIAG, F_CONVEX_HULL, F_DISTANCE, F_ENDPOINTS, F_FILL, F_FILL_SMALL, F_HBREAK, F_INVERT, F_LIFE, F_MAJORITY, F_REMOVE, F_SHRINK, F_SKEL, F_SKELPE, F_SPUR, F_THICKEN, F_THIN, F_VBREAK, F_OPENLINES) or (is_binary and function_name in (F_CLOSE, F_DILATE, F_ERODE, F_OPEN))): # All of these have an iterations argument or it makes no # sense to iterate if function_name == F_BRANCHPOINTS: return morph.branchpoints(pixel_data, mask) elif function_name == F_BRIDGE: return morph.bridge(pixel_data, mask, count) elif function_name == F_CLEAN: return morph.clean(pixel_data, mask, count) elif function_name == F_CLOSE: if mask is None: return scind.binary_closing(pixel_data, strel, iterations=count) else: return (scind.binary_closing(pixel_data & mask, strel, iterations=count) | (pixel_data & ~ mask)) elif function_name == F_CONVEX_HULL: if mask is None: return morph.convex_hull_image(pixel_data) else: return morph.convex_hull_image(pixel_data & mask) elif function_name == F_DIAG: return morph.diag(pixel_data, mask, count) elif function_name == F_DILATE: return scind.binary_dilation(pixel_data, strel, iterations=count, mask=mask) elif function_name == F_DISTANCE: image = scind.distance_transform_edt(pixel_data) if function.rescale_values.value: image = image / np.max(image) return image elif function_name == F_ENDPOINTS: return morph.endpoints(pixel_data, mask) elif function_name == F_ERODE: return scind.binary_erosion(pixel_data, strel, iterations=count, mask=mask) elif function_name == F_FILL: return morph.fill(pixel_data, mask, count) elif function_name == F_FILL_SMALL: def small_fn(area, foreground): return (not foreground) and (area <= custom_repeats) return morph.fill_labeled_holes(pixel_data, mask, small_fn) elif function_name == F_HBREAK: return morph.hbreak(pixel_data, mask, count) elif function_name == F_INVERT: if is_binary: if mask is None: return ~ pixel_data result = pixel_data.copy() result[mask] = ~result[mask] return result elif mask is None: return 1 - pixel_data else: result = pixel_data.copy() result[mask] = 1 - result[mask] return result elif function_name == F_LIFE: return morph.life(pixel_data, count) elif function_name == F_MAJORITY: return morph.majority(pixel_data, mask, count) elif function_name == F_OPEN: if mask is None: return scind.binary_opening(pixel_data, strel, iterations=count) else: return (scind.binary_opening(pixel_data & mask, strel, iterations=count) | (pixel_data & ~ mask)) elif function_name == F_OPENLINES: return morph.openlines(pixel_data, linelength=custom_repeats, mask=mask) elif function_name == F_REMOVE: return morph.remove(pixel_data, mask, count) elif function_name == F_SHRINK: return morph.binary_shrink(pixel_data, count) elif function_name == F_SKEL: return morph.skeletonize(pixel_data, mask) elif function_name == F_SKELPE: return morph.skeletonize( pixel_data, mask, scind.distance_transform_edt(pixel_data) * poisson_equation(pixel_data)) elif function_name == F_SPUR: return morph.spur(pixel_data, mask, count) elif function_name == F_THICKEN: return morph.thicken(pixel_data, mask, count) elif function_name == F_THIN: return morph.thin(pixel_data, mask, count) elif function_name == F_VBREAK: return morph.vbreak(pixel_data, mask) else: raise NotImplementedError("Unimplemented morphological function: %s" % function_name) else: for i in range(count): if function_name == F_BOTHAT: new_pixel_data = morph.black_tophat(pixel_data, mask=mask, footprint=strel) elif function_name == F_CLOSE: new_pixel_data = morph.closing(pixel_data, mask=mask, footprint=strel) elif function_name == F_DILATE: new_pixel_data = morph.grey_dilation(pixel_data, mask=mask, footprint=strel) elif function_name == F_ERODE: new_pixel_data = morph.grey_erosion(pixel_data, mask=mask, footprint=strel) elif function_name == F_OPEN: new_pixel_data = morph.opening(pixel_data, mask=mask, footprint=strel) elif function_name == F_TOPHAT: new_pixel_data = morph.white_tophat(pixel_data, mask=mask, footprint=strel) else: raise NotImplementedError("Unimplemented morphological function: %s" % function_name) if np.all(new_pixel_data == pixel_data): break pixel_data = new_pixel_data return pixel_data