def _indices_interp(self): """Interpolated values of the profile index data.""" return np.linspace( start=0, stop=len(self.values) - 1, num=(len(self.values) - 1) * self.interpolation_factor, )
def create_control_point_sequence(beam, beam_collimation, rotation_direction, dose_rate, gantry, coll, nominal_energy): num_cps = len(gantry) meterset_weight = np.linspace(0, 1, num_cps) cp_bounds = bounding_control_points(beam, beam_collimation, rotation_direction, dose_rate) control_point_sequence = pydicom.Sequence( [copy.deepcopy(cp_bounds["first"])]) control_point_sequence[0].GantryAngle = str(gantry[0]) control_point_sequence[0].BeamLimitingDeviceAngle = str(coll[0]) control_point_sequence[0].NominalBeamEnergy = nominal_energy for i in range(1, num_cps - 1): cp = copy.deepcopy(cp_bounds["mid"]) cp.GantryAngle = str(gantry[i]) cp.BeamLimitingDeviceAngle = str(coll[i]) cp.CumulativeMetersetWeight = str(round(meterset_weight[i], 6)) cp.ControlPointIndex = str(i) control_point_sequence.append(cp) cp = copy.deepcopy(cp_bounds["last"]) cp.GantryAngle = str(gantry[-1]) cp.BeamLimitingDeviceAngle = str(coll[-1]) cp.ControlPointIndex = str(num_cps - 1) control_point_sequence.append(cp) return control_point_sequence
def visual_circle_and_ellipse(insert_x, insert_y, width, length, circle_centre): t = np.linspace(0, 2 * np.pi) circle = { "x": width / 2 * np.sin(t) + circle_centre[0], "y": width / 2 * np.cos(t) + circle_centre[1], } x_shift, y_shift, rotation_angle = visual_alignment_of_equivalent_ellipse( insert_x, insert_y, width, length, None) rotation_matrix = np.array([ [np.cos(rotation_angle), -np.sin(rotation_angle)], [np.sin(rotation_angle), np.cos(rotation_angle)], ]) ellipse = np.array([length / 2 * np.sin(t), width / 2 * np.cos(t)]).T rotated_ellipse = ellipse @ rotation_matrix translated_ellipse = rotated_ellipse + np.array([y_shift, x_shift]) ellipse = {"x": translated_ellipse[:, 1], "y": translated_ellipse[:, 0]} return circle, ellipse
def _each_edge(current_edge_length, orthogonal_edge_length): half_field_range = np.linspace(-orthogonal_edge_length / 4, orthogonal_edge_length / 4, 51) a_side_lookup = -current_edge_length / 2 + penumbra_range b_side_lookup = current_edge_length / 2 + penumbra_range current_axis_lookup = np.concatenate([a_side_lookup, b_side_lookup]) return current_axis_lookup, half_field_range
def calculate_coordinates_shell_2d(distance, distance_step_size): """Create points along the circumference of a circle. The spacing between points is not larger than the defined distance_step_size """ amount_to_check = np.ceil(2 * np.pi * distance / distance_step_size).astype(int) + 1 theta = np.linspace(0, 2 * np.pi, amount_to_check + 1)[:-1:] x_coords = distance * np.cos(theta) y_coords = distance * np.sin(theta) return (x_coords, y_coords)
def calculate_coordinates_shell_3d(distance, distance_step_size): """Create points along the surface of a sphere (a shell) where no gap between points is larger than the defined distance_step_size""" number_of_rows = np.ceil(np.pi * distance / distance_step_size).astype(int) + 1 elevation = np.linspace(0, np.pi, number_of_rows) row_radii = distance * np.sin(elevation) row_circumference = 2 * np.pi * row_radii amount_in_row = np.ceil(row_circumference / distance_step_size).astype(int) + 1 x_coords = [] y_coords = [] z_coords = [] for i, phi in enumerate(elevation): azimuth = np.linspace(0, 2 * np.pi, amount_in_row[i] + 1)[:-1:] x_coords.append(distance * np.sin(phi) * np.cos(azimuth)) y_coords.append(distance * np.sin(phi) * np.sin(azimuth)) z_coords.append(distance * np.cos(phi) * np.ones_like(azimuth)) return (np.hstack(x_coords), np.hstack(y_coords), np.hstack(z_coords))
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 merge_view_horz(volume, dx, dy): junctions = [] # creating merged volume merge_vol = np.zeros((volume.shape[0], volume.shape[1])) # creating vector for processing along cols (one row) amplitude = np.zeros( (volume.shape[0], volume.shape[2])) # 1 if it is vertical 0 if the bars are horizontal y = np.linspace(0, 0 + (volume.shape[0] * dy), volume.shape[0], endpoint=False) # definition of the distance axis # x = np.arange(0,) #definition of the distance axis # merging the two images together ampl_resamp = np.zeros(((volume.shape[0]) * 10, volume.shape[2])) # amp_peak = np.zeros((volume.shape[0]) * 10) for item in tqdm(range(0, volume.shape[2])): merge_vol = merge_vol + volume[:, :, item] amplitude[:, item] = volume[:, int(volume.shape[1] / 2), item] ampl_resamp[:, item] = signal.resample( amplitude[:, item], int(len(amplitude)) * 10) # resampling the amplitude vector # amp_peak = amp_peak + ampl_resamp[:, item] / volume.shape[2] fig, ax = plt.subplots(nrows=2, squeeze=True, figsize=(6, 8)) extent = (0, 0 + (volume.shape[1] * dx), 0, 0 + (volume.shape[0] * dy)) ax[0].imshow(merge_vol, extent=extent, aspect="auto") ax[0].set_xlabel("x distance [mm]") ax[0].set_ylabel("y distance [mm]") ax[1].plot(y, amplitude, label="Amplitude profile") ax[1].set_ylabel("amplitude") ax[1].set_xlabel("y distance [mm]") ax[1].legend() fig.suptitle("Merged volume", fontsize=16) # peaks, peak_type, peak_figs = peak_find(ampl_resamp, dy) peaks, peak_type, peak_figs = pf.peak_find(ampl_resamp, dy) # junction_figs = minimize_junction_Y(ampl_resamp, peaks, peak_type, dy / 10) junction_figs = minY.minimize_junction_Y(ampl_resamp, peaks, peak_type, dy / 10) junctions.append(junction_figs) return fig, peak_figs, junctions
def define_penumbra_points_at_origin(edge_lengths, penumbra): penumbra_range = np.linspace(-penumbra / 2, penumbra / 2, 11) def _each_edge(current_edge_length, orthogonal_edge_length): half_field_range = np.linspace(-orthogonal_edge_length / 4, orthogonal_edge_length / 4, 51) a_side_lookup = -current_edge_length / 2 + penumbra_range b_side_lookup = current_edge_length / 2 + penumbra_range current_axis_lookup = np.concatenate([a_side_lookup, b_side_lookup]) return current_axis_lookup, half_field_range edge_points_left_right = _each_edge(edge_lengths[0], edge_lengths[1]) edge_points_top_bot = _each_edge(edge_lengths[1], edge_lengths[0]) xx_left_right, yy_left_right = np.meshgrid(*edge_points_left_right) xx_top_bot, yy_top_bot = np.meshgrid(*edge_points_top_bot[::-1]) return xx_left_right, yy_left_right, xx_top_bot, yy_top_bot
def resample_contour(contour, n=51): tck, _ = splprep([contour[0], contour[1], contour[2]], s=0, k=1) new_points = splev(np.linspace(0, 1, n), tck) return new_points
def peak_find(ampl_resamp, dx): peak_figs = [] peaks = [] peak_type = [] for j in range(0, ampl_resamp.shape[1] - 1): amp_base_res = signal.convolve(ampl_resamp[:, j], ampl_resamp[:, j], mode="full") amp_base_res = signal.resample(amp_base_res / np.amax(amp_base_res), int(np.ceil(len(amp_base_res) / 2))) for k in range(j + 1, ampl_resamp.shape[1]): amp_overlay_res = signal.convolve(ampl_resamp[:, k], ampl_resamp[:, k], mode="full") amp_overlay_res = signal.resample( amp_overlay_res / np.amax(amp_overlay_res), int(np.ceil(len(amp_overlay_res) / 2)), ) # amp_overlay_res = signal.savgol_filter(ampl_resamp[:, k], 1501, 1) peak1, _ = find_peaks(amp_base_res, prominence=0.5) peak2, _ = find_peaks(amp_overlay_res, prominence=0.5) if ( abs(peak2 - peak1) < 2500 ): # if the two peaks are separated the two fields are not adjacent. amp_peak = ampl_resamp[:, j] + ampl_resamp[:, k] x = np.linspace( 0, 0 + (len(amp_peak) * dx / 10), len(amp_peak), endpoint=False) # definition of the distance axis peak_pos, _ = find_peaks( signal.savgol_filter( amp_peak[min(peak1[0], peak2[0] ):max(peak1[0], peak2[0])], 201, 3, ), prominence=0.010, ) pos_prominence = signal.peak_prominences( signal.savgol_filter( amp_peak[min(peak1[0], peak2[0] ):max(peak1[0], peak2[0])], 201, 3, ), peak_pos, ) # print('#peaks pos det=', len(peak_pos), peak_pos) # print('#pos peaks prominence=', pos_prominence[0]) peak_neg, _ = find_peaks( signal.savgol_filter( -amp_peak[min(peak1[0], peak2[0] ):max(peak1[0], peak2[0])], 201, 3, ), prominence=0.010, ) neg_prominence = signal.peak_prominences( signal.savgol_filter( -amp_peak[min(peak1[0], peak2[0] ):max(peak1[0], peak2[0])], 201, 3, ), peak_neg, ) # print('#peaks neg det=',len(peak_neg),peak_neg) # print('#neg peaks prominence=', neg_prominence[0]) # we now need to select the peak with the largest prominence positve or negative # we add all the peaks and prominences toghether peaks_all = np.concatenate((peak_pos, peak_neg), axis=None) prom_all = np.concatenate( (pos_prominence[0], neg_prominence[0]), axis=None) # print('all peaks',peaks_all,prom_all) if peaks_all.size != 0: peak = peaks_all[np.argmax(prom_all)] if peak in peak_pos: peak_type.append(1) peaks.append(min(peak1[0], peak2[0]) + peak) # print('pos peak') elif peak in peak_neg: peak_type.append(0) peaks.append(min(peak1[0], peak2[0]) + peak) # print('neg peak') fig = plt.figure(figsize=(10, 6)) plt.plot(x, amp_peak, label="Total amplitude profile") plt.plot( x[min(peak1[0], peak2[0]) + peak], amp_peak[min(peak1[0], peak2[0]) + peak], "x", label="Peaks detected", ) plt.ylabel("amplitude [a.u.]") plt.xlabel("distance [mm]") plt.legend() fig.suptitle("Junctions", fontsize=16) peak_figs.append(fig) elif peaks_all.size == 0: peaks.append(0) peak_type.append(0) print("no peak has been found") fig = plt.figure(figsize=(10, 6)) plt.plot(x, amp_peak, label="Total amplitude profile") # plt.plot(x[min(peak1[0], peak2[0]) + peak], amp_peak[min(peak1[0], peak2[0]) + peak], "x", # label='Peaks detected') plt.ylabel("amplitude [a.u.]") plt.xlabel("distance [mm]") plt.legend() fig.suptitle("Junctions", fontsize=16) peak_figs.append(fig) # else: # print(j, k, 'the data is not contiguous finding another curve in dataset') # print('peaks_here=',peaks) return peaks, peak_type, peak_figs
def _indices(self): """Values of the profile index data.""" return np.linspace(start=0, stop=len(self.values) - 1, num=len(self.values))
def _radii(self): return np.linspace( start=self.radius * (1 - self.width_ratio), stop=self.radius * (1 + self.width_ratio), num=self.num_profiles, )
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
def image_with_overlays( fig, ax, 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="(mm)", ): rect_crosshair_dx = [ -edge_lengths[0] / 2, edge_lengths[0], -edge_lengths[0], edge_lengths[0], ] rect_crosshair_dy = [ -edge_lengths[1] / 2, edge_lengths[1], 0, -edge_lengths[1] ] rect_dx = [-edge_lengths[0] / 2, 0, edge_lengths[0], 0, -edge_lengths[0]] rect_dy = [-edge_lengths[1] / 2, edge_lengths[1], 0, -edge_lengths[1], 0] c = ax.contourf(x, y, img, 100) fig.colorbar(c, ax=ax, label=pixel_value_label) ax.plot(*draw_by_diff(rect_dx, rect_dy, field_transform), "k", lw=3) ax.plot(*draw_by_diff(rect_crosshair_dx, rect_crosshair_dy, field_transform), "k", lw=1) ax.plot(x_field_interp[0], x_field_interp[1], "k", lw=0.5, alpha=0.3) ax.plot(y_field_interp[0], y_field_interp[1], "k", lw=0.5, alpha=0.3) if bb_centre is not None: bb_radius = bb_diameter / 2 bb_crosshair = np.array([-bb_radius, bb_radius]) t = np.linspace(0, 2 * np.pi) circle_x_origin = bb_diameter / 2 * np.sin(t) circle_y_origin = bb_diameter / 2 * np.cos(t) circle_x = circle_x_origin + bb_centre[0] circle_y = circle_y_origin + bb_centre[1] ax.plot([bb_centre[0]] * 2, bb_crosshair + bb_centre[1], "k", lw=1) ax.plot(bb_crosshair + bb_centre[0], [bb_centre[1]] * 2, "k", lw=1) ax.plot(circle_x, circle_y, "k", lw=3) ax.plot(x_bb_interp[0], x_bb_interp[1], "k", lw=0.5, alpha=0.3) ax.plot(y_bb_interp[0], y_bb_interp[1], "k", lw=0.5, alpha=0.3) ax.plot( [field_centre[0], bb_centre[0]], [field_centre[1], bb_centre[1]], c="C3", lw=3, ) ax.axis("equal") long_edge = np.sqrt(np.sum((np.array(edge_lengths))**2)) long_edge_fraction = long_edge * 0.6 ax.set_xlim([ field_centre[0] - long_edge_fraction, field_centre[0] + long_edge_fraction ]) ax.set_ylim([ field_centre[1] - long_edge_fraction, field_centre[1] + long_edge_fraction ]) ax.set_xlabel(f"iView panel absolute x-pos {units}") ax.set_ylabel(f"iView panel absolute y-pos {units}")