def __call__(self, x, y): if np.shape(x) != np.shape(y): raise ValueError("x and y required to be the same shape") result = self._interpolation.ev(np.ravel(x), np.ravel(y)) result.shape = x.shape return result
def dose_inside_cube(x_dose, y_dose, z_dose, dose, cube): """Find the dose just within the given cube. """ cube_definition = cubify(cube) print(cube_definition) vertices = cube_vertices(cube_definition) bounding_box = get_bounding_box(vertices) x_outside = (x_dose < bounding_box[1][0]) | (x_dose > bounding_box[1][1]) y_outside = (y_dose < bounding_box[0][0]) | (y_dose > bounding_box[0][1]) z_outside = (z_dose < bounding_box[2][0]) | (z_dose > bounding_box[2][1]) xx, yy, zz = np.meshgrid( x_dose[np.invert(x_outside)], y_dose[np.invert(y_outside)], z_dose[np.invert(z_outside)], ) where_x = np.where(np.invert(x_outside))[0] where_y = np.where(np.invert(y_outside))[0] where_z = np.where(np.invert(z_outside))[0] bounded_dose = dose[ where_y[0] : where_y[-1] + 1, where_x[0] : where_x[-1] + 1, where_z[0] : where_z[-1] + 1, ] points_to_test = np.array( [ [y, x, z, d] for y, x, z, d in zip( np.ravel(yy), np.ravel(xx), np.ravel(zz), np.ravel(bounded_dose) ) ] ) inside_cube = [ test_if_in_cube(point_test, cube_definition) for point_test in points_to_test[:, 0:3] ] points_inside_cube = points_to_test[inside_cube, :] ax = plot_cube(cube_definition) ax.scatter( points_inside_cube[:, 1], points_inside_cube[:, 0], points_inside_cube[:, 2], c=points_inside_cube[:, 3], alpha=0.4, ) return ax
def get_interpolated_dose(coords_grid, dose_interpolation): coords_grid_ij_indexing = np.array([ np.ravel(coords_grid[:, :, 1]), np.ravel(coords_grid[:, :, 0]), np.ravel(coords_grid[:, :, 2]), ]).T interpolated_dose = dose_interpolation(coords_grid_ij_indexing) coords_dim = np.shape(coords_grid) interpolated_dose = np.reshape(interpolated_dose, (coords_dim[0], coords_dim[1])) return interpolated_dose
def apply_transform(xx, yy, transform): xx = np.array(xx, copy=False) yy = np.array(yy, copy=False) xx_flat = np.ravel(xx) transformed = transform @ np.vstack( [xx_flat, np.ravel(yy), np.ones_like(xx_flat)]) xx_transformed = transformed[0] yy_transformed = transformed[1] xx_transformed.shape = xx.shape yy_transformed.shape = yy.shape return xx_transformed, yy_transformed
def define_rotation_field_points_at_origin(edge_lengths, penumbra): x_half_range = edge_lengths[0] / 2 + penumbra / 2 y_half_range = edge_lengths[1] / 2 + penumbra / 2 num_x = np.ceil(x_half_range * 2 * 8) + 1 num_y = np.ceil(y_half_range * 2 * 8) + 1 x = np.linspace(-x_half_range, x_half_range, int(num_x)) y = np.linspace(-y_half_range, y_half_range, int(num_y)) xx, yy = np.meshgrid(x, y) xx_flat = np.ravel(xx) yy_flat = np.ravel(yy) inside = np.logical_and((np.abs(xx_flat) < x_half_range), (np.abs(yy_flat) < y_half_range)) xx_flat = xx_flat[np.invert(inside)] yy_flat = yy_flat[np.invert(inside)] return xx_flat, yy_flat
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 from_user_inputs( cls, axes_reference, dose_reference, axes_evaluation, dose_evaluation, dose_percent_threshold, distance_mm_threshold, lower_percent_dose_cutoff=20, interp_fraction=10, max_gamma=None, local_gamma=False, global_normalisation=None, skip_once_passed=False, random_subset=None, ram_available=None, quiet=False, ): if max_gamma is None: max_gamma = np.inf axes_reference, axes_evaluation = run_input_checks( axes_reference, dose_reference, axes_evaluation, dose_evaluation) dose_percent_threshold = expand_dims_to_1d(dose_percent_threshold) distance_mm_threshold = expand_dims_to_1d(distance_mm_threshold) if global_normalisation is None: global_normalisation = np.max(dose_reference) lower_dose_cutoff = lower_percent_dose_cutoff / 100 * global_normalisation maximum_test_distance = np.max(distance_mm_threshold) * max_gamma evaluation_interpolation = scipy.interpolate.RegularGridInterpolator( axes_evaluation, np.array(dose_evaluation), bounds_error=False, fill_value=np.inf, ) dose_reference = np.array(dose_reference) reference_dose_above_threshold = dose_reference >= lower_dose_cutoff mesh_axes_reference = np.meshgrid(*axes_reference, indexing="ij") flat_mesh_axes_reference = np.array( [np.ravel(item) for item in mesh_axes_reference]) reference_points_to_calc = reference_dose_above_threshold reference_points_to_calc = np.ravel(reference_points_to_calc) if random_subset is not None: to_calc_index = np.where(reference_points_to_calc)[0] np.random.shuffle(to_calc_index) random_subset_to_calc = np.full_like(reference_points_to_calc, False, dtype=bool) random_subset_to_calc[ # pylint: disable=unsupported-assignment-operation to_calc_index[0:random_subset]] = True reference_points_to_calc = random_subset_to_calc flat_dose_reference = np.ravel(dose_reference) return cls( flat_mesh_axes_reference, flat_dose_reference, reference_points_to_calc, dose_percent_threshold, distance_mm_threshold, evaluation_interpolation, interp_fraction, max_gamma, lower_dose_cutoff, maximum_test_distance, global_normalisation, local_gamma, skip_once_passed, ram_available, quiet, )
def gamma_filter_numpy(axes_reference, dose_reference, axes_evaluation, dose_evaluation, distance_mm_threshold, dose_threshold, lower_dose_cutoff=0, **_): coord_diffs = [ coord_ref[:, None] - coord_eval[None, :] for coord_ref, coord_eval in zip(axes_reference, axes_evaluation) ] all_in_vicinity = [ np.where(np.abs(diff) < distance_mm_threshold) for diff in coord_diffs ] ref_coord_points = create_point_combination( [in_vicinity[0] for in_vicinity in all_in_vicinity]) eval_coord_points = create_point_combination( [in_vicinity[1] for in_vicinity in all_in_vicinity]) distances = np.sqrt( np.sum( [ coord_diff[ref_points, eval_points]**2 for ref_points, eval_points, coord_diff in zip( ref_coord_points, eval_coord_points, coord_diffs) ], axis=0, )) within_distance_threshold = distances < distance_mm_threshold distances = distances[within_distance_threshold] ref_coord_points = ref_coord_points[:, within_distance_threshold] eval_coord_points = eval_coord_points[:, within_distance_threshold] dose_diff = ( dose_evaluation[eval_coord_points[0, :], eval_coord_points[1, :], eval_coord_points[2, :]] - dose_reference[ref_coord_points[0, :], ref_coord_points[1, :], ref_coord_points[2, :]]) gamma = np.sqrt((dose_diff / dose_threshold)**2 + (distances / distance_mm_threshold)**2) gamma_pass = gamma < 1 eval_pass = eval_coord_points[:, gamma_pass] ravel_index = convert_to_ravel_index(eval_pass) gamma_pass_array = np.zeros_like(dose_evaluation).astype(np.bool) gamma_pass_array = np.ravel(gamma_pass_array) dose_above_cut_off = np.ravel(dose_evaluation) > lower_dose_cutoff gamma_pass_array[ravel_index] = True gamma_pass_percentage = np.mean(gamma_pass_array[dose_above_cut_off]) * 100 return gamma_pass_percentage
def image_analysis_figure( x, y, img, bb_centre, field_centre, field_rotation, bb_diameter, edge_lengths, penumbra, units="(mm)", ): field = imginterp.create_interpolated_field(x, y, img) x_half_bound = edge_lengths[0] / 2 + penumbra * 3 y_half_bound = edge_lengths[1] / 2 + penumbra * 3 x_axis = np.linspace(-x_half_bound, x_half_bound, 200) y_axis = np.linspace(-y_half_bound, y_half_bound, 200) field_transform = interppoints.translate_and_rotate_transform( field_centre, field_rotation) x_field_interp, y_field_interp = createaxis.transform_axis( x_axis, y_axis, field_transform) if bb_centre is not None: bb_transform = interppoints.translate_and_rotate_transform( bb_centre, 0) x_bb_interp, y_bb_interp = createaxis.transform_axis( x_axis, y_axis, bb_transform) else: x_bb_interp, y_bb_interp = None, None fig, axs = plt.subplots(ncols=2, nrows=4, figsize=(12, 15)) gs = axs[0, 0].get_gridspec() for ax in np.ravel(axs[0:2, 0:2]): ax.remove() ax_big = fig.add_subplot(gs[0:2, 0:2]) axs[0, 0] = ax_big pixel_value_label = "Scaled image pixel value" image_with_overlays( fig, ax_big, x, y, img, field_transform, bb_centre, field_centre, bb_diameter, edge_lengths, x_field_interp, y_field_interp, x_bb_interp, y_bb_interp, pixel_value_label, units=units, ) profile_flip_plot(axs[2, 0], x_axis, field(*x_field_interp)) axs[2, 0].set_xlim([ edge_lengths[0] / 2 - penumbra * 2, edge_lengths[0] / 2 + penumbra * 2 ]) axs[2, 0].set_title("Flipped profile about field centre [field x-axis]") axs[2, 0].set_xlabel(f"Distance from field centre {units}") axs[2, 0].set_ylabel(pixel_value_label) profile_flip_plot(axs[2, 1], y_axis, field(*y_field_interp)) axs[2, 1].set_xlim([ edge_lengths[1] / 2 - penumbra * 2, edge_lengths[1] / 2 + penumbra * 2 ]) axs[2, 1].set_title("Flipped profile about field centre [field y-axis]") axs[2, 1].set_xlabel(f"Distance from field centre {units}") axs[2, 1].set_ylabel(pixel_value_label) if bb_centre is not None: x_mask = (x_axis >= -bb_diameter / 2 - penumbra) & ( x_axis <= bb_diameter / 2 + penumbra) profile_flip_plot(axs[3, 0], x_axis[x_mask], field(*x_bb_interp)[x_mask]) axs[3, 0].set_xlim( [-bb_diameter / 2 - penumbra, bb_diameter / 2 + penumbra]) axs[3, 0].set_title("Flipped profile about BB centre [panel x-axis]") axs[3, 0].set_xlabel(f"Displacement from BB centre {units}") axs[3, 0].set_ylabel(pixel_value_label) y_mask = (y_axis >= -bb_diameter / 2 - penumbra) & ( y_axis <= bb_diameter / 2 + penumbra) profile_flip_plot(axs[3, 1], y_axis[y_mask], field(*y_bb_interp)[y_mask]) axs[3, 1].set_xlim( [-bb_diameter / 2 - penumbra, bb_diameter / 2 + penumbra]) axs[3, 1].set_title("Flipped profile about BB centre [panel y-axis]") axs[3, 1].set_xlabel(f"Displacement from BB centre {units}") axs[3, 1].set_ylabel(pixel_value_label) plt.tight_layout() return fig, axs