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)
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
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
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
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
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
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
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
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
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
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
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
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