示例#1
0
def overlap_volume(oar, tv):
    """
    Calculate the overlap volume of two rois
    :param oar: organ-at-risk as a "sets of points" formatted dictionary
    :type oar: dict
    :param tv: treatment volume as a "sets of points" formatted dictionary
    :type tv: dict
    :rtype: float
    """

    intersection_volume = 0.
    all_z_values = [round(float(z), 2) for z in list(tv)]
    all_z_values = np.sort(all_z_values)
    thicknesses = np.abs(np.diff(all_z_values))
    thicknesses = np.append(thicknesses, np.min(thicknesses))
    all_z_values = all_z_values.tolist()

    for z in list(tv):
        # z in coord will not necessarily go in order of z, convert z to float to lookup thickness
        # also used to check for top and bottom slices, to add area of those contours

        if z in list(oar):
            thickness = thicknesses[all_z_values.index(round(float(z), 2))]
            shapely_tv = points_to_shapely_polygon(tv[z])
            shapely_oar = points_to_shapely_polygon(oar[z])
            if shapely_oar and shapely_tv:
                intersection_volume += shapely_tv.intersection(
                    shapely_oar).area * thickness

    return round(intersection_volume / 1000., 2)
示例#2
0
def union(rois):
    """
    Calculate the geometric union of the provided rois
    :param rois: rois formatted as "sets of points" dictionaries
    :type rois: list
    :return: a "sets of points" dictionary representing the union of the rois
    :rtype: dict
    """

    new_roi = {}

    all_z_values = []
    for roi in rois:
        for z in list(roi):
            if z not in all_z_values:
                all_z_values.append(z)

    for z in all_z_values:

        if z not in list(new_roi):
            new_roi[z] = []

        # Convert to shapely objects
        current_slice = None
        for roi in rois:
            # Make sure current roi has at least 3 points in z plane
            if z in list(roi) and len(roi[z][0]) > 2:
                if not current_slice:
                    current_slice = points_to_shapely_polygon(roi[z])
                else:
                    current_slice = current_slice.union(points_to_shapely_polygon(roi[z]))

        if current_slice:
            if current_slice.type != 'MultiPolygon':
                current_slice = [current_slice]

            for polygon in current_slice:
                xy = polygon.exterior.xy
                x_coord = xy[0]
                y_coord = xy[1]
                points = []
                for i in range(len(x_coord)):
                    points.append([x_coord[i], y_coord[i], round(float(z), 2)])
                new_roi[z].append(points)

                if hasattr(polygon, 'interiors'):
                    for interior in polygon.interiors:
                        xy = interior.coords.xy
                        x_coord = xy[0]
                        y_coord = xy[1]
                        points = []
                        for i in range(len(x_coord)):
                            points.append([x_coord[i], y_coord[i], round(float(z), 2)])
                        new_roi[z].append(points)
        else:
            # print('WARNING: no contour found for slice %s' % z)
            pass
    return new_roi
示例#3
0
def centroid(roi):
    """
    :param roi: a "sets of points" formatted dictionary
    :return: centroid or the roi in x, y, z dicom coordinates (mm)
    :rtype: list
    """
    centroids = {'x': [], 'y': [], 'z': [], 'area': []}

    for z in list(roi):
        shapely_roi = points_to_shapely_polygon(roi[z])
        if shapely_roi and shapely_roi.area > 0:
            slice_centroid = shapely_roi.centroid
            polygon_count = len(slice_centroid.xy[0])
            for i in range(polygon_count):
                centroids['x'].append(slice_centroid.xy[0][i])
                centroids['y'].append(slice_centroid.xy[1][i])
                centroids['z'].append(float(z))
                if polygon_count > 1:
                    centroids['area'].append(shapely_roi[i].area)
                else:
                    centroids['area'].append(shapely_roi.area)

    x = np.array(centroids['x'])
    y = np.array(centroids['y'])
    z = np.array(centroids['z'])
    w = np.array(centroids['area'])
    w_sum = np.sum(w)

    volumetric_centroid = [
        float(np.sum(x * w) / w_sum),
        float(np.sum(y * w) / w_sum),
        float(np.sum(z * w) / w_sum)
    ]

    return volumetric_centroid
示例#4
0
def volume(roi):
    """
    :param roi: a "sets of points" formatted dictionary
    :return: volume in cm^3 of roi
    :rtype: float
    """

    # oar and ptv are lists using str(z) as keys
    # each item is an ordered list of points representing a polygon
    # polygon n is inside polygon n-1, then the current accumulated polygon is
    #    polygon n subtracted from the accumulated polygon up to and including polygon n-1
    #    Same method DICOM uses to handle rings and islands

    vol = 0.
    all_z_values = [round(float(z), 2) for z in list(roi)]
    all_z_values = np.sort(all_z_values)
    thicknesses = np.abs(np.diff(all_z_values))
    thicknesses = np.append(thicknesses, np.min(thicknesses))
    all_z_values = all_z_values.tolist()

    for z in list(roi):
        # z in coord will not necessarily go in order of z, convert z to float to lookup thickness
        # also used to check for top and bottom slices, to add area of those contours

        thickness = thicknesses[all_z_values.index(round(float(z), 2))]
        shapely_roi = points_to_shapely_polygon(roi[z])
        if shapely_roi:
            vol += shapely_roi.area * thickness

    return round(vol / 1000., 2)
示例#5
0
def cross_section(roi):
    """
    Calculate the cross section of a given roi
    :param roi: a "sets of points" formatted dictionary
    :type roi: dict
    :return: max and median cross-sectional area of all slices in cm^2
    :rtype: dict
    """
    areas = []

    for z in list(roi):
        shapely_roi = points_to_shapely_polygon(roi[z])
        if shapely_roi and shapely_roi.area > 0:
            slice_centroid = shapely_roi.centroid
            polygon_count = len(slice_centroid.xy[0])
            for i in range(polygon_count):
                if polygon_count > 1:
                    areas.append(shapely_roi[i].area)
                else:
                    areas.append(shapely_roi.area)

    areas = np.array(areas)

    area = {
        'max': float(np.max(areas) / 100.),
        'median': float(np.median(areas) / 100.)
    }

    return area
示例#6
0
def is_point_inside_roi(point, roi):
    """
    Check if a point is within an ROI
    :param point: x, y, z
    :type point: list
    :param roi:roi: a "sets of points" formatted dictionary
    :type roi: dict
    :return: Whether or not the poin is within the roi
    :rtype: bool
    """
    z_keys = list(roi.keys())
    roi_z = np.array([float(z) for z in z_keys])
    if np.max(roi_z) > point[2] > np.min(roi_z):
        nearest_z_index = (np.abs(roi_z - point[2])).argmin()
        nearest_z_key = z_keys[nearest_z_index]
        if abs(float(nearest_z_key) - point[2]) < 0.5:  # make sure point is within 0.5mm
            if len(roi[nearest_z_key]) > 2:  # make sure there are 3 points to make a polygon
                shapely_roi = points_to_shapely_polygon(roi[nearest_z_key])
                shapely_point = Point(point[0], point[1])
                return shapely_point.within(shapely_roi)
    return False