def _execute(data, air_region: SensibleROI, cores=None, chunksize=None, progress=None): log = getLogger(__name__) with progress: progress.update(msg="Normalization by air region") if isinstance(air_region, list): air_region = SensibleROI.from_list(air_region) # initialise same number of air sums img_num = data.shape[0] with pu.temp_shared_array((img_num, 1, 1), data.dtype) as air_sums: # turn into a 1D array, from the 3D that is returned air_sums = air_sums.reshape(img_num) calc_sums_partial = ptsm.create_partial(_calc_sum, fwd_function=ptsm.return_to_second, air_left=air_region.left, air_top=air_region.top, air_right=air_region.right, air_bottom=air_region.bottom) data, air_sums = ptsm.execute(data, air_sums, calc_sums_partial, cores, chunksize, progress=progress) air_sums_partial = ptsm.create_partial(_divide_by_air_sum, fwd_function=ptsm.inplace) data, air_sums = ptsm.execute(data, air_sums, air_sums_partial, cores, chunksize, progress=progress) avg = np.average(air_sums) max_avg = np.max(air_sums) / avg min_avg = np.min(air_sums) / avg log.info(f"Normalization by air region. " f"Average: {avg}, max ratio: {max_avg}, min ratio: {min_avg}.")
def create_factors(data: np.ndarray, roi=None, cores=None, chunksize=None, progress=None): """ Calculate the scale factors as the mean of the ROI :param data: The data stack from which the scale factors will be calculated :param roi: Region of interest for which the scale factors will be calculated :param cores: Number of cores that will perform the calculation :param chunksize: How many chunks of work each core will receive :return: The scale factor for each image. """ progress = Progress.ensure_instance(progress, num_steps=data.shape[0]) with progress: img_num = data.shape[0] # make sure to clean up if for some reason the scale factor array still exists with pu.temp_shared_array((img_num, 1, 1)) as scale_factors: # turn into a 1D array, from the 3D that is returned scale_factors = scale_factors.reshape(img_num) # calculate the scale factor from original image calc_sums_partial = ptsm.create_partial(_calc_avg, fwd_function=ptsm.return_to_second, roi_left=roi[0] if roi else 0, roi_top=roi[1] if roi else 0, roi_right=roi[2] if roi else data[0].shape[1] - 1, roi_bottom=roi[3] if roi else data[0].shape[0] - 1) data, scale_factors = ptsm.execute(data, scale_factors, calc_sums_partial, cores, chunksize) return scale_factors
def find_center(images: Images, progress: Progress) -> Tuple[ScalarCoR, Degrees]: # assume the ROI is the full image, i.e. the slices are ALL rows of the image slices = np.arange(images.height) with pu.temp_shared_array((images.height, )) as shift: # this is the area that is looked into for the shift after overlapping the images search_range = get_search_range(images.width) func = shared_mem.create_partial(do_search, shared_mem.fwd_index_only, image_width=images.width, p0=images.projection(0), p180=np.fliplr( images.proj180deg.data[0]), search_range=search_range) shared_mem.execute(shift, func, progress=progress, msg="Finding correlation on row") par = np.polyfit(slices, shift, deg=1) m = par[0] q = par[1] LOG.debug(f"m={m}, q={q}") theta = Degrees(np.rad2deg(np.arctan(0.5 * m))) offset = np.round(m * images.height * 0.5 + q) * 0.5 LOG.info(f"found offset: {-offset} and tilt {theta}") return ScalarCoR(images.h_middle + -offset), theta
def generate_images(shape=g_shape, dtype=np.float32, automatic_free=True) -> Images: import inspect import uuid array_name = f"{str(uuid.uuid4())}{inspect.stack()[1].function}" if automatic_free: with pu.temp_shared_array(shape, dtype, force_name=array_name) as d: return _set_random_data(d, shape, array_name) else: d = pu.create_array(shape, dtype, array_name) return _set_random_data(d, shape, array_name)
def _calculate_correlation_error(images, shared_search_range, min_correlation_error, progress): # if the projections are passed in the partial they are copied to every process on every iteration # this makes the multiprocessing significantly slower # so they are copied into a shared array to avoid that copying with pu.temp_shared_array((2, images.height, images.width)) as shared_projections: shared_projections[0][:] = images.projection(0) shared_projections[1][:] = np.fliplr(images.proj180deg.data[0]) do_search_partial = ps.create_partial(do_calculate_correlation_err, ps.inplace3, image_width=images.width) ps.shared_list = [min_correlation_error, shared_search_range, shared_projections] ps.execute(do_search_partial, num_operations=min_correlation_error.shape[0], progress=progress, msg="Finding correlation on row")
def find_center(images: Images, progress: Progress) -> Tuple[ScalarCoR, Degrees]: # assume the ROI is the full image, i.e. the slices are ALL rows of the image slices = np.arange(images.height) with pu.temp_shared_array((images.height, )) as shift: search_range = get_search_range(images.width) with pu.temp_shared_array((len(search_range), images.height)) as min_correlation_error: with pu.temp_shared_array((len(search_range), ), dtype=np.int32) as shared_search_range: shared_search_range[:] = np.asarray(search_range, dtype=np.int32) _calculate_correlation_error(images, shared_search_range, min_correlation_error, progress) # Originally the output of do_search is stored in dimensions # corresponding to (search_range, square sum). This is awkward to navigate # we transpose store to make the array hold (square sum, search range) # so that each store[row] accesses the information for the row's square sum across all search ranges _find_shift(images, search_range, min_correlation_error, shift) par = np.polyfit(slices, shift, deg=1) m = par[0] q = par[1] LOG.debug(f"m={m}, q={q}") theta = Degrees(np.rad2deg(np.arctan(0.5 * m))) offset = np.round(m * images.height * 0.5 + q) * 0.5 LOG.info(f"found offset: {-offset} and tilt {theta}") return ScalarCoR(images.h_middle + -offset), theta
def _execute(data, flat=None, dark=None, cores=None, chunksize=None, progress=None): """ A benchmark justifying the current implementation, performed on 500x2048x2048 images. #1 Separate runs Subtract (sequential with np.subtract(data, dark, out=data)) - 13s Divide (par) - 1.15s #2 Separate parallel runs Subtract (par) - 5.5s Divide (par) - 1.15s #3 Added subtract into _divide so that it is: np.true_divide( np.subtract(data, dark, out=data), norm_divide, out=data) Subtract then divide (par) - 55s """ with progress: progress.update(msg="Applying background correction") with pu.temp_shared_array((1, data.shape[1], data.shape[2]), data.dtype) as norm_divide: # remove a dimension, I found this to be the easiest way to do it norm_divide = norm_divide.reshape(data.shape[1], data.shape[2]) # subtract dark from flat and copy into shared array with [:] norm_divide[:] = np.subtract(flat, dark) # prevent divide-by-zero issues, and negative pixels make no sense norm_divide[norm_divide == 0] = MINIMUM_PIXEL_VALUE # subtract the dark from all images f = ptsm.create_partial(_subtract, fwd_function=ptsm.inplace_second_2d) data, dark = ptsm.execute(data, dark, f, cores, chunksize, progress=progress) # divide the data by (flat - dark) f = ptsm.create_partial(_divide, fwd_function=ptsm.inplace_second_2d) data, norm_divide = ptsm.execute(data, norm_divide, f, cores, chunksize, progress=progress) return data
def gen_img_shared_array_with_val(val=1., shape=g_shape): with pu.temp_shared_array(shape) as d: n = np.full(shape, val) # move the data in the shared array d[:] = n[:] return d
def gen_empty_shared_array(shape=g_shape): with pu.temp_shared_array(shape) as d: return d
def generate_shared_array(shape=g_shape, dtype=np.float32) -> np.ndarray: with pu.temp_shared_array(shape, dtype) as generated_array: np.copyto(generated_array, np.random.rand(shape[0], shape[1], shape[2]).astype(dtype)) return generated_array
def shared_deepcopy(images: Images) -> np.ndarray: with pu.temp_shared_array(images.data.shape) as copy: np.copyto(copy, images.data) return copy