def __init__( self, width: float, height: float, center: Union[Point, Tuple], as_int: bool = False, ): """ Parameters ---------- width : number Width of the rectangle. height : number Height of the rectangle. center : Point, iterable, optional Center point of rectangle. as_int : bool If False (default), inputs are left as-is. If True, all inputs are converted to integers. """ if as_int: self.width = int(np.round(width)) self.height = int(np.round(height)) else: self.width = width self.height = height self._as_int = as_int self.center = Point(center, as_int=as_int)
def get_delivery_data_items(single_icom_stream): shrunk_stream, mu = extract.extract(single_icom_stream, "Delivery MU") shrunk_stream, gantry = extract.extract(shrunk_stream, "Gantry") shrunk_stream, collimator = extract.extract(shrunk_stream, "Collimator") shrunk_stream, mlc = extract.extract_coll(shrunk_stream, b"MLCX", 160) mlc = np.array(mlc) mlc = mlc.reshape((80, 2)) mlc = np.fliplr(np.flipud(mlc * 10)) mlc[:, 1] = -mlc[:, 1] mlc = np.round(mlc, 10) shrunk_stream, jaw = extract.extract_coll(shrunk_stream, b"ASYMY", 2) jaw = np.round(np.array(jaw) * 10, 10) jaw = np.flipud(jaw) return mu, gantry, collimator, mlc, jaw
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 gamma_filter_brute_force(axes_reference, dose_reference, axes_evaluation, dose_evaluation, distance_mm_threshold, dose_threshold, lower_dose_cutoff=0, **_): xx_ref, yy_ref, zz_ref = np.meshgrid(*axes_reference, indexing="ij") gamma_array = np.ones_like(dose_evaluation).astype(np.float) * np.nan mesh_index = np.meshgrid( *[np.arange(len(coord_eval)) for coord_eval in axes_evaluation]) eval_index = np.reshape(np.array(mesh_index), (3, -1)) run_index = np.arange(np.shape(eval_index)[1]) np.random.shuffle(run_index) sys.stdout.write(" ") for counter, point_index in enumerate(run_index): i, j, k = eval_index[:, point_index] eval_x = axes_evaluation[0][i] eval_y = axes_evaluation[1][j] eval_z = axes_evaluation[2][k] if dose_evaluation[i, j, k] < lower_dose_cutoff: continue distance = np.sqrt((xx_ref - eval_x)**2 + (yy_ref - eval_y)**2 + (zz_ref - eval_z)**2) dose_diff = dose_evaluation[i, j, k] - dose_reference gamma = np.min( np.sqrt((dose_diff / dose_threshold)**2 + (distance / distance_mm_threshold)**2)) gamma_array[i, j, k] = gamma if counter // 30 == counter / 30: percent_pass = str( np.round(calculate_pass_rate(gamma_array), decimals=1)) sys.stdout.write( "\rPercent Pass: {0}% | Percent Complete: {1:.2f}%".format( percent_pass, counter / np.shape(eval_index)[1] * 100)) sys.stdout.flush() return calculate_pass_rate(gamma_array)
def format_coords_for_dicom(all_merged): dicom_format_coords_by_z = {} for z, merged in all_merged.items(): coords = get_coords_from_polygon_or_multipolygon(merged) new_contour_data = [] for coord in coords: stacked_coords = np.hstack( list(zip(coord[0], coord[1], z * np.ones_like(coord[1])))) stacked_coords = np.round(stacked_coords, 1) stacked_coords = stacked_coords.tolist() new_contour_data.append(stacked_coords) dicom_format_coords_by_z[z] = new_contour_data return dicom_format_coords_by_z
def _apply_mask_to_delivery_data(self: DeliveryGeneric, mask) -> DeliveryGeneric: cls = type(self) new_delivery_data = [] for item in self: new_delivery_data.append(np.array(item)[mask]) new_monitor_units = new_delivery_data[0] try: first_monitor_unit_item = new_monitor_units[0] except IndexError: return cls(*new_delivery_data) new_delivery_data[0] = np.round( np.array(new_delivery_data[0], copy=False) - first_monitor_unit_item, decimals=7, ) return cls(*new_delivery_data)
def create_dataframe(data, column_names, time_increment): """Converts the provided data into a pandas dataframe.""" dataframe = pd.DataFrame(data=data, columns=column_names) dataframe.index = np.round(dataframe.index * time_increment, 2) return dataframe
def peak_detect( values, threshold: Union[float, int] = None, min_distance: Union[float, int] = 10, max_number: int = None, search_region=(0.0, 1.0), find_min_instead: bool = False, ): """Find the peaks or valleys of a 1D signal. Uses the difference (np.diff) in signal to find peaks. Current limitations include: 1) Only for use in 1-D data; 2D may be possible with the gradient function. 2) Will not detect peaks at the very edge of array (i.e. 0 or -1 index) Parameters ---------- values : array-like Signal values to search for peaks within. threshold : int, float The value the peak must be above to be considered a peak. This removes "peaks" that are in a low-value region. If passed an int, the actual value is the threshold. E.g. when passed 15, any peak less with a value <15 is removed. If passed a float, it will threshold as a percent. Must be between 0 and 1. E.g. when passed 0.4, any peak <40% of the maximum value will be removed. min_distance : int, float If passed an int, parameter is the number of elements apart a peak must be from neighboring peaks. If passed a float, must be between 0 and 1 and represents the ratio of the profile to exclude. E.g. if passed 0.05 with a 1000-element profile, the minimum peak width will be 0.05*1000 = 50 elements. max_number : int Specify up to how many peaks will be returned. E.g. if 3 is passed in and 5 peaks are found, only the 3 largest peaks will be returned. find_min_instead : bool If False (default), peaks will be returned. If True, valleys will be returned. Returns ------- max_vals : numpy.array The values of the peaks found. max_idxs : numpy.array The x-indices (locations) of the peaks. Raises ------ ValueError If float not between 0 and 1 passed to threshold. """ peak_vals = ( [] ) # a list to hold the y-values of the peaks. Will be converted to a numpy array peak_idxs = [] # ditto for x-values (index) of y data. if find_min_instead: values = -values # """Limit search to search region""" left_end = search_region[0] if is_float_like(left_end): left_index = int(left_end * len(values)) elif is_int_like(left_end): left_index = left_end else: raise ValueError(f"{left_end} must be a float or int") right_end = search_region[1] if is_float_like(right_end): right_index = int(right_end * len(values)) elif is_int_like(right_end): right_index = right_end else: raise ValueError(f"{right_end} must be a float or int") # minimum peak spacing calc if isinstance(min_distance, float): if 0 > min_distance >= 1: raise ValueError( "When min_peak_width is passed a float, value must be between 0 and 1" ) else: min_distance = int(min_distance * len(values)) values = values[left_index:right_index] # """Determine threshold value""" if isinstance(threshold, float) and threshold < 1: data_range = values.max() - values.min() threshold = threshold * data_range + values.min() elif isinstance(threshold, float) and threshold >= 1: raise ValueError("When threshold is passed a float, value must be less than 1") elif threshold is None: threshold = values.min() # """Take difference""" values_diff = np.diff( values.astype(float) ) # y and y_diff must be converted to signed type. # """Find all potential peaks""" for idx in range(len(values_diff) - 1): # For each item of the diff array, check if: # 1) The y-value is above the threshold. # 2) The value of y_diff is positive (negative for valley search), it means the y-value changed upward. # 3) The next y_diff value is zero or negative (or positive for valley search); a positive-then-negative diff value means the value # is a peak of some kind. If the diff is zero it could be a flat peak, which still counts. # 1) if values[idx + 1] < threshold: continue y1_gradient = values_diff[idx] > 0 y2_gradient = values_diff[idx + 1] <= 0 # 2) & 3) if y1_gradient and y2_gradient: # If the next value isn't zero it's a single-pixel peak. Easy enough. if values_diff[idx + 1] != 0: peak_vals.append(values[idx + 1]) peak_idxs.append(idx + 1 + left_index) # elif idx >= len(y_diff) - 1: # pass # Else if the diff value is zero, it could be a flat peak, or it could keep going up; we don't know yet. else: # Continue on until we find the next nonzero diff value. try: shift = 0 while values_diff[(idx + 1) + shift] == 0: shift += 1 if (idx + 1 + shift) >= (len(values_diff) - 1): break # If the next diff is negative (or positive for min), we've found a peak. Also put the peak at the center of the flat # region. is_a_peak = values_diff[(idx + 1) + shift] < 0 if is_a_peak: peak_vals.append(values[int((idx + 1) + np.round(shift / 2))]) peak_idxs.append((idx + 1 + left_index) + np.round(shift / 2)) except IndexError: pass # convert to numpy arrays peak_vals = np.array(peak_vals) peak_idxs = np.array(peak_idxs) # """Enforce the min_peak_distance by removing smaller peaks.""" # For each peak, determine if the next peak is within the min peak width range. index = 0 while index < len(peak_idxs) - 1: # If the second peak is closer than min_peak_distance to the first peak, find the larger peak and remove the other one. if peak_idxs[index] > peak_idxs[index + 1] - min_distance: if peak_vals[index] > peak_vals[index + 1]: idx2del = index + 1 else: idx2del = index peak_vals = np.delete(peak_vals, idx2del) peak_idxs = np.delete(peak_idxs, idx2del) else: index += 1 # """If Maximum Number passed, return only up to number given based on a sort of peak values.""" if max_number is not None and len(peak_idxs) > max_number: sorted_peak_vals = peak_vals.argsort() # type: ignore # sorts low to high peak_vals = peak_vals[ sorted_peak_vals[ -max_number: # pylint: disable = invalid-unary-operand-type ] ] peak_idxs = peak_idxs[ sorted_peak_vals[ -max_number: # pylint: disable = invalid-unary-operand-type ] ] # If we were looking for minimums, convert the values back to the original sign if find_min_instead: peak_vals = ( -peak_vals # type: ignore # pylint: disable = invalid-unary-operand-type ) return peak_vals, peak_idxs