Example #1
0
 def __init__(
     self,
     width: float,
     height: float,
     center: Union[Point, Tuple],
     as_int: bool = False,
 ):
     """
     Parameters
     ----------
     width : number
         Width of the rectangle.
     height : number
         Height of the rectangle.
     center : Point, iterable, optional
         Center point of rectangle.
     as_int : bool
         If False (default), inputs are left as-is. If True, all inputs are converted to integers.
     """
     if as_int:
         self.width = int(np.round(width))
         self.height = int(np.round(height))
     else:
         self.width = width
         self.height = height
     self._as_int = as_int
     self.center = Point(center, as_int=as_int)
Example #2
0
def get_delivery_data_items(single_icom_stream):
    shrunk_stream, mu = extract.extract(single_icom_stream, "Delivery MU")
    shrunk_stream, gantry = extract.extract(shrunk_stream, "Gantry")
    shrunk_stream, collimator = extract.extract(shrunk_stream, "Collimator")

    shrunk_stream, mlc = extract.extract_coll(shrunk_stream, b"MLCX", 160)
    mlc = np.array(mlc)
    mlc = mlc.reshape((80, 2))
    mlc = np.fliplr(np.flipud(mlc * 10))
    mlc[:, 1] = -mlc[:, 1]
    mlc = np.round(mlc, 10)

    shrunk_stream, jaw = extract.extract_coll(shrunk_stream, b"ASYMY", 2)
    jaw = np.round(np.array(jaw) * 10, 10)
    jaw = np.flipud(jaw)

    return mu, gantry, collimator, mlc, jaw
Example #3
0
def _determine_calc_grid_and_adjustments(mlc, jaw, leaf_pair_widths, grid_resolution):
    min_y = np.min(-jaw[:, 0])
    max_y = np.max(jaw[:, 1])

    leaf_centres, top_of_reference_leaf = _determine_leaf_centres(leaf_pair_widths)
    grid_reference_position = _determine_reference_grid_position(
        top_of_reference_leaf, grid_resolution
    )

    top_grid_pos = (
        np.round((max_y - grid_reference_position) / grid_resolution)
    ) * grid_resolution + grid_reference_position

    bot_grid_pos = (
        grid_reference_position
        - (np.round((-min_y + grid_reference_position) / grid_resolution))
        * grid_resolution
    )

    grid = dict()
    grid["jaw"] = np.arange(
        bot_grid_pos, top_grid_pos + grid_resolution, grid_resolution
    ).astype("float")

    grid_leaf_map = np.argmin(
        np.abs(grid["jaw"][:, None] - leaf_centres[None, :]), axis=1
    )

    adjusted_grid_leaf_map = grid_leaf_map - np.min(grid_leaf_map)

    leaves_to_be_calced = np.unique(grid_leaf_map)
    adjusted_mlc = mlc[:, leaves_to_be_calced, :]

    min_x = np.round(np.min(-adjusted_mlc[:, :, 0]) / grid_resolution) * grid_resolution
    max_x = np.round(np.max(adjusted_mlc[:, :, 1]) / grid_resolution) * grid_resolution

    grid["mlc"] = np.arange(min_x, max_x + grid_resolution, grid_resolution).astype(
        "float"
    )

    return grid, adjusted_grid_leaf_map, adjusted_mlc
Example #4
0
def gamma_filter_brute_force(axes_reference,
                             dose_reference,
                             axes_evaluation,
                             dose_evaluation,
                             distance_mm_threshold,
                             dose_threshold,
                             lower_dose_cutoff=0,
                             **_):

    xx_ref, yy_ref, zz_ref = np.meshgrid(*axes_reference, indexing="ij")
    gamma_array = np.ones_like(dose_evaluation).astype(np.float) * np.nan

    mesh_index = np.meshgrid(
        *[np.arange(len(coord_eval)) for coord_eval in axes_evaluation])

    eval_index = np.reshape(np.array(mesh_index), (3, -1))
    run_index = np.arange(np.shape(eval_index)[1])
    np.random.shuffle(run_index)

    sys.stdout.write("    ")

    for counter, point_index in enumerate(run_index):
        i, j, k = eval_index[:, point_index]
        eval_x = axes_evaluation[0][i]
        eval_y = axes_evaluation[1][j]
        eval_z = axes_evaluation[2][k]

        if dose_evaluation[i, j, k] < lower_dose_cutoff:
            continue

        distance = np.sqrt((xx_ref - eval_x)**2 + (yy_ref - eval_y)**2 +
                           (zz_ref - eval_z)**2)

        dose_diff = dose_evaluation[i, j, k] - dose_reference

        gamma = np.min(
            np.sqrt((dose_diff / dose_threshold)**2 +
                    (distance / distance_mm_threshold)**2))

        gamma_array[i, j, k] = gamma

        if counter // 30 == counter / 30:
            percent_pass = str(
                np.round(calculate_pass_rate(gamma_array), decimals=1))
            sys.stdout.write(
                "\rPercent Pass: {0}% | Percent Complete: {1:.2f}%".format(
                    percent_pass, counter / np.shape(eval_index)[1] * 100))
            sys.stdout.flush()

    return calculate_pass_rate(gamma_array)
Example #5
0
def format_coords_for_dicom(all_merged):
    dicom_format_coords_by_z = {}

    for z, merged in all_merged.items():
        coords = get_coords_from_polygon_or_multipolygon(merged)
        new_contour_data = []
        for coord in coords:
            stacked_coords = np.hstack(
                list(zip(coord[0], coord[1], z * np.ones_like(coord[1]))))
            stacked_coords = np.round(stacked_coords, 1)
            stacked_coords = stacked_coords.tolist()

            new_contour_data.append(stacked_coords)

        dicom_format_coords_by_z[z] = new_contour_data

    return dicom_format_coords_by_z
Example #6
0
    def _apply_mask_to_delivery_data(self: DeliveryGeneric,
                                     mask) -> DeliveryGeneric:
        cls = type(self)

        new_delivery_data = []
        for item in self:
            new_delivery_data.append(np.array(item)[mask])

        new_monitor_units = new_delivery_data[0]
        try:
            first_monitor_unit_item = new_monitor_units[0]
        except IndexError:
            return cls(*new_delivery_data)

        new_delivery_data[0] = np.round(
            np.array(new_delivery_data[0], copy=False) -
            first_monitor_unit_item,
            decimals=7,
        )

        return cls(*new_delivery_data)
Example #7
0
def create_dataframe(data, column_names, time_increment):
    """Converts the provided data into a pandas dataframe."""
    dataframe = pd.DataFrame(data=data, columns=column_names)
    dataframe.index = np.round(dataframe.index * time_increment, 2)

    return dataframe
Example #8
0
def peak_detect(
    values,
    threshold: Union[float, int] = None,
    min_distance: Union[float, int] = 10,
    max_number: int = None,
    search_region=(0.0, 1.0),
    find_min_instead: bool = False,
):
    """Find the peaks or valleys of a 1D signal.

    Uses the difference (np.diff) in signal to find peaks. Current limitations include:
        1) Only for use in 1-D data; 2D may be possible with the gradient function.
        2) Will not detect peaks at the very edge of array (i.e. 0 or -1 index)

    Parameters
    ----------
    values : array-like
        Signal values to search for peaks within.
    threshold : int, float
        The value the peak must be above to be considered a peak. This removes "peaks"
        that are in a low-value region.
        If passed an int, the actual value is the threshold.
        E.g. when passed 15, any peak less with a value <15 is removed.
        If passed a float, it will threshold as a percent. Must be between 0 and 1.
        E.g. when passed 0.4, any peak <40% of the maximum value will be removed.
    min_distance : int, float
        If passed an int, parameter is the number of elements apart a peak must be from neighboring peaks.
        If passed a float, must be between 0 and 1 and represents the ratio of the profile to exclude.
        E.g. if passed 0.05 with a 1000-element profile, the minimum peak width will be 0.05*1000 = 50 elements.
    max_number : int
        Specify up to how many peaks will be returned. E.g. if 3 is passed in and 5 peaks are found, only the 3 largest
        peaks will be returned.
    find_min_instead : bool
        If False (default), peaks will be returned.
        If True, valleys will be returned.

    Returns
    -------
    max_vals : numpy.array
        The values of the peaks found.
    max_idxs : numpy.array
        The x-indices (locations) of the peaks.

    Raises
    ------
    ValueError
        If float not between 0 and 1 passed to threshold.
    """
    peak_vals = (
        []
    )  # a list to hold the y-values of the peaks. Will be converted to a numpy array
    peak_idxs = []  # ditto for x-values (index) of y data.

    if find_min_instead:
        values = -values

    # """Limit search to search region"""
    left_end = search_region[0]
    if is_float_like(left_end):
        left_index = int(left_end * len(values))
    elif is_int_like(left_end):
        left_index = left_end
    else:
        raise ValueError(f"{left_end} must be a float or int")

    right_end = search_region[1]
    if is_float_like(right_end):
        right_index = int(right_end * len(values))
    elif is_int_like(right_end):
        right_index = right_end
    else:
        raise ValueError(f"{right_end} must be a float or int")

    # minimum peak spacing calc
    if isinstance(min_distance, float):
        if 0 > min_distance >= 1:
            raise ValueError(
                "When min_peak_width is passed a float, value must be between 0 and 1"
            )
        else:
            min_distance = int(min_distance * len(values))

    values = values[left_index:right_index]

    # """Determine threshold value"""
    if isinstance(threshold, float) and threshold < 1:
        data_range = values.max() - values.min()
        threshold = threshold * data_range + values.min()
    elif isinstance(threshold, float) and threshold >= 1:
        raise ValueError("When threshold is passed a float, value must be less than 1")
    elif threshold is None:
        threshold = values.min()

    # """Take difference"""
    values_diff = np.diff(
        values.astype(float)
    )  # y and y_diff must be converted to signed type.

    # """Find all potential peaks"""
    for idx in range(len(values_diff) - 1):
        # For each item of the diff array, check if:
        # 1) The y-value is above the threshold.
        # 2) The value of y_diff is positive (negative for valley search), it means the y-value changed upward.
        # 3) The next y_diff value is zero or negative (or positive for valley search); a positive-then-negative diff value means the value
        # is a peak of some kind. If the diff is zero it could be a flat peak, which still counts.

        # 1)
        if values[idx + 1] < threshold:
            continue

        y1_gradient = values_diff[idx] > 0
        y2_gradient = values_diff[idx + 1] <= 0

        # 2) & 3)
        if y1_gradient and y2_gradient:
            # If the next value isn't zero it's a single-pixel peak. Easy enough.
            if values_diff[idx + 1] != 0:
                peak_vals.append(values[idx + 1])
                peak_idxs.append(idx + 1 + left_index)
            # elif idx >= len(y_diff) - 1:
            #     pass
            # Else if the diff value is zero, it could be a flat peak, or it could keep going up; we don't know yet.
            else:
                # Continue on until we find the next nonzero diff value.
                try:
                    shift = 0
                    while values_diff[(idx + 1) + shift] == 0:
                        shift += 1
                        if (idx + 1 + shift) >= (len(values_diff) - 1):
                            break
                    # If the next diff is negative (or positive for min), we've found a peak. Also put the peak at the center of the flat
                    # region.
                    is_a_peak = values_diff[(idx + 1) + shift] < 0
                    if is_a_peak:
                        peak_vals.append(values[int((idx + 1) + np.round(shift / 2))])
                        peak_idxs.append((idx + 1 + left_index) + np.round(shift / 2))
                except IndexError:
                    pass

    # convert to numpy arrays
    peak_vals = np.array(peak_vals)
    peak_idxs = np.array(peak_idxs)

    # """Enforce the min_peak_distance by removing smaller peaks."""
    # For each peak, determine if the next peak is within the min peak width range.
    index = 0
    while index < len(peak_idxs) - 1:

        # If the second peak is closer than min_peak_distance to the first peak, find the larger peak and remove the other one.
        if peak_idxs[index] > peak_idxs[index + 1] - min_distance:
            if peak_vals[index] > peak_vals[index + 1]:
                idx2del = index + 1
            else:
                idx2del = index
            peak_vals = np.delete(peak_vals, idx2del)
            peak_idxs = np.delete(peak_idxs, idx2del)
        else:
            index += 1

    # """If Maximum Number passed, return only up to number given based on a sort of peak values."""
    if max_number is not None and len(peak_idxs) > max_number:
        sorted_peak_vals = peak_vals.argsort()  # type: ignore  # sorts low to high
        peak_vals = peak_vals[
            sorted_peak_vals[
                -max_number:  # pylint: disable = invalid-unary-operand-type
            ]
        ]
        peak_idxs = peak_idxs[
            sorted_peak_vals[
                -max_number:  # pylint: disable = invalid-unary-operand-type
            ]
        ]

    # If we were looking for minimums, convert the values back to the original sign
    if find_min_instead:
        peak_vals = (
            -peak_vals  # type: ignore  # pylint: disable = invalid-unary-operand-type
        )

    return peak_vals, peak_idxs