Esempio n. 1
0
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)
    shift = pu.create_array((images.height, ))

    search_range = get_search_range(images.width)
    min_correlation_error = pu.create_array((len(search_range), images.height))
    shared_search_range = pu.create_array((len(search_range), ),
                                          dtype=np.int32)
    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
Esempio n. 2
0
 def load_files(self, files, memory_name=None) -> np.ndarray:
     # Zeroing here to make sure that we can allocate the memory.
     # If it's not possible better crash here than later.
     num_images = len(files)
     shape = (num_images, self.img_shape[0], self.img_shape[1])
     data = pu.create_array(shape, self.data_dtype, memory_name)
     return self._do_files_load_seq(data, files, memory_name)
Esempio n. 3
0
def generate_images_for_parallel(shape=(15, 8, 10), dtype=np.float32) -> Images:
    """
    Doesn't do anything special, just makes a number of images big enough to be
    ran in parallel from the logic of multiprocessing_necessary
    """
    d = pu.create_array(shape, dtype)
    return _set_random_data(d, shape)
Esempio n. 4
0
def generate_shared_array(shape=g_shape,
                          dtype=np.float32,
                          seed: Optional[int] = None) -> np.ndarray:
    generated_array = pu.create_array(shape, dtype)
    np.copyto(generated_array,
              gen_img_numpy_rand(shape, seed=seed).astype(dtype))
    return generated_array
def execute(load_func, file_name, dtype, name, indices=None, progress=None):
    """
    Load a single image FILE that is expected to be a stack of images.

    Parallel execution can be slower depending on the storage system.

    On HDD I've found it's about 50% SLOWER, thus not recommended!

    :param file_name: list of image file paths given as strings

    :param load_func: file name extension if fixed (to set the expected image
                      format)

    :param dtype: data type for the output numpy array

    :return: stack of images as a 3-elements tuple: numpy array with sample
             images, white image, and dark image.
    """
    # create shared array
    new_data = load_func(file_name)

    if indices:
        new_data = new_data[indices[0]:indices[1]:indices[2]]

    img_shape = new_data.shape
    data = pu.create_array(img_shape, dtype=dtype)

    # we could just move with data[:] = new_data[:] but then we don't get
    # loading bar information, and I doubt there's any performance gain
    data = do_stack_load_seq(data, new_data, img_shape, name, progress)

    # Nexus doesn't load flat/dark images yet, if the functionality is
    # requested it should be changed here
    return Images(data, file_name)
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)
Esempio n. 7
0
 def _create_images(self, data_array: np.ndarray, name: str) -> Images:
     """
     Use a data array to create an Images object.
     :param data_array: The images array obtained from the NeXus file.
     :param name: The name of the image dataset.
     :return: An Images object.
     """
     data = pu.create_array(data_array.shape,
                            self.view.pixelDepthComboBox.currentText())
     data[:] = data_array
     return Images(data, [f"{name} {self.title}"])
Esempio n. 8
0
    def copy(self, flip_axes=False) -> 'Images':
        shape = (self.data.shape[1], self.data.shape[0], self.data.shape[2]) if flip_axes else self.data.shape
        data_copy = pu.create_array(shape, self.data.dtype)
        if flip_axes:
            data_copy[:] = np.swapaxes(self.data, 0, 1)
        else:
            data_copy[:] = self.data[:]

        images = Images(data_copy,
                        indices=deepcopy(self.indices),
                        metadata=deepcopy(self.metadata),
                        sinograms=not self.is_sinograms if flip_axes else self.is_sinograms)
        return images
Esempio n. 9
0
    def copy_roi(self, roi: SensibleROI):
        shape = (self.data.shape[0], roi.height, roi.width)

        data_copy = pu.create_array(shape, self.data.dtype)
        data_copy[:] = self.data[:, roi.top:roi.bottom, roi.left:roi.right]

        images = Images(data_copy,
                        indices=deepcopy(self.indices),
                        metadata=deepcopy(self.metadata),
                        sinograms=self._is_sinograms)

        mark_cropped(images, roi)
        return images
Esempio n. 10
0
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
    shared_projections = pu.create_array((2, images.height, images.width))
    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")
Esempio n. 11
0
def _create_reshaped_array(images, rebin_param):
    old_shape = images.data.shape
    num_images = old_shape[0]

    # use SciPy's calculation to find the expected dimensions
    # int to avoid visible deprecation warning
    if isinstance(rebin_param, tuple):
        expected_dimy = int(rebin_param[0])
        expected_dimx = int(rebin_param[1])
    else:
        expected_dimy = int(rebin_param * old_shape[1])
        expected_dimx = int(rebin_param * old_shape[2])

    # allocate memory for images with new dimensions
    shape = (num_images, expected_dimy, expected_dimx)
    return pu.create_array(shape, images.dtype)
Esempio n. 12
0
    def filter_func(images: Images,
                    region_of_interest: Optional[Union[List[int], List[float],
                                                       SensibleROI]] = None,
                    progress=None) -> Images:
        """Execute the Crop Coordinates by Region of Interest filter. This does
        NOT do any checks if the Region of interest is out of bounds!

        If the region of interest is out of bounds, the crop will **FAIL** at
        runtime.

        If the region of interest is in bounds, but has overlapping coordinates
        the crop give back a 0 shape of the coordinates that were wrong.

        :param images: Input data as a 3D numpy.ndarray

        :param region_of_interest: Crop original images using these coordinates.
                                   The selection is a rectangle and expected order
                                   is - Left Top Right Bottom.

        :return: The processed 3D numpy.ndarray
        """

        if region_of_interest is None:
            region_of_interest = SensibleROI.from_list([0, 0, 50, 50])
        if isinstance(region_of_interest, list):
            region_of_interest = SensibleROI.from_list(region_of_interest)

        assert isinstance(region_of_interest, SensibleROI)

        h.check_data_stack(images)

        sample = images.data
        shape = (sample.shape[0], region_of_interest.height,
                 region_of_interest.width)
        if any((s < 0 for s in shape)):
            raise ValueError(
                "It seems the Region of Interest is outside of the current image dimensions.\n"
                "This can happen on the image preview right after a previous Crop Coordinates."
            )

        output = pu.create_array(shape, images.dtype)
        images.data = execute_single(sample,
                                     region_of_interest,
                                     progress,
                                     out=output)

        return images
Esempio n. 13
0
def _execute(data: np.ndarray,
             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")

        norm_divide = pu.create_array((data.shape[1], data.shape[2]),
                                      data.dtype)

        # 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
        do_subtract = ps.create_partial(_subtract,
                                        fwd_function=ps.inplace_second_2d)
        ps.shared_list = [data, dark]
        ps.execute(do_subtract, data.shape[0], progress, cores=cores)

        # divide the data by (flat - dark)
        do_divide = ps.create_partial(_divide,
                                      fwd_function=ps.inplace_second_2d)
        ps.shared_list = [data, norm_divide]
        ps.execute(do_divide, data.shape[0], progress, cores=cores)

    return data
Esempio n. 14
0
def _create_reshaped_array(old_shape, dtype, rebin_param,
                           sample_name: Optional[str]):
    num_images = old_shape[0]

    # use SciPy's calculation to find the expected dimensions
    # int to avoid visible deprecation warning
    if isinstance(rebin_param, tuple):
        expected_dimy = int(rebin_param[0])
        expected_dimx = int(rebin_param[1])
    else:
        expected_dimy = int(rebin_param * old_shape[1])
        expected_dimx = int(rebin_param * old_shape[2])

    # allocate memory for images with new dimensions
    shape = (num_images, expected_dimy, expected_dimx)
    data = pu.create_array(shape, dtype, sample_name)

    return data
Esempio n. 15
0
    def filter_func(images: Images,
                    region_of_interest: Optional[Union[List[int], List[float], SensibleROI]] = None,
                    progress=None) -> Images:
        """
        Execute the Crop Coordinates by Region of Interest filter.
        This does NOT do any checks if the Region of interest is out of bounds!

        If the region of interest is out of bounds, the crop will **FAIL** at
        runtime.

        If the region of interest is in bounds, but has overlapping coordinates
        the crop give back a 0 shape of the coordinates that were wrong.

        :param images: Input data as a 3D numpy.ndarray

        :param region_of_interest: Crop original images using these coordinates.
                                   The selection is a rectangle and expected order
                                   is - Left Top Right Bottom.

        :return: The processed 3D numpy.ndarray
        """

        if region_of_interest is None:
            region_of_interest = SensibleROI.from_list([0, 0, 50, 50])
        if isinstance(region_of_interest, list):
            region_of_interest = SensibleROI.from_list(region_of_interest)

        assert isinstance(region_of_interest, SensibleROI)

        h.check_data_stack(images)

        sample = images.data
        shape = (sample.shape[0], region_of_interest.height, region_of_interest.width)
        sample_name = images.memory_filename
        if sample_name is not None:
            images.free_memory(delete_filename=False)
        output = pu.create_array(shape, sample.dtype, sample_name)
        images.data = execute_single(sample, region_of_interest, progress, out=output)

        return images
def _execute(data: np.ndarray,
             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]
        air_sums = pu.create_array((img_num, ), data.dtype)

        do_calculate_air_sums = ps.create_partial(_calc_sum,
                                                  ps.return_to_second_at_i,
                                                  air_left=air_region.left,
                                                  air_top=air_region.top,
                                                  air_right=air_region.right,
                                                  air_bottom=air_region.bottom)

        ps.shared_list = [data, air_sums]
        ps.execute(do_calculate_air_sums, data.shape[0], progress, cores=cores)

        do_divide = ps.create_partial(_divide_by_air_sum,
                                      fwd_function=ps.inplace2)
        ps.shared_list = [data, air_sums]
        ps.execute(do_divide, data.shape[0], progress, cores=cores)

        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}.")
Esempio n. 17
0
def generate_shared_array(shape=g_shape, dtype=np.float32) -> np.ndarray:
    generated_array = pu.create_array(shape, dtype)
    np.copyto(generated_array, np.random.rand(shape[0], shape[1], shape[2]).astype(dtype))
    return generated_array
Esempio n. 18
0
def generate_images(shape=g_shape,
                    dtype=np.float32,
                    seed: Optional[int] = None) -> Images:
    d = pu.create_array(shape, dtype)
    return _set_random_data(d, shape, seed=seed)
Esempio n. 19
0
def generate_images(shape=g_shape, dtype=np.float32) -> Images:
    d = pu.create_array(shape, dtype)
    return _set_random_data(d, shape)
Esempio n. 20
0
 def create_empty_images(shape, dtype, metadata):
     arr = pu.create_array(shape, dtype)
     return Images(arr, metadata=metadata)
Esempio n. 21
0
def gen_img_shared_array_with_val(val=1., shape=g_shape):
    d = pu.create_array(shape)
    n = np.full(shape, val)
    # move the data in the shared array
    d[:] = n[:]
    return d
Esempio n. 22
0
 def create_empty_images(shape, dtype, metadata):
     shared_name = pu.create_shared_name()
     arr = pu.create_array(shape, dtype, shared_name)
     return Images(arr, memory_filename=shared_name, metadata=metadata)
def _execute(data: np.ndarray,
             air_region: SensibleROI,
             normalisation_mode: str,
             flat_field: Optional[np.ndarray],
             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]
        air_means = pu.create_array((img_num, ), data.dtype)

        do_calculate_air_means = ps.create_partial(
            _calc_mean,
            ps.return_to_second_at_i,
            air_left=air_region.left,
            air_top=air_region.top,
            air_right=air_region.right,
            air_bottom=air_region.bottom)

        ps.shared_list = [data, air_means]
        ps.execute(do_calculate_air_means,
                   data.shape[0],
                   progress,
                   cores=cores)

        if normalisation_mode == 'Preserve Max':
            air_maxs = pu.create_array((img_num, ), data.dtype)
            do_calculate_air_max = ps.create_partial(_calc_max,
                                                     ps.return_to_second_at_i)

            ps.shared_list = [data, air_maxs]
            ps.execute(do_calculate_air_max,
                       data.shape[0],
                       progress,
                       cores=cores)

            if np.isnan(air_maxs).any():
                raise ValueError("Image contains invalid (NaN) pixels")

            # calculate the before and after maximum
            init_max = air_maxs.max()
            post_max = (air_maxs / air_means).max()
            air_means *= post_max / init_max

        elif normalisation_mode == 'Stack Average':
            air_means /= air_means.mean()

        elif normalisation_mode == 'Flat Field' and flat_field is not None:
            flat_mean = pu.create_array((flat_field.shape[0], ),
                                        flat_field.dtype)
            ps.shared_list = [flat_field, flat_mean]
            ps.execute(do_calculate_air_means,
                       flat_field.shape[0],
                       progress,
                       cores=cores)
            air_means /= flat_mean.mean()

        if np.isnan(air_means).any():
            raise ValueError("Air region contains invalid (NaN) pixels")

        do_divide = ps.create_partial(_divide_by_air, fwd_function=ps.inplace2)
        ps.shared_list = [data, air_means]
        ps.execute(do_divide, data.shape[0], progress, cores=cores)

        avg = np.average(air_means)
        max_avg = np.max(air_means) / avg
        min_avg = np.min(air_means) / avg

        log.info(
            f"Normalization by air region. "
            f"Average: {avg}, max ratio: {max_avg}, min ratio: {min_avg}.")