owl.analysis

owl.analysis.hdr_combine(images, exposures, image_range=(None, None), invalid_relative_range=(0.1, 0.1), target_exposure=None)[source]

Combine a stack of images taken with different exposures.

If a given pixel is valid in multiple images, the weighted average of the intensity in all the images will be taken into account in the final value returned.

Pixels are combined on a per pixel basis. This means that the images may be of any shape, so long as the shape is consistent between images.

Parameters:
  • images (list of array-like objects) – An array containing the image data. These can be multi-dimensional arrays so long as they all have the same shape.

  • exposures (list of exposures) – List of the exposures taken for all the images.

  • image_range (tuple of floats (min, max)) – The range of the image. If not provided, limits are taken to be be those from skimage.util.dtype.dtype_limits with clip_negative=True.

  • invalid_relative_range (tuple of floats (min_invalid, max_invalid)) – Pixels within a certain fraction of the minimum and maximum are considered to be invalid with the exception of the image taken with minimal and maximal exposure.

  • target_exposure (float) – If this is set the outputted image will be scaled to match this exposure value. If None is given it will be set to the lowest exposure value from the give list.

Examples

>>> image_low_exposure = np.asarray([[0.6, 0.6],
...                                  [0.01, 0.01]])
>>> image_high_exposure = np.asarray([[1.0, 1.0],
...                                   [0.16, 0.16]])
>>> exposures = [1, 10]
>>> hdr_combine([image_low_exposure, image_high_exposure], exposures)
array([[0.6  , 0.6  ],
       [0.016, 0.016]])

Notice that in this case, the pixels on the first row were correctly exposed during the short exposure. The pixels on the second row were correctly exposed during the longer exposure. HDR combine selects the pixels that were correctly exposed in both cases and creates a composite from the input images.

owl.analysis.equalize_intensity(images, params, *, roi_fraction_up_down=(0.3, 0.3), roi_fraction_left_right=(0.3, 0.3))[source]

Often, images might have slight variations from camera to camera.

This attempts to estimate these intensity variations by looking at the region around the overlap. Given an image with an overlap of $(N_y, N_x)$ pixels, this algorithm will analyze a number of pixels equal to $(N_y * overlapFraction_y, N_x * overlapFraction_x)$ and ensure that the average intensity in that region is the same.

Parameters:
  • images (mcam_data) – must be at least 4D (grayscale), with (image_y, image_x) as the leading dimensions.

  • params (dictionary) – Stitching parameters from the fourier stitcher.

  • roi_fraction_up_down ((float, float)) – Tuple describing the fraction of the overlap in each dimension to use for intensity calibration when comparing the images in the Y dimension.

  • roi_fraction_left_right ((float, float)) – Tuple describing the fraction of the overlap in each dimension to use for intensity calibration when comparing the images in the X dimension.

Returns:

  • images_out – mcam_data of the same shape, but float-like, with the intensity normalized.

  • intensity_variations – The relative intensity to which each image was multiplied.

Example

This example will load data in, stitch the data, then use the stitching parameters to normalize the intensity of the image so as to normalize the imaging lighting intensity between acquisitions.

This normalization will work best with images of specular targets illuminated with incoherent light.

>>> from owl import mcam_data
>>> from owl.color import rgb2gray
>>> from owl.analysis import equalize_intensity
>>> from owl.visualize import view_large_image
>>> data = mcam_data.load('your_data_set', output_mode='rgb')
>>> data_gray = rgb2gray(data)
>>> data_float = data.astype('float32') / 255
>>> params = find_stitching_parameters(data_gray)
>>> data_equalized, variations = equalize_intensity(data_float, params)
>>> image_corrected = generate_composite(data_equalized, params)
>>> view_large_image(image_corrected)
owl.analysis.find_in_focus_indices(data, *, search_axis=0, image_dims=2)[source]

Find the indices that are in focus for each camera.

The provided data should be something that looks like grayscale mcam_data with an additional dimension, the one you wish to search along.

For example, the mcam_data might look something like:

>>> raw_data
<xarray.DataArray 'stack-329162f6' (z: 12, image_y: 6, image_x: 4, y: 2432, x: 4320)>
dask.array<shape=(12, 6, 4, 2432, 4320), dtype=uint8, chunksize=(1, 1, 1, 2432, 4320)>
Coordinates:
  * image_y           (image_y) int64 0 1 2 3 4 5
  * image_x           (image_x) int64 0 1 2 3
  * y                  (y) int64 0 1 2 3 4 5 6 ... 2426 2427 2428 2429 2430 2431
  * x                  (x) int64 0 1 2 3 4 5 6 ... 4314 4315 4316 4317 4318 4319
  * z                  (z) float64 0.0 0.5 1.0 1.5 2.0 ... 3.5 4.0 4.5 5.0 5.5
Attributes:
    sys_info:          Python: 3.6.6 | packaged by conda-forge | (default, Ju...
    imagename_format:  cam{image_y}_{image_x}.bmp

With z being the dimension we wish to search along.

The function would then be called, returning a delayed object that consists of the indices corresponding to the slice in focus for each camera. Here we ask that the metric only be computed considering every other pixel. This is an acceptable compromise since it avoids any Bayer Pattern artifacts and speeds up the computation.

>>> in_focus_z_indices = find_in_focus_indices(raw_data[..., ::2, ::2])
>>> in_focus_z_indices
<xarray.DataArray 'z_stage' (image_y: 6, image_x: 4)>
dask.array<shape=(6, 4), dtype=int64, chunksize=(1, 1)>
Coordinates:
  * image_y  (image_y) int64 0 1 2 3 4 5
  * image_x  (image_x) int64 0 1 2 3
    Data should be a dask array with the dimension you want to search for
    being the leading dimension.
Parameters:
  • data (ArrayLike [N_z, N_cameras_y, N_cameras_x, y, x]) – A 5D data volume containing info about all cameras.

  • search_axis (int ????) – Not supported yet, but hopefully we can at some point specify which axis to search along. This doesn’t seem to be useful immediately. For now this is simply set to the axis of N_z.

  • image_dims (int) – The number of dimensions each image has. For now, this is hardcoded to 2, grayscale image, but in the future may be expanded to support more than grayscale images.

Returns:

in_focus_indices – The indices where each camera has the best focus. This is potentially a delayed object.

Return type:

ArrayLike [image_y, image_x]

owl.analysis.slice_in_focus_images(data, in_focus_indices)[source]

Picks out the images with that are in focus from MCAM Data.

The data is assumed to be an xarray with the leading dimension being the one we wish to slice into. For example, if the stack is a focal stack where the leading dimension contains measurements at 10 different stage heights z, the array might have the following dimensions

>>> data = mcam_data.new(N_cameras=(10, 6, 4),
                         dims=['z_stage', 'image_y', 'image_x', 'y', 'x'])
>>> data.shape
(10, 6, 4, 2432, 4320)

in_focus_indices is an array of shape [N_image_y, N_image_x] containing the index that should be sliced in for each camera.

>>> color_data = slice_in_focus_images(color_data, in_focus_z_index)
>>> color_data
<xarray.DataArray 'stack-e599e60' (image_y: 6, image_x: 4, y: 2432, x: 4320, rgb: 3)>
dask.array<shape=(6, 4, 2432, 4320, 3), dtype=uint8, chunksize=(1, 1, 2432, 4320, 3)>
Coordinates:
  * image_y           (image_y) int64 0 1 2 3 4 5
  * image_x           (image_x) int64 0 1 2 3
  * y                  (y) int64 0 1 2 3 4 5 6 ... 2426 2427 2428 2429 2430 2431
  * x                  (x) int64 0 1 2 3 4 5 6 ... 4314 4315 4316 4317 4318 4319
    bayer_pattern      (image_y, image_x) <U4 'bggr' 'bggr' ... 'bggr' 'bggr'
    camera_number      (image_y, image_x) int64 20 21 23 22 16 ... 6 0 1 3 2
    exposure           (image_y, image_x) int64 500 500 500 ... 500 500 500
    gain               (image_y, image_x) int64 1 1 1 1 1 1 1 ... 1 1 1 1 1 1
  * rgb                (rgb) <U1 'r' 'g' 'b'
    acquisition_count  (image_y, image_x) int64 5 6 3 3 4 5 6 ... 4 3 3 4 3 3
    trigger            (image_y, image_x) int64 175 176 173 ... 174 173 173
    z                  (image_y, image_x) float64 2.0 2.5 1.0 ... 1.5 1.0 1.0
Attributes:
    sys_info:          Python: 3.6.6 | packaged by conda-forge | (default, Ju...
    imagename_format:  cam{image_y}_{image_x}.bmp
>>> color_data = color_data.persist(scheduler='threads')
>>> color_data
<xarray.DataArray 'stack-e599e60' (image_y: 6, image_x: 4, y: 2432, x: 4320, rgb: 3)>
dask.array<shape=(6, 4, 2432, 4320, 3), dtype=uint8, chunksize=(1, 1, 2432, 4320, 3)>
Coordinates:
  * image_y           (image_y) int64 0 1 2 3 4 5
  * image_x           (image_x) int64 0 1 2 3
  * y                  (y) int64 0 1 2 3 4 5 6 ... 2426 2427 2428 2429 2430 2431
  * x                  (x) int64 0 1 2 3 4 5 6 ... 4314 4315 4316 4317 4318 4319
    bayer_pattern      (image_y, image_x) <U4 'bggr' 'bggr' ... 'bggr' 'bggr'
    camera_number      (image_y, image_x) int64 20 21 23 22 16 ... 6 0 1 3 2
    exposure           (image_y, image_x) int64 500 500 500 ... 500 500 500
    gain               (image_y, image_x) int64 1 1 1 1 1 1 1 ... 1 1 1 1 1 1
  * rgb                (rgb) <U1 'r' 'g' 'b'
    acquisition_count  (image_y, image_x) int64 5 6 3 3 4 5 6 ... 4 3 3 4 3 3
    trigger            (image_y, image_x) int64 175 176 173 ... 174 173 173
    z                  (image_y, image_x) float64 2.0 2.5 1.0 ... 1.5 1.0 1.0
Attributes:
    sys_info:          Python: 3.6.6 | packaged by conda-forge | (default, Ju...
    imagename_format:  cam{image_y}_{image_x}.bmp

The use of persist instead of compute keeps the data discontinuous in memory. This is useful since the final array, especially for Gigacam data might be several GB large. Moving that around needlessly is not necessary unless you require it to be fully contiguous.

Parameters:
  • data (ArrayLike[N_z, N_cameras_y, N_cameras_x, y, x, [...]]) – The mcam_data you wish to slice into.

  • in_focus_indices ([N_cameras_y, N_cameras_x]) – The in-focus indices for each camera.

Returns:

in_focus_data – The mcam_data at the desired slice.

Return type:

ArrayLikee[N_cameras_y, N_cameras_x, y, x, […]]

owl.analysis.find_best_z_position_array(mcam_dataset, tqdm=<function _tqdm>, stop_event=None)[source]

Find array of most in focused images considering images individually

Given a list of z heights and a directory holding the z-stack images measure the focus of individual images and give an array of the most in focus individual images.

Measures the focus of the images and organizes data with measure_slice_array

Parameters:
  • mcam_dataset (xarray Dataset) – MCAM data.

  • tqdm – tqdm progress indicator.

  • stop_event – An event that can be set to stop the analysis early.

Returns:

  • measured_list_array (xarray.DataArray) – Array of focus scores.

  • best_images (xarray.DataArray) – DataArray of most in focused image for each sensor.