def test_morphsnakes_simple_shape_chan_vese(): img = gaussian_blob() ls1 = circle_level_set(img.shape, (5, 5), 3) ls2 = circle_level_set(img.shape, (5, 5), 6) acwe_ls1 = morphological_chan_vese(img, iterations=10, init_level_set=ls1) acwe_ls2 = morphological_chan_vese(img, iterations=10, init_level_set=ls2) assert_array_equal(acwe_ls1, acwe_ls2) assert acwe_ls1.dtype == acwe_ls2.dtype == np.int8
def test_morphsnakes_black(): img = np.zeros((11, 11)) ls = circle_level_set(img.shape, (5, 5), 3) ref_zeros = np.zeros(img.shape, dtype=np.int8) ref_ones = np.ones(img.shape, dtype=np.int8) acwe_ls = morphological_chan_vese(img, iterations=6, init_level_set=ls) assert_array_equal(acwe_ls, ref_zeros) gac_ls = morphological_geodesic_active_contour(img, iterations=6, init_level_set=ls) assert_array_equal(gac_ls, ref_zeros) gac_ls2 = morphological_geodesic_active_contour(img, iterations=6, init_level_set=ls, balloon=1, threshold=-1, smoothing=0) assert_array_equal(gac_ls2, ref_ones) assert acwe_ls.dtype == gac_ls.dtype == gac_ls2.dtype == np.int8
def test_morphsnakes_simple_shape_geodesic_active_contour(): img = np.float_(circle_level_set((11, 11), (5, 5), 3.5)) gimg = inverse_gaussian_gradient(img, alpha=10.0, sigma=1.0) ls = circle_level_set(img.shape, (5, 5), 6) ref = np.array( [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0], [0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0], [0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=np.int8) gac_ls = morphological_geodesic_active_contour(gimg, iterations=10, init_level_set=ls, balloon=-1) assert_array_equal(gac_ls, ref) assert gac_ls.dtype == np.int8
def test_morphsnakes_simple_shape_geodesic_active_contour(): img = np.float_(circle_level_set((11, 11), (5, 5), 3.5)) gimg = inverse_gaussian_gradient(img, alpha=10.0, sigma=1.0) ls = circle_level_set(img.shape, (5, 5), 6) ref = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0], [0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0], [0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=np.int8) gac_ls = morphological_geodesic_active_contour(gimg, iterations=10, init_level_set=ls, balloon=-1) assert_array_equal(gac_ls, ref) assert gac_ls.dtype == np.int8
def _init(x0, y0, x1, y1): a = (x0, y0) b = (x1, y1) radius = math.sqrt(sum([(c - d)**2 for c, d in zip(a, b)])) init = circle_level_set((512, 512), (y0, x0), radius) return init
def test_deprecated_circle_level_set(): img = gaussian_blob() with expected_warnings(['circle_level_set is deprecated']): ls1 = circle_level_set(img.shape, (5, 5), 3)
def main(): current_path = os.path.abspath(os.path.dirname(__file__)) dice_results = [] hausdorff_results = [] # Final report text file initialization summary_file = open('contour_report.txt', 'w+') summary_file.write('Script run: {a}\n'.format( a=datetime.now().strftime('%Y-%m-%d %H:%M:%S'))) for image_number in range(1, 110): image_path = os.path.join(current_path, 'input_images', '{}_no_contour.jpg'.format(image_number)) image = io.imread(image_path) image = color.rgb2gray(image) # Displaying the image to get the clicks coordinates fig = plt.figure() plt.imshow(image, cmap='gray') plt.axis('Off') cid = fig.canvas.mpl_connect('button_press_event', onclick) plt.show() # Calculating the radius, used to initialize the level set algorithm radius = np.sqrt( np.square(coords[1][0] - coords[0][0]) + np.square(coords[1][1] - coords[0][1])) # Main part of the script - image processing and creating the contour: # 1. Histogram equalization # 2. Gaussian filtration # 3. Anisotropic diffusion filtration # 4. Inverse Gaussian Gradient # 5. Unsharp Masking # 6. Morphological snakes # Hyperparameter optimization were performed using grid search for every method. equalized_image = equalize_hist(image) filtered_image = nd.gaussian_filter(equalized_image, sigma=5) diff_image = anisodiff(filtered_image, niter=50) gimage = inverse_gaussian_gradient(diff_image, alpha=50) sharpened_image = unsharp_mask(gimage, radius=100, amount=1.0) init_level_set = circle_level_set(gimage.shape, coords[0], radius) level_set = morphological_geodesic_active_contour(sharpened_image, 250, init_level_set, smoothing=4, balloon=1) level_set = nd.morphology.binary_fill_holes(level_set).astype(int) contour_access = os.path.join('input_images', '{}_contour.jpg'.format(image_number)) given_contour = contour_to_binary_mask(contour_access, level_set.shape) dice_result = dice(given_contour, level_set) hausdorff_result = hausdorff(given_contour, level_set) print('Dice =', dice_result) print('Hausdorff =', hausdorff_result) # Updating the report with new coefficients. summary_file.write('\n{a}:\nDice: {b}\nHausdorff: {c}\n'.format( a=image_number, b=format(dice_result, '.2f'), c=format(hausdorff_result, '.2f'))) dice_results.append(dice_result) hausdorff_results.append(hausdorff_result) # Displaying the result plt.figure() plt.imshow(image, cmap="gray") plt.axis('Off') plt.contour(level_set, [0.5], colors='r') plt.show() # Calculating and appending mean and standard deviation of the test to the end of the report summary_file.write('\n\nDICE:\nMEAN: {d}\nSTD DEV: {e}'.format( d=format(mean(dice_results), '.2f'), e=format(stddev(dice_results), '.2f'))) summary_file.write('\n\nHAUSDORFF:\nMEAN: {d}\nSTD DEV: {e}'.format( d=format(mean(hausdorff_results), '.2f'), e=format(stddev(hausdorff_results), '.2f'))) summary_file.close()
def process(self, args, sigma=0.8, iterations=25, writeback=False): """ use expansive active contours to transform point geometries into best-fit boundary polygons demarking object footprints """ # open centroid shape file centroid = self.openCentroidFile(args.centroid_pathname) # open image file image = self.openImageFile(args.image_pathname) # create footprint shape file footprint = self.createOutputFile(args.out_pathname, image) # check validity of files if centroid is not None and image is not None and footprint is not None: # convert centroid locations to pixel coordinates coords = self.getCentroidImageCoordinates(centroid, image) # random.shuffle( coords ) # for each x, y centroid location for idx, coord in enumerate(coords): # check valid sub-image if coord[0] + self._object_size < image[ 'ds'].RasterXSize and coord[ 1] + self._object_size < image['ds'].RasterYSize: # extract sub-image - check for error sub_image = image['band'].ReadAsArray( coord[0], coord[1], self._object_size, self._object_size) sub_image = gaussian_filter(sub_image, sigma=sigma) sub_image = sub_image / (2 ^ 16 - 1) # define initial state of active contour init_ls = circle_level_set( sub_image.shape, (self._object_halfsize, self._object_halfsize), 5) if writeback is True: # callback for animation ls = morphological_chan_vese( sub_image, iterations=iterations, init_level_set=init_ls, smoothing=1, lambda1=1, lambda2=1, iter_callback=self.visualCallback(sub_image)) else: # callback for animation ls = morphological_chan_vese(sub_image, iterations=iterations, init_level_set=init_ls, smoothing=1, lambda1=1, lambda2=1) # compute simplified polygon polyline = self.getPolyline(ls) # convert polyline to geometry wkt = self.getGeometry(polyline, coord, image['transform']) # create new polygon feature feature = ogr.Feature(footprint['defn']) feature.SetField('id', idx) feature.SetGeometry(ogr.CreateGeometryFromWkt(wkt)) # add feature footprint['layer'].CreateFeature(feature) feature = None # create animated gif if idx == 5 and writeback: imageio.mimsave( 'C:\\Users\\Chris.Williams\\Desktop\\animate.gif', self._images, fps=5, palettesize=64, subrectangles=True) break # delete variables to force write footprint['layer'] = None footprint['ds'] = None # buffer up footprint polygons by 3 metres self.getBufferedPolygons(args.out_pathname, 3) return
def morphSnakes(fp, frame, alph, sig, thresh): """{ ****************************************************************** * Fnc: read_arf() * Desc: reads an arf file and returns dictionary of parsed info * Inputs: fp - full path to arf file * Outputs: ****************************************************************** }""" ############################ Read Public Release Data ################################################## # print('Reading data...') _, fn = os.path.split(fp) basename, _ext = os.path.splitext(fn) df = pd.read_csv(f"{DATA_DIR}/Metric/{basename}.bbox_met", header=None, names=[ "site", "unknown1", "unknown2", "sensor", "scenario", "frame", "ply_id", "unknown3", "unknown4", "upperx", "uppery", "unknown5", "unknown6", "unknown7", "unknown8", "unknown9", "unknown10", "unknown11" ]) agt = read_agt(f"{DATA_DIR}/cegr/agt/{basename}.agt") f = open(fp, "rb") header = f.read(8 * 4) header = list(struct.iter_unpack(">I", header)) fptr = np.memmap(fp, dtype="uint16", mode='r', shape=(header[5][0], header[2][0], header[3][0]), offset=32) frames, cols, rows = fptr.shape # print('Data loaded!') im = fptr[frame].T.byteswap() tgtx, tgty = map( int, agt['Agt']['TgtSect'][f'TgtUpd.{frame}'][f'Tgt.{frame}'] ['PixLoc'].split()) upper_left_x, upper_left_y = df[df['frame'] == frame + 1][['upperx', 'uppery']].iloc[0] tgt_width = 2 * (tgtx - upper_left_x) tgt_height = 2 * (tgty - upper_left_y) ######################################### Find BB with GAC ################################################ def store_evolution_in(lst): """Returns a callback function to store the evolution of the level sets in the given list. """ def _store(x): lst.append(np.copy(x)) return _store plt.ioff() fig, axes = plt.subplots(2, 2, figsize=(8, 8)) ax = axes.flatten() # Morphological GAC image = img_as_float(im.T) gimage = inverse_gaussian_gradient(image, alpha=alph, sigma=sig) ax[0].imshow(image, cmap="gray") ax[0].set_axis_off() ax[0].set_title("MWIR", fontsize=12) ax[1].imshow(gimage, cmap="gray") ax[1].set_axis_off() ax[1].set_title("Inverse Gaussian Gradient", fontsize=12) ######################################## Set Initial Contour ########################################### ## Here you will want to set it as the bounding box, then you can set the morph snake to shrink instead ## of dialate. ######################################################################################################## # Initial level set init_ls = circle_level_set(image.shape, center=(tgty, tgtx), radius=5) # init_ls[10:-10, 10:-10] = 1 # List with intermediate results for plotting the evolution evolution = [] callback = store_evolution_in(evolution) ## Initialize the Morph Snake, you will want it to shrink (balloon=-1) ls = morphological_geodesic_active_contour(gimage, 230, init_ls, smoothing=1, balloon=1, threshold=thresh, iter_callback=callback) ax[2].imshow(image, cmap="gray") ax[2].set_axis_off() ax[2].contour(ls, [0.5], colors='r') ax[2].set_title("Center Point GAC Segmentation", fontsize=12) ax[3].imshow(ls, cmap="gray") ax[3].set_axis_off() contour = ax[3].contour(evolution[0], [0.5], colors='g') contour.collections[0].set_label("Iteration 0") contour = ax[3].contour(evolution[100], [0.5], colors='y') contour.collections[0].set_label("Iteration 100") contour = ax[3].contour(evolution[-1], [0.5], colors='r') contour.collections[0].set_label("Iteration 230") ax[3].legend(loc="upper right") title = "Center Point GAC Evolution" ax[3].set_title(title, fontsize=12) fig.tight_layout() fig.tight_layout(rect=[0, 0.03, 1, 0.95]) plt.suptitle( f'Morph Snakes, alpha={alph}, sigma={sig}, threshold={thresh}') # plt.savefig(f"{DATA_DIR}\\morphSnakes\\{basename}_{alph}_{sig}_{thresh}.png") # plt.close(fig) plt.show()
def level_set3D(data, seed3D, resol, lambda1=1, lambda2=4, smoothing=0, iterations=100, rad=3, method='ACWE', alpha=100, sigma=2, balloon=1): ## init res_fac = resol[1] / resol[ 0] # resolution factor to scale chunk to real dimensions N = 60 # approximately the chunk size (in mm) around nodule num_slices = int(round(N / res_fac)) # number of slices before reg = 30 # window of interest centered around seed point s = seed3D[1:] # 2D seed point for (x,y)-plane num = seed3D[0] # slice number seed point is selected from # apply lungmask on each slice of interest around seed point tmp_data = np.zeros((num_slices, data.shape[1], data.shape[2])) ii = 0 for i in range(data.shape[0]): if (i >= num - int(round(num_slices / 2)) and i <= num + int(round(num_slices / 2)) - 1): mask = lungmask_pro(data[i, :, :].copy()) tmp = data[i, :, :].copy() tmp[mask == 0] = np.amin( tmp ) # OBS: -1024 maybe not far away enough from lung intensities tmp_data[ii, :, :] = tmp.copy() ii += 1 # only apply level set on small volume around seed point -> increases speed and accuracy for fixed number of iterations tmp = tmp_data.copy() tmp = tmp[:, s[0] - reg:s[0] + reg, s[1] - reg:s[1] + reg] # transform chunk to true size (in mm) by stretching the slice-axis relevant to a resolution factor tmp = zoom(tmp.copy(), zoom=[res_fac, 1, 1], order=1) # apply 3D level set from single seed point from initial 3D blob around current seed point inits = circle_level_set(tmp.shape, center=(int(num_slices / 2 * res_fac), reg, reg), radius=rad) # choose between two types of level set methods if method == 'ACWE': tmp = morphological_chan_vese(tmp.copy(), iterations=iterations, init_level_set=inits, smoothing=smoothing, lambda1=lambda1, lambda2=lambda2).astype(int) elif method == 'GAC': tmp = tmp.astype(np.float32) tmp = inverse_gaussian_gradient(tmp, alpha=alpha, sigma=alpha) tmp = morphological_geodesic_active_contour(tmp, iterations=iterations, init_level_set=inits, smoothing=smoothing, threshold='auto', balloon=1) else: print('Please choose a valid method!') return None # if no nodule was segmented, break if (len(np.unique(tmp)) == 1): #print('No nodule was segmented. Try changing parameters...') return None # check if leakage has occured #if ((tmp[0,0,0] > 0) or (tmp[0,0,-1] > 0) or (tmp[0,-1,0] > 0) or (tmp[0,-1,-1] > 0) or (tmp[-1,0,0] > 0) or (tmp[-1,-1,0] > 0) or (tmp[-1,0,-1] > 0) or (tmp[-1,-1,-1] > 0)): # if ((len(np.unique(tmp[0,:,:])) > 1) or (len(np.unique(tmp[:,0,:])) > 1) or (len(np.unique(tmp[:,:,0])) > 1) or # (len(np.unique(tmp[-1,:,:])) > 1) or (len(np.unique(tmp[:,-1,:])) > 1) or (len(np.unique(tmp[:,:,-1])) > 1)): # print("Leakage problems? Growing reached boundaries... Discards segmentation") # return None # only keep segments connected to seed point (blood vessels will hopefully not be connected with nodule after level set, if leakage has occured) labels_tmp = label(tmp.copy()) res = np.zeros(tmp.shape) if (labels_tmp[int(num_slices / 2 * res_fac), reg, reg] > 0): res[labels_tmp == labels_tmp[int(num_slices / 2 * res_fac), reg, reg]] = 1 # need to transform chunk back to original size res = zoom(res.copy(), zoom=[1 / res_fac, 1, 1], order=1) # # just in case some parts are not connected anymore after interpolation -> remove not connected components # labels_tmp = label(res.copy()) # res = np.zeros(res.shape) # if (labels_tmp[int(num_slices/2), reg, reg] > 0): # res[labels_tmp == labels_tmp[int(num_slices/2), reg, reg]] = 1 # get the final nodule mask to the original image stack shape # but handle cases where seed point is selected at ends of image stack, and window is outside of range new_res = np.zeros(data.shape) if (num + int(num_slices / 2) > new_res.shape[0]): new_res[num - int(num_slices / 2):num + int(num_slices / 2), s[0] - reg:s[0] + reg, s[1] - reg:s[1] + reg] = res[:num + int(num_slices / 2) - new_res.shape[0]] elif (num - int(num_slices / 2) < 0): new_res[0:num + int(num_slices / 2), s[0] - reg:s[0] + reg, s[1] - reg:s[1] + reg] = res[:num + int(num_slices / 2)] else: new_res[num - int(np.floor(num_slices / 2)):num + int(np.ceil(num_slices / 2)), s[0] - reg:s[0] + reg, s[1] - reg:s[1] + reg] = res return new_res