Пример #1
0
    def test_record_parameters_in_metadata(self):
        imgs = Images(np.asarray([1]))

        imgs.record_operation('test_func',
                              'A pretty name',
                              this=765,
                              that=495.0,
                              roi=(1, 2, 3, 4))

        expected = {
            'operation_history': [{
                'name': 'test_func',
                'display_name': 'A pretty name',
                'kwargs': {
                    'this': 765,
                    'that': 495.0,
                    'roi': (1, 2, 3, 4)
                },
            }]
        }

        self.assertIn(const.TIMESTAMP,
                      imgs.metadata[const.OPERATION_HISTORY][0])

        imgs.metadata[const.OPERATION_HISTORY][0].pop(const.TIMESTAMP)
        self.assertEqual(imgs.metadata, expected)
Пример #2
0
    def test_metadata_round_trip(self):
        # Create dummy image stack
        sample = th.gen_img_shared_array_with_val(42.)
        images = Images(sample)
        images.metadata['message'] = 'hello, world!'

        # Save image stack
        saver.save(images, self.output_directory)

        # Load image stack back
        dataset = loader.load(self.output_directory)
        loaded_images = dataset.sample

        # Ensure properties have been preserved
        self.assertEqual(loaded_images.metadata, images.metadata)

        loaded_images.free_memory()
        if dataset.dark_before:
            dataset.dark_before.free_memory()
        if dataset.dark_after:
            dataset.dark_after.free_memory()
        if dataset.flat_before:
            dataset.flat_before.free_memory()
        if dataset.flat_after:
            dataset.flat_after.free_memory()
Пример #3
0
    def full(images: Images,
             cors: List[ScalarCoR],
             recon_params: ReconstructionParameters,
             progress: Optional[Progress] = None) -> Images:
        progress = Progress.ensure_instance(progress, num_steps=images.height)
        output_shape = (images.num_sinograms, images.width, images.width)
        output_images: Images = Images.create_empty_images(
            output_shape, images.dtype, images.metadata)
        output_images.record_operation('AstraRecon.full', 'Reconstruction',
                                       **recon_params.to_dict())

        # FIXME multiple GPU support - just starting up a Pool doesn't seem to work
        # the GPUs can't initialise the memory properly. Not sure why
        # num_gpus = AstraRecon._count_gpus()
        # LOG.info(f"Running with {num_gpus} GPUs")
        # partial = ptsm.create_partial(AstraRecon.single, ptsm.fwd_gpu_recon,
        #                               num_gpus=num_gpus, cors=cors,
        #                               proj_angles=proj_angles, recon_params=recon_params)
        # ptsm.execute(images.sinograms, output_images.data, partial, num_gpus, progress=progress)

        proj_angles = images.projection_angles()
        for i in range(images.height):
            output_images.data[i] = AstraRecon.single_sino(
                images.sino(i), cors[i], proj_angles, recon_params)
            progress.update(1, "Reconstructed slice")

        return output_images
Пример #4
0
def execute(load_func,
            sample_path,
            flat_path,
            dark_path,
            img_format,
            dtype,
            indices,
            progress=None) -> Dataset:
    """
    Reads a stack of images into memory, assuming dark and flat images
    are in separate directories.

    If several files are found in the same directory (for example you
    give image0001.fits and there's also image0002.fits,
    image0003.fits) these will also be loaded as the usual convention
    in ImageJ and related imaging tools, using the last digits to sort
    the images in the stack.

    Usual type in fits is 16-bit pixel depth, data type is denoted with:
        '>i2' - uint16
        '>f2' - float16
        '>f4' - float32

    :returns: Images object
    """

    if not sample_path:
        raise RuntimeError("No filenames were provided.")

    # The following codes assume that all images have the same size and properties as the first.
    # This is always true in the case of raw data
    first_sample_img = load_func(sample_path[0])

    # select the files loaded based on the indices, if any are provided
    chosen_input_filenames = sample_path[
        indices[0]:indices[1]:indices[2]] if indices else sample_path

    # get the shape of all images
    img_shape = first_sample_img.shape

    # forward all arguments to internal class for easy re-usage
    il = ImageLoader(load_func, img_format, img_shape, dtype, indices,
                     progress)

    # we load the flat and dark first, because if they fail we don't want to
    # fail after we've loaded a big stack into memory
    flat_data, flat_filenames, flat_mfname = il.load_data(flat_path)
    dark_data, dark_filenames, dark_mfname = il.load_data(dark_path)
    sample_data, sample_mfname = il.load_sample_data(chosen_input_filenames)

    return Dataset(
        Images(sample_data,
               chosen_input_filenames,
               indices,
               memory_filename=sample_mfname),
        Images(flat_data, flat_filenames, memory_filename=flat_mfname)
        if flat_data is not None else None,
        Images(dark_data, dark_filenames, memory_filename=dark_mfname)
        if dark_data is not None else None)
Пример #5
0
def update_image_operations(images: Images, model):
    """
    TODO: remove this function / integrate in recon GUI
    Updates the image operation history with the results in the given model.
    """
    images.record_operation(const.OPERATION_NAME_COR_TILT_FINDING,
                            display_name="Calculated COR/Tilt",
                            **model.stack_properties)
Пример #6
0
def _set_random_data(data, shape, array_name):
    n = np.random.rand(*shape)
    # move the data in the shared array
    data[:] = n[:]

    images = Images(data)
    images.memory_filename = array_name
    return images
Пример #7
0
    def filter_func(images: Images,
                    rebin_param=0.5,
                    mode=None,
                    cores=None,
                    chunksize=None,
                    progress=None) -> Images:
        """
        :param images: Sample data which is to be processed. Expects radiograms
        :param rebin_param: int, float or tuple
                            int - Percentage of current size.
                            float - Fraction of current size.
                            tuple - Size of the output image (x, y).
        :param mode: Interpolation to use for re-sizing
                     ('nearest', 'lanczos', 'bilinear', 'bicubic' or 'cubic').
        :param cores: The number of cores that will be used to process the data.
        :param chunksize: The number of chunks that each worker will receive.

        :return: The processed 3D numpy.ndarray
        """
        h.check_data_stack(images)

        if isinstance(rebin_param, tuple):
            param_valid = rebin_param[0] > 0 and rebin_param[1] > 0
        else:
            param_valid = rebin_param > 0

        if param_valid:
            sample = images.data
            sample_name: Optional[str]
            if images.memory_filename is not None:
                sample_name = images.memory_filename
                images.free_memory(delete_filename=False)
            else:
                # this case is true when the filter preview is being calculated
                sample_name = None
                # force single core execution as it's faster for a single image
                cores = 1
            empty_resized_data = _create_reshaped_array(
                sample.shape, sample.dtype, rebin_param, sample_name)

            f = ptsm.create_partial(skimage.transform.resize,
                                    ptsm.return_to_second_but_dont_use_it,
                                    mode=mode,
                                    output_shape=empty_resized_data.shape[1:])
            ptsm.execute(sample,
                         empty_resized_data,
                         f,
                         cores,
                         chunksize,
                         progress=progress,
                         msg="Applying Rebin")
            images.data = empty_resized_data

        return images
Пример #8
0
    def test_image_eq_method(self):
        data_array = np.arange(64, dtype=float).reshape([4, 4, 4])
        data_images = Images(data_array.copy())
        data_images2 = Images(data_array.copy())

        self.assertEqual(data_images, data_array)
        self.assertEqual(data_images, data_images2)

        data_array[1, 1, 1] *= 2
        self.assertNotEqual(data_images, data_array)

        self.assertRaises(ValueError, lambda a, b: a == b, data_images, 1.0)
Пример #9
0
    def __init__(self, images: Images, slice_idx: int, initial_cor: ScalarCoR,
                 recon_params: ReconstructionParameters):
        self.image_width = images.width
        self.sino = images.sino(slice_idx)

        # Initial parameters
        self.centre_cor = initial_cor.value
        self.cor_step = 50

        # Cache projection angles
        self.proj_angles = images.projection_angles()
        self.recon_params = recon_params
        self.reconstructor = get_reconstructor_for(recon_params.algorithm)
Пример #10
0
    def test_parse_metadata_file(self):
        json_file = io.StringIO('{"a_int": 42, "a_string": "yes", "a_arr": ["one", "two", '
                                '"three"], "a_float": 3.65e-05, "a_bool": true}')

        imgs = Images(np.asarray([1]))
        imgs.load_metadata(json_file)

        def validate_prop(k, v):
            self.assertEqual(imgs.metadata[k], v)

        validate_prop('a_bool', True)
        validate_prop('a_string', 'yes')
        validate_prop('a_int', 42)
        validate_prop('a_arr', ['one', 'two', 'three'])
Пример #11
0
    def test_do_stack_reconstruct_slice(self):
        test_data = Images(np.ndarray(shape=(200, 250), dtype=np.float32))
        test_data.record_operation = mock.Mock()
        task_mock = mock.Mock(result=test_data, error=None)
        self.presenter._get_slice_index = mock.Mock(return_value=7)

        self.presenter._on_stack_reconstruct_slice_done(task_mock)

        self.view.show_recon_volume.assert_called_once()
        np.array_equal(self.view.show_recon_volume.call_args[0][0].data, test_data)
        test_data.record_operation.assert_called_once_with('AstraRecon.single_sino',
                                                           'Slice Reconstruction',
                                                           slice_idx=7,
                                                           **self.view.recon_params().to_dict())
Пример #12
0
    def test_metadata_round_trip(self):
        # Create dummy image stack
        sample = th.gen_img_numpy_rand()
        images = Images(sample)
        images.metadata['message'] = 'hello, world!'

        # Save image stack
        saver.save(images, self.output_directory)

        # Load image stack back
        dataset = loader.load(self.output_directory)
        loaded_images = dataset.sample

        # Ensure properties have been preserved
        self.assertEqual(loaded_images.metadata, images.metadata)
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)
Пример #14
0
def _set_random_data(data, shape):
    n = np.random.rand(*shape)
    # move the data in the shared array
    data[:] = n[:]

    images = Images(data)
    return images
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
Пример #16
0
def _set_random_data(data, shape, seed: Optional[int] = None):
    n = gen_img_numpy_rand(shape, seed=seed)
    # move the data in the shared array
    data[:] = n[:]

    images = Images(data)
    return images
Пример #17
0
    def filter_func(images: Images,
                    min_input: float = 0.0,
                    max_input: float = 10000.0,
                    max_output: float = 256.0,
                    progress=None,
                    data_type=None) -> Images:
        images.data[images.data < min_input] = 0
        images.data[images.data > max_input] = 0
        images.data *= (max_output / images.data.max())

        if data_type is not None:
            if data_type == uint16 and not images.dtype == uint16:
                images.data = images.data.astype(uint16)
            elif data_type == float32 and not images.dtype == float32:
                images.data = images.data.astype(float32)

        return images
Пример #18
0
 def find_cor(images: Images, slice_idx: int, start_cor: float,
              recon_params: ReconstructionParameters) -> float:
     return tomopy.find_center(images.sinograms,
                               images.projection_angles(
                                   recon_params.max_projection_angle).value,
                               ind=slice_idx,
                               init=start_cor,
                               sinogram_order=True)
Пример #19
0
    def setUp(self):
        self.model = ReconstructWindowModel(CorTiltPointQtModel())

        # Mock stack
        self.stack = mock.create_autospec(StackVisualiserView)
        data = Images(data=np.ndarray(shape=(10, 128, 256), dtype=np.float32))
        self.stack.presenter = StackVisualiserPresenter(self.stack, data)

        self.model.initial_select_data(self.stack)
    def setUp(self):
        # Mock view
        self.make_view()

        self.presenter = ReconstructWindowPresenter(self.view, None)

        # Mock stack
        self.sv_view = mock.create_autospec(StackVisualiserView)

        data = Images(data=np.ndarray(shape=(128, 10, 128), dtype=np.float32))
        data.pixel_size = TEST_PIXEL_SIZE
        self.sv_view.presenter = StackVisualiserPresenter(self.sv_view, data)

        self.presenter.model.initial_select_data(self.sv_view)
        self.view.get_stack_visualiser = mock.Mock(return_value=self.sv_view)

        import uuid
        self.uuid = uuid.uuid4()
Пример #21
0
    def full(images: Images,
             cors: List[ScalarCoR],
             recon_params: ReconstructionParameters,
             progress: Optional[Progress] = None) -> Images:
        progress = Progress.ensure_instance(progress, num_steps=images.height)
        output_shape = (images.num_sinograms, images.width, images.width)
        output_images: Images = Images.create_empty_images(
            output_shape, images.dtype, images.metadata)
        output_images.record_operation('AstraRecon.full', 'Reconstruction',
                                       **recon_params.to_dict())

        proj_angles = images.projection_angles(
            recon_params.max_projection_angle)
        for i in range(images.height):
            output_images.data[i] = AstraRecon.single_sino(
                images.sino(i), cors[i], proj_angles, recon_params)
            progress.update(1, "Reconstructed slice")

        return output_images
Пример #22
0
    def filter_func(data: Images) -> Images:
        """
        Executes the filter algorithm on a given set of image data with the given parameters.

        :param data: the image data to apply the filter to
        :param kwargs: any additional arguments which the specific filter uses
        :return: the image data after applying the filter
        """
        raise_not_implemented("filter_func")
        return Images(np.asarray([]))
Пример #23
0
    def create_stack_window(self,
                            stack: Images,
                            position: Qt.DockWidgetArea = Qt.DockWidgetArea.
                            RightDockWidgetArea,
                            floating: bool = False) -> StackVisualiserView:
        stack.make_name_unique(self.stack_names)
        stack_vis = StackVisualiserView(self, stack)

        # this puts the new stack window into the centre of the window
        self.splitter.addWidget(stack_vis)
        self.setCentralWidget(self.splitter)

        # add the dock widget into the main window
        self.addDockWidget(position, stack_vis)

        stack_vis.setFloating(floating)

        self.presenter.add_stack_to_dictionary(stack_vis)
        return stack_vis
Пример #24
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}"])
Пример #25
0
    def filter_func(images: Images,
                    min_input: float = 0.0,
                    max_input: float = 10000.0,
                    max_output: float = 256.0,
                    progress=None,
                    data_type=None) -> Images:
        np.clip(images.data, min_input, max_input, out=images.data)
        # offset - it removes any negative values so that they don't overflow when in uint16 range
        images.data -= nanmin(images.data)
        data_max = nanmax(images.data)
        # slope
        images.data *= (max_output / data_max)

        if data_type is not None:
            if data_type == uint16 and not images.dtype == uint16:
                images.data = images.data.astype(uint16)
            elif data_type == float32 and not images.dtype == float32:
                images.data = images.data.astype(float32)

        return images
Пример #26
0
    def filter_func(data: Images) -> Images:
        """
        Executes the filter algorithm on a given set of image data with the given parameters.
        The body of this function does not need to include pre and post processing steps - these should be
        included in the do_before_wrapper and do_after_wrapper, respectively.

        :param data: the image data to apply the filter to
        :param kwargs: any additional arguments which the specific filter uses
        :return: the image data after applying the filter
        """
        raise_not_implemented("filter_func")
        return Images(np.asarray([]))
Пример #27
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
Пример #28
0
    def __init__(self, images: Images, slice_idx: int, initial_cor: ScalarCoR, recon_params: ReconstructionParameters,
                 iters_mode: bool):
        self.image_width = images.width
        self.sino = images.sino(slice_idx)

        # Initial parameters
        if iters_mode:
            self.centre_value: Union[int, float] = INIT_ITERS_CENTRE_VALUE
            self.step = INIT_ITERS_STEP
            self.initial_cor = initial_cor
            self._recon_preview = self._recon_iters_preview
            self._divide_step = self._divide_iters_step
        else:
            self.centre_value = initial_cor.value
            self.step = self.image_width * 0.05
            self._recon_preview = self._recon_cor_preview
            self._divide_step = self._divide_cor_step

        # Cache projection angles
        self.proj_angles = images.projection_angles(recon_params.max_projection_angle)
        self.recon_params = recon_params
        self.reconstructor = get_reconstructor_for(recon_params.algorithm)
Пример #29
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."
            )

        # allocate output first BEFORE freeing the original data,
        # otherwise it's possible to free and then fail allocation for output
        # at which point you're left with no data
        output = pu.allocate_output(images, shape)
        images.data = execute_single(sample,
                                     region_of_interest,
                                     progress,
                                     out=output)

        return images
Пример #30
0
def _execute_gpu(data, size, mode, progress=None):
    log = getLogger(__name__)
    progress = Progress.ensure_instance(progress,
                                        num_steps=data.shape[0],
                                        task_name="Median filter GPU")
    cuda = gpu.CudaExecuter(data.dtype)

    with progress:
        log.info("GPU median filter, with pixel data type: {0}, filter "
                 "size/width: {1}.".format(data.dtype, size))

        data = cuda.median_filter(data, size, mode, progress)

    return Images(data)