コード例 #1
0
def create_transformed_mesh(width_data, length_data, factor_data):
    """Return factor data meshgrid."""
    x = np.arange(
        np.floor(np.min(width_data)) - 1,
        np.ceil(np.max(width_data)) + 1, 0.1)
    y = np.arange(
        np.floor(np.min(length_data)) - 1,
        np.ceil(np.max(length_data)) + 1, 0.1)

    xx, yy = np.meshgrid(x, y)

    zz = spline_model_with_deformability(
        xx,
        convert2_ratio_perim_area(xx, yy),
        width_data,
        convert2_ratio_perim_area(width_data, length_data),
        factor_data,
    )

    zz[xx > yy] = np.nan

    no_data_x = np.all(np.isnan(zz), axis=0)
    no_data_y = np.all(np.isnan(zz), axis=1)

    x = x[np.invert(no_data_x)]
    y = y[np.invert(no_data_y)]

    zz = zz[np.invert(no_data_y), :]
    zz = zz[:, np.invert(no_data_x)]

    return x, y, zz
コード例 #2
0
ファイル: findfield.py プロジェクト: lc52520/pymedphys
def get_initial_centre(x, y, img):
    wl_image = pymedphys._vendor.pylinac.winstonlutz.WLImageOld(  # pylint: disable = protected-access
        img)
    min_x = np.min(x)
    dx = x[1] - x[0]
    min_y = np.min(y)
    dy = y[1] - y[0]

    field_centre = [
        wl_image.field_cax.x * dx + min_x,
        wl_image.field_cax.y * dy + min_y,
    ]

    return field_centre
コード例 #3
0
ファイル: cube.py プロジェクト: gkayal/pymedphys
def calc_min_distance(cube_definition, contours):
    vertices = cube_vertices(cube_definition)

    vectors = cube_vectors(cube_definition)
    unit_vectors = [vector / np.linalg.norm(vector) for vector in vectors]

    plane_norms = np.array(
        [
            unit_vectors[1],
            -unit_vectors[0],
            -unit_vectors[1],
            unit_vectors[0],
            unit_vectors[2],
            -unit_vectors[2],
        ]
    )

    plane_points = np.array(
        [vertices[0], vertices[1], vertices[2], vertices[0], vertices[0], vertices[3]]
    )

    plane_origin_dist = -np.sum(plane_points * plane_norms, axis=1)

    distance_to_planes = np.dot(plane_norms, contours) + plane_origin_dist[:, None]

    min_dist_squared = np.min(distance_to_planes ** 2, axis=0)

    return min_dist_squared
コード例 #4
0
ファイル: shell.py プロジェクト: lc52520/pymedphys
def multi_thresholds_gamma_calc(
    options: GammaInternalFixedOptions,
    current_gamma,
    min_relative_dose_difference,
    distance,
    to_be_checked,
):

    gamma_at_distance = np.sqrt(
        (min_relative_dose_difference[:, None, None] /
         (options.dose_percent_threshold[None, :, None] / 100))**2 +
        (distance / options.distance_mm_threshold[None, None, :])**2)

    current_gamma[to_be_checked, :, :] = np.min(
        np.concatenate(
            [
                gamma_at_distance[None, :, :, :],
                current_gamma[None, to_be_checked, :, :],
            ],
            axis=0,
        ),
        axis=0,
    )

    still_searching_for_gamma = current_gamma > (
        distance / options.distance_mm_threshold[None, None, :])

    if options.skip_once_passed:
        still_searching_for_gamma = still_searching_for_gamma & (current_gamma
                                                                 >= 1)

    return current_gamma, still_searching_for_gamma
コード例 #5
0
ファイル: findfield.py プロジェクト: lc52520/pymedphys
def check_aspect_ratio(edge_lengths):
    if not np.allclose(*edge_lengths):
        if np.min(edge_lengths) > 0.95 * np.max(edge_lengths):
            raise ValueError(
                "For non-square rectangular fields, "
                "to accurately determine the rotation, "
                "need to have the small edge be less than 95% of the long edge."
            )
コード例 #6
0
ファイル: cube.py プロジェクト: gkayal/pymedphys
def get_bounding_box(points):
    x_min = np.min(points[:, 1])
    x_max = np.max(points[:, 1])
    y_min = np.min(points[:, 0])
    y_max = np.max(points[:, 0])
    z_min = np.min(points[:, 2])
    z_max = np.max(points[:, 2])

    max_range = np.array([x_max - x_min, y_max - y_min, z_max - z_min]).max() / 2.0

    mid_x = (x_max + x_min) * 0.5
    mid_y = (y_max + y_min) * 0.5
    mid_z = (z_max + z_min) * 0.5

    return [
        [mid_y - max_range, mid_y + max_range],
        [mid_x - max_range, mid_x + max_range],
        [mid_z - max_range, mid_z + max_range],
    ]
コード例 #7
0
ファイル: mudensity.py プロジェクト: lc52520/pymedphys
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
コード例 #8
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)
コード例 #9
0
def spline_model(width_test, ratio_perim_area_test, width_data,
                 ratio_perim_area_data, factor_data):
    """Return the result of the spline model.

    The bounding box is chosen so as to allow extrapolation. The spline orders
    are two in the width direction and one in the perimeter/area direction. For
    justification on using this method for modelling electron insert factors
    see the *Methods: Bivariate spline model* section within
    <http://dx.doi.org/10.1016/j.ejmp.2015.11.002>.

    Parameters
    ----------
    width_test : np.ndarray
        The width point(s) which are to have the electron insert factor
        interpolated.
    ratio_perim_area_test : np.ndarray
        The perimeter/area which are to have the electron insert factor
        interpolated.

    width_data : np.ndarray
        The width data points for the relevant applicator, energy and ssd.
    ratio_perim_area_data : np.ndarray
        The perimeter/area data points for the relevant applicator, energy and
        ssd.
    factor_data : np.ndarray
        The insert factor data points for the relevant applicator, energy and
        ssd.

    Returns
    -------
    result : np.ndarray
        The interpolated electron insert factors for width_test and
        ratio_perim_area_test.

    """
    bbox = [
        np.min([np.min(width_data), np.min(width_test)]),
        np.max([np.max(width_data), np.max(width_test)]),
        np.min([np.min(ratio_perim_area_data),
                np.min(ratio_perim_area_test)]),
        np.max([np.max(ratio_perim_area_data),
                np.max(ratio_perim_area_test)]),
    ]

    spline = scipy.interpolate.SmoothBivariateSpline(width_data,
                                                     ratio_perim_area_data,
                                                     factor_data,
                                                     kx=2,
                                                     ky=1,
                                                     bbox=bbox)

    return spline.ev(width_test, ratio_perim_area_test)
コード例 #10
0
ファイル: cube.py プロジェクト: gkayal/pymedphys
def align_cube_to_structure(
    structure_name: str,
    dcm_struct: pydicom.dataset.FileDataset,
    quiet=False,
    niter=10,
    x0=None,
):
    """Align a cube to a dicom structure set.

    Designed to allow arbitrary references frames within a dicom file
    to be extracted via contouring a cube.

    Parameters
    ----------
    structure_name
        The DICOM label of the cube structure
    dcm_struct
        The pydicom reference to the DICOM structure file.
    quiet : ``bool``
        Tell the function to not print anything. Defaults to False.
    x0 : ``np.ndarray``, optional
        A 3x3 array with each row defining a 3-D point in space.
        These three points are used as initial conditions to search for
        a cube that fits the contours. Choosing initial values close to
        the structure set, and in the desired orientation will allow
        consistent results. See examples within
        `pymedphys.experimental.cubify`_ on what the
        effects of each of the three points are on the resulting cube.
        By default, this parameter is defined using the min/max values
        of the contour structure.

    Returns
    -------
    cube_definition_array
        Four 3-D points the define the vertices of the cube.

    vectors
        The vectors between the points that can be used to traverse the cube.

    Examples
    --------
    >>> import numpy as np
    >>> import pydicom
    >>> import pymedphys
    >>> from pymedphys.experimental import align_cube_to_structure
    >>>
    >>> struct_path = str(pymedphys.data_path('example_structures.dcm'))
    >>> dcm_struct = pydicom.dcmread(struct_path, force=True)
    >>> structure_name = 'ANT Box'
    >>> cube_definition_array, vectors = align_cube_to_structure(
    ...     structure_name, dcm_struct, quiet=True, niter=1)
    >>> np.round(cube_definition_array)
    array([[-266.,  -31.,   43.],
           [-266.,   29.,   42.],
           [-207.,  -31.,   33.],
           [-276.,  -31.,  -16.]])
    >>>
    >>> np.round(vectors, 1)
    array([[  0.7,  59.9,  -0.5],
           [ 59.2,  -0.7,  -9.7],
           [ -9.7,  -0.4, -59.2]])
    """

    contours = pull_structure(structure_name, dcm_struct)
    contour_points = contour_to_points(contours)

    def to_minimise(cube):
        cube_definition = cubify([tuple(cube[0:3]), tuple(cube[3:6]), tuple(cube[6::])])
        min_dist_squared = calc_min_distance(cube_definition, contour_points)
        return np.sum(min_dist_squared)

    if x0 is None:
        concatenated_contours = [
            np.concatenate(contour_coord) for contour_coord in contours
        ]

        bounds = [
            (np.min(concatenated_contour), np.max(concatenated_contour))
            for concatenated_contour in concatenated_contours
        ]

        x0 = np.array(
            [
                (bounds[1][0], bounds[0][0], bounds[2][1]),
                (bounds[1][0], bounds[0][1], bounds[2][1]),
                (bounds[1][1], bounds[0][0], bounds[2][1]),
            ]
        )

    if quiet:

        def print_fun(x, f, accepted):  # pylint: disable = unused-argument
            pass

    else:

        def print_fun(x, f, accepted):  # pylint: disable = unused-argument
            print("at minimum %.4f accepted %d" % (f, int(accepted)))

    result = basinhopping(to_minimise, x0, callback=print_fun, niter=niter, stepsize=5)

    cube = result.x

    cube_definition = cubify([tuple(cube[0:3]), tuple(cube[3:6]), tuple(cube[6::])])

    cube_definition_array = np.array([np.array(list(item)) for item in cube_definition])

    vectors = [
        cube_definition_array[1] - cube_definition_array[0],
        cube_definition_array[2] - cube_definition_array[0],
        cube_definition_array[3] - cube_definition_array[0],
    ]

    return cube_definition_array, vectors
コード例 #11
0
def plot_results(
    grid_xx, grid_yy, logfile_mu_density, mosaiq_mu_density, diff_colour_scale=0.1
):
    min_val = np.min([logfile_mu_density, mosaiq_mu_density])
    max_val = np.max([logfile_mu_density, mosaiq_mu_density])

    plt.figure()
    plt.pcolormesh(grid_xx, grid_yy, logfile_mu_density, vmin=min_val, vmax=max_val)
    plt.colorbar()
    plt.title("Logfile MU density")
    plt.xlabel("MLC direction (mm)")
    plt.ylabel("Jaw direction (mm)")
    plt.gca().invert_yaxis()

    plt.figure()
    plt.pcolormesh(grid_xx, grid_yy, mosaiq_mu_density, vmin=min_val, vmax=max_val)
    plt.colorbar()
    plt.title("Mosaiq MU density")
    plt.xlabel("MLC direction (mm)")
    plt.ylabel("Jaw direction (mm)")
    plt.gca().invert_yaxis()

    scaled_diff = (logfile_mu_density - mosaiq_mu_density) / max_val

    plt.figure()
    plt.pcolormesh(
        grid_xx,
        grid_yy,
        scaled_diff,
        vmin=-diff_colour_scale / 2,
        vmax=diff_colour_scale / 2,
    )
    plt.colorbar(label="Limited colour range = {}".format(diff_colour_scale / 2))
    plt.title("(Logfile - Mosaiq MU density) / Maximum MU Density")
    plt.xlabel("MLC direction (mm)")
    plt.ylabel("Jaw direction (mm)")
    plt.gca().invert_yaxis()

    plt.show()

    plt.figure()
    plt.pcolormesh(
        grid_xx, grid_yy, scaled_diff, vmin=-diff_colour_scale, vmax=diff_colour_scale
    )
    plt.colorbar(label="Limited colour range = {}".format(diff_colour_scale))
    plt.title("(Logfile - Mosaiq MU density) / Maximum MU Density")
    plt.xlabel("MLC direction (mm)")
    plt.ylabel("Jaw direction (mm)")
    plt.gca().invert_yaxis()

    plt.show()

    absolute_range = np.max([-np.min(scaled_diff), np.max(scaled_diff)])

    plt.figure()
    plt.pcolormesh(
        grid_xx, grid_yy, scaled_diff, vmin=-absolute_range, vmax=absolute_range
    )
    plt.colorbar(label="No limited colour range")
    plt.title("(Logfile - Mosaiq MU density) / Maximum MU Density")
    plt.xlabel("MLC direction (mm)")
    plt.ylabel("Jaw direction (mm)")
    plt.gca().invert_yaxis()

    plt.show()
コード例 #12
0
ファイル: shell.py プロジェクト: lc52520/pymedphys
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
コード例 #13
0
ファイル: shell.py プロジェクト: lc52520/pymedphys
def gamma_loop(options: GammaInternalFixedOptions):
    still_searching_for_gamma = np.full_like(options.flat_dose_reference,
                                             True,
                                             dtype=bool)

    current_gamma = np.inf * np.ones((
        len(options.flat_dose_reference),
        len(options.dose_percent_threshold),
        len(options.distance_mm_threshold),
    ))

    distance_step_size = np.min(
        options.distance_mm_threshold) / options.interp_fraction

    to_be_checked = options.reference_points_to_calc & still_searching_for_gamma

    distance = 0.0

    force_search_distances = np.sort(options.distance_mm_threshold)
    while distance <= options.maximum_test_distance:
        if not options.quiet:
            sys.stdout.write(
                "\rCurrent distance: {0:.2f} mm | "
                "Number of reference points remaining: {1}".format(
                    distance, np.sum(to_be_checked)))

        min_relative_dose_difference = calculate_min_dose_difference(
            options, distance, to_be_checked, distance_step_size)

        current_gamma, still_searching_for_gamma_all = multi_thresholds_gamma_calc(
            options,
            current_gamma,
            min_relative_dose_difference,
            distance,
            to_be_checked,
        )

        still_searching_for_gamma = np.any(np.any(
            still_searching_for_gamma_all, axis=-1),
                                           axis=-1)

        to_be_checked = options.reference_points_to_calc & still_searching_for_gamma

        if np.sum(to_be_checked) == 0:
            break

        relevant_distances = options.distance_mm_threshold[np.any(
            np.any(
                options.reference_points_to_calc[:, None, None]
                & still_searching_for_gamma_all,
                axis=0,
            ),
            axis=0,
        )]

        distance_step_size = np.min(
            relevant_distances) / options.interp_fraction

        distance_step_size = np.max([
            distance / options.interp_fraction / options.max_gamma,
            distance_step_size
        ])

        distance += distance_step_size
        if len(force_search_distances) != 0:
            if distance >= force_search_distances[0]:
                distance = force_search_distances[0]
                force_search_distances = np.delete(force_search_distances, 0)

    return current_gamma
コード例 #14
0
def gantry_tol_from_gantry_angles(gantry_angles):
    min_diff = np.min(np.diff(sorted(gantry_angles)))
    gantry_tol = np.min([min_diff / 2 - 0.1, 3])

    return gantry_tol