def _calc_flatness(self, method: str, vert_position: float, horiz_position: float): vert_profile = SingleProfile( self.image. array[:, int(round(self.image.array.shape[1] * vert_position))]) horiz_profile = SingleProfile(self.image.array[ int(round(self.image.array.shape[0] * horiz_position)), :]) # calc flatness from profile flatness_calculation = FLATNESS_EQUATIONS[method.lower()] vert_flatness, vert_max, vert_min, vert_lt, vert_rt = flatness_calculation( vert_profile) horiz_flatness, horiz_max, horiz_min, horiz_lt, horiz_rt = flatness_calculation( horiz_profile) return { 'method': method, 'horizontal': { 'value': horiz_flatness, 'profile': horiz_profile, 'profile max': horiz_max, 'profile min': horiz_min, 'profile left': horiz_lt, 'profile right': horiz_rt, }, 'vertical': { 'value': vert_flatness, 'profile': vert_profile, 'profile max': vert_max, 'profile min': vert_min, 'profile left': vert_lt, 'profile right': vert_rt, }, }
def _get_reasonable_start_point(self): """Set the algorithm starting point automatically. Notes ----- The determination of an automatic start point is accomplished by finding the Full-Width-80%-Max. Finding the maximum pixel does not consistently work, esp. in the presence of a pin prick. The FW80M is a more consistent metric for finding a good start point. """ # sum the image along each axis within the central 1/3 (avoids outlier influence from say, gantry shots) top_third = int(self.image.array.shape[0] / 3) bottom_third = int(top_third * 2) left_third = int(self.image.array.shape[1] / 3) right_third = int(left_third * 2) central_array = self.image.array[top_third:bottom_third, left_third:right_third] x_sum = np.sum(central_array, 0) y_sum = np.sum(central_array, 1) # Calculate Full-Width, 80% Maximum center fwxm_x_point = SingleProfile(x_sum).fwxm_center(80) + left_third fwxm_y_point = SingleProfile(y_sum).fwxm_center(80) + top_third center_point = Point(fwxm_x_point, fwxm_y_point) return center_point
def _calc_symmetry(self, method: str, vert_position: float, horiz_position: float): vert_profile = SingleProfile( self.image. array[:, int(round(self.image.array.shape[1] * vert_position))]) horiz_profile = SingleProfile(self.image.array[ int(round(self.image.array.shape[0] * horiz_position)), :]) # calc sym from profile symmetry_calculation = SYMMETRY_EQUATIONS[method.lower()] vert_sym, vert_sym_array, vert_lt, vert_rt = symmetry_calculation( vert_profile) horiz_sym, horiz_sym_array, horiz_lt, horiz_rt = symmetry_calculation( horiz_profile) return { 'method': method, 'horizontal': { 'profile': horiz_profile, 'value': horiz_sym, 'array': horiz_sym_array, 'profile left': horiz_lt, 'profile right': horiz_rt, }, 'vertical': { 'profile': vert_profile, 'value': vert_sym, 'array': vert_sym_array, 'profile left': vert_lt, 'profile right': vert_rt, }, }
def test_interpolation(self): # centered field field = generate_open_field() # no interp p = SingleProfile(field.image[:, int(field.shape[1] / 2)], interpolation=Interpolation.NONE) self.assertEqual(len(p.values), len(field.image[:, int(field.shape[1] / 2)])) # linear interp p = SingleProfile(field.image[:, int(field.shape[1] / 2)], interpolation=Interpolation.LINEAR, interpolation_factor=10) self.assertEqual(len(p.values), len(field.image[:, int(field.shape[1] / 2)])*10) p = SingleProfile(field.image[:, int(field.shape[1] / 2)], interpolation=Interpolation.LINEAR, dpmm=1/field.pixel_size, interpolation_resolution_mm=0.1) # right length self.assertEqual(len(p.values), len(field.image[:, int(field.shape[1] / 2)])*field.pixel_size/0.1) # right dpmm self.assertEqual(p.dpmm, 10) # spline interp p = SingleProfile(field.image[:, int(field.shape[1] / 2)], interpolation=Interpolation.SPLINE, interpolation_factor=10) self.assertEqual(len(p.values), len(field.image[:, int(field.shape[1] / 2)])*10) p = SingleProfile(field.image[:, int(field.shape[1] / 2)], interpolation=Interpolation.SPLINE, dpmm=1/field.pixel_size, interpolation_resolution_mm=0.1) # right length self.assertEqual(len(p.values), len(field.image[:, int(field.shape[1] / 2)])*field.pixel_size/0.1) # right dpmm self.assertEqual(p.dpmm, 10)
def test_geometric_center(self): # centered field field = generate_open_field() p = SingleProfile(field.image[:, int(field.shape[1]/2)], interpolation=Interpolation.NONE) self.assertAlmostEqual(p.geometric_center()['index (exact)'], field.shape[0]/2, delta=1) # offset field should still be centered field = generate_open_field(center=(20, 20)) p = SingleProfile(field.image[:, int(field.shape[1]/2)], interpolation=Interpolation.NONE) self.assertAlmostEqual(p.geometric_center()['index (exact)'], field.shape[0]/2, delta=1)
def test_beam_center(self): # centered field field = generate_open_field() p = SingleProfile(field.image[:, int(field.shape[1]/2)], interpolation=Interpolation.NONE) self.assertAlmostEqual(p.beam_center()['index (exact)'], field.shape[0]/2, delta=1) # offset field field = generate_open_field(center=(10, 10)) p = SingleProfile(field.image[:, int(field.shape[1]/2)], interpolation=Interpolation.NONE) self.assertAlmostEqual(p.beam_center()['index (exact)'], 422, delta=1)
def _find_bb(self): """Find the BB within the radiation field. Iteratively searches for a circle-like object by lowering a low-pass threshold value until found. Returns ------- Point The weighted-pixel value location of the BB. """ def is_boxlike(array): """Whether the binary object's dimensions are symmetric, i.e. box-like""" ymin, ymax, xmin, xmax = get_bounding_box(array) y = abs(ymax - ymin) x = abs(xmax - xmin) if x > max(y * 1.05, y+3) or x < min(y * 0.95, y-3): return False return True # get initial starting conditions hmin = np.percentile(self.array, 5) hmax = self.array.max() spread = hmax - hmin max_thresh = hmax # search for the BB by iteratively lowering the low-pass threshold value until the BB is found. found = False while not found: try: lower_thresh = hmax - spread / 2 t = np.where((max_thresh > self) & (self >= lower_thresh), 1, 0) labeled_arr, num_roi = ndimage.measurements.label(t) roi_sizes, bin_edges = np.histogram(labeled_arr, bins=num_roi + 1) bw_node_cleaned = np.where(labeled_arr == np.argsort(roi_sizes)[-3], 1, 0) expected_fill_ratio = np.pi / 4 actual_fill_ratio = get_filled_area_ratio(bw_node_cleaned) if (expected_fill_ratio * 1.1 < actual_fill_ratio) or (actual_fill_ratio < expected_fill_ratio * 0.9): raise ValueError if not is_boxlike(bw_node_cleaned): raise ValueError except (IndexError, ValueError): max_thresh -= 0.05 * spread if max_thresh < hmin: raise ValueError("Unable to locate the BB") else: found = True # determine the center of mass of the BB inv_img = Image.load(self.array) inv_img.invert() x_arr = np.abs(np.average(bw_node_cleaned, weights=inv_img, axis=0)) x_com = SingleProfile(x_arr).fwxm_center(interpolate=True) y_arr = np.abs(np.average(bw_node_cleaned, weights=inv_img, axis=1)) y_com = SingleProfile(y_arr).fwxm_center(interpolate=True) return Point(x_com, y_com)
def find_bb(self): """Find the BB within the radiation field. Dervived from pylinac WL test. Looks at the central 60x60 pixels and finds a bb Returns ------- Point The weighted-pixel value location of the BB. """ span=20 bbox=[int(self.cax.y-span),int(self.cax.x-span),int(self.cax.y+span),int(self.cax.x+span)] subim=ndimage.gaussian_filter(np.array(self.array[bbox[0]:bbox[2],bbox[1]:bbox[3]],dtype=np.float),sigma=(1,1),order=0) self._bbox_max=np.max(subim) self._bbox_min=np.min(subim) hmin, hmax = np.percentile(subim, [10, 100.0]) spread = hmax - hmin max_thresh = hmax lower_thresh = hmax - spread*.2 # search for the BB by iteratively lowering the low-pass threshold value until the BB is found. found = False while not found: try: binary_arr = np.logical_and((subim <= max_thresh), (subim >= lower_thresh)) labeled_arr, num_roi = ndimage.measurements.label(binary_arr) roi_sizes, bin_edges = np.histogram(labeled_arr, bins=num_roi + 1) bw_bb_img = np.where(labeled_arr == np.argsort(roi_sizes)[-2], 1, 0) if not self._is_round(bw_bb_img): raise ValueError if not self._is_modest_size(bw_bb_img,subim.shape): raise ValueError if not self._is_symmetric(bw_bb_img): raise ValueError except (IndexError, ValueError): lower_thresh -= 0.05 * spread if lower_thresh < hmin: raise ValueError("Unable to locate the BB. Make sure the field edges do not obscure the BB and that there is no artifact in the images.") else: found = True # determine the center of mass of the BB x_arr = np.abs(np.average(subim*bw_bb_img, axis=0)) #plt.figure() #plt.plot(x_arr) #plt.figure() #plt.imshow(bw_bb_img) x_com = SingleProfile(np.array(x_arr,dtype=np.float)).fwxm_center(interpolate=True) y_arr = np.abs(np.average(subim*bw_bb_img, axis=1)) y_com = SingleProfile(y_arr).fwxm_center(interpolate=True) self.bb=Point(x_com+bbox[1], y_com+bbox[0])
def median_profiles(self): """Return two median profiles from the open and dmlc image. For visual comparison.""" # dmlc median profile dmlc_prof = SingleProfile(np.median(self.image_dmlc, axis=0)) dmlc_prof.stretch() # open median profile open_prof = SingleProfile(np.median(self.image_open, axis=0)) open_prof.stretch() # normalize the profiles to near the same value norm_val = np.percentile(dmlc_prof.values, 90) dmlc_prof.normalize(norm_val) norm_val = np.percentile(open_prof.values, 90) open_prof.normalize(norm_val) return dmlc_prof, open_prof
def _determine_measured_gap(profile: np.ndarray) -> float: """Return the measured gap based on profile height""" mid_value = profile[int(len(profile) / 2)] prof = SingleProfile(profile, normalization_method=Normalization.NONE) if mid_value < profile.mean(): prof.invert() _, props = find_peaks(prof.values, max_number=1) if mid_value < profile.mean(): return -props['prominences'][0] else: return props['prominences'][0]
def find_mlc_peak(self, mlc_center): """Determine the center of the picket.""" mlc_rows = np.arange(mlc_center - self.sample_width, mlc_center + self.sample_width + 1) if self.settings.orientation == orientations['UD']: pix_vals = np.median(self.picket_array[mlc_rows, :], axis=0) else: pix_vals = np.median(self.picket_array[:, mlc_rows], axis=1) if max(pix_vals) > np.percentile(self.picket_array, 80): prof = SingleProfile(pix_vals) fw80mc = prof.fwxm_center(70, interpolate=True) return fw80mc + self.approximate_idx - self.spacing
def _determine_center(self, plane): """Automatically find the center of the field based on FWHM.""" if not self._img_is_loaded: raise AttributeError("An image has not yet been loaded") self.image.check_inversion() self.image.ground() col_prof = np.median(self.image, 0) col_prof = SingleProfile(col_prof) row_prof = np.median(self.image, 1) row_prof = SingleProfile(row_prof) x_cen = col_prof.fwxm_center() y_cen = row_prof.fwxm_center() if _is_crossplane(plane): return y_cen elif _is_inplane(plane): return x_cen elif _is_both_planes(plane): return y_cen, x_cen
def _get_profile(self, plane, position): """Get a profile at the given position along the specified plane.""" if not self._img_is_loaded: raise AttributeError("An image has not yet been loaded") position = self._convert_position(position, plane) # if position == 'auto': # y, x = self._determine_center() # else: # if _is_crossplane(plane): # self._check_position_inbounds(position, plane) # y = position # elif _is_inplane(plane): # self._check_position_inbounds(position, plane) # x = position if _is_crossplane(plane): prof = SingleProfile(self.image[position[0], :]) elif _is_inplane(plane): prof = SingleProfile(self.image[:, position[0]]) return prof
def test_normalization(self): array = np.random.rand(1, 100).squeeze() # don't apply normalization max_v = array.max() p = SingleProfile(array, normalization_method=Normalization.NONE, interpolation=Interpolation.NONE, ground=False) self.assertEqual(max_v, p.values.max()) # apply max norm p = SingleProfile(array, normalization_method=Normalization.MAX, interpolation=Interpolation.NONE) self.assertEqual(1.0, p.values.max()) # make sure interpolation doesn't affect the norm p = SingleProfile(array, normalization_method=Normalization.MAX, interpolation=Interpolation.LINEAR) self.assertEqual(1.0, p.values.max()) # test out a real field field = generate_open_field() p = SingleProfile(field.image[:, 500], normalization_method=Normalization.MAX) self.assertEqual(1.0, p.values.max()) # filtered beam center is less than max value p = SingleProfile(field.image[:, 500], normalization_method=Normalization.BEAM_CENTER) self.assertGreaterEqual(p.values.max(), 1.0)
def to_profiles( self, n_detectors_row: int = 63, **kwargs ) -> Tuple[SingleProfile, SingleProfile, SingleProfile, SingleProfile]: """Convert the SNC data to SingleProfiles. These can be analyzed directly or passed to other modules like flat/sym. Parameters ---------- n_detectors_row : int The number of detectors in a given row. Note that they Y profile includes 2 extra detectors from the other 3. """ x_prof = SingleProfile(self.integrated_dose[:n_detectors_row], **kwargs) y_prof = SingleProfile( self.integrated_dose[n_detectors_row:2 * n_detectors_row + 2], **kwargs) pos_prof = SingleProfile( self.integrated_dose[2 * n_detectors_row + 2:3 * n_detectors_row + 2], **kwargs) neg_prof = SingleProfile( self.integrated_dose[3 * n_detectors_row + 2:4 * n_detectors_row + 2], **kwargs) return x_prof, y_prof, pos_prof, neg_prof
def setUpClass(cls): cls.profile = SingleProfile(cls.ydata, normalize_sides=cls.normalize_sides)