Ejemplo n.º 1
0
def create_bb_attenuation_func(diameter, penumbra, max_attenuation):
    dx = diameter / 100
    radius = diameter / 2
    image_half_width = penumbra * 2 + radius

    x = np.arange(-image_half_width, image_half_width + dx, dx)
    xx, yy = np.meshgrid(x, x)

    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        z = np.sqrt(radius**2 - xx**2 - yy**2) / radius

    z[np.isnan(z)] = 0

    sig = profiles.scaled_penumbra_sig() * penumbra
    sig_pixel = sig / dx

    filtered = scipy.ndimage.gaussian_filter(z, sig_pixel)
    interp = scipy.interpolate.RegularGridInterpolator((x, x),
                                                       filtered,
                                                       bounds_error=False,
                                                       fill_value=None)

    def attenuation(x, y):
        return 1 - interp((x, y)) * max_attenuation

    return attenuation
Ejemplo n.º 2
0
    def from_pulse(self, centre, width, domain, increment, meta={}):
        """ create pulse of unit height

        Parameters
        ----------
        centre : float
        width : float
        domain : tuple
            (x_left, x_right)
        increment : float
        meta : dict, optional

        Returns
        -------
        Profile


        """
        x_vals = np.arange(domain[0], domain[1] + increment, increment)
        y = []
        for x in x_vals:
            if abs(x) > (centre + width / 2.0):
                y.append(0.0)
            elif abs(x) < (centre + width / 2.0):
                y.append(1.0)
            else:
                y.append(0.5)
        return Profile().from_lists(x_vals, y, meta=meta)
Ejemplo n.º 3
0
def create_bb_points_function(bb_diameter):
    max_distance = bb_diameter * 0.5
    min_distance = 0
    num_steps = 11
    min_dist_between_points = (max_distance - min_distance) / num_steps
    distances = np.arange(min_distance, max_distance + min_dist_between_points,
                          min_dist_between_points)

    x = []
    y = []
    dist = []

    for _, distance in enumerate(distances):
        (
            new_x,
            new_y,
        ) = pymedphys._utilities.createshells.calculate_coordinates_shell_2d(  # pylint: disable = protected-access
            distance, min_dist_between_points)
        x.append(new_x)
        y.append(new_y)
        dist.append(distance * np.ones_like(new_x))

    x = np.concatenate(x)
    y = np.concatenate(y)
    dist = np.concatenate(dist)

    def points_to_check(bb_centre):
        x_shifted = x + bb_centre[0]
        y_shifted = y + bb_centre[1]

        return x_shifted, y_shifted

    return points_to_check, dist
Ejemplo n.º 4
0
 def _radians(self):
     interval = (2 * np.pi) / self.size
     rads = np.arange(
         0 + self.start_angle, (2 * np.pi) + self.start_angle - interval, interval
     )
     if self.ccw:
         rads = rads[::-1]
     return rads
Ejemplo n.º 5
0
def get_grid(
    max_leaf_gap=__DEFAULT_MAX_LEAF_GAP,
    grid_resolution=__DEFAULT_GRID_RESOLUTION,
    leaf_pair_widths=__DEFAULT_LEAF_PAIR_WIDTHS,
):
    """Get the MU Density grid for plotting purposes.

    Examples
    --------
    See `pymedphys.mudensity.calculate`_.
    """

    leaf_pair_widths = np.array(leaf_pair_widths)

    grid = dict()

    grid["mlc"] = np.arange(
        -max_leaf_gap / 2, max_leaf_gap / 2 + grid_resolution, grid_resolution
    ).astype("float")

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

    # It might be better to use round instead of ceil here.
    total_leaf_widths = np.sum(leaf_pair_widths)
    top_grid_pos = (
        np.ceil((total_leaf_widths / 2 - grid_reference_position) / grid_resolution)
        * grid_resolution
        + grid_reference_position
    )

    bot_grid_pos = (
        grid_reference_position
        - np.ceil((total_leaf_widths / 2 + grid_reference_position) / grid_resolution)
        * grid_resolution
    )

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

    return grid
Ejemplo n.º 6
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
Ejemplo n.º 7
0
def run_wlutz(
        field,
        edge_lengths,
        penumbra,
        field_centre,
        field_rotation,
        find_bb=True,
        pixel_size=0.1,
        pylinac_versions=("v2.2.6", "v2.2.7"),
):
    centralised_straight_field = create_centralised_field(
        field, field_centre, field_rotation)

    half_x_range = edge_lengths[0] / 2 + penumbra * 3
    half_y_range = edge_lengths[1] / 2 + penumbra * 3

    x_range = np.arange(-half_x_range, half_x_range + pixel_size, pixel_size)
    y_range = np.arange(-half_y_range, half_y_range + pixel_size, pixel_size)

    xx_range, yy_range = np.meshgrid(x_range, y_range)
    centralised_image = centralised_straight_field(xx_range, yy_range)

    results = {}
    for key in pylinac_versions:
        pylinac_field_centre, pylinac_bb_centre = run_pylinac_with_class(
            VERSION_TO_CLASS_MAP[key],
            centralised_image,
            pixel_size,
            half_x_range,
            half_y_range,
            field_centre,
            field_rotation,
            find_bb=find_bb,
        )
        results[key] = {
            "field_centre": pylinac_field_centre,
            "bb_centre": pylinac_bb_centre,
        }

    return results
Ejemplo n.º 8
0
def make_histogram(sinogram, num_bins=10):
    """ make a leaf-open-time histogram

    Return a histogram of leaf-open-times for the provided sinogram
    comprised of the specified number of bins, in the form of a list
    of tuples: [(bin, count)...] where bin is a 2-element array setting
    the bounds and count in the number leaf-open-times in the bin.

    Parameters
    ----------
    sinogram : np.array
    num_bins : int

    Returns
    -------
    histogram : list of tuples: [(bin, count)...]
        bin is a 2-element array

    """

    lfts = sinogram.flatten()

    bin_inc = (max(lfts) - min(lfts)) / num_bins
    bin_min = min(lfts)
    bin_max = max(lfts)

    bins_strt = np.arange(bin_min, bin_max, bin_inc)
    bins_stop = np.arange(bin_inc, bin_max + bin_inc, bin_inc)
    bins = np.dstack((bins_strt, bins_stop))[0]

    counts = [0 for b in bins]

    for lft in lfts:
        for idx, bin in enumerate(bins):
            if lft >= bin[0] and lft < bin[1]:
                counts[idx] = counts[idx] + 1

    histogram = list(zip(bins, counts))

    return histogram
Ejemplo n.º 9
0
def _calc_blocked_by_device(grid, positions, grid_resolution, time_steps):
    blocked_by_device = {}

    for device, value in positions.items():
        blocked_by_device[device] = dict()

        for multiplier, (start, end) in value.items():
            dt = (end - start) / (time_steps - 1)
            travel = start[None, :] + np.arange(
                0, time_steps)[:, None] * dt[None, :]
            travel_diff = multiplier * (grid[device][None, None, :] -
                                        travel[:, :, None])

            blocked_by_device[device][multiplier] = _calc_blocked_t(
                travel_diff, grid_resolution)

    return blocked_by_device
Ejemplo n.º 10
0
    def resample_x(self, step):
        """ resampled x-values at a given increment

        Resulting profile has stepsize of the indicated step based on
        linear interpolation over the points of the source profile.

        Parameters
        ----------
        step : float
            sampling increment

        Returns
        -------
        Profile

        """

        new_x = np.arange(self.x[0], self.x[-1], step)
        new_y = self.interp(new_x)
        return Profile(new_x, new_y, self.meta)
Ejemplo n.º 11
0
    def make_symmetric(self):
        """ avg of corresponding points

        Created by averaging over corresponding +/- distances,
        except at the endpoints.

        Returns
        -------
        Profile

        """

        reflected = Profile(x=-self.x[::-1], y=self.y[::-1])

        step = self.get_increment()
        new_x = np.arange(min(self.x), max(self.x), step)
        new_y = [self.y[0]]
        for n in new_x[1:-1]:  # AVOID EXTRAPOLATION
            new_y.append(0.5 * self.interp(n) + 0.5 * reflected.interp(n))
        new_y.append(reflected.y[0])

        return Profile(x=new_x, y=new_y, meta=self.meta)
Ejemplo n.º 12
0
    def cross_calibrate(self, reference, measured):
        """ density mapping, reference -> measured

        Calculated by overlaying intensity curves and observing values at
        corresponding points. Note that the result is an unsmoothed, collection
        of points.

        Parameters
        ----------
        reference : string
        measured : string
            file names with path

        Returns
        -------
        Profile

        """

        _, ext = os.path.splitext(reference)
        assert ext == ".prs"
        reference = Profile().from_snc_profiler(reference, "rad")
        _, ext = os.path.splitext(measured)
        assert ext == ".png"
        measured = Profile().from_narrow_png(measured)
        measured = measured.align_to(reference)

        dist_vals = np.arange(
            max(min(measured.x), min(reference.x)),
            min(max(measured.x), max(reference.x)),
            max(reference.get_increment(), measured.get_increment()),
        )

        calib_curve = [(measured.get_y(i), reference.get_y(i))
                       for i in dist_vals]

        return Profile().from_tuples(calib_curve)
Ejemplo n.º 13
0
    def resample_y(self, step):
        """ resampled y-values at a given increment

        Resulting profile has nonuniform step-size, but each step
        represents and approximately equal step in dose.

        Parameters
        ----------
        step : float
            sampling increment

        Returns
        -------
        Profile

        """

        temp_x = np.arange(min(self.x), max(self.x),
                           0.01 * self.get_increment())
        temp_y = self.interp(temp_x)

        resamp_x = [temp_x[0]]
        resamp_y = [temp_y[0]]

        last_y = temp_y[0]

        for i, _ in enumerate(temp_x):
            if np.abs(temp_y[i] - last_y) >= step:
                resamp_x.append(temp_x[i])
                resamp_y.append(temp_y[i])
                last_y = temp_y[i]

        if temp_x[-1] not in resamp_x:
            resamp_x.append(temp_x[-1])
            resamp_y.append(temp_y[-1])

        return Profile().from_lists(resamp_x, resamp_y, meta=self.meta)
Ejemplo n.º 14
0
def main(energy, dose_rate, prepend=""):
    total_mu = "{:.6f}".format(float(dose_rate))
    dose_rate = str(int(dose_rate))
    nominal_energy = str(
        float("".join(i for i in energy if i in "0123456789.")))
    fff = "fff" in str(energy).lower()

    vmat_example, fff_example, collimation = load_templates()

    fff_fluence_mode = fff_example.BeamSequence[0].PrimaryFluenceModeSequence
    beam_collimation = (collimation.BeamSequence[0].ControlPointSequence[0].
                        BeamLimitingDevicePositionSequence)

    gantry_step_size = 15.0

    gantry_beam_1 = from_bipolar(np.arange(-180, 181, gantry_step_size))
    gantry_beam_1[0] = 180.1
    gantry_beam_1[-1] = 179.9

    coll_beam_1 = from_bipolar(np.arange(-180, 1, gantry_step_size / 2))
    coll_beam_1[0] = 180.1

    gantry_beam_2 = from_bipolar(np.arange(180, -181, -gantry_step_size))
    gantry_beam_2[-1] = 180.1
    gantry_beam_2[0] = 179.9

    coll_beam_2 = from_bipolar(np.arange(180, -1, -gantry_step_size / 2))
    coll_beam_2[0] = 179.9

    gant_directions = ["CW", "CC"]
    coll_directions = ["CC", "CW"]

    control_point_sequence_beam1 = create_control_point_sequence(
        vmat_example.BeamSequence[0],
        beam_collimation,
        coll_directions[0],
        dose_rate,
        gantry_beam_1,
        coll_beam_1,
        nominal_energy,
    )
    control_point_sequence_beam2 = create_control_point_sequence(
        vmat_example.BeamSequence[1],
        beam_collimation,
        coll_directions[1],
        dose_rate,
        gantry_beam_2,
        coll_beam_2,
        nominal_energy,
    )

    plan = copy.deepcopy(vmat_example)
    plan.BeamSequence[0].ControlPointSequence = control_point_sequence_beam1
    plan.BeamSequence[1].ControlPointSequence = control_point_sequence_beam2

    num_cps = len(gantry_beam_1)

    for beam_sequence, direction in zip(plan.BeamSequence, gant_directions):
        beam_sequence.NumberOfControlPoints = str(num_cps)
        beam_sequence.BeamName = (
            f"WLutzArc-{prepend}-{energy}-{dose_rate.zfill(4)}-{direction}")

    if fff:
        for beam_sequence in plan.BeamSequence:
            beam_sequence.PrimaryFluenceModeSequence = fff_fluence_mode

    plan.FractionGroupSequence[0].ReferencedBeamSequence[
        0].BeamMeterset = total_mu
    plan.FractionGroupSequence[0].ReferencedBeamSequence[
        1].BeamMeterset = total_mu

    plan.RTPlanLabel = f"{prepend}-{energy}-{dose_rate}"
    plan.RTPlanName = plan.RTPlanLabel
    plan.PatientID = "WLutzArc"

    return plan
Ejemplo n.º 15
0
def xyz_axes_from_dataset(ds, coord_system="DICOM"):
    r"""Returns the x, y and z axes of a DICOM dataset's
    pixel array in the specified coordinate system.

    For DICOM RT Dose datasets, these are the x, y, z axes of the
    dose grid.

    Parameters
    ----------
    ds : pydicom.dataset.Dataset
        A DICOM dataset that contains pixel data. Supported modalities
        include 'CT' and 'RTDOSE'.

    coord_system : str, optional
        The coordinate system in which to return the `x`, `y` and `z`
        axes of the DICOM dataset. The accepted, case-insensitive
        values of `coord_system` are:

        'DICOM' or 'd':
            Return axes in the DICOM coordinate system.

        'patient', 'IEC patient' or 'p':
            Return axes in the IEC patient coordinate system.

        'fixed', 'IEC fixed' or 'f':
            Return axes in the IEC fixed coordinate system.

    Returns
    -------
    (x, y, z)
        A tuple containing three `numpy.ndarray`s corresponding to the `x`,
        `y` and `z` axes of the DICOM dataset's pixel array in the
        specified coordinate system.

    Notes
    -----
    Supported scan orientations [1]_:

    =========================== ==========================
    Orientation                 ds.ImageOrientationPatient
    =========================== ==========================
    Feet First Decubitus Left   [0, 1, 0, 1, 0, 0]
    Feet First Decubitus Right  [0, -1, 0, -1, 0, 0]
    Feet First Prone            [1, 0, 0, 0, -1, 0]
    Feet First Supine           [-1, 0, 0, 0, 1, 0]
    Head First Decubitus Left   [0, -1, 0, 1, 0, 0]
    Head First Decubitus Right  [0, 1, 0, -1, 0, 0]
    Head First Prone            [-1, 0, 0, 0, -1, 0]
    Head First Supine           [1, 0, 0, 0, 1, 0]
    =========================== ==========================

    References
    ----------
    .. [1] O. McNoleg, "Generalized coordinate transformations for Monte
       Carlo (DOSXYZnrc and VMC++) verifications of DICOM compatible
       radiotherapy treatment plans", arXiv:1406.0014, Table 1,
       https://arxiv.org/ftp/arxiv/papers/1406/1406.0014.pdf
    """

    position = np.array(ds.ImagePositionPatient)
    orientation = np.array(ds.ImageOrientationPatient)

    if not (
        np.array_equal(np.abs(orientation), np.array([1, 0, 0, 0, 1, 0]))
        or np.array_equal(np.abs(orientation), np.array([0, 1, 0, 1, 0, 0]))
    ):
        raise ValueError(
            "Dose grid orientation is not supported. Dose "
            "grid slices must be aligned along the "
            "superoinferior axis of patient."
        )

    is_decubitus = orientation[0] == 0
    is_head_first = _orientation_is_head_first(orientation, is_decubitus)

    di = float(ds.PixelSpacing[0])
    dj = float(ds.PixelSpacing[1])

    col_range = np.arange(0, ds.Columns * di, di)
    row_range = np.arange(0, ds.Rows * dj, dj)

    if is_decubitus:
        x_dicom_fixed = orientation[1] * position[1] + col_range
        y_dicom_fixed = orientation[3] * position[0] + row_range
    else:
        x_dicom_fixed = orientation[0] * position[0] + col_range
        y_dicom_fixed = orientation[4] * position[1] + row_range

    if is_head_first:
        z_dicom_fixed = position[2] + np.array(ds.GridFrameOffsetVector)
    else:
        z_dicom_fixed = -position[2] + np.array(ds.GridFrameOffsetVector)

    if coord_system.upper() in ("FIXED", "IEC FIXED", "F"):
        x = x_dicom_fixed
        y = z_dicom_fixed
        z = -np.flip(y_dicom_fixed)

    elif coord_system.upper() in ("DICOM", "D", "PATIENT", "IEC PATIENT", "P"):

        if orientation[0] == 1:
            x = x_dicom_fixed
        elif orientation[0] == -1:
            x = np.flip(x_dicom_fixed)
        elif orientation[1] == 1:
            y_d = x_dicom_fixed
        elif orientation[1] == -1:
            y_d = np.flip(x_dicom_fixed)

        if orientation[4] == 1:
            y_d = y_dicom_fixed
        elif orientation[4] == -1:
            y_d = np.flip(y_dicom_fixed)
        elif orientation[3] == 1:
            x = y_dicom_fixed
        elif orientation[3] == -1:
            x = np.flip(y_dicom_fixed)

        if not is_head_first:
            z_d = np.flip(z_dicom_fixed)
        else:
            z_d = z_dicom_fixed

        if coord_system.upper() in ("DICOM", "D"):
            y = y_d
            z = z_d
        elif coord_system.upper() in ("PATIENT", "IEC PATIENT", "P"):
            y = z_d
            z = -np.flip(y_d)

    return (x, y, z)
Ejemplo n.º 16
0
def create_axes(image, dpcm=100):
    shape = np.shape(image)
    x_span = (np.arange(0, shape[0]) - shape[0] // 2) * 10 / dpcm
    y_span = (np.arange(0, shape[1]) - shape[1] // 2) * 10 / dpcm

    return x_span, y_span
Ejemplo n.º 17
0
def calculate_min_dose_difference(options, distance, to_be_checked,
                                  distance_step_size):
    """Determine the minimum dose difference.

    Calculated for a given distance from each reference point.
    """

    min_relative_dose_difference = np.nan * np.ones_like(
        options.flat_dose_reference[to_be_checked])

    num_dimensions = np.shape(options.flat_mesh_axes_reference)[0]

    coordinates_at_distance_shell = pymedphys._utilities.createshells.calculate_coordinates_shell(  # pylint: disable = protected-access
        distance, num_dimensions, distance_step_size)

    num_points_in_shell = np.shape(coordinates_at_distance_shell)[1]

    estimated_ram_needed = (np.uint64(num_points_in_shell) *
                            np.uint64(np.count_nonzero(to_be_checked)) *
                            np.uint64(32) * np.uint64(num_dimensions) *
                            np.uint64(2))

    num_slices = np.floor(
        estimated_ram_needed / options.ram_available).astype(int) + 1

    if not options.quiet:
        sys.stdout.write(
            " | Points tested per reference point: {} | RAM split count: {}".
            format(num_points_in_shell, num_slices))
        sys.stdout.flush()

    all_checks = np.where(np.ravel(to_be_checked))[0]
    index = np.arange(len(all_checks))
    sliced = np.array_split(index, num_slices)

    sorted_sliced = [np.sort(current_slice) for current_slice in sliced]

    for current_slice in sorted_sliced:
        to_be_checked_sliced = np.full_like(to_be_checked, False, dtype=bool)
        to_be_checked_sliced[  # pylint: disable=unsupported-assignment-operation
            all_checks[current_slice]] = True

        assert np.all(to_be_checked[to_be_checked_sliced])

        axes_reference_to_be_checked = options.flat_mesh_axes_reference[:,
                                                                        to_be_checked_sliced]

        evaluation_dose = interpolate_evaluation_dose_at_distance(
            options.evaluation_interpolation,
            axes_reference_to_be_checked,
            coordinates_at_distance_shell,
        )

        if options.local_gamma:
            with np.errstate(divide="ignore"):
                relative_dose_difference = (
                    evaluation_dose -
                    options.flat_dose_reference[to_be_checked_sliced][None, :]
                ) / (
                    options.flat_dose_reference[to_be_checked_sliced][None, :])
        else:
            relative_dose_difference = (
                evaluation_dose -
                options.flat_dose_reference[to_be_checked_sliced][None, :]
            ) / options.global_normalisation

        min_relative_dose_difference[current_slice] = np.min(
            np.abs(relative_dose_difference), axis=0)

    return min_relative_dose_difference
Ejemplo n.º 18
0
def _interp_coords(coord):
    return scipy.interpolate.interp1d(np.arange(len(coord)), coord)
Ejemplo n.º 19
0
def read_narrow_png(file_name, step_size=0.1):
    """  Extract a an relative-density profilee from a narrow png file.

    Source file is a full color PNG that is sufficiently narrow that
    density uniform along its short dimension. The image density along
    its long dimension is reflective of a dose distribution. Requires
    Python PIL.

    Parameters
    ----------
    file_name : str
    step-size : float, optional
        Distance output increment in cm, defaults to 1 mm

    Returns
    -------
    array_like
        Image profile as a list of (distance, density) tuples, where density
        is an average color intensity value as represented in the source file.

    Raises
    ------
    ValueError
        If image is not narrow, i.e. aspect ratio <= 5
    AssertionError
        If step_size is too small, i.e. step_size <= 12.7 / dpi

    """

    image_file = Image.open(file_name)
    assert image_file.mode == "RGB"
    dpi_horiz, dpi_vert = image_file.info["dpi"]

    image_array = mpimg.imread(file_name)

    # DIMENSIONS TO AVG ACROSS DIFFERENT FOR HORIZ VS VERT IMG
    if image_array.shape[0] > 5 * image_array.shape[1]:  # VERT
        image_vector = np.average(image_array, axis=(1, 2))
        pixel_size_in_cm = 2.54 / dpi_vert
    elif image_array.shape[1] > 5 * image_array.shape[0]:  # HORIZ
        image_vector = np.average(image_array, axis=(0, 2))
        pixel_size_in_cm = 2.54 / dpi_horiz
    else:
        raise ValueError("The PNG file is not a narrow strip.")
    assert step_size > 5 * pixel_size_in_cm, "step size too small"

    if image_vector.shape[0] % 2 == 0:
        image_vector = image_vector[:-1]  # SO ZERO DISTANCE IS MID-PIXEL

    length_in_cm = image_vector.shape[0] * pixel_size_in_cm
    full_resolution_distances = np.arange(
        -length_in_cm / 2, length_in_cm / 2, pixel_size_in_cm
    )

    # TO MOVE FROM FILM RESOLUTION TO DESIRED PROFILE RESOLUTION
    num_pixels_to_avg_over = int(step_size / pixel_size_in_cm)
    sample_indices = np.arange(
        num_pixels_to_avg_over / 2,
        len(full_resolution_distances),
        num_pixels_to_avg_over,
    ).astype(int)
    downsampled_distances = list(full_resolution_distances[sample_indices])

    downsampled_density = []
    for idx in sample_indices:  # AVERAGE OVER THE SAMPLING WINDOW
        avg_density = np.average(
            image_vector[
                int(idx - num_pixels_to_avg_over / 2) : int(
                    idx + num_pixels_to_avg_over / 2
                )
            ]
        )
        downsampled_density.append(avg_density)

    zipped_profile = list(zip(downsampled_distances, downsampled_density))
    return zipped_profile
Ejemplo n.º 20
0
    def from_narrow_png(self, file_name, step_size=0.1):
        """ import from png file

        Source file is a full color PNG, sufficiently narrow that
        density is uniform along its short dimension. The image density along
        its long dimension is reflective of a dose distribution.

        Parameters
        ----------
        file_name : str
        step-size : float, optional

        Returns
        -------
        Profile

        Raises
        ------
        ValueError
            if aspect ratio <= 5, i.e. not narrow
        AssertionError
            if step_size <= 12.7 over dpi, i.e. small

        """
        image_file = PIL.Image.open(file_name)
        assert image_file.mode == "RGB"
        dpi_horiz, dpi_vert = image_file.info["dpi"]

        image_array = mpimg.imread(file_name)

        # DIMENSIONS TO AVG ACROSS DIFFERENT FOR HORIZ VS VERT IMG
        if image_array.shape[0] > 5 * image_array.shape[1]:  # VERT
            image_vector = np.average(image_array, axis=(1, 2))
            pixel_size_in_cm = 2.54 / dpi_vert
        elif image_array.shape[1] > 5 * image_array.shape[0]:  # HORIZ
            image_vector = np.average(image_array, axis=(0, 2))
            pixel_size_in_cm = 2.54 / dpi_horiz
        else:
            raise ValueError("The PNG file is not a narrow strip.")
        assert step_size > 5 * pixel_size_in_cm, "step size too small"

        if image_vector.shape[0] % 2 == 0:
            image_vector = image_vector[:-1]  # SO ZERO DISTANCE IS MID-PIXEL

        length_in_cm = image_vector.shape[0] * pixel_size_in_cm
        full_resolution_distances = np.arange(-length_in_cm / 2,
                                              length_in_cm / 2,
                                              pixel_size_in_cm)

        # TO MOVE FROM FILM RESOLUTION TO DESIRED PROFILE RESOLUTION
        num_pixels_to_avg_over = int(step_size / pixel_size_in_cm)
        sample_indices = np.arange(
            num_pixels_to_avg_over / 2,
            len(full_resolution_distances),
            num_pixels_to_avg_over,
        ).astype(int)
        downsampled_distances = list(full_resolution_distances[sample_indices])

        downsampled_density = []
        for idx in sample_indices:  # AVERAGE OVER THE SAMPLING WINDOW
            avg_density = np.average(
                image_vector[int(idx - num_pixels_to_avg_over /
                                 2):int(idx + num_pixels_to_avg_over / 2)])
            downsampled_density.append(avg_density)

        zipped_profile = list(zip(downsampled_distances, downsampled_density))
        return Profile().from_tuples(zipped_profile)