Example #1
0
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)
Example #2
0
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
Example #3
0
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
Example #4
0
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
Example #5
0
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
Example #6
0
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
Example #7
0
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()
Example #8
0
    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
Example #10
0
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
Example #11
0
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
Example #12
0
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
Example #13
0
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
Example #14
0
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
Example #15
0
    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)
Example #16
0
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
Example #17
0
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
Example #18
0
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
Example #19
0
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)
Example #20
0
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
Example #21
0
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
Example #23
0
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()
        ])
Example #24
0
        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()
Example #25
0
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
Example #26
0
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
Example #27
0
    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
Example #29
0
    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
Example #30
0
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