def convert_IEC_angle_to_bipolar(angle): angle = np.copy(angle) if np.all(angle == 180): return angle angle[angle > 180] = angle[angle > 180] - 360 is_180 = np.where(angle == 180)[0] not_180 = np.where(np.invert(angle == 180))[0] where_closest_left_leaning = np.argmin(np.abs(is_180[:, None] - not_180[None, :]), axis=1) where_closest_right_leaning = (len(not_180) - 1 - np.argmin( np.abs(is_180[::-1, None] - not_180[None, ::-1]), axis=1)[::-1]) closest_left_leaning = not_180[where_closest_left_leaning] closest_right_leaning = not_180[where_closest_right_leaning] assert np.all( np.sign(angle[closest_left_leaning]) == np.sign( angle[closest_right_leaning]) ), "Unable to automatically determine whether angle is 180 or -180" angle[is_180] = np.sign(angle[closest_left_leaning]) * angle[is_180] return angle
def calc_logfile_mu_density_bygantry(index, config, logfile_group, gantry_angle, grid_resolution=1): logfile_mu_density = None for filehash in logfile_group: filepath = get_filepath(index, config, filehash) logfile_delivery_data = pymedphys.Delivery.from_logfile(filepath) a_logfile_mu_density = [ pymedphys.mudensity.grid(grid_resolution=grid_resolution), logfile_delivery_data.mudensity(gantry_angle, grid_resolution=grid_resolution), ] if logfile_mu_density is None: logfile_mu_density = a_logfile_mu_density else: assert np.all(logfile_mu_density[0] == a_logfile_mu_density[0]) assert np.all(logfile_mu_density[1] == a_logfile_mu_density[1]) logfile_mu_density[2] += a_logfile_mu_density[2] return logfile_mu_density
def get_logfile_mu_density_bygantry(logfile_groups, mosaiq_gantry_angles, logfile_delivery_data_bygantry): logfile_mu_density_bygantry = dict() for logfile_group in logfile_groups: delivery_data = logfile_delivery_data_bygantry[logfile_group] logfile_mu_density_bygantry[logfile_group] = dict() for file_hash in logfile_group: for mosaiq_gantry_angle in mosaiq_gantry_angles: num_control_points = len( delivery_data[file_hash][mosaiq_gantry_angle]["mu"]) if num_control_points > 0: mu_density = [ pymedphys.mudensity.grid(), delivery_data[file_hash] [mosaiq_gantry_angle].mudensity(), ] if (mosaiq_gantry_angle not in logfile_mu_density_bygantry[logfile_group]): logfile_mu_density_bygantry[logfile_group][ mosaiq_gantry_angle] = list(mu_density) else: assert np.all( logfile_mu_density_bygantry[logfile_group] [mosaiq_gantry_angle][0] == mu_density[0]) assert np.all( logfile_mu_density_bygantry[logfile_group] [mosaiq_gantry_angle][1] == mu_density[1]) logfile_mu_density_bygantry[logfile_group][ mosaiq_gantry_angle][2] += mu_density[2] return logfile_mu_density_bygantry
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
def compare_logfile_group_bygantry(index, config, cursor, logfile_group, gantry_angle, grid_resolution=1): field_id = get_field_id_from_logfile_group(index, logfile_group) mosaiq_delivery_data = pymedphys.Delivery.from_mosaiq(cursor, field_id) mosaiq_mu_density = [ pymedphys.mudensity.grid(grid_resolution=grid_resolution), mosaiq_delivery_data.mudensity(gantry_angle, grid_resolution=grid_resolution), ] normalisation = calc_normalisation(mosaiq_delivery_data) logfile_mu_density = calc_logfile_mu_density_bygantry( index, config, logfile_group, gantry_angle) grid_xx = logfile_mu_density[0] grid_yy = logfile_mu_density[1] assert np.all(grid_xx == mosaiq_mu_density[0]) assert np.all(grid_yy == mosaiq_mu_density[1]) comparison = calc_comparison(logfile_mu_density[2], mosaiq_mu_density[2], normalisation) print(comparison) plot_results(grid_xx, grid_yy, logfile_mu_density[2], mosaiq_mu_density[2]) return comparison
def compare_mosaiq_fields(servers, field_ids): unique_servers = list(set(servers)) with pymedphys.mosaiq.connect(unique_servers) as cursors: deliveries = [ pymedphys.Delivery.from_mosaiq(cursors[server], field_id) for server, field_id in zip(servers, field_ids) ] mu_density_results = [ delivery_data.mudensity() for delivery_data in deliveries ] mu_densities = [results[2] for results in mu_density_results] labels = [ "Server: `{}` | Field ID: `{}`".format(server, field_id) for server, field_id in zip(servers, field_ids) ] plot_gantry_collimator(labels, deliveries) plot_mu_densities(labels, mu_density_results) mu_densities_match = np.all([ np.all(np.abs(mu_density_a - mu_density_b) < 0.1) for mu_density_a, mu_density_b in itertools.combinations( mu_densities, 2) ]) plt.show() print("MU Densities match: {}".format(mu_densities_match)) return deliveries, mu_densities
def from_mosaiq(cls, cursor, field_id): mosaiq_delivery_data = cls._from_mosaiq_base(cursor, field_id) reference_data = ( mosaiq_delivery_data.monitor_units, mosaiq_delivery_data.mlc, mosaiq_delivery_data.jaw, ) delivery_data = cls._from_mosaiq_base(cursor, field_id) test_data = (delivery_data.monitor_units, delivery_data.mlc, delivery_data.jaw) agreement = False while not agreement: agreements = [] for ref, test in zip(reference_data, test_data): agreements.append(np.all(ref == test)) agreement = np.all(agreements) if not agreement: print("Converted Mosaiq delivery data was conflicting.") print( "MU agreement: {}\nMLC agreement: {}\n" "Jaw agreement: {}".format(*agreements) ) print("Trying again...") reference_data = test_data delivery_data = cls._from_mosaiq_base(cursor, field_id) test_data = ( delivery_data.monitor_units, delivery_data.mlc, delivery_data.jaw, ) return delivery_data
def get_comparison_results(mosaiq_mu_density_bygantry, logfile_mu_density_bygantry, normalisation): comparison_results = dict() mosaiq_gantry_angles = mosaiq_mu_density_bygantry.keys() logfile_groups = list(logfile_mu_density_bygantry.keys()) for mosaiq_gantry_angle in mosaiq_gantry_angles: comparison_results[mosaiq_gantry_angle] = dict() comparison_results[mosaiq_gantry_angle]["comparisons"] = {} grid_xx = mosaiq_mu_density_bygantry[mosaiq_gantry_angle][0] grid_yy = mosaiq_mu_density_bygantry[mosaiq_gantry_angle][1] mosaiq_mu_density = mosaiq_mu_density_bygantry[mosaiq_gantry_angle][2] for logfile_group in logfile_groups: assert np.all(grid_xx == logfile_mu_density_bygantry[logfile_group] [mosaiq_gantry_angle][0]) assert np.all(grid_yy == logfile_mu_density_bygantry[logfile_group] [mosaiq_gantry_angle][1]) logfile_mu_density = logfile_mu_density_bygantry[logfile_group][ mosaiq_gantry_angle][2] comparison = calc_comparison(logfile_mu_density, mosaiq_mu_density, normalisation) comparison_results[mosaiq_gantry_angle]["comparisons"][ logfile_group] = comparison comparisons = np.array([ comparison_results[mosaiq_gantry_angle]["comparisons"] [logfile_group] for logfile_group in logfile_groups ]) comparison_results[mosaiq_gantry_angle]["median"] = np.median( comparisons) ref = np.argmin( np.abs(comparisons - comparison_results[mosaiq_gantry_angle]["median"])) comparison_results[mosaiq_gantry_angle][ "median_filehash_group"] = logfile_groups[ref] comparison_results[mosaiq_gantry_angle][ "filehash_groups"] = logfile_groups return comparison_results
def fetch_and_verify_mosaiq_sql(cursor, field_id): reference_results = delivery_data_sql(cursor, field_id) test_results = delivery_data_sql(cursor, field_id) agreement = False while not agreement: agreements = [] for ref, test in zip(reference_results, test_results): agreements.append(np.all(ref == test)) agreement = np.all(agreements) if not agreement: print("Mosaiq sql query gave conflicting data.") print("Trying again...") reference_results = test_results test_results = delivery_data_sql(cursor, field_id) return test_results
def pcolormesh_grid(x, y, grid_resolution=None): if grid_resolution is None: diffs = np.hstack([np.diff(x), np.diff(y)]) assert np.all(np.abs(diffs - diffs[0]) < 10 ** -12) grid_resolution = diffs[0] new_x = np.concatenate([x - grid_resolution / 2, [x[-1] + grid_resolution / 2]]) new_y = np.concatenate([y - grid_resolution / 2, [y[-1] + grid_resolution / 2]]) return new_x, new_y
def get_logfile_mosaiq_results( index, config, field_id_key_map, filehash, cursors, grid_resolution=1 ): file_info = index[filehash] delivery_details = file_info["delivery_details"] field_id = delivery_details["field_id"] centre = get_centre(config, file_info) server = get_sql_servers(config)[centre] mosaiq_delivery_data = pymedphys.Delivery.from_mosaiq(cursors[server], field_id) mosaiq_results = mu_density_from_delivery_data( mosaiq_delivery_data, grid_resolution=grid_resolution ) consecutive_keys = find_consecutive_logfiles( field_id_key_map, field_id, filehash, index ) logfilepaths = [get_filepath(index, config, key) for key in consecutive_keys] logfile_results = calc_and_merge_logfile_mudensity( logfilepaths, grid_resolution=grid_resolution ) try: assert np.all(logfile_results[0] == mosaiq_results[0]) assert np.all(logfile_results[1] == mosaiq_results[1]) except AssertionError: print(np.shape(logfile_results[0])) print(np.shape(mosaiq_results[0])) raise grid_xx = logfile_results[0] grid_yy = logfile_results[1] logfile_mu_density = logfile_results[2] mosaiq_mu_density = mosaiq_results[2] return grid_xx, grid_yy, logfile_mu_density, mosaiq_mu_density
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 _calc_blocked_t(travel_diff, grid_resolution): blocked_t = np.ones_like(travel_diff) * np.nan fully_blocked = travel_diff <= -grid_resolution / 2 fully_open = travel_diff >= grid_resolution / 2 blocked_t[fully_blocked] = 1 blocked_t[fully_open] = 0 transient = ~fully_blocked & ~fully_open blocked_t[transient] = (-travel_diff[transient] + grid_resolution / 2) / grid_resolution assert np.all(~np.isnan(blocked_t)) return blocked_t
def assert_array_agreement(unique_logfile_gantry_angles, mosaiq_gantry_angles, allowed_deviation): difference_matrix = np.abs(unique_logfile_gantry_angles[:, None] - mosaiq_gantry_angles[None, :]) agreement_matrix = difference_matrix <= allowed_deviation row_agreement = np.any(agreement_matrix, axis=1) at_least_one_agreement = np.all(row_agreement) assert at_least_one_agreement, ( "There is a logfile gantry angle that deviates by more than {} degrees" " from the Mosaiq control points. Unsure how to handle this.\n\n" "Logfile: {}\nMosaiq: {}\nDifference Matrix:\n{}\n" "Agreement Matrix:\n{}".format( allowed_deviation, unique_logfile_gantry_angles, mosaiq_gantry_angles, difference_matrix, agreement_matrix, ))
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
def _gantry_angle_mask(self, gantry_angle, gantry_angle_tol): near_angle = np.abs(np.array(self.gantry) - gantry_angle) <= gantry_angle_tol assert np.all(np.diff(np.where(near_angle)[0]) == 1) return near_angle
def calc_single_control_point( mlc, jaw, delivered_mu=1, leaf_pair_widths=__DEFAULT_LEAF_PAIR_WIDTHS, grid_resolution=__DEFAULT_GRID_RESOLUTION, min_step_per_pixel=__DEFAULT_MIN_STEP_PER_PIXEL, ): """Calculate the MU Density for a single control point. Examples -------- >>> from pymedphys._imports import numpy as np >>> from pymedphys._mudensity.mudensity import ( ... calc_single_control_point, display_mu_density) >>> >>> leaf_pair_widths = (2, 2) >>> mlc = np.array([ ... [ ... [1, 1], ... [2, 2], ... ], ... [ ... [2, 2], ... [3, 3], ... ] ... ]) >>> jaw = np.array([ ... [1.5, 1.2], ... [1.5, 1.2] ... ]) >>> grid, mu_density = calc_single_control_point( ... mlc, jaw, leaf_pair_widths=leaf_pair_widths) >>> display_mu_density(grid, mu_density) >>> >>> grid['mlc'] array([-3., -2., -1., 0., 1., 2., 3.]) >>> >>> grid['jaw'] array([-1.5, -0.5, 0.5, 1.5]) >>> >>> np.round(mu_density, 2) array([[0. , 0.07, 0.43, 0.5 , 0.43, 0.07, 0. ], [0. , 0.14, 0.86, 1. , 0.86, 0.14, 0. ], [0.14, 0.86, 1. , 1. , 1. , 0.86, 0.14], [0.03, 0.17, 0.2 , 0.2 , 0.2 , 0.17, 0.03]]) """ leaf_pair_widths = np.array(leaf_pair_widths) leaf_division = leaf_pair_widths / grid_resolution if not np.all(leaf_division.astype(int) == leaf_division): raise ValueError( "The grid resolution needs to exactly divide every leaf pair width." ) if ( not np.max(np.abs(jaw)) # pylint: disable = unneeded-not <= np.sum(leaf_pair_widths) / 2 ): raise ValueError( "The jaw should not travel further out than the maximum leaf limits. " f"Max travel was {np.max(np.abs(jaw))}" ) (grid, grid_leaf_map, mlc) = _determine_calc_grid_and_adjustments( mlc, jaw, leaf_pair_widths, grid_resolution ) positions = { "mlc": { 1: (-mlc[0, :, 0], -mlc[1, :, 0]), # left -1: (mlc[0, :, 1], mlc[1, :, 1]), # right }, "jaw": { 1: (-jaw[0::-1, 0], -jaw[1::, 0]), # bot -1: (jaw[0::-1, 1], jaw[1::, 1]), # top }, } time_steps = _calc_time_steps(positions, grid_resolution, min_step_per_pixel) blocked_by_device = _calc_blocked_by_device( grid, positions, grid_resolution, time_steps ) device_open = _calc_device_open(blocked_by_device) mlc_open, jaw_open = _remap_mlc_and_jaw(device_open, grid_leaf_map) open_fraction = _calc_open_fraction(mlc_open, jaw_open) mu_density = open_fraction * delivered_mu return grid, mu_density