Beispiel #1
0
def get_mu_densities_for_file_hashes(index, config, cursor, file_hashes):
    field_ids = {
        index[file_hash]["delivery_details"]["field_id"]
        for file_hash in file_hashes
    }

    assert len(field_ids) == 1
    field_id = field_ids.pop()

    logfile_groups = group_consecutive_logfiles(file_hashes, index)
    logfile_groups = [tuple(group) for group in logfile_groups]

    mosaiq_delivery_data = pymedphys.Delivery.from_mosaiq(cursor, field_id)
    mosaiq_gantry_angles = np.unique(mosaiq_delivery_data.gantry)

    logfile_delivery_data_bygantry = get_logfile_delivery_data_bygantry(
        index, config, logfile_groups, mosaiq_gantry_angles)
    logfile_mu_density_bygantry = get_logfile_mu_density_bygantry(
        logfile_groups, mosaiq_gantry_angles, logfile_delivery_data_bygantry)
    mosaiq_delivery_data_bygantry = get_mosaiq_delivery_data_bygantry(
        mosaiq_delivery_data)
    mosaiq_mu_density_bygantry = get_mosaiq_mu_density_bygantry(
        mosaiq_delivery_data_bygantry)

    normalisation = calc_normalisation(mosaiq_delivery_data)

    return (mosaiq_mu_density_bygantry, logfile_mu_density_bygantry,
            normalisation)
Beispiel #2
0
def create_bb_to_minimise(field, bb_diameter):
    """This is a numpy vectorised version of `create_bb_to_minimise_simple`
    """

    points_to_check_edge_agreement, dist = interppoints.create_bb_points_function(
        bb_diameter)
    dist_mask = np.unique(dist)[:, None] == dist[None, :]
    num_in_mask = np.sum(dist_mask, axis=1)
    mask_count_per_item = np.sum(num_in_mask[:, None] * dist_mask, axis=0)
    mask_mean_lookup = np.where(dist_mask)[0]

    def to_minimise_edge_agreement(centre):
        x, y = points_to_check_edge_agreement(centre)

        results = field(x, y)
        masked_results = results * dist_mask
        mask_mean = np.sum(masked_results, axis=1) / num_in_mask
        diff_to_mean_square = (results - mask_mean[mask_mean_lookup])**2
        mean_of_layers = np.sum(
            diff_to_mean_square[1::] /
            mask_count_per_item[1::]) / (len(mask_mean) - 1)

        return mean_of_layers

    return to_minimise_edge_agreement
Beispiel #3
0
def calc_normalisation(mosaiq_delivery_data):
    all_gantry_angles = mosaiq_delivery_data.mudensity
    mosaiq_gantry_angles = np.unique(mosaiq_delivery_data.gantry)
    number_of_gantry_angles = len(mosaiq_gantry_angles)

    normalisation = np.sum(all_gantry_angles) / number_of_gantry_angles

    return normalisation
Beispiel #4
0
def get_field_id_from_logfile_group(index, logfile_group):
    field_ids = []

    for logfile_hash in logfile_group:
        field_ids.append(index[logfile_hash]["delivery_details"]["field_id"])

    assert len(np.unique(field_ids)) == 1

    field_id = field_ids[0]

    return field_id
Beispiel #5
0
def extract_contours_and_image_sequences(contour_sequence):
    contours_by_z = {}
    image_sequence_by_z = {}

    expected_contour_keys = {
        pydicom.tag.Tag(*tag)
        for tag in [
            (0x3006, 0x0050),
            (0x3006, 0x0046),
            (0x3006, 0x0042),
            (0x3006, 0x0016),
        ]
    }

    for contour in contour_sequence:
        if contour.ContourGeometricType != CONTOUR_GEOMETRIC_TYPE:
            raise ValueError(
                f"Only {CONTOUR_GEOMETRIC_TYPE} type is supported")

        if set(contour.keys()) != expected_contour_keys:
            raise ValueError("Unexpected contour sequence format")

        contour_data = contour.ContourData

        x = np.array(contour_data[0::3])
        y = np.array(contour_data[1::3])
        z = np.array(contour_data[2::3])

        unique_z = np.unique(z)

        if len(unique_z) != 1:
            raise ValueError("All z values should be equal")

        z = unique_z[0]
        polygon = shapely.geometry.Polygon(zip(x, y))

        try:
            contours_by_z[z].append(polygon)
        except KeyError:
            contours_by_z[z] = [polygon]

        try:
            image_sequence_by_z[z].append(contour.ContourImageSequence)
        except KeyError:
            image_sequence_by_z[z] = [contour.ContourImageSequence]

    return contours_by_z, image_sequence_by_z
Beispiel #6
0
def get_logfile_delivery_data_bygantry(index, config, logfile_groups,
                                       mosaiq_gantry_angles):
    logfile_delivery_data_bygantry = dict()

    for logfile_group in logfile_groups:
        logfile_delivery_data_bygantry[logfile_group] = dict()

        for file_hash in logfile_group:
            filepath = get_filepath(index, config, file_hash)
            logfile_delivery_data = pymedphys.Delivery.from_logfile(filepath)
            mu = np.array(logfile_delivery_data.monitor_units)

            filtered = (
                logfile_delivery_data._filter_cps(
                )  # pylint: disable = protected-access
            )

            mu = filtered.monitor_units
            mlc = filtered.mlc
            jaw = filtered.jaw
            logfile_gantry_angles = filtered.gantry

            gantry_tolerance = get_gantry_tolerance(index, file_hash, config)
            unique_logfile_gantry_angles = np.unique(logfile_gantry_angles)

            assert_array_agreement(unique_logfile_gantry_angles,
                                   mosaiq_gantry_angles, gantry_tolerance)

            logfile_delivery_data_bygantry[logfile_group][file_hash] = dict()

            for mosaiq_gantry_angle in mosaiq_gantry_angles:
                logfile_delivery_data_bygantry[logfile_group][file_hash][
                    mosaiq_gantry_angle] = dict()
                agrees_within_tolerance = (
                    np.abs(logfile_gantry_angles - mosaiq_gantry_angle) <=
                    gantry_tolerance)

                logfile_delivery_data_bygantry[logfile_group][file_hash][
                    mosaiq_gantry_angle]["mu"] = mu[agrees_within_tolerance]
                logfile_delivery_data_bygantry[logfile_group][file_hash][
                    mosaiq_gantry_angle]["mlc"] = mlc[agrees_within_tolerance]
                logfile_delivery_data_bygantry[logfile_group][file_hash][
                    mosaiq_gantry_angle]["jaw"] = jaw[agrees_within_tolerance]

    return logfile_delivery_data_bygantry
Beispiel #7
0
def create_bb_to_minimise_simple(field, bb_diameter):

    points_to_check_edge_agreement, dist = interppoints.create_bb_points_function(
        bb_diameter)
    dist_mask = np.unique(dist)[:, None] == dist[None, :]

    def to_minimise_edge_agreement(centre):
        x, y = points_to_check_edge_agreement(centre)

        total_minimisation = 0

        for current_mask in dist_mask[1::]:
            current_layer = field(x[current_mask], y[current_mask])
            total_minimisation += np.mean(
                (current_layer - np.mean(current_layer))**2)

        return total_minimisation / (len(dist_mask) - 1)

    return to_minimise_edge_agreement
Beispiel #8
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
Beispiel #9
0
def convert_numbers_to_string(name, lookup, column):
    dtype = np.array([item for _, item in lookup.items()]).dtype
    result = np.empty_like(column).astype(dtype)
    result[:] = ""

    for i, item in lookup.items():
        result[column.values == int(i)] = item

    if np.any(result == ""):
        print(lookup)
        print(np.where(result == ""))
        print(column[result == ""].values)
        unconverted_entries = np.unique(column[result == ""])
        raise Exception(
            "The conversion lookup list for converting {} is incomplete. "
            "The following data numbers were not converted:\n"
            "{}\n"
            "Please update the trf2csv conversion script to include these "
            "in its definitions.".format(name, unconverted_entries))

    return result
Beispiel #10
0
def create_bb_predictor(bb_x, bb_y, gantries, directions, default_tol=0.1):
    bb_coords_keys = ["x", "y"]
    direction_options = np.unique(directions)
    prediction_functions = {}

    for bb_coords_key, bb_coords in zip(bb_coords_keys, [bb_x, bb_y]):
        for current_direction in direction_options:
            prediction_functions[(
                bb_coords_key,
                current_direction)] = define_inner_prediction_func(
                    gantries, bb_coords, directions, current_direction)

    def predict_bb(gantry, direction, tol=default_tol):
        results = []
        for bb_coords_key in bb_coords_keys:
            results.append(prediction_functions[(bb_coords_key,
                                                 direction)](gantry, tol))

        return results

    return predict_bb
Beispiel #11
0
    def _gantry_angle_masks(self,
                            gantry_angles,
                            gantry_tol,
                            allow_missing_angles=False):
        masks = [
            self._gantry_angle_mask(gantry_angle, gantry_tol)
            for gantry_angle in gantry_angles
        ]

        for mask in masks:
            if np.all(mask == 0):
                continue

            # TODO: Apply mask by more than just gantry angle to appropriately
            # extract beam index even when multiple beams have the same gantry
            # angle
            is_duplicate_gantry_angles = (np.sum(
                np.abs(np.diff(np.concatenate([[0], mask, [0]])))) != 2)

            if is_duplicate_gantry_angles:
                raise ValueError("Duplicate gantry angles not yet supported")

        try:
            assert np.all(np.sum(masks, axis=0) == 1), (
                "Not all beams were captured by the gantry tolerance of "
                " {}".format(gantry_tol))
        except AssertionError:
            if not allow_missing_angles:
                print("Allowable gantry angles = {}".format(gantry_angles))
                gantry = np.array(self.gantry, copy=False)
                out_of_tolerance = np.unique(
                    gantry[np.sum(masks, axis=0) == 0]).tolist()
                print("The gantry angles out of tolerance were {}".format(
                    out_of_tolerance))

                raise

        return masks
Beispiel #12
0
def get_mosaiq_delivery_data_bygantry(mosaiq_delivery_data):
    mu = np.array(mosaiq_delivery_data.monitor_units)
    mlc = np.array(mosaiq_delivery_data.mlc)
    jaw = np.array(mosaiq_delivery_data.jaw)
    gantry_angles = np.array(mosaiq_delivery_data.gantry)
    unique_mosaiq_gantry_angles = np.unique(gantry_angles)

    mosaiq_delivery_data_bygantry = dict()

    for mosaiq_gantry_angle in unique_mosaiq_gantry_angles:
        gantry_angle_matches = gantry_angles == mosaiq_gantry_angle

        diff_mu = np.concatenate([[0], np.diff(mu)])[gantry_angle_matches]
        gantry_angle_specific_mu = np.cumsum(diff_mu)

        mosaiq_delivery_data_bygantry[mosaiq_gantry_angle] = dict()
        mosaiq_delivery_data_bygantry[mosaiq_gantry_angle][
            "mu"] = gantry_angle_specific_mu
        mosaiq_delivery_data_bygantry[mosaiq_gantry_angle]["mlc"] = mlc[
            gantry_angle_matches]
        mosaiq_delivery_data_bygantry[mosaiq_gantry_angle]["jaw"] = jaw[
            gantry_angle_matches]

    return mosaiq_delivery_data_bygantry
Beispiel #13
0
def absolute_scans_from_mephysto(mephysto_file, absolute_dose,
                                 depth_of_absolute_dose_mm):
    distance, relative_dose, scan_curvetype, scan_depth = api.load_mephysto(
        mephysto_file)

    depth_testing = scan_depth[~np.isnan(scan_depth)]
    depth_testing = np.unique(depth_testing)

    if depth_of_absolute_dose_mm == "dmax":
        choose_mephysto = scan_curvetype == "PDD"
        mephysto_pdd_depth = distance[choose_mephysto][0]
        mephysto_dose = relative_dose[choose_mephysto][0]
        depth_of_absolute_dose_mm = mephysto_pdd_depth[np.argmax(
            mephysto_dose)]

    mephysto_pdd_depth, mephysto_pdd_dose = mephysto_absolute_depth_dose(
        absolute_dose,
        depth_of_absolute_dose_mm,
        distance,
        relative_dose,
        scan_curvetype,
    )

    scans = {
        "depth_dose": {
            "displacement": mephysto_pdd_depth,
            "dose": mephysto_pdd_dose
        },
        "profiles": {},
    }

    for depth_test in depth_testing:
        (
            mephysto_distance_inplane,
            mephysto_normalised_dose_inplane,
        ) = mephysto_absolute_profiles(
            "INPLANE_PROFILE",
            depth_test,
            distance,
            relative_dose,
            scan_curvetype,
            scan_depth,
            mephysto_pdd_depth,
            mephysto_pdd_dose,
        )

        (
            mephysto_distance_crossplane,
            mephysto_normalised_dose_crossplane,
        ) = mephysto_absolute_profiles(
            "CROSSPLANE_PROFILE",
            depth_test,
            distance,
            relative_dose,
            scan_curvetype,
            scan_depth,
            mephysto_pdd_depth,
            mephysto_pdd_dose,
        )

        scans["profiles"][depth_test] = {
            "inplane": {
                "displacement": mephysto_distance_inplane,
                "dose": mephysto_normalised_dose_inplane,
            },
            "crossplane": {
                "displacement": mephysto_distance_crossplane,
                "dose": mephysto_normalised_dose_crossplane,
            },
        }

    return scans