def get_surface_distances(truth, pred_binary): hausdorff_distance_filter = sitk.HausdorffDistanceImageFilter() statistics_image_filter = sitk.StatisticsImageFilter() pred_bin_0 = sitk.GetImageFromArray(pred_binary) truth = sitk.GetImageFromArray(truth) reference_distance_map = sitk.Abs(sitk.SignedMaurerDistanceMap(truth, squaredDistance=False)) reference_surface = sitk.LabelContour(truth) segmented_distance_map = sitk.Abs(sitk.SignedMaurerDistanceMap(pred_bin_0, squaredDistance=False)) segmented_surface = sitk.LabelContour(pred_bin_0) statistics_image_filter.Execute(reference_surface) num_reference_surface_pixels = int(statistics_image_filter.GetSum()) statistics_image_filter.Execute(segmented_surface) num_segmented_surface_pixels = int(statistics_image_filter.GetSum()) if num_segmented_surface_pixels == 0: return 100 hausdorff_distance_filter.Execute(truth, pred_bin_0) seg2ref_distance_map = reference_distance_map * sitk.Cast(segmented_surface, sitk.sitkFloat32) ref2seg_distance_map = segmented_distance_map * sitk.Cast(reference_surface, sitk.sitkFloat32) seg2ref_distance_map_arr = sitk.GetArrayViewFromImage(seg2ref_distance_map) seg2ref_distances = list(seg2ref_distance_map_arr[seg2ref_distance_map_arr != 0]) seg2ref_distances = seg2ref_distances + list(np.zeros(num_segmented_surface_pixels - len(seg2ref_distances))) ref2seg_distance_map_arr = sitk.GetArrayViewFromImage(ref2seg_distance_map) ref2seg_distances = list(ref2seg_distance_map_arr[ref2seg_distance_map_arr != 0]) ref2seg_distances = ref2seg_distances + list(np.zeros(num_reference_surface_pixels - len(ref2seg_distances))) all_surface_distances = seg2ref_distances + ref2seg_distances return hausdorff_distance_filter.GetHausdorffDistance(), np.mean(all_surface_distances)
def mask_euclidean_distance(mask_1,mask_2): """ Code modified from the SimpleITK example notebook: http://insightsoftwareconsortium.github.io/SimpleITK-Notebooks/Python_html/34_Segmentation_Evaluation.html Steps of the function: - In mask_1, calculate the distances between each point of the mask and the mask contour, to obtain a map of distances. Do the same for mask_2. - Multiply the map of distances of mask_1 and the contour of mask_2. This is an implicit way to extract the distances corresponding to the contour of mask_2 from mask_1 distance map. Do the same for mask_2. - Convert the masked maps to numpy - Extract all the points that are not zero, and pad the difference between this last array and the number of points with zeros (there could be some distances == 0) Input: mask_1 and mask 2: two masks to compare (usually one is a newly segmented mask and the other is a ground truth mask) Outputs: average surface distance, root mean square distance, and maximum distance """ # Use the absolute values of the distance map to compute the surface distances (distance map sign, outside or inside # relationship, is irrelevant) mask_1_distance_map = sitk.Abs(sitk.SignedMaurerDistanceMap(mask_1, squaredDistance=False)) mask_1_surface = sitk.LabelContour(mask_1) # Symmetric surface distance measures mask_2_distance_map = sitk.Abs(sitk.SignedMaurerDistanceMap(mask_2, squaredDistance=False)) mask_2_surface = sitk.LabelContour(mask_2) # Multiply the binary surface segmentations with the distance maps. The resulting distance # maps contain non-zero values only on the surface (they can also contain zero on the surface) m1_to_m2_distance_map = mask_1_distance_map*sitk.Cast(mask_2_surface, sitk.sitkFloat32) m2_to_m1_distance_map = mask_2_distance_map*sitk.Cast(mask_1_surface, sitk.sitkFloat32) statistics_image_filter = sitk.StatisticsImageFilter() # Get the number of pixels in the mask_1 surface by counting all pixels that are 1 statistics_image_filter.Execute(mask_1_surface) num_mask_1_surface_pixels = int(statistics_image_filter.GetSum()) # Get the number of pixels in the mask_2 surface by counting all pixels that are 1 statistics_image_filter.Execute(mask_2_surface) num_mask_2_surface_pixels = int(statistics_image_filter.GetSum()) # Get all non-zero distances and then add zero distances if required. m1_to_m2_distance_map_arr = sitk.GetArrayViewFromImage(m1_to_m2_distance_map) m1_to_m2_distances = list(m1_to_m2_distance_map_arr[m1_to_m2_distance_map_arr!=0]) m1_to_m2_distances = m1_to_m2_distances + list(np.zeros(num_mask_2_surface_pixels - len(m1_to_m2_distances))) m2_to_m1_distance_map_arr = sitk.GetArrayViewFromImage(m2_to_m1_distance_map) m2_to_m1_distances = list(m2_to_m1_distance_map_arr[m2_to_m1_distance_map_arr!=0]) m2_to_m1_distances = m2_to_m1_distances + list(np.zeros(num_mask_1_surface_pixels - len(m2_to_m1_distances))) all_surface_distances = m1_to_m2_distances + m2_to_m1_distances # average surface distance mean_distance = np.mean(all_surface_distances) # maximum distance stddev_distance = np.std(all_surface_distances) return mean_distance, stddev_distance
def symmetric_surface_measures_sitk(lloutput, lltarget): # http://insightsoftwareconsortium.github.io/SimpleITK-Notebooks/Python_html/34_Segmentation_Evaluation.html try: all_surface_distances = [] for loutput, ltarget in zip(lloutput, lltarget): for output, target in zip(loutput, ltarget): if np.count_nonzero(sitk.GetArrayFromImage(target)) == 0: continue segmented_surface = sitk.LabelContour(output) reference_surface = sitk.LabelContour(target) reference_distance_map = sitk.Abs( sitk.SignedMaurerDistanceMap(target, squaredDistance=False)) segmented_distance_map = sitk.Abs( sitk.SignedMaurerDistanceMap(output, squaredDistance=False)) seg2ref_distance_map = reference_distance_map * sitk.Cast( segmented_surface, sitk.sitkFloat32) ref2seg_distance_map = segmented_distance_map * sitk.Cast( reference_surface, sitk.sitkFloat32) statistics_image_filter = sitk.StatisticsImageFilter() statistics_image_filter.Execute(reference_surface) num_reference_surface_pixels = int( statistics_image_filter.GetSum()) statistics_image_filter.Execute(segmented_surface) num_segmented_surface_pixels = int( statistics_image_filter.GetSum()) seg2ref_distance_map_arr = sitk.GetArrayViewFromImage( seg2ref_distance_map) seg2ref_distances = list( seg2ref_distance_map_arr[seg2ref_distance_map_arr != 0]) seg2ref_distances = seg2ref_distances + list( np.zeros(num_segmented_surface_pixels - len(seg2ref_distances))) ref2seg_distance_map_arr = sitk.GetArrayViewFromImage( ref2seg_distance_map) ref2seg_distances = list( ref2seg_distance_map_arr[ref2seg_distance_map_arr != 0]) ref2seg_distances = ref2seg_distances + list( np.zeros(num_reference_surface_pixels - len(ref2seg_distances))) all_surface_distances += seg2ref_distances + ref2seg_distances if len(all_surface_distances) > 0: mean_symmetric_surface_distance = np.mean(all_surface_distances) median_symmetric_surface_distance = np.median( all_surface_distances) std_symmetric_surface_distance = np.std(all_surface_distances) max_symmetric_surface_distance = np.max(all_surface_distances) return mean_symmetric_surface_distance, median_symmetric_surface_distance, std_symmetric_surface_distance, max_symmetric_surface_distance else: return np.nan, np.nan, np.nan, np.nan except Exception as e: print(e) raise return np.nan, np.nan, np.nan, np.nan
def Way2(segmentation, reference_segmentation): surface_distance_results = np.zeros((1, len(SurfaceDistanceMeasures.__members__.items()))) label = 1 reference_distance_map = sitk.Abs(sitk.SignedMaurerDistanceMap(reference_segmentation, squaredDistance=False)) reference_surface = sitk.LabelContour(reference_segmentation) statistics_image_filter = sitk.StatisticsImageFilter() # Get the number of pixels in the reference surface by counting all pixels that are 1. statistics_image_filter.Execute(reference_surface) num_reference_surface_pixels = int(statistics_image_filter.GetSum()) hausdorff_distance_filter = sitk.HausdorffDistanceImageFilter() hausdorff_distance_filter.Execute(reference_segmentation, segmentation) surface_distance_results[ 0, SurfaceDistanceMeasures.hausdorff_distance.value] = hausdorff_distance_filter.GetHausdorffDistance() # Symmetric surface distance measures segmented_distance_map = sitk.Abs(sitk.SignedMaurerDistanceMap(segmentation, squaredDistance=False, useImageSpacing=True)) segmented_surface = sitk.LabelContour(segmentation) # Multiply the binary surface segmentations with the distance maps. The resulting distance # maps contain non-zero values only on the surface (they can also contain zero on the surface) seg2ref_distance_map = reference_distance_map * sitk.Cast(segmented_surface, sitk.sitkFloat32) ref2seg_distance_map = segmented_distance_map * sitk.Cast(reference_surface, sitk.sitkFloat32) # Get the number of pixels in the reference surface by counting all pixels that are 1. statistics_image_filter.Execute(segmented_surface) num_segmented_surface_pixels = int(statistics_image_filter.GetSum()) # Get all non-zero distances and then add zero distances if required. seg2ref_distance_map_arr = sitk.GetArrayViewFromImage(seg2ref_distance_map) seg2ref_distances = list(seg2ref_distance_map_arr[seg2ref_distance_map_arr != 0]) seg2ref_distances = seg2ref_distances + \ list(np.zeros(num_segmented_surface_pixels - len(seg2ref_distances))) ref2seg_distance_map_arr = sitk.GetArrayViewFromImage(ref2seg_distance_map) ref2seg_distances = list(ref2seg_distance_map_arr[ref2seg_distance_map_arr != 0]) ref2seg_distances = ref2seg_distances + \ list(np.zeros(num_reference_surface_pixels - len(ref2seg_distances))) all_surface_distances = seg2ref_distances + ref2seg_distances # The maximum of the symmetric surface distances is the Hausdorff distance between the surfaces. In # general, it is not equal to the Hausdorff distance between all voxel/pixel points of the two # segmentations, though in our case it is. More on this below. surface_distance_results[0, SurfaceDistanceMeasures.avg_surface_distance.value] = np.mean(all_surface_distances) surface_distance_results[0, SurfaceDistanceMeasures.median_surface_distance.value] = np.median( all_surface_distances) surface_distance_results[0, SurfaceDistanceMeasures.std_surface_distance.value] = np.std(all_surface_distances) surface_distance_results[0, SurfaceDistanceMeasures.max_surface_distance.value] = np.max(all_surface_distances) return surface_distance_results
def surface_measures_sitk(output, target): label_intensity_statistics_filter = sitk.LabelIntensityStatisticsImageFilter( ) if type(output) == list: mean_surface_distance = 0 median_surface_distance = 0 std_surface_distance = 0 max_surface_distance = 0 count = 0 for output in output: for i in range(len(output)): try: segmented_surface = sitk.LabelContour(output[i]) reference_distance_map = sitk.Abs( sitk.SignedMaurerDistanceMap(target[0][i], squaredDistance=False)) label_intensity_statistics_filter.Execute( segmented_surface, reference_distance_map) label = 1 mean_surface_distance = mean_surface_distance + label_intensity_statistics_filter.GetMean( label) median_surface_distance = median_surface_distance + label_intensity_statistics_filter.GetMedian( label) std_surface_distance = std_surface_distance + label_intensity_statistics_filter.GetStandardDeviation( label) max_surface_distance = max_surface_distance + label_intensity_statistics_filter.GetMaximum( label) count = count + 1 except Exception as e: #print(e) pass if count == 0: return np.inf, np.inf, np.inf, np.inf return mean_surface_distance / count, median_surface_distance / count, std_surface_distance / count, max_surface_distance / count else: segmented_surface = sitk.LabelContour(output) reference_distance_map = sitk.Abs( sitk.SignedMaurerDistanceMap(target, squaredDistance=False)) label_intensity_statistics_filter.Execute(segmented_surface, reference_distance_map) label = 1 mean_surface_distance = label_intensity_statistics_filter.GetMean( label) median_surface_distance = label_intensity_statistics_filter.GetMedian( label) std_surface_distance = label_intensity_statistics_filter.GetStandardDeviation( label) max_surface_distance = label_intensity_statistics_filter.GetMaximum( label) return mean_surface_distance, median_surface_distance, std_surface_distance, max_surface_distance
def get_volume_metrics(truth, pred_binary): hausdorff_distance_filter = sitk.HausdorffDistanceImageFilter() overlap_measures_filter = sitk.LabelOverlapMeasuresImageFilter() statistics_image_filter = sitk.StatisticsImageFilter() label = 1 pred_bin_0 = sitk.GetImageFromArray(pred_binary) truth = sitk.GetImageFromArray(truth) surface_distance_results = np.zeros((len(SurfaceDistanceMeasures.__members__.items()),)) overlap_results = np.zeros((len(OverlapMeasures.__members__.items()),)) # print(truth.GetSize()) # print(pred_bin_0.GetSize()) # print(truth.GetPixelIDTypeAsString()) # print(pred_bin_0.GetPixelIDTypeAsString()) overlap_measures_filter.Execute(truth, pred_bin_0) overlap_results[OverlapMeasures.jaccard.value] = overlap_measures_filter.GetJaccardCoefficient() overlap_results[OverlapMeasures.dice.value] = overlap_measures_filter.GetDiceCoefficient() overlap_results[OverlapMeasures.volume_similarity.value] = overlap_measures_filter.GetVolumeSimilarity() overlap_results[OverlapMeasures.false_negative.value] = overlap_measures_filter.GetFalseNegativeError() overlap_results[OverlapMeasures.false_positive.value] = overlap_measures_filter.GetFalsePositiveError() reference_distance_map = sitk.Abs(sitk.SignedMaurerDistanceMap(truth, squaredDistance=False)) reference_surface = sitk.LabelContour(truth) segmented_distance_map = sitk.Abs(sitk.SignedMaurerDistanceMap(pred_bin_0, squaredDistance=False)) segmented_surface = sitk.LabelContour(pred_bin_0) statistics_image_filter.Execute(reference_surface) num_reference_surface_pixels = int(statistics_image_filter.GetSum()) statistics_image_filter.Execute(segmented_surface) num_segmented_surface_pixels = int(statistics_image_filter.GetSum()) if num_segmented_surface_pixels == 0: return [100, 100, 100, 100, 100], [100, 100, 100, 100, 100] hausdorff_distance_filter.Execute(truth, pred_bin_0) surface_distance_results[SurfaceDistanceMeasures.hausdorff_distance.value] = \ hausdorff_distance_filter.GetHausdorffDistance() seg2ref_distance_map = reference_distance_map*sitk.Cast(segmented_surface, sitk.sitkFloat32) ref2seg_distance_map = segmented_distance_map*sitk.Cast(reference_surface, sitk.sitkFloat32) seg2ref_distance_map_arr = sitk.GetArrayViewFromImage(seg2ref_distance_map) seg2ref_distances = list(seg2ref_distance_map_arr[seg2ref_distance_map_arr!=0]) seg2ref_distances = seg2ref_distances + \ list(np.zeros(num_segmented_surface_pixels - len(seg2ref_distances))) ref2seg_distance_map_arr = sitk.GetArrayViewFromImage(ref2seg_distance_map) ref2seg_distances = list(ref2seg_distance_map_arr[ref2seg_distance_map_arr!=0]) ref2seg_distances = ref2seg_distances + \ list(np.zeros(num_reference_surface_pixels - len(ref2seg_distances))) all_surface_distances = seg2ref_distances + ref2seg_distances surface_distance_results[SurfaceDistanceMeasures.mean_surface_distance.value] = np.mean(all_surface_distances) surface_distance_results[SurfaceDistanceMeasures.median_surface_distance.value] = np.median(all_surface_distances) surface_distance_results[SurfaceDistanceMeasures.std_surface_distance.value] = np.std(all_surface_distances) surface_distance_results[SurfaceDistanceMeasures.max_surface_distance.value] = np.max(all_surface_distances) return overlap_results, surface_distance_results
def surface_distance(seg: sitk.Image, reference_segmentation: sitk.Image) -> float: """ Symmetric surface distances taking into account the image spacing https://github.com/InsightSoftwareConsortium/SimpleITK-Notebooks/blob/master/Python/34_Segmentation_Evaluation.ipynb :param seg: mask 1 :param reference_segmentation: mask 2 :return: mean distance """ statistics_image_filter = sitk.StatisticsImageFilter() # Get the number of pixels in the reference surface by counting all pixels that are 1. reference_surface = sitk.LabelContour(reference_segmentation) statistics_image_filter.Execute(reference_surface) num_reference_surface_pixels = int(statistics_image_filter.GetSum()) reference_distance_map = sitk.Abs( sitk.SignedMaurerDistanceMap(reference_segmentation, squaredDistance=False, useImageSpacing=True)) reference_surface = sitk.LabelContour(reference_segmentation) # Symmetric surface distance measures segmented_distance_map = sitk.Abs( sitk.SignedMaurerDistanceMap(seg, squaredDistance=False, useImageSpacing=True)) segmented_surface = sitk.LabelContour(seg) # Multiply the binary surface segmentations with the distance maps. The resulting distance # maps contain non-zero values only on the surface (they can also contain zero on the surface) seg2ref_distance_map = reference_distance_map * sitk.Cast( segmented_surface, sitk.sitkFloat32) ref2seg_distance_map = segmented_distance_map * sitk.Cast( reference_surface, sitk.sitkFloat32) # Get the number of pixels in the reference surface by counting all pixels that are 1. statistics_image_filter.Execute(segmented_surface) num_segmented_surface_pixels = int(statistics_image_filter.GetSum()) seg2ref_distance_map_arr = sitk.GetArrayViewFromImage(seg2ref_distance_map) seg2ref_distances = _add_zero_distances(num_segmented_surface_pixels, seg2ref_distance_map_arr) ref2seg_distance_map_arr = sitk.GetArrayViewFromImage(ref2seg_distance_map) ref2seg_distances = _add_zero_distances(num_reference_surface_pixels, ref2seg_distance_map_arr) all_surface_distances = seg2ref_distances + ref2seg_distances return np.mean(all_surface_distances).item()
def from_dicom_pet(cls, path, series_id=None, type="SUV"): ''' Reads the PET scan and returns the data frame and the image dosage in SITK format There are two types of existing formats which has to be mentioned in the type type: SUV: gets the image with each pixel value having SUV value ACT: gets the image with each pixel value having activity concentration SUV = Activity concenteration/(Injected dose quantity/Body weight) Please refer to the pseudocode: https://qibawiki.rsna.org/index.php/Standardized_Uptake_Value_(SUV) If there is no data on SUV/ACT then backup calculation is done based on the formula in the documentation, although, it may have some error. ''' pet = read_image(path, series_id) path_one = pathlib.Path(path, os.listdir(path)[0]).as_posix() df = dcmread(path_one) calc = False try: if type == "SUV": factor = df.to_json_dict()['70531000']["Value"][0] else: factor = df.to_json_dict()['70531009']['Value'][0] except: warnings.warn( "Scale factor not available in DICOMs. Calculating based on metadata, may contain errors" ) factor = cls.calc_factor(df, type) calc = True img_pet = sitk.Cast(pet, sitk.sitkFloat32) #SimpleITK reads some pixel values as negative but with correct value img_pet = sitk.Abs(img_pet * factor) metadata = {} return cls(img_pet, df, factor, calc, metadata)
def calculate_msd(mask_a, mask_b): """ Calulate mean average surface distance between mask a and b """ mask_b.CopyInformation(mask_a) # masks need to occupy exactly the same space and no spacial information is # saved in the matlab script contour_list = [sitk.LabelContour(m) for m in [mask_a, mask_b]] n_voxel = [] mean_val = [] for a, b in [(0, 1), (1, 0)]: distance_map = sitk.Abs( sitk.SignedMaurerDistanceMap(contour_list[a], squaredDistance=False, useImageSpacing=True)) stat_intensity_filter = sitk.LabelIntensityStatisticsImageFilter() stat_intensity_filter.Execute(contour_list[b], distance_map) n_voxel.append(stat_intensity_filter.GetNumberOfPixels(1)) mean_val.append(stat_intensity_filter.GetMean(1)) # combine the two values to get 'symmetric' values MSD = (n_voxel[0] * mean_val[0] + n_voxel[1] * mean_val[1]) / (n_voxel[0] + n_voxel[1]) return MSD
def evaluate_distance_to_reference(reference_volume, test_volume, resample_factor=1): """ Evaluates the distance from the surface of a test volume to a reference Input: reference_volume: binary volume SimpleITK image test_volume: binary volume SimpleITK image Output: values : the distance to each point on the reference volume surface """ # TO DO # come up with a better resampling strategy # e.g. resample images prior to this process? # compute the distance map from the test volume surface test_distance_map = sitk.Abs( sitk.SignedMaurerDistanceMap(test_volume, squaredDistance=False, useImageSpacing=True)) # get the distance from the test surface to the reference surface ref_surface = sitk.LabelContour(reference_volume) ref_surface_pts = sitk.GetArrayFromImage(ref_surface) == 1 surface_values = sitk.GetArrayFromImage(test_distance_map)[ref_surface_pts] # resample to keep the points to a reasonable amount values = surface_values[::resample_factor] return values
def compute_surface_metrics(label_a, label_b, verbose=False): """Compute surface distance metrics between two labels. Surface metrics computed are: hausdorffDistance, meanSurfaceDistance, medianSurfaceDistance, maximumSurfaceDistance, sigmaSurfaceDistance Args: label_a (sitk.Image): A mask to compare label_b (sitk.Image): Another mask to compare verbose (bool, optional): Whether to print verbose output. Defaults to False. Returns: dict: Dictionary object containing surface distance metrics """ hausdorff_distance = sitk.HausdorffDistanceImageFilter() hausdorff_distance.Execute(label_a, label_b) hd = hausdorff_distance.GetHausdorffDistance() mean_sd_list = [] max_sd_list = [] std_sd_list = [] median_sd_list = [] num_points = [] for (la, lb) in ((label_a, label_b), (label_b, label_a)): label_intensity_stat = sitk.LabelIntensityStatisticsImageFilter() reference_distance_map = sitk.Abs( sitk.SignedMaurerDistanceMap(la, squaredDistance=False, useImageSpacing=True) ) moving_label_contour = sitk.LabelContour(lb) label_intensity_stat.Execute(moving_label_contour, reference_distance_map) mean_sd_list.append(label_intensity_stat.GetMean(1)) max_sd_list.append(label_intensity_stat.GetMaximum(1)) std_sd_list.append(label_intensity_stat.GetStandardDeviation(1)) median_sd_list.append(label_intensity_stat.GetMedian(1)) num_points.append(label_intensity_stat.GetNumberOfPixels(1)) if verbose: print(" Boundary points: {0} {1}".format(num_points[0], num_points[1])) mean_surf_dist = np.dot(mean_sd_list, num_points) / np.sum(num_points) max_surf_dist = np.max(max_sd_list) std_surf_dist = np.sqrt( np.dot( num_points, np.add(np.square(std_sd_list), np.square(np.subtract(mean_sd_list, mean_surf_dist))), ) ) median_surf_dist = np.mean(median_sd_list) result = {} result["hausdorffDistance"] = hd result["meanSurfaceDistance"] = mean_surf_dist result["medianSurfaceDistance"] = median_surf_dist result["maximumSurfaceDistance"] = max_surf_dist result["sigmaSurfaceDistance"] = std_surf_dist return result
def evaluateDistanceToSurface(testVolume, debug=False): """ Evaluates the distance from the origin of a volume to the surface Input: referenceVolume: binary volume SimpleITK image, or alternatively a distance map testVolume: binary volume SimpleITK image Output: theta, phi, values """ centre = np.array( measurements.center_of_mass(sitk.GetArrayFromImage(testVolume)))[::-1] centre_int = tuple(int(i) for i in centre) blank_volume = (0 * testVolume) blank_volume[centre_int] = 1 referenceDistanceMap = sitk.Abs( sitk.SignedMaurerDistanceMap(blank_volume, squaredDistance=False, useImageSpacing=True)) testSurface = sitk.LabelContour(testVolume) distanceImage = sitk.Multiply(referenceDistanceMap, sitk.Cast(testSurface, sitk.sitkFloat32)) distanceArray = sitk.GetArrayFromImage(distanceImage) if debug: print("Distance measures: {0:.2f},{1:.2f},{2:.2f}".format( distanceArray.min(), (distanceArray[distanceArray != 0.0]).mean(), distanceArray.max())) # Calculate centre of mass in real coordinates testSurfaceArray = sitk.GetArrayFromImage(testSurface) testSurfaceLocations = np.where(testSurfaceArray == 1) testSurfaceLocationsArray = np.array(testSurfaceLocations) # Calculate each point on the surface in real coordinates pts = testSurfaceLocationsArray.T ptsReal = vectorisedTransformIndexToPhysicalPoint(testSurface, pts) centreReal = vectorisedTransformIndexToPhysicalPoint( testSurface, centre[::-1]) ptsDiff = ptsReal - centreReal # Convert to spherical polar coordinates - base at north pole rho = np.sqrt((ptsDiff * ptsDiff).sum(axis=1)) theta = np.pi / 2. - np.arccos(ptsDiff.T[0] / rho) phi = np.arctan2(ptsDiff.T[2], -1.0 * ptsDiff.T[1]) # Convert to spherical polar coordinates - base at 0Lat0Lon # rho = np.sqrt((ptsDiff**2).sum(axis=1)) # theta = np.pi/2.-np.arccos(ptsDiff.T[2]/rho) # phi = np.arctan2(ptsDiff.T[1],ptsDiff.T[0]) # Extract values values = distanceArray[testSurfaceLocations] if debug: print(' {0}'.format(values.shape)) return theta, phi, values
def evaluate_distance_on_surface(reference_volume, test_volume, abs_distance=True, reference_as_distance_map=False): """ Evaluates a distance map on a surface Input: reference_volume: binary volume SimpleITK image, or alternatively a distance map test_volume: binary volume SimpleITK image Output: theta, phi, values """ if reference_as_distance_map: reference_distance_map = reference_volume else: if abs_distance: reference_distance_map = sitk.Abs( sitk.SignedMaurerDistanceMap(reference_volume, squaredDistance=False, useImageSpacing=True)) else: reference_distance_map = sitk.SignedMaurerDistanceMap( reference_volume, squaredDistance=False, useImageSpacing=True) test_surface = sitk.LabelContour(test_volume) distance_image = sitk.Multiply(reference_distance_map, sitk.Cast(test_surface, sitk.sitkFloat32)) distance_array = sitk.GetArrayFromImage(distance_image) # Get centre of mass of reference volume reference_volume_array = sitk.GetArrayFromImage(reference_volume) reference_volume_locations = np.where(reference_volume_array == 1) com_index = reference_volume_locations.mean(axis=1) com_real = vectorised_transform_index_to_physical_point( reference_volume, com_index) # Calculate centre of mass in real coordinates test_surface_array = sitk.GetArrayFromImage(test_surface) test_surface_locations = np.where(test_surface_array == 1) test_surface_locations_array = np.array(test_surface_locations) # Calculate each point on the surface in real coordinates pts = test_surface_locations_array.T pts_real = vectorised_transform_index_to_physical_point(test_surface, pts) pts_diff = pts_real - com_real # Convert to spherical polar coordinates - base at north pole rho = np.sqrt((pts_diff * pts_diff).sum(axis=1)) theta = np.pi / 2.0 - np.arccos(pts_diff.T[0] / rho) phi = -1 * np.arctan2(pts_diff.T[2], -1.0 * pts_diff.T[1]) # Extract values values = distance_array[test_surface_locations] return theta, phi, values
def surfaceMetrics(imFixed, imMoving, verbose=False): """ HD, meanSurfDist, medianSurfDist, maxSurfDist, stdSurfDist """ hausdorffDistance = sitk.HausdorffDistanceImageFilter() hausdorffDistance.Execute(imFixed, imMoving) HD = hausdorffDistance.GetHausdorffDistance() meanSDList = [] maxSDList = [] stdSDList = [] medianSDList = [] numPoints = [] for (imA, imB) in ((imFixed, imMoving), (imMoving, imFixed)): labelIntensityStat = sitk.LabelIntensityStatisticsImageFilter() referenceDistanceMap = sitk.Abs( sitk.SignedMaurerDistanceMap(imA, squaredDistance=False, useImageSpacing=True)) movingLabelContour = sitk.LabelContour(imB) labelIntensityStat.Execute(movingLabelContour, referenceDistanceMap) meanSDList.append(labelIntensityStat.GetMean(1)) maxSDList.append(labelIntensityStat.GetMaximum(1)) stdSDList.append(labelIntensityStat.GetStandardDeviation(1)) medianSDList.append(labelIntensityStat.GetMedian(1)) numPoints.append(labelIntensityStat.GetNumberOfPixels(1)) if verbose: print(" Boundary points: {0} {1}".format( numPoints[0], numPoints[1])) meanSurfDist = np.dot(meanSDList, numPoints) / np.sum(numPoints) maxSurfDist = np.max(maxSDList) stdSurfDist = np.sqrt( np.dot( numPoints, np.add(np.square(stdSDList), np.square(np.subtract(meanSDList, meanSurfDist))))) medianSurfDist = np.mean(medianSDList) resultDict = {} resultDict['hausdorffDistance'] = HD resultDict['meanSurfaceDistance'] = meanSurfDist resultDict['medianSurfaceDistance'] = medianSurfDist resultDict['maximumSurfaceDistance'] = maxSurfDist resultDict['sigmaSurfaceDistance'] = stdSurfDist return resultDict
def assertImageAlmostEqual(self, img1, img2, max_difference=1e-8, msg=None): """ Compare with the maximum absolute difference between two images """ minMaxFilter = sitk.StatisticsImageFilter() minMaxFilter.Execute(sitk.Abs(img1 - img2)) if (minMaxFilter.GetMaximum() > max_difference): print("min: ", minMaxFilter.GetMinimum(), " max: ", minMaxFilter.GetMaximum(), " mean: ", minMaxFilter.GetMean()) self.fail(msg)
def compute_metric_masd(imFixed, imMoving): meanSDList = [] numPoints = [] for (imA, imB) in ((imFixed, imMoving), (imMoving, imFixed)): labelIntensityStat = sitk.LabelIntensityStatisticsImageFilter() referenceDistanceMap = sitk.Abs( sitk.SignedMaurerDistanceMap(imA, squaredDistance=False, useImageSpacing=True)) movingLabelContour = sitk.LabelContour(imB) labelIntensityStat.Execute(movingLabelContour, referenceDistanceMap) meanSDList.append(labelIntensityStat.GetMean(1)) numPoints.append(labelIntensityStat.GetNumberOfPixels(1)) meanSurfDist = np.dot(meanSDList, numPoints) / np.sum(numPoints) return meanSurfDist
def computeSurfaceDistance(labelImageReference, labelImageMeasure): """ Calculate the surface-to-surface distance from a reference label to test label Returns an array of surface distances - This can be used to calculate mean surface distance, Haussdorf distance, etc. """ labelImageReference = sitk.Cast(labelImageReference, sitk.sitkUInt8) labelImageMeasure = sitk.Cast(labelImageMeasure, sitk.sitkUInt8) referenceDistanceMap = sitk.GetArrayFromImage( sitk.Abs( sitk.SignedMaurerDistanceMap(labelImageReference, squaredDistance=False, useImageSpacing=True))) labelImageMeasureContour = sitk.GetArrayFromImage( sitk.LabelContour(labelImageMeasure)) surfaceDistanceArr = referenceDistanceMap[np.where( labelImageMeasureContour == 1)] return surfaceDistanceArr
def ITK(segmentation, reference_segmentation): surface_distance_results = np.zeros((1, len(SurfaceDistanceMeasures.__members__.items()))) segmented_surface = sitk.LabelContour(segmentation) reference_distance_map = sitk.Abs(sitk.SignedMaurerDistanceMap(reference_segmentation, squaredDistance=False, useImageSpacing=True)) label_intensity_statistics_filter = sitk.LabelIntensityStatisticsImageFilter() label_intensity_statistics_filter.Execute(segmented_surface, reference_distance_map) hausdorff_distance_filter = sitk.HausdorffDistanceImageFilter() hausdorff_distance_filter.Execute(reference_segmentation, segmentation) surface_distance_results[0, SurfaceDistanceMeasures.hausdorff_distance.value] = hausdorff_distance_filter.GetHausdorffDistance() surface_distance_results[0, SurfaceDistanceMeasures.max_surface_distance.value] = label_intensity_statistics_filter.GetMaximum(1) surface_distance_results[0, SurfaceDistanceMeasures.avg_surface_distance.value] = label_intensity_statistics_filter.GetMean(1) surface_distance_results[0, SurfaceDistanceMeasures.median_surface_distance.value] = label_intensity_statistics_filter.GetMedian(1) surface_distance_results[0, SurfaceDistanceMeasures.std_surface_distance.value] = label_intensity_statistics_filter.GetStandardDeviation(1) # surface_distance_results_df = pd.DataFrame(data=surface_distance_results, index = list(range(1)), # columns=[name for name, _ in SurfaceDistanceMeasuresITK.__members__.items()]) return surface_distance_results
def compute_metric_masd(label_a, label_b, auto_crop=True): """Compute the mean absolute distance between two labels Args: label_a (sitk.Image): A mask to compare label_b (sitk.Image): Another mask to compare Returns: float: The mean absolute surface distance """ if ( sitk.GetArrayViewFromImage(label_a).sum() == 0 or sitk.GetArrayViewFromImage(label_b).sum() == 0 ): return np.nan if auto_crop: largest_region = (label_a + label_b) > 0 crop_box_size, crop_box_index = label_to_roi(largest_region) label_a = crop_to_roi(label_a, size=crop_box_size, index=crop_box_index) label_b = crop_to_roi(label_b, size=crop_box_size, index=crop_box_index) mean_sd_list = [] num_points = [] for (la, lb) in ((label_a, label_b), (label_b, label_a)): label_intensity_stat = sitk.LabelIntensityStatisticsImageFilter() reference_distance_map = sitk.Abs( sitk.SignedMaurerDistanceMap(la, squaredDistance=False, useImageSpacing=True) ) moving_label_contour = sitk.LabelContour(lb) label_intensity_stat.Execute(moving_label_contour, reference_distance_map) mean_sd_list.append(label_intensity_stat.GetMean(1)) num_points.append(label_intensity_stat.GetNumberOfPixels(1)) mean_surf_dist = np.dot(mean_sd_list, num_points) / np.sum(num_points) return float(mean_surf_dist)
def evaluateDistanceOnSurface(referenceVolume, testVolume, debug=False, absDistance=True, referenceAsDistanceMap=False): """ Evaluates a distance map on a surface Input: referenceVolume: binary volume SimpleITK image, or alternatively a distance map testVolume: binary volume SimpleITK image Output: theta, phi, values """ if referenceAsDistanceMap: referenceDistanceMap = referenceVolume else: if absDistance: referenceDistanceMap = sitk.Abs( sitk.SignedMaurerDistanceMap(referenceVolume, squaredDistance=False, useImageSpacing=True)) else: referenceDistanceMap = sitk.SignedMaurerDistanceMap( referenceVolume, squaredDistance=False, useImageSpacing=True) testSurface = sitk.LabelContour(testVolume) distanceImage = sitk.Multiply(referenceDistanceMap, sitk.Cast(testSurface, sitk.sitkFloat32)) distanceArray = sitk.GetArrayFromImage(distanceImage) if debug: print("Distance measures: {0:.2f},{1:.2f},{2:.2f}".format( distanceArray.min(), (distanceArray[distanceArray != 0.0]).mean(), distanceArray.max())) # Calculate centre of mass in real coordinates testSurfaceArray = sitk.GetArrayFromImage(testSurface) testSurfaceLocations = np.where(testSurfaceArray == 1) testSurfaceLocationsArray = np.array(testSurfaceLocations) COMIndex = testSurfaceLocationsArray.mean(axis=1) COMReal = vectorisedTransformIndexToPhysicalPoint(testSurface, COMIndex) # Calculate each point on the surface in real coordinates pts = testSurfaceLocationsArray.T ptsReal = vectorisedTransformIndexToPhysicalPoint(testSurface, pts) ptsDiff = ptsReal - COMReal # Convert to spherical polar coordinates - base at north pole rho = np.sqrt((ptsDiff * ptsDiff).sum(axis=1)) theta = np.pi / 2. - np.arccos(ptsDiff.T[0] / rho) phi = np.arctan2(ptsDiff.T[2], -1.0 * ptsDiff.T[1]) # Convert to spherical polar coordinates - base at 0Lat0Lon # rho = np.sqrt((ptsDiff**2).sum(axis=1)) # theta = np.pi/2.-np.arccos(ptsDiff.T[2]/rho) # phi = np.arctan2(ptsDiff.T[1],ptsDiff.T[0]) # Extract values values = distanceArray[testSurfaceLocations] if debug: print(' {0}'.format(values.shape)) return theta, phi, values
def computeQualityMeasures(lP, lT, spacing): """ :param lP: prediction, shape (x, y, z) :param lT: ground truth, shape (x, y, z) :param spacing: shape order (x, y, z) :return: quality: dict contains metircs """ pred = lP.astype(int) # float data does not support bit_and and bit_or gdth = lT.astype(int) # float data does not support bit_and and bit_or fp_array = copy.deepcopy(pred) # keep pred unchanged fn_array = copy.deepcopy(gdth) gdth_sum = np.sum(gdth) pred_sum = np.sum(pred) intersection = gdth & pred union = gdth | pred intersection_sum = np.count_nonzero(intersection) union_sum = np.count_nonzero(union) tp_array = intersection tmp = pred - gdth fp_array[tmp < 1] = 0 tmp2 = gdth - pred fn_array[tmp2 < 1] = 0 tn_array = np.ones(gdth.shape) - union tp, fp, fn, tn = np.sum(tp_array), np.sum(fp_array), np.sum(fn_array), np.sum(tn_array) smooth = 0.001 precision = tp / (pred_sum + smooth) recall = tp / (gdth_sum + smooth) false_positive_rate = fp / (fp + tn + smooth) false_negtive_rate = fn / (fn + tp + smooth) jaccard = intersection_sum / (union_sum + smooth) dice = 2 * intersection_sum / (gdth_sum + pred_sum + smooth) quality = dict() labelPred = sitk.GetImageFromArray(lP, isVector=False) labelPred.SetSpacing(spacing) labelTrue = sitk.GetImageFromArray(lT, isVector=False) labelTrue.SetSpacing(spacing) # spacing order (x, y, z) # Dice,Jaccard,Volume Similarity.. dicecomputer = sitk.LabelOverlapMeasuresImageFilter() dicecomputer.Execute(labelTrue > 0.5, labelPred > 0.5) quality["dice"] = dice quality["jaccard"] = jaccard quality["precision"] = precision quality["recall"] = recall quality["false_negtive_rate"] = false_negtive_rate quality["false_positive_rate"] = false_positive_rate quality["volume_similarity"] = dicecomputer.GetVolumeSimilarity() slice_idx = 300 # Surface distance measures signed_distance_map = sitk.SignedMaurerDistanceMap(labelTrue > 0.5, squaredDistance=False, useImageSpacing=True) # It need to be adapted. # show_itk(signed_distance_map, slice_idx) ref_distance_map = sitk.Abs(signed_distance_map) # show_itk(ref_distance_map, slice_idx) ref_surface = sitk.LabelContour(labelTrue > 0.5, fullyConnected=True) # show_itk(ref_surface, slice_idx) ref_surface_array = sitk.GetArrayViewFromImage(ref_surface) statistics_image_filter = sitk.StatisticsImageFilter() statistics_image_filter.Execute(ref_surface > 0.5) num_ref_surface_pixels = int(statistics_image_filter.GetSum()) signed_distance_map_pred = sitk.SignedMaurerDistanceMap(labelPred > 0.5, squaredDistance=False, useImageSpacing=True) # show_itk(signed_distance_map_pred, slice_idx) seg_distance_map = sitk.Abs(signed_distance_map_pred) # show_itk(seg_distance_map, slice_idx) seg_surface = sitk.LabelContour(labelPred > 0.5, fullyConnected=True) # show_itk(seg_surface, slice_idx) seg_surface_array = sitk.GetArrayViewFromImage(seg_surface) seg2ref_distance_map = ref_distance_map * sitk.Cast(seg_surface, sitk.sitkFloat32) # show_itk(seg2ref_distance_map, slice_idx) ref2seg_distance_map = seg_distance_map * sitk.Cast(ref_surface, sitk.sitkFloat32) # show_itk(ref2seg_distance_map, slice_idx) statistics_image_filter.Execute(seg_surface > 0.5) num_seg_surface_pixels = int(statistics_image_filter.GetSum()) seg2ref_distance_map_arr = sitk.GetArrayViewFromImage(seg2ref_distance_map) seg2ref_distances = list(seg2ref_distance_map_arr[seg2ref_distance_map_arr != 0]) seg2ref_distances = seg2ref_distances + list(np.zeros(num_seg_surface_pixels - len(seg2ref_distances))) ref2seg_distance_map_arr = sitk.GetArrayViewFromImage(ref2seg_distance_map) ref2seg_distances = list(ref2seg_distance_map_arr[ref2seg_distance_map_arr != 0]) ref2seg_distances = ref2seg_distances + list(np.zeros(num_ref_surface_pixels - len(ref2seg_distances))) # all_surface_distances = seg2ref_distances + ref2seg_distances quality["mean_surface_distance"] = np.mean(all_surface_distances) quality["median_surface_distance"] = np.median(all_surface_distances) quality["std_surface_distance"] = np.std(all_surface_distances) quality["95_surface_distance"] = np.percentile(all_surface_distances, 95) quality["Hausdorff"] = np.max(all_surface_distances) return quality
def run_iar(atlas_set, structure_name, smooth_maps=False, smooth_sigma=1, z_score="MAD", outlier_method="IQR", min_best_atlases=10, n_factor=1.5, iteration=0, single_step=False, project_on_sphere=False): """ Perform iterative atlas removal on the atlas_set """ if iteration == 0: # Run some checks in the data # If there is a MAJOR error we need to check # Begin the process logger.info("Iterative atlas removal: ") logger.info(" Beginning process") # Get remaining case identifiers to loop through remaining_id_list = list(atlas_set.keys()) # Generate the surface projections # 1. Set the consensus surface using the reference volume probability_label = combine_labels(atlas_set, structure_name)[structure_name] # Modify resolution for better statistics if project_on_sphere: if len(remaining_id_list) < 12: logger.info(" Less than 12 atlases, resolution set: 3x3 sqr deg") resolution = 3 elif len(remaining_id_list) < 7: logger.info(" Less than 7 atlases, resolution set: 6x6 sqr deg") resolution = 6 else: resolution = 1 else: if len(remaining_id_list) < 12: logger.info(" Less than 12 atlases, resample factor set: 5") resample_factor = 5 elif len(remaining_id_list) < 7: logger.info(" Less than 7 atlases, resolution set: 6x6 sqr deg") resample_factor = 10 else: resample_factor = 1 g_val_list = [] logger.info(" Calculating surface distance maps: ") for test_id in remaining_id_list: logger.info(" {0}".format(test_id)) # 2. Calculate the distance from the surface to the consensus surface test_volume = atlas_set[test_id]["DIR"][structure_name] # This next step ensures non-binary labels are treated properly # We use 0.1 to capture the outer edge of the test delineation, if it is probabilistic test_volume = process_probability_image(test_volume, 0.1) if project_on_sphere: reference_volume = process_probability_image(probability_label, threshold=0.999) # note: we use a threshold slightly below 1 to ensure the consensus (reference) volume is a suitable binary volume # Compute the reference distance map reference_distance_map = sitk.Abs( sitk.SignedMaurerDistanceMap(reference_volume, squaredDistance=False, useImageSpacing=True)) # Compute the distance to test surfaces, across the surface of the reference theta, phi, values = evaluate_distance_on_surface( reference_distance_map, test_volume, reference_as_distance_map=True) _, _, g_vals = regrid_spherical_data(theta, phi, values, resolution=resolution) g_val_list.append(g_vals) else: reference_volume = process_probability_image(probability_label, threshold=0.95) # note: we use a threshold slightly below 1 to ensure the consensus (reference) volume is a suitable binary volume # we have the flexibility to modify the reference volume when we do not use spherical projection # a larger surface means more evaluations and better statistics, so we prefer a lower threshold # but not too low, or it may include some errors # Compute distance to reference, from the test volume values = evaluate_distance_to_reference( reference_volume, test_volume, resample_factor=resample_factor) g_val_list.append(values) q_results = {} for i, (test_id, g_vals) in enumerate(zip(remaining_id_list, g_val_list)): g_val_list_test = g_val_list[:] g_val_list_test.pop(i) if project_on_sphere and smooth_maps: g_vals = filters.gaussian_filter(g_vals, sigma=smooth_sigma, mode="wrap") # b) i] Compute the Z-scores over the projected surface if z_score.lower() == "std": g_val_mean = np.mean(g_val_list_test, axis=0) g_val_std = np.std(g_val_list_test, axis=0) if np.any(g_val_std == 0): logger.info(" Std Dev zero count: {0}".format( np.sum(g_val_std == 0))) g_val_std[g_val_std == 0] = g_val_std.mean() z_score_vals_array = (g_vals - g_val_mean) / g_val_std elif z_score.lower() == "mad": g_val_median = np.median(g_val_list_test, axis=0) g_val_mad = 1.4826 * median_absolute_deviation(g_val_list_test, axis=0) if np.any(~np.isfinite(g_val_mad)): logger.info("Error in MAD") logger.info(g_val_mad) if np.any(g_val_mad == 0): logger.info(" MAD zero count: {0}".format( np.sum(g_val_mad == 0))) g_val_mad[g_val_mad == 0] = np.median(g_val_mad) z_score_vals_array = (g_vals - g_val_median) / g_val_mad else: logger.error(" Error!") logger.error(" z_score must be one of: MAD, STD") sys.exit() z_score_vals = np.ravel(z_score_vals_array) logger.debug(" [{0}] Statistics of mZ-scores".format(test_id)) logger.debug(" Min(Z) = {0:.2f}".format(z_score_vals.min())) logger.debug(" Q1(Z) = {0:.2f}".format( np.percentile(z_score_vals, 25))) logger.debug(" Mean(Z) = {0:.2f}".format(z_score_vals.mean())) logger.debug(" Median(Z) = {0:.2f}".format( np.percentile(z_score_vals, 50))) logger.debug(" Q3(Z) = {0:.2f}".format( np.percentile(z_score_vals, 75))) logger.debug(" Max(Z) = {0:.2f}\n".format( z_score_vals.max())) # Calculate excess area from Gaussian: the Q-metric bins = np.linspace(-15, 15, 501) z_density, bin_edges = np.histogram(z_score_vals, bins=bins, density=True) bin_centers = (bin_edges[1:] + bin_edges[:-1]) / 2.0 try: popt, _ = curve_fit(f=gaussian_curve, xdata=bin_centers, ydata=z_density) z_ideal = gaussian_curve(bin_centers, *popt) z_diff = np.abs(z_density - z_ideal) except: logger.debug( 'IAR couldnt fit curve, estimating with sampled statistics.') z_ideal = gaussian_curve(bin_centers, a=1, m=z_density.mean(), s=z_density.std()) z_diff = np.abs(z_density - z_ideal) # Integrate to get the q_value q_value = np.trapz(z_diff * np.abs(bin_centers)**2, bin_centers) q_results[test_id] = np.float64(q_value) # Exclude (at most) the worst 3 atlases for outlier detection # With a minimum number, this helps provide more robust estimates at low numbers result_list = list(q_results.values()) best_results = np.sort(result_list)[:max( [min_best_atlases, len(result_list) - 3])] if outlier_method.lower() == "iqr": outlier_limit = np.percentile( best_results, 75, axis=0) + n_factor * np.subtract( *np.percentile(best_results, [75, 25], axis=0)) elif outlier_method.lower() == "std": outlier_limit = np.mean( best_results, axis=0) + n_factor * np.std(best_results, axis=0) else: logger.error(" Error!") logger.error(" outlier_method must be one of: IQR, STD") sys.exit() logger.info(" Analysing results") logger.info(" Outlier limit: {0:06.3f}".format(outlier_limit)) keep_id_list = [] logger.info("{0},{1},{2},{3:.4g}\n".format( iteration, " ".join(remaining_id_list), " ".join(["{0:.4g}".format(i) for i in list(q_results.values())]), outlier_limit, )) for idx, result in q_results.items(): accept = result <= outlier_limit logger.info(" {0}: Q = {1:06.3f} [{2}]".format( idx, result, { True: "KEEP", False: "REMOVE" }[accept])) if accept: keep_id_list.append(idx) if len(keep_id_list) < len(remaining_id_list): logger.info("\n Step {0} Complete".format(iteration)) logger.info(" Num. Removed = {0} --\n".format( len(remaining_id_list) - len(keep_id_list))) iteration += 1 atlas_set_new = {i: atlas_set[i] for i in keep_id_list} if single_step: return atlas_set_new return run_iar(atlas_set=atlas_set_new, structure_name=structure_name, smooth_maps=smooth_maps, smooth_sigma=smooth_sigma, z_score=z_score, outlier_method=outlier_method, min_best_atlases=min_best_atlases, n_factor=n_factor, iteration=iteration, project_on_sphere=project_on_sphere) logger.info(" End point reached. Keeping:\n {0}".format(keep_id_list)) return atlas_set
def Evaluate(reference_segmentation_f, segmentations_f): # Empty numpy arrays to hold the results reference_segmentation = reference_segmentation_f #sitk.GetImageFromArray(reference_segmentation_f, isVector=False) segmentations = segmentations_f # sitk.GetImageFromArray(segmentations_f,isVector=False) segmentations = sitk.BinaryThreshold(segmentations, lowerThreshold=0.5, upperThreshold=1) reference_segmentation = sitk.BinaryThreshold(reference_segmentation, lowerThreshold=1, upperThreshold=1) overlap_results = np.zeros( (len(segmentations), len(OverlapMeasures.__members__.items()))) surface_distance_results = np.zeros( (len(segmentations), len(SurfaceDistanceMeasures.__members__.items()))) hausdorff_distance_filter = sitk.HausdorffDistanceImageFilter() label = 1 reference_distance_map = sitk.Abs( sitk.SignedMaurerDistanceMap(reference_segmentation, squaredDistance=False)) reference_surface = sitk.LabelContour(reference_segmentation) statistics_image_filter = sitk.StatisticsImageFilter() statistics_image_filter.Execute(reference_surface) num_reference_surface_pixels = int(statistics_image_filter.GetSum()) #for i, seg in enumerate(segmentations): # Overlap measures seg = segmentations i = 0 overlap_measures_filter = sitk.LabelOverlapMeasuresImageFilter() overlap_measures_filter.Execute(reference_segmentation, seg) results_x['Dice'].append(overlap_measures_filter.GetDiceCoefficient()) results_x['Jaccard'].append( overlap_measures_filter.GetJaccardCoefficient()) results_x['Similarity'].append( overlap_measures_filter.GetVolumeSimilarity()) results_x['GetFalseNegativeError'].append( overlap_measures_filter.GetFalseNegativeError()) results_x['GetFalsePositiveError'].append( overlap_measures_filter.GetFalsePositiveError()) print('Dice', overlap_measures_filter.GetDiceCoefficient()) print('Jaccard', overlap_measures_filter.GetJaccardCoefficient()) print('Similarity', overlap_measures_filter.GetVolumeSimilarity()) print('GetFalseNegativeError', overlap_measures_filter.GetFalseNegativeError()) print('GetFalsePositiveError', overlap_measures_filter.GetFalsePositiveError()) overlap_results[i, OverlapMeasures.jaccard. value] = overlap_measures_filter.GetJaccardCoefficient() overlap_results[i, OverlapMeasures.dice. value] = overlap_measures_filter.GetDiceCoefficient() overlap_results[i, OverlapMeasures.volume_similarity. value] = overlap_measures_filter.GetVolumeSimilarity() overlap_results[i, OverlapMeasures.false_negative. value] = overlap_measures_filter.GetFalseNegativeError() overlap_results[i, OverlapMeasures.false_positive. value] = overlap_measures_filter.GetFalsePositiveError() # Hausdorff distance hausdorff_distance_filter.Execute(reference_segmentation, seg) surface_distance_results[ i, SurfaceDistanceMeasures.hausdorff_distance. value] = hausdorff_distance_filter.GetHausdorffDistance() print('Hausdorff', hausdorff_distance_filter.GetHausdorffDistance()) results_x['Hausdorff'].append( hausdorff_distance_filter.GetHausdorffDistance()) # Symmetric surface distance measures segmented_distance_map = sitk.Abs( sitk.SignedMaurerDistanceMap(seg, squaredDistance=False)) segmented_surface = sitk.LabelContour(seg) # Multiply the binary surface segmentations with the distance maps. The resulting distance # maps contain non-zero values only on the surface (they can also contain zero on the surface) seg2ref_distance_map = reference_distance_map * sitk.Cast( segmented_surface, sitk.sitkFloat32) ref2seg_distance_map = segmented_distance_map * sitk.Cast( reference_surface, sitk.sitkFloat32) # Get the number of pixels in the reference surface by counting all pixels that are 1. statistics_image_filter.Execute(segmented_surface) num_segmented_surface_pixels = int(statistics_image_filter.GetSum()) # Get all non-zero distances and then add zero distances if required. seg2ref_distance_map_arr = sitk.GetArrayViewFromImage(seg2ref_distance_map) seg2ref_distances = list( seg2ref_distance_map_arr[seg2ref_distance_map_arr != 0]) seg2ref_distances = seg2ref_distances + \ list(np.zeros(num_segmented_surface_pixels - len(seg2ref_distances))) ref2seg_distance_map_arr = sitk.GetArrayViewFromImage(ref2seg_distance_map) ref2seg_distances = list( ref2seg_distance_map_arr[ref2seg_distance_map_arr != 0]) ref2seg_distances = ref2seg_distances + \ list(np.zeros(num_reference_surface_pixels - len(ref2seg_distances))) all_surface_distances = seg2ref_distances + ref2seg_distances surface_distance_results[i, SurfaceDistanceMeasures.mean_surface_distance. value] = np.mean(all_surface_distances) surface_distance_results[i, SurfaceDistanceMeasures.median_surface_distance. value] = np.median(all_surface_distances) surface_distance_results[i, SurfaceDistanceMeasures.std_surface_distance. value] = np.std(all_surface_distances) surface_distance_results[i, SurfaceDistanceMeasures.max_surface_distance. value] = np.max(all_surface_distances) print('mean_surface_distance', np.mean(all_surface_distances)) print('median_surface_distance', np.median(all_surface_distances)) print('std_surface_distance', np.std(all_surface_distances)) print('max_surface_distance', np.max(all_surface_distances)) results_x['mean_surface_distance'].append(np.mean(all_surface_distances)) results_x['median_surface_distance'].append( np.median(all_surface_distances)) results_x['std_surface_distance'].append(np.std(all_surface_distances)) results_x['max_surface_distance'].append(np.max(all_surface_distances)) # Print the matrices np.set_printoptions(precision=3) # Graft our results matrix into pandas data frames overlap_results_df = pd.DataFrame( data=overlap_results, index=list(range(len(segmentations))), columns=[name for name, _ in OverlapMeasures.__members__.items()]) surface_distance_results_df = pd.DataFrame( data=surface_distance_results, index=list(range(len(segmentations))), columns=[ name for name, _ in SurfaceDistanceMeasures.__members__.items() ])
path = os.path.join(SAVE_PATH, val) save_image(result, ref_affine, save_path=path) dices = [] hds = [] asds = [] jaccards = [] for i in label_map: overlap_measures_filter = sitk.LabelOverlapMeasuresImageFilter() hausdorff_distance_filter = sitk.HausdorffDistanceImageFilter() statistics_image_filter = sitk.StatisticsImageFilter() seg_map = (result == i) * 1 gt_map = (val_gt == i) * 1 seg_map = sitk.GetImageFromArray(seg_map) gt_map = sitk.GetImageFromArray(gt_map) seg_distance_map = sitk.Abs( sitk.SignedMaurerDistanceMap(seg_map, squaredDistance=False)) seg_surface = sitk.LabelContour(seg_map) gt_distance_map = sitk.Abs( sitk.SignedMaurerDistanceMap(gt_map, squaredDistance=False)) gt_surface = sitk.LabelContour(gt_map) statistics_image_filter.Execute(gt_surface) num_reference_surface_pixels = int( statistics_image_filter.GetSum()) statistics_image_filter.Execute(seg_surface) num_segmented_surface_pixels = int( statistics_image_filter.GetSum()) overlap_measures_filter.Execute(gt_map, seg_map) dice = overlap_measures_filter.GetDiceCoefficient()
def computeQualityMeasures(lP, lT): quality = dict() labelPred = sitk.GetImageFromArray(lP, isVector=False) labelTrue = sitk.GetImageFromArray(lT, isVector=False) #Hausdorff Distance hausdorffcomputer = sitk.HausdorffDistanceImageFilter() hausdorffcomputer.Execute(labelTrue > 0.5, labelPred > 0.5) quality["avgHausdorff"] = hausdorffcomputer.GetAverageHausdorffDistance() quality["Hausdorff"] = hausdorffcomputer.GetHausdorffDistance() AvgHausdorff_list.append(quality["avgHausdorff"]) Hausdorff_list.append(quality["Hausdorff"]) #Dice,Jaccard,Volume Similarity.. dicecomputer = sitk.LabelOverlapMeasuresImageFilter() dicecomputer.Execute(labelTrue > 0.5, labelPred > 0.5) quality["dice"] = dicecomputer.GetDiceCoefficient() quality["jaccard"] = dicecomputer.GetJaccardCoefficient() quality["volume_similarity"] = dicecomputer.GetVolumeSimilarity() quality["false_negative"] = dicecomputer.GetFalseNegativeError() quality["false_positive"] = dicecomputer.GetFalsePositiveError() Dice_list.append(quality["dice"]) Jaccard_list.append(quality["jaccard"]) Volume_list.append(quality["volume_similarity"]) False_negative_list.append(quality["false_negative"]) False_positive_list.append(quality["false_positive"]) #Surface distance measures label = 1 ref_distance_map = sitk.Abs( sitk.SignedMaurerDistanceMap(labelTrue > 0.5, squaredDistance=False)) ref_surface = sitk.LabelContour(labelTrue > 0.5) statistics_image_filter = sitk.StatisticsImageFilter() statistics_image_filter.Execute(labelTrue > 0.5) num_ref_surface_pixels = int(statistics_image_filter.GetSum()) seg_distance_map = sitk.Abs( sitk.SignedMaurerDistanceMap(labelPred > 0.5, squaredDistance=False)) seg_surface = sitk.LabelContour(labelPred > 0.5) seg2ref_distance_map = ref_distance_map * sitk.Cast( seg_surface, sitk.sitkFloat32) ref2seg_distance_map = seg_distance_map * sitk.Cast( ref_surface, sitk.sitkFloat32) statistics_image_filter.Execute(labelPred > 0.5) num_seg_surface_pixels = int(statistics_image_filter.GetSum()) seg2ref_distance_map_arr = sitk.GetArrayViewFromImage(seg2ref_distance_map) seg2ref_distances = list( seg2ref_distance_map_arr[seg2ref_distance_map_arr != 0]) seg2ref_distances = seg2ref_distances + list( np.zeros(num_seg_surface_pixels - len(seg2ref_distances))) ref2seg_distance_map_arr = sitk.GetArrayViewFromImage(ref2seg_distance_map) ref2seg_distances = list( ref2seg_distance_map_arr[ref2seg_distance_map_arr != 0]) ref2seg_distances = ref2seg_distances + list( np.zeros(num_ref_surface_pixels - len(ref2seg_distances))) all_surface_distances = seg2ref_distances + ref2seg_distances quality["mean_surface_distance"] = np.mean(all_surface_distances) quality["median_surface_distance"] = np.median(all_surface_distances) quality["std_surface_distance"] = np.std(all_surface_distances) quality["max_surface_distance"] = np.max(all_surface_distances) mean_surface_dis_list.append(quality["mean_surface_distance"]) median_surface_dis_list.append(quality["median_surface_distance"]) std_surface_dis_list.append(quality["std_surface_distance"]) max_surface_dis_list.append(quality["max_surface_distance"]) return quality
def get_sum_metrics(batch_output, batch_target, metrics_type, test=False, printDice=False): if torch.is_tensor(batch_output): batch_output = batch_output.data.cpu().numpy() if torch.is_tensor(batch_target): batch_target = batch_target.data.cpu().numpy() assert batch_output.shape == batch_target.shape assert len(batch_output.shape) == 4 spacing = (1, 1) size = batch_output.shape[0] metrics = dict.fromkeys(metrics_type, 0) dices = [] for i in range(size): output = batch_output[i, 0] target = batch_target[i, 0] labelPred = sitk.GetImageFromArray(output, isVector=False) labelPred.SetSpacing(spacing) labelTrue = sitk.GetImageFromArray(target, isVector=False) labelTrue.SetSpacing(spacing) # spacing order (x, y, z) # voxel_metrics pred = output.astype(int) gdth = target.astype(int) fp_array = copy.deepcopy(pred) # keep pred unchanged fn_array = copy.deepcopy(gdth) gdth_sum = np.sum(gdth) pred_sum = np.sum(pred) intersection = gdth & pred union = gdth | pred intersection_sum = np.count_nonzero(intersection) union_sum = np.count_nonzero(union) tp_array = intersection tmp = pred - gdth fp_array[tmp < 1] = 0 tmp2 = gdth - pred fn_array[tmp2 < 1] = 0 tn_array = np.ones(gdth.shape) - union tp, fp, fn, tn = np.sum(tp_array), np.sum(fp_array), np.sum( fn_array), np.sum(tn_array) smooth = EPSILON precision = (tp) / (pred_sum + smooth) recall = (tp) / (gdth_sum + smooth) false_positive_rate = (fp) / (fp + tn + smooth) false_negtive_rate = (fn) / (fn + tp + smooth) jaccard = (intersection_sum) / (union_sum + smooth) dice = (2 * intersection_sum) / (gdth_sum + pred_sum + smooth) ppv = (intersection_sum) / (pred_sum + smooth) dicecomputer = sitk.LabelOverlapMeasuresImageFilter() dicecomputer.Execute(labelTrue > 0.5, labelPred > 0.5) # distance_metrics signed_distance_map = sitk.SignedMaurerDistanceMap( labelTrue > 0.5, squaredDistance=False, useImageSpacing=True) # It need to be adapted. ref_distance_map = sitk.Abs(signed_distance_map) ref_surface = sitk.LabelContour(labelTrue > 0.5, fullyConnected=True) statistics_image_filter = sitk.StatisticsImageFilter() statistics_image_filter.Execute(ref_surface > 0.5) num_ref_surface_pixels = int(statistics_image_filter.GetSum()) signed_distance_map_pred = sitk.SignedMaurerDistanceMap( labelPred > 0.5, squaredDistance=False, useImageSpacing=True) seg_distance_map = sitk.Abs(signed_distance_map_pred) seg_surface = sitk.LabelContour(labelPred > 0.5, fullyConnected=True) seg2ref_distance_map = ref_distance_map * sitk.Cast( seg_surface, sitk.sitkFloat32) ref2seg_distance_map = seg_distance_map * sitk.Cast( ref_surface, sitk.sitkFloat32) statistics_image_filter.Execute(seg_surface > 0.5) num_seg_surface_pixels = int(statistics_image_filter.GetSum()) seg2ref_distance_map_arr = sitk.GetArrayViewFromImage( seg2ref_distance_map) seg2ref_distances = list( seg2ref_distance_map_arr[seg2ref_distance_map_arr != 0]) seg2ref_distances = seg2ref_distances + list( np.zeros(num_seg_surface_pixels - len(seg2ref_distances))) ref2seg_distance_map_arr = sitk.GetArrayViewFromImage( ref2seg_distance_map) ref2seg_distances = list( ref2seg_distance_map_arr[ref2seg_distance_map_arr != 0]) ref2seg_distances = ref2seg_distances + list( np.zeros(num_ref_surface_pixels - len(ref2seg_distances))) # all_surface_distances = seg2ref_distances + ref2seg_distances metrics['dice'] += dice metrics['jaccard'] += jaccard metrics['precision'] += precision metrics['recall'] += recall metrics['fpr'] += false_positive_rate metrics['fnr'] += false_negtive_rate metrics['vs'] += dicecomputer.GetVolumeSimilarity() metrics['ppv'] += ppv metrics["msd"] += np.mean(all_surface_distances) metrics["mdsd"] += np.median(all_surface_distances) metrics["stdsd"] += np.std(all_surface_distances) metrics["hd95"] += np.percentile(all_surface_distances, 95) metrics["hd"] += np.max(all_surface_distances) if printDice: dices.append(dice) if printDice: return metrics, dices return metrics
def perform_spot_measurements(self): def get_ee_distances(distance_stats_filter, map: bool = False): labels_ = [] edge_distances = [] size_um = [] size_pixels = [] for label in distance_stats_filter.GetLabels(): # Using minimum for each label gives us edge to edge distance if map: labels_.append(self.colocalization_map[label].__str__()) edge_distances.append( distance_stats_filter.GetMinimum(label)) size_um.append( distance_stats_filter.GetPhysicalSize(label=label)) size_pixels.append( distance_stats_filter.GetNumberOfPixels(label=label)) else: labels_.append(label) edge_distances.append( distance_stats_filter.GetMinimum(label)) size_um.append( distance_stats_filter.GetPhysicalSize(label=label)) size_pixels.append( distance_stats_filter.GetNumberOfPixels(label=label)) # Construct dataframe df = pd.DataFrame(list( zip(labels_, edge_distances, size_pixels, size_um)), columns=[ "Labels", 'edge edge distance to DAPI [um]', "# Voxels", "Size [um]" ]) return df def get_cc_distances(shape_stats_filter, map: bool = False): if map: labels, centroids = zip( *[(self.colocalization_map[label].__str__(), shape_stats_filter.GetCentroid(label)) for label in shape_stats_filter.GetLabels()]) else: labels, centroids = zip( *[(label, shape_stats_filter.GetCentroid(label)) for label in shape_stats_filter.GetLabels()]) centroids = np.array(centroids) dapi_stats_filter = sitk.LabelShapeStatisticsImageFilter() dapi_stats_filter.Execute(self.channel_images["Blue"].labelled) nuclei_labels, nuclei_centroids = zip( *[(nucleus, dapi_stats_filter.GetCentroid(nucleus)) for nucleus in dapi_stats_filter.GetLabels()]) nuclei_centroids = np.array(nuclei_centroids) # Compute minimal distances and matching labels all_distances = -2 * np.dot(centroids, nuclei_centroids.T) all_distances += np.sum(centroids**2, axis=1)[:, np.newaxis] all_distances += np.sum(nuclei_centroids**2, axis=1) all_distances = np.sqrt(all_distances) min_indexes = np.argmin(all_distances, axis=1) # construct dataframe df = pd.DataFrame(list( zip(labels, all_distances[np.arange(len(min_indexes)), min_indexes])), columns=[ "Labels", 'centroid centroid distance [um]' ]) return df def get_actual_intensity_measurements(intensity_filter, map: bool = False): labels, intensities = zip( *[(label, intensity_filter.GetMean(label)) for label in intensity_filter.GetLabels()]) df = pd.DataFrame(list(zip(labels, intensities)), columns=["Labels", 'Mean Intensity']) return df distance_map_from_all_nuclei = sitk.Abs( sitk.SignedMaurerDistanceMap(self.channel_images["Blue"].labelled, squaredDistance=False, useImageSpacing=True)) self.dfs = {} for item in self.marker_names: int_stats_filter = sitk.LabelIntensityStatisticsImageFilter() int_stats_filter.Execute(self.channel_images[item].labelled, distance_map_from_all_nuclei) logger.debug(f"Constucting edge to edge for {item}.") ee_distances = get_ee_distances(int_stats_filter) del int_stats_filter logger.debug(f"Constucting centroid to centroid for {item}.") cc_distances = get_cc_distances( self.channel_images[item].shape_stats) logger.debug(f"Constucting intensities for {item}.") intensities = get_actual_intensity_measurements( self.channel_images[item].int_stats, self.channel_images[item].shape_stats) logger.debug(f"Creating dataframes for {item}.") temp_df = pd.merge(ee_distances, cc_distances, on="Labels") self.dfs[item] = pd.merge(temp_df, intensities, on="Labels") del ee_distances, cc_distances, intensities, temp_df logger.debug(f"Running dataframe calculations for {item}.") self.dfs[item]['Integrated Density'] = self.dfs[item][ 'Mean Intensity'] * self.dfs[item]['# Voxels'] int_stats_filter = sitk.LabelIntensityStatisticsImageFilter() int_stats_filter.Execute(self.colocalizations, distance_map_from_all_nuclei) logger.debug(f"Constucting edge to edge for Colocalizations.") ee_distances = get_ee_distances(int_stats_filter, map=True).reset_index() del int_stats_filter logger.debug(f"Constucting centroid to centroid for Colocalizations.") cc_distances = get_cc_distances(self.coloc_stats, map=True).reset_index() logger.debug(f"Creating dataframes for Colocalizations.") df = pd.merge(ee_distances, cc_distances, on="Labels") self.dfs["Coloc"] = pd.DataFrame(df[df.index_x == df.index_y], columns=[ "Labels", "edge edge distance to DAPI [um]", "# Voxels", "Size [um]", "centroid centroid distance [um]" ]).reset_index(drop=True) del ee_distances, cc_distances
def __overlap_measures__(prediction_handle, truth_handle, perform_distance_measures=False): """ :param prediction_handle: A prediction handle of a single site :param truth_handle: A ground truth handle of a single site :param perform_distance_measures: Binary, include distance measures :return: a dictionary of overlap measures, optional distance measures """ out_dict = {} overlap_measures_filter = sitk.LabelOverlapMeasuresImageFilter() overlap_measures_filter.Execute(truth_handle, prediction_handle) out_dict[OverlapMeasures.jaccard.name] = overlap_measures_filter.GetJaccardCoefficient() out_dict[OverlapMeasures.dice.name] = overlap_measures_filter.GetDiceCoefficient() out_dict[OverlapMeasures.volume_similarity.name] = overlap_measures_filter.GetVolumeSimilarity() out_dict[OverlapMeasures.false_negative.name] = overlap_measures_filter.GetFalseNegativeError() out_dict[OverlapMeasures.false_positive.name] = overlap_measures_filter.GetFalsePositiveError() if perform_distance_measures: statistics_image_filter = sitk.StatisticsImageFilter() reference_surface = sitk.LabelContour(truth_handle) reference_distance_map = sitk.Abs(sitk.SignedMaurerDistanceMap(truth_handle, squaredDistance=False, useImageSpacing=True)) statistics_image_filter.Execute(reference_surface) num_reference_surface_pixels = int(statistics_image_filter.GetSum()) segmented_distance_map = sitk.Abs( sitk.SignedMaurerDistanceMap(prediction_handle, squaredDistance=False, useImageSpacing=True)) segmented_surface = sitk.LabelContour(prediction_handle) # Multiply the binary surface segmentations with the distance maps. The resulting distance # maps contain non-zero values only on the surface (they can also contain zero on the surface) seg2ref_distance_map = reference_distance_map * sitk.Cast(segmented_surface, sitk.sitkFloat32) ref2seg_distance_map = segmented_distance_map * sitk.Cast(reference_surface, sitk.sitkFloat32) # Get the number of pixels in the reference surface by counting all pixels that are 1. statistics_image_filter.Execute(segmented_surface) num_segmented_surface_pixels = int(statistics_image_filter.GetSum()) # Get all non-zero distances and then add zero distances if required. seg2ref_distance_map_arr = sitk.GetArrayViewFromImage(seg2ref_distance_map) seg2ref_distances = list(seg2ref_distance_map_arr[seg2ref_distance_map_arr != 0]) seg2ref_distances = seg2ref_distances + \ list(np.zeros(num_segmented_surface_pixels - len(seg2ref_distances))) ref2seg_distance_map_arr = sitk.GetArrayViewFromImage(ref2seg_distance_map) ref2seg_distances = list(ref2seg_distance_map_arr[ref2seg_distance_map_arr != 0]) ref2seg_distances = ref2seg_distances + \ list(np.zeros(num_reference_surface_pixels - len(ref2seg_distances))) all_surface_distances = seg2ref_distances + ref2seg_distances # The maximum of the symmetric surface distances is the Hausdorff distance between the surfaces. In # general, it is not equal to the Hausdorff distance between all voxel/pixel points of the two # segmentations, though in our case it is. More on this below. out_dict[SurfaceDistanceMeasures.mean_surface_distance.name] = np.mean( all_surface_distances) out_dict[SurfaceDistanceMeasures.median_surface_distance.name] = np.median( all_surface_distances) out_dict[SurfaceDistanceMeasures.std_surface_distance.name] = np.std( all_surface_distances) out_dict[SurfaceDistanceMeasures.max_surface_distance.name] = np.max( all_surface_distances) return out_dict
def process(self, A): out_dict_base, metric, metric_range, file = A pat_name = os.path.split(file)[-1].split('.')[0] out_dict = {} for name, _ in OverlapMeasures.__members__.items(): out_dict[name] = {} for name, _ in SurfaceDistanceMeasures.__members__.items(): out_dict[name] = {} for name in out_dict.keys(): for i in metric_range: out_dict[name]['{}_{}'.format(metric, i)] = [] print(pat_name) base_truth = sitk.ReadImage(file.replace('_Image', '_Truth'), sitk.sitkFloat32) prediction = sitk.ReadImage(file.replace('_Image', '_Prediction')) liver = sitk.GetArrayFromImage(base_truth) liver[liver > 0] = 1 out_annotation = sitk.GetArrayFromImage(prediction) out_annotation[..., 4] *= 1 # Tired of 5 encroaching on 4 # out_annotation[out_annotation<.5] = 0 out_annotation = to_categorical(np.argmax(out_annotation, axis=-1)) truth = sitk.GetArrayFromImage(base_truth) out_truth = to_categorical(truth) out_annotation = self.Iterate_Lobe_Annotations_Class.iterate_annotations( out_annotation, liver, spacing=base_truth.GetSpacing(), max_iteration=10) fixed_pred = sitk.GetImageFromArray( np.squeeze(np.argmax(out_annotation, axis=-1) / 8)) fixed_pred.SetSpacing(base_truth.GetSpacing()) fixed_pred.SetOrigin(base_truth.GetOrigin()) fixed_pred.SetDirection(base_truth.GetDirection()) sitk.WriteImage(fixed_pred, file.replace('_Image', '_PredictionOutput')) overlap_measures_filter = sitk.LabelOverlapMeasuresImageFilter() hausdorff_distance_filter = sitk.HausdorffDistanceImageFilter() statistics_image_filter = sitk.StatisticsImageFilter() overlap_results = np.zeros( (8, len(OverlapMeasures.__members__.items()))) surface_distance_results = np.zeros( (8, len(SurfaceDistanceMeasures.__members__.items()))) for i, metric_value in enumerate(range(1, 9)): truth = sitk.BinaryThreshold(sitk.Cast( sitk.GetImageFromArray(out_truth[..., metric_value]), sitk.sitkFloat32), lowerThreshold=0.001) threshold_pred = sitk.BinaryThreshold(sitk.Cast( sitk.GetImageFromArray(out_annotation[..., metric_value]), sitk.sitkFloat32), lowerThreshold=0.001) reference_distance_map = sitk.Abs( sitk.SignedMaurerDistanceMap(truth, squaredDistance=False, useImageSpacing=True)) reference_surface = sitk.LabelContour(truth) statistics_image_filter.Execute(reference_surface) num_reference_surface_pixels = int( statistics_image_filter.GetSum()) print(metric) print(metric_value) overlap_measures_filter.Execute(truth, threshold_pred) overlap_results[ i, OverlapMeasures.jaccard. value] = overlap_measures_filter.GetJaccardCoefficient() overlap_results[ i, OverlapMeasures.dice. value] = overlap_measures_filter.GetDiceCoefficient() overlap_results[ i, OverlapMeasures.volume_similarity. value] = overlap_measures_filter.GetVolumeSimilarity() overlap_results[ i, OverlapMeasures.false_negative. value] = overlap_measures_filter.GetFalseNegativeError() overlap_results[ i, OverlapMeasures.false_positive. value] = overlap_measures_filter.GetFalsePositiveError() # Hausdorff distance hausdorff_distance_filter.Execute(truth, threshold_pred) surface_distance_results[ i, SurfaceDistanceMeasures.hausdorff_distance. value] = hausdorff_distance_filter.GetHausdorffDistance() # Symmetric surface distance measures segmented_distance_map = sitk.Abs( sitk.SignedMaurerDistanceMap(threshold_pred, squaredDistance=False, useImageSpacing=True)) segmented_surface = sitk.LabelContour(threshold_pred) # Multiply the binary surface segmentations with the distance maps. The resulting distance # maps contain non-zero values only on the surface (they can also contain zero on the surface) seg2ref_distance_map = reference_distance_map * sitk.Cast( segmented_surface, sitk.sitkFloat32) ref2seg_distance_map = segmented_distance_map * sitk.Cast( reference_surface, sitk.sitkFloat32) # Get the number of pixels in the reference surface by counting all pixels that are 1. statistics_image_filter.Execute(segmented_surface) num_segmented_surface_pixels = int( statistics_image_filter.GetSum()) # Get all non-zero distances and then add zero distances if required. seg2ref_distance_map_arr = sitk.GetArrayViewFromImage( seg2ref_distance_map) seg2ref_distances = list( seg2ref_distance_map_arr[seg2ref_distance_map_arr != 0]) seg2ref_distances = seg2ref_distances + \ list(np.zeros(num_segmented_surface_pixels - len(seg2ref_distances))) ref2seg_distance_map_arr = sitk.GetArrayViewFromImage( ref2seg_distance_map) ref2seg_distances = list( ref2seg_distance_map_arr[ref2seg_distance_map_arr != 0]) ref2seg_distances = ref2seg_distances + \ list(np.zeros(num_reference_surface_pixels - len(ref2seg_distances))) all_surface_distances = seg2ref_distances + ref2seg_distances # The maximum of the symmetric surface distances is the Hausdorff distance between the surfaces. In # general, it is not equal to the Hausdorff distance between all voxel/pixel points of the two # segmentations, though in our case it is. More on this below. surface_distance_results[ i, SurfaceDistanceMeasures.mean_surface_distance.value] = np.mean( all_surface_distances) surface_distance_results[ i, SurfaceDistanceMeasures.median_surface_distance. value] = np.median(all_surface_distances) surface_distance_results[ i, SurfaceDistanceMeasures.std_surface_distance.value] = np.std( all_surface_distances) surface_distance_results[ i, SurfaceDistanceMeasures.max_surface_distance.value] = np.max( all_surface_distances) for _, measured_name in enumerate(OverlapMeasures): out_dict[measured_name.name]['{}_{}'.format( metric, metric_value)].append(overlap_results[i, measured_name.value]) for _, measured_name in enumerate(SurfaceDistanceMeasures): out_dict[measured_name.name]['{}_{}'.format( metric, metric_value)].append( surface_distance_results[i, measured_name.value]) out_pickle = os.path.join('.', 'Patient_Out', '{}.pkl'.format(pat_name)) save_obj(out_pickle, out_dict) out_dict_base[pat_name] = out_dict return out_dict_base
def evaluation_sample(SEG_np, GT_np): SEG = sitk.GetImageFromArray(SEG_np) GT = sitk.GetImageFromArray(GT_np) # Empty numpy arrays to hold the results overlap_results = np.zeros((len(OverlapMeasures.__members__.items()))) surface_distance_results = np.zeros( (len(SurfaceDistanceMeasures.__members__.items()))) # Compute the evaluation criteria # Note that for the overlap measures filter, because we are dealing with a single label we # use the combined, all labels, evaluation measures without passing a specific label to the methods. overlap_measures_filter = sitk.LabelOverlapMeasuresImageFilter() hausdorff_distance_filter = sitk.HausdorffDistanceImageFilter() # Use the absolute values of the distance map to compute the surface distances (distance map sign, outside or inside # relationship, is irrelevant) reference_distance_map = sitk.Abs( sitk.SignedMaurerDistanceMap(SEG, squaredDistance=False)) reference_surface = sitk.LabelContour(SEG) statistics_image_filter = sitk.StatisticsImageFilter() # Get the number of pixels in the reference surface by counting all pixels that are 1. statistics_image_filter.Execute(reference_surface) num_reference_surface_pixels = int(statistics_image_filter.GetSum()) # Overlap measures overlap_measures_filter.Execute(SEG, GT) overlap_results[OverlapMeasures.jaccard. value] = overlap_measures_filter.GetJaccardCoefficient() overlap_results[OverlapMeasures.dice. value] = overlap_measures_filter.GetDiceCoefficient() overlap_results[OverlapMeasures.volume_similarity. value] = overlap_measures_filter.GetVolumeSimilarity() overlap_results[OverlapMeasures.false_negative. value] = overlap_measures_filter.GetFalseNegativeError() overlap_results[OverlapMeasures.false_positive. value] = overlap_measures_filter.GetFalsePositiveError() overlap_results[OverlapMeasures.sensitive.value] = 1 - overlap_results[ OverlapMeasures.false_negative.value] overlap_results[OverlapMeasures.specificity.value] = specificity( SEG_np, GT_np) # Hausdorff distance hausdorff_distance_filter.Execute(SEG, GT) surface_distance_results[ SurfaceDistanceMeasures.hausdorff_distance. value] = hausdorff_distance_filter.GetHausdorffDistance() # Symmetric surface distance measures segmented_distance_map = sitk.Abs( sitk.SignedMaurerDistanceMap(GT, squaredDistance=False)) segmented_surface = sitk.LabelContour(GT) # Multiply the binary surface SEG with the distance maps. The resulting distance # maps contain non-zero values only on the surface (they can also contain zero on the surface) seg2ref_distance_map = reference_distance_map * sitk.Cast( segmented_surface, sitk.sitkFloat32) ref2seg_distance_map = segmented_distance_map * sitk.Cast( reference_surface, sitk.sitkFloat32) # Get the number of pixels in the reference surface by counting all pixels that are 1. statistics_image_filter.Execute(segmented_surface) num_segmented_surface_pixels = int(statistics_image_filter.GetSum()) # Get all non-zero distances and then add zero distances if required. seg2ref_distance_map_arr = sitk.GetArrayViewFromImage(seg2ref_distance_map) seg2ref_distances = list( seg2ref_distance_map_arr[seg2ref_distance_map_arr != 0]) seg2ref_distances = seg2ref_distances + list( np.zeros(num_segmented_surface_pixels - len(seg2ref_distances))) ref2seg_distance_map_arr = sitk.GetArrayViewFromImage(ref2seg_distance_map) ref2seg_distances = list( ref2seg_distance_map_arr[ref2seg_distance_map_arr != 0]) ref2seg_distances = ref2seg_distances + list( np.zeros(num_reference_surface_pixels - len(ref2seg_distances))) all_surface_distances = seg2ref_distances + ref2seg_distances surface_distance_results[SurfaceDistanceMeasures.mean_surface_distance. value] = np.mean(all_surface_distances) surface_distance_results[SurfaceDistanceMeasures.median_surface_distance. value] = np.median(all_surface_distances) surface_distance_results[SurfaceDistanceMeasures.std_surface_distance. value] = np.std(all_surface_distances) surface_distance_results[SurfaceDistanceMeasures.max_surface_distance. value] = np.max(all_surface_distances) # Print the matrices np.set_printoptions(precision=3) return overlap_results, surface_distance_results