From f182f7b7afc11d8e19d8811f9cb838e8642fa7b1 Mon Sep 17 00:00:00 2001 From: maffettone Date: Fri, 5 Apr 2024 10:29:31 -0700 Subject: [PATCH 1/6] feat: Dask get_fccd_images --- .gitignore | 1 + csxtools/fastccd/dask.py | 53 ++++++++++++++++++++++++++++ csxtools/fastccd/images.py | 20 ++++++++--- csxtools/image/dask.py | 36 +++++++++++++++++++ csxtools/image/transform.py | 17 ++++++--- csxtools/utils.py | 69 +++++++++++++++++-------------------- 6 files changed, 149 insertions(+), 47 deletions(-) create mode 100644 csxtools/fastccd/dask.py create mode 100644 csxtools/image/dask.py diff --git a/.gitignore b/.gitignore index b023f45..71f72d4 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ var/ *.egg *.eggs doc/_build +venv/ # PyInstaller # Usually these files are written by a python script from a template diff --git a/csxtools/fastccd/dask.py b/csxtools/fastccd/dask.py new file mode 100644 index 0000000..c4e9281 --- /dev/null +++ b/csxtools/fastccd/dask.py @@ -0,0 +1,53 @@ +from typing import Tuple + +import numpy as np +from dask.array import Array as DaskArray +from numpy.typing import ArrayLike + +GAIN_8 = 0x0000 +GAIN_2 = 0x8000 +GAIN_1 = 0xC000 +BAD_PIXEL = 0x2000 +PIXEL_MASK = 0x1FFF + + +def correct_images(images: DaskArray, dark: ArrayLike, flat: ArrayLike, gain: Tuple[float, float, float]): + """_summary_ + + Parameters + ---------- + images : DaskArray + Input array of images to correct of shape (N, y, x) where N is the + number of images and x and y are the image size. + dark : ArrayLike + Input array of dark images. This should be of shape (3, y, x). + dark[0] is the gain 8 (most sensitive setting) dark image with + dark[2] being the gain 1 (least sensitive) dark image. + flat : ArrayLike + Input array for the flatfield correction. This should be of shape + (y, x) + gain : Tuple[float, float, float] + These are the gain multiplication factors for the three different + gain settings + + // Note GAIN_1 is the least sensitive setting which means we need to multiply the + // measured values by 8. Conversly GAIN_8 is the most sensitive and therefore only + // does not need a multiplier + """ + + # Shape checking: + if dark.ndim != 3: + raise ValueError(f"Expected 3D array, got {dark.ndim}D array for darks") + if dark.shape[0] != 3: + raise ValueError(f"Expected 3 dark images, got {dark.shape[0]}") + if dark.shape[-2:] != images.shape[-2]: + raise ValueError(f"Dark images shape {dark.shape[-2:]} does not match images shape {images.shape[-2]}") + if flat.shape != images.shape[-2:]: + raise ValueError(f"Flatfield shape {flat.shape} does not match images shape {images.shape[-2]}") + + corrected = np.where(images & BAD_PIXEL, np.NaN, images) + corrected = np.where(images & GAIN_1, flat * gain[-1] * (corrected - dark[-1, ...]), corrected) + corrected = np.where(images & GAIN_2, flat * gain[-2] * (corrected - dark[-2, ...]), corrected) + corrected = np.where(images & GAIN_8, flat * gain[-3] * (corrected - dark[-3, ...]), corrected) + + return corrected diff --git a/csxtools/fastccd/images.py b/csxtools/fastccd/images.py index 59296b6..6f8a908 100644 --- a/csxtools/fastccd/images.py +++ b/csxtools/fastccd/images.py @@ -1,12 +1,15 @@ +import logging +import time as ttime + import numpy as np + from ..ext import fastccd -import time as ttime +from .dask import correct_images as dask_correct_images -import logging logger = logging.getLogger(__name__) -def correct_images(images, dark=None, flat=None, gain=(1, 4, 8)): +def correct_images(images, dark=None, flat=None, gain=(1, 4, 8), *, dask=False): """Subtract backgrond and gain correct images This routine subtrtacts the backgrond and corrects the images @@ -27,6 +30,11 @@ def correct_images(images, dark=None, flat=None, gain=(1, 4, 8)): gain : tuple, optional These are the gain multiplication factors for the three different gain settings + dask : bool, optional + Do computation in dask instead of in C extension over full array. + This returns a DaskArray or DaskArrayClient with pending execution instead of a numpy array. + You can use the .compute() method to get the numpy array. + Default is False. Returns ------- @@ -49,8 +57,10 @@ def correct_images(images, dark=None, flat=None, gain=(1, 4, 8)): else: flat = np.asarray(flat, dtype=np.float32) - data = fastccd.correct_images(images.astype(np.uint16), - dark, flat, gain) + if dask: + data = dask_correct_images(images.astype(np.uint16), dark, flat, gain) + else: + data = fastccd.correct_images(images.astype(np.uint16), dark, flat, gain) t = ttime.time() - t logger.info("Corrected image stack in %.3f seconds", t) diff --git a/csxtools/image/dask.py b/csxtools/image/dask.py new file mode 100644 index 0000000..ba69289 --- /dev/null +++ b/csxtools/image/dask.py @@ -0,0 +1,36 @@ +from typing import Literal, Union + +import dask.array as da +from dask.array import Array as DaskArray + + +def rotate90(images: DaskArray, sense: Union[Literal["cw"], Literal["ccw"]] = "cw") -> DaskArray: + """ + Rotate images by 90 degrees using Dask. + This whole function is a moot wrapper around `da.rot90` from Dask, but written + explicitly to match the old C code. + + Parameters + ---------- + images : da.Array + Input Dask array of images to rotate of shape (N, y, x), + where N is the number of images and y, x are the image dimensions. + sense : str, optional + 'cw' to rotate clockwise, 'ccw' to rotate anticlockwise. + Default is 'cw'. + + Returns + ------- + da.Array + The rotated images as a Dask array. + """ + # Rotate images. The axes (1, 2) specify the plane of rotation (y-x plane for each image). + # k controls the direction and repetitions of the rotation. + if sense == "ccw": + k = 1 + elif sense == "cw": + k = -1 + else: + raise ValueError("sense must be 'cw' or 'ccw'") + rotated_images = da.rot90(images, k=k, axes=(-2, -1)) + return rotated_images diff --git a/csxtools/image/transform.py b/csxtools/image/transform.py index 52ae75c..1828510 100644 --- a/csxtools/image/transform.py +++ b/csxtools/image/transform.py @@ -1,7 +1,8 @@ from ..ext import image as extimage +from .dask import rotate90 as dask_rotate90 -def rotate90(a, sense='ccw'): +def rotate90(a, sense="ccw", *, dask=True): """Rotate a stack of images by 90 degrees This routine rotates a stack of images by 90. The rotation is performed @@ -14,6 +15,11 @@ def rotate90(a, sense='ccw'): Input array to be rotated. This should be of shape (N, y, x). sense : string 'cw' to rotate clockwise, 'ccw' to rotate anitclockwise + dask : bool, optional + Do computation in dask instead of in C extension over full array. + This returns a DaskArray or DaskArrayClient with pending execution instead of a numpy array. + You can use the .compute() method to get the numpy array. + Default is False. Returns ------- @@ -22,11 +28,14 @@ def rotate90(a, sense='ccw'): """ - if sense == 'ccw': + if sense == "ccw": sense = 1 - elif sense == 'cw': + elif sense == "cw": sense = 0 else: raise ValueError("sense must be 'cw' or 'ccw'") - return extimage.rotate90(a, sense) + if dask: + return dask_rotate90(a, sense) + else: + return extimage.rotate90(a, sense) diff --git a/csxtools/utils.py b/csxtools/utils.py index 2749568..a827cc9 100644 --- a/csxtools/utils.py +++ b/csxtools/utils.py @@ -1,17 +1,17 @@ -import numpy as np +import logging import time as ttime +import numpy as np +from databroker.assets.handlers import AreaDetectorHDF5TimestampHandler + from .fastccd import correct_images from .image import rotate90, stackmean from .settings import detectors -from databroker.assets.handlers import AreaDetectorHDF5TimestampHandler -import logging logger = logging.getLogger(__name__) -def get_fastccd_images(light_header, dark_headers=None, - flat=None, gain=(1, 4, 8), tag=None, roi=None): +def get_fastccd_images(light_header, dark_headers=None, flat=None, gain=(1, 4, 8), tag=None, roi=None, *, dask=False): """Retreive and correct FastCCD Images from associated headers Retrieve FastCCD Images from databroker and correct for: @@ -50,6 +50,9 @@ def get_fastccd_images(light_header, dark_headers=None, coordinates of the upper-left corner and width and height of the ROI: e.g., (x, y, w, h) + dask : bool, optional + Use dask for computation. Default is False. + Returns ------- dask.array : corrected images @@ -57,7 +60,7 @@ def get_fastccd_images(light_header, dark_headers=None, """ if tag is None: - tag = detectors['fccd'] + tag = detectors["fccd"] # Now lets sort out the ROI if roi is not None: @@ -72,8 +75,7 @@ def get_fastccd_images(light_header, dark_headers=None, logger.warning("Processing without dark images") else: if dark_headers[0] is None: - raise NotImplementedError("Use of header metadata to find dark" - " images is not implemented yet.") + raise NotImplementedError("Use of header metadata to find dark" " images is not implemented yet.") # Read the images for the dark headers t = ttime.time() @@ -91,25 +93,20 @@ def get_fastccd_images(light_header, dark_headers=None, tt = ttime.time() b = bgnd_events.astype(dtype=np.uint16) - logger.info("Image conversion took %.3f seconds", - ttime.time() - tt) + logger.info("Image conversion took %.3f seconds", ttime.time() - tt) b = correct_images(b, gain=(1, 1, 1)) tt = ttime.time() b = stackmean(b) - logger.info("Mean of image stack took %.3f seconds", - ttime.time() - tt) + logger.info("Mean of image stack took %.3f seconds", ttime.time() - tt) else: - if (i == 0): - logger.warning("Missing dark image" - " for gain setting 8") - elif (i == 1): - logger.warning("Missing dark image" - " for gain setting 2") - elif (i == 2): - logger.warning("Missing dark image" - " for gain setting 1") + if i == 0: + logger.warning("Missing dark image" " for gain setting 8") + elif i == 1: + logger.warning("Missing dark image" " for gain setting 2") + elif i == 2: + logger.warning("Missing dark image" " for gain setting 1") dark.append(b) @@ -125,7 +122,7 @@ def get_fastccd_images(light_header, dark_headers=None, if flat is not None and roi is not None: flat = _crop(flat, roi) - return _correct_fccd_images(events, bgnd, flat, gain) + return _correct_fccd_images(events, bgnd, flat, gain, dask=dask) def get_images_to_4D(images, dtype=None): @@ -147,8 +144,7 @@ def get_images_to_4D(images, dtype=None): >>> a = get_images_to_4D(images, dtype=np.float32) """ - im = np.array([np.asarray(im, dtype=dtype) for im in images], - dtype=dtype) + im = np.array([np.asarray(im, dtype=dtype) for im in images], dtype=dtype) return im @@ -183,9 +179,9 @@ def _get_images(header, tag, roi=None): return images -def _correct_fccd_images(image, bgnd, flat, gain): - image = correct_images(image, bgnd, flat, gain) - image = rotate90(image, 'cw') +def _correct_fccd_images(image, bgnd, flat, gain, *, dask=False): + image = correct_images(image, bgnd, flat, gain, dask=dask) + image = rotate90(image, "cw", dask=dask) return image @@ -196,11 +192,11 @@ def _crop_images(image, roi): def _crop(image, roi): image_shape = image.shape # Assuming ROI is specified in the "rotated" (correct) orientation - roi = [image_shape[-2]-roi[3], roi[0], image_shape[-1]-roi[1], roi[2]] - return image.T[roi[1]:roi[3], roi[0]:roi[2]].T + roi = [image_shape[-2] - roi[3], roi[0], image_shape[-1] - roi[1], roi[2]] + return image.T[roi[1] : roi[3], roi[0] : roi[2]].T -def get_fastccd_timestamps(header, tag='fccd_image'): +def get_fastccd_timestamps(header, tag="fccd_image"): """Return the FastCCD timestamps from the Areadetector Data File Return a list of numpy arrays of the timestamps for the images as @@ -218,8 +214,7 @@ def get_fastccd_timestamps(header, tag='fccd_image'): list of arrays of the timestamps """ - with header.db.reg.handler_context( - {'AD_HDF5': AreaDetectorHDF5TimestampHandler}): + with header.db.reg.handler_context({"AD_HDF5": AreaDetectorHDF5TimestampHandler}): timestamps = list(header.data(tag)) return timestamps @@ -259,9 +254,8 @@ def calculate_flatfield(image, limits=(0.6, 1.4)): return flat - def get_fastccd_flatfield(light, dark, flat=None, limits=(0.6, 1.4), half_interval=False): - """Calculate a flatfield from two headers + """Calculate a flatfield from two headers This routine calculates the flatfield using the :func:calculate_flatfield() function after obtaining the images from @@ -278,7 +272,7 @@ def get_fastccd_flatfield(light, dark, flat=None, limits=(0.6, 1.4), half_interv limits : tuple limits used for returning corrected pixel flatfield The tuple setting lower and upper bound. np.nan returned value is outside bounds half_interval : boolean or tuple to perform calculation for only half of the FastCCD - Default is False. If True, then the hard-code portion is retained. Customize image + Default is False. If True, then the hard-code portion is retained. Customize image manipulation using a tuple of length 2 for (row_start, row_stop). @@ -291,7 +285,7 @@ def get_fastccd_flatfield(light, dark, flat=None, limits=(0.6, 1.4), half_interv images = stackmean(images) if half_interval: if isinstance(half_interval, bool): - row_start, row_stop = (7, 486) #hard coded for the broken half of the fccd + row_start, row_stop = (7, 486) # hard coded for the broken half of the fccd else: row_start, row_stop = half_interval print(row_start, row_stop) @@ -299,8 +293,7 @@ def get_fastccd_flatfield(light, dark, flat=None, limits=(0.6, 1.4), half_interv flat = calculate_flatfield(images, limits) removed = np.sum(np.isnan(flat)) if removed != 0: - logger.warning("Flatfield correction removed %d pixels (%.2f %%)" % - (removed, removed * 100 / flat.size)) + logger.warning("Flatfield correction removed %d pixels (%.2f %%)" % (removed, removed * 100 / flat.size)) return flat From ee1df1632114ccb20170548b279de7e06f1b827d Mon Sep 17 00:00:00 2001 From: maffettone Date: Fri, 5 Apr 2024 10:37:55 -0700 Subject: [PATCH 2/6] fix: default false, use bool val for clockwise --- csxtools/image/dask.py | 15 +++++---------- csxtools/image/transform.py | 2 +- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/csxtools/image/dask.py b/csxtools/image/dask.py index ba69289..9fd0514 100644 --- a/csxtools/image/dask.py +++ b/csxtools/image/dask.py @@ -1,10 +1,8 @@ -from typing import Literal, Union - import dask.array as da from dask.array import Array as DaskArray -def rotate90(images: DaskArray, sense: Union[Literal["cw"], Literal["ccw"]] = "cw") -> DaskArray: +def rotate90(images: DaskArray, sense: bool) -> DaskArray: """ Rotate images by 90 degrees using Dask. This whole function is a moot wrapper around `da.rot90` from Dask, but written @@ -15,9 +13,8 @@ def rotate90(images: DaskArray, sense: Union[Literal["cw"], Literal["ccw"]] = "c images : da.Array Input Dask array of images to rotate of shape (N, y, x), where N is the number of images and y, x are the image dimensions. - sense : str, optional - 'cw' to rotate clockwise, 'ccw' to rotate anticlockwise. - Default is 'cw'. + sense : bool + False to rotate clockwise, True to rotate anticlockwise. Returns ------- @@ -26,11 +23,9 @@ def rotate90(images: DaskArray, sense: Union[Literal["cw"], Literal["ccw"]] = "c """ # Rotate images. The axes (1, 2) specify the plane of rotation (y-x plane for each image). # k controls the direction and repetitions of the rotation. - if sense == "ccw": + if sense: k = 1 - elif sense == "cw": + elif sense: k = -1 - else: - raise ValueError("sense must be 'cw' or 'ccw'") rotated_images = da.rot90(images, k=k, axes=(-2, -1)) return rotated_images diff --git a/csxtools/image/transform.py b/csxtools/image/transform.py index 1828510..754de0c 100644 --- a/csxtools/image/transform.py +++ b/csxtools/image/transform.py @@ -2,7 +2,7 @@ from .dask import rotate90 as dask_rotate90 -def rotate90(a, sense="ccw", *, dask=True): +def rotate90(a, sense="ccw", *, dask=False): """Rotate a stack of images by 90 degrees This routine rotates a stack of images by 90. The rotation is performed From 2795117d08d55f85d3868652fe69bd505dcc76dd Mon Sep 17 00:00:00 2001 From: maffettone Date: Fri, 5 Apr 2024 10:57:19 -0700 Subject: [PATCH 3/6] maint: add pyproject toml for black --- pyproject.toml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b64c25a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,25 @@ +[tool.black] +line-length = 115 +include = '\.pyi?$' +exclude = ''' +( + /( + \.eggs # exclude a few common directories in the + | \.git # root of the project + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | \.pytest_cache + | _build + | build + | buck-out + | build + | dist + | docs + | blib2to3 + | tests/data + )/ + | versioneer.py +) +''' From d9356018c5f0c1fe0d96e04f02ef41edec541d9c Mon Sep 17 00:00:00 2001 From: maffettone Date: Fri, 5 Apr 2024 10:57:57 -0700 Subject: [PATCH 4/6] fix: remove ragged array in tests --- tests/test_image.py | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/tests/test_image.py b/tests/test_image.py index bc4944d..dac5180 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -1,16 +1,16 @@ -from csxtools.image import (rotate90, stackmean, stacksum, stackstd, - stackvar, stackstderr, images_mean, images_sum) import numpy as np -from numpy.testing import assert_array_equal, assert_array_almost_equal +from numpy.testing import assert_array_almost_equal, assert_array_equal + +from csxtools.image import images_mean, images_sum, rotate90, stackmean, stackstd, stackstderr, stacksum, stackvar def test_rotate90(): - x = np.arange(4*20, dtype=np.float32).reshape(4, 20) - y = rotate90(np.array([x, x, x, x]), 'ccw') + x = np.arange(4 * 20, dtype=np.float32).reshape(4, 20) + y = rotate90(np.array([x, x, x, x]), "ccw") for i in y: assert_array_equal(i, np.rot90(x, 1)) - y = rotate90(np.array([x, x, x, x]), 'cw') + y = rotate90(np.array([x, x, x, x]), "cw") for i in y: assert_array_equal(i, np.rot90(x, -1)) @@ -52,45 +52,42 @@ def test_stacksum(): x[23] = np.nan x[40] = np.nan m, n = stacksum(x) - assert_array_almost_equal(m, np.ones((100, 100), dtype=np.float32) * 2000, - decimal=3) + assert_array_almost_equal(m, np.ones((100, 100), dtype=np.float32) * 2000, decimal=3) assert_array_equal(n, np.ones((100, 100), dtype=np.float32) * (1000 - 3)) def test_stackstd(): - x = np.repeat(np.arange(1000, dtype=np.float32), 400).reshape( - (1000, 20, 20)) + x = np.repeat(np.arange(1000, dtype=np.float32), 400).reshape((1000, 20, 20)) m, n = stackstd(x) assert_array_almost_equal(m, np.std(x, axis=0), 2) assert_array_equal(n, np.ones((20, 20), dtype=np.float32) * 1000.0) def test_stackvar(): - x = np.repeat(np.arange(1000, dtype=np.float32), 400).reshape( - (1000, 20, 20)) + x = np.repeat(np.arange(1000, dtype=np.float32), 400).reshape((1000, 20, 20)) m, n = stackvar(x) assert_array_almost_equal(m, np.var(x, axis=0), 0) assert_array_equal(n, np.ones((20, 20), dtype=np.float32) * 1000.0) def test_stackstderr(): - x = np.repeat(np.arange(1000, dtype=np.float32), 400).reshape( - (1000, 20, 20)) + x = np.repeat(np.arange(1000, dtype=np.float32), 400).reshape((1000, 20, 20)) m, n = stackstderr(x) assert_array_almost_equal(m, np.std(x, axis=0) / np.sqrt(n), 3) assert_array_equal(n, np.ones((20, 20), dtype=np.float32) * 1000.0) def test_images_mean(): - x = np.array([np.repeat(ii*np.ones(ii*100, dtype=np.float32), 400).reshape( - (ii*100, 20, 20)) for ii in range(1, 11)]) + x = [ + np.repeat(ii * np.ones(ii * 100, dtype=np.float32), 400).reshape((ii * 100, 20, 20)) for ii in range(1, 11) + ] m = images_mean(x) assert_array_equal(m, np.array([np.mean(x1) for x1 in x]), 3) def test_images_sum(): - x = np.array([np.repeat(ii*np.ones(ii*100, dtype=np.float32), 400).reshape( - (ii*100, 20, 20)) for ii in range(1, 11)]) + x = [ + np.repeat(ii * np.ones(ii * 100, dtype=np.float32), 400).reshape((ii * 100, 20, 20)) for ii in range(1, 11) + ] m = images_sum(x) - assert_array_equal(m, np.array([np.sum(np.mean(x1, axis=0)) - for x1 in x]), 3) + assert_array_equal(m, np.array([np.sum(np.mean(x1, axis=0)) for x1 in x]), 3) From 98772cb6d4ebcbe06fc8169b08d66c9634e5edc0 Mon Sep 17 00:00:00 2001 From: maffettone Date: Fri, 5 Apr 2024 11:23:57 -0700 Subject: [PATCH 5/6] fix: drop pyproject, breaks build system --- pyproject.toml | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index b64c25a..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,25 +0,0 @@ -[tool.black] -line-length = 115 -include = '\.pyi?$' -exclude = ''' -( - /( - \.eggs # exclude a few common directories in the - | \.git # root of the project - | \.hg - | \.mypy_cache - | \.tox - | \.venv - | \.pytest_cache - | _build - | build - | buck-out - | build - | dist - | docs - | blib2to3 - | tests/data - )/ - | versioneer.py -) -''' From ec916d18042612c4294fa647bbfe8044a2d5d08b Mon Sep 17 00:00:00 2001 From: "Dr. Phil Maffettone" <43007690+maffettone@users.noreply.github.com> Date: Wed, 26 Jun 2024 13:17:36 -0700 Subject: [PATCH 6/6] Apply suggestions from code review. Fix logic and clean docs Co-authored-by: Padraic Shafer <76011594+padraic-shafer@users.noreply.github.com> --- csxtools/fastccd/dask.py | 10 +++++----- csxtools/image/dask.py | 8 ++++++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/csxtools/fastccd/dask.py b/csxtools/fastccd/dask.py index c4e9281..0aef0fc 100644 --- a/csxtools/fastccd/dask.py +++ b/csxtools/fastccd/dask.py @@ -12,17 +12,17 @@ def correct_images(images: DaskArray, dark: ArrayLike, flat: ArrayLike, gain: Tuple[float, float, float]): - """_summary_ + """Apply intensity corrections to a stack of images. Parameters ---------- images : DaskArray - Input array of images to correct of shape (N, y, x) where N is the + Input array of images to correct; has shape (N, y, x) where N is the number of images and x and y are the image size. dark : ArrayLike Input array of dark images. This should be of shape (3, y, x). - dark[0] is the gain 8 (most sensitive setting) dark image with - dark[2] being the gain 1 (least sensitive) dark image. + dark[0] is the GAIN_8 (most sensitive setting) dark image with + dark[2] being the GAIN_1 (least sensitive) dark image. flat : ArrayLike Input array for the flatfield correction. This should be of shape (y, x) @@ -31,7 +31,7 @@ def correct_images(images: DaskArray, dark: ArrayLike, flat: ArrayLike, gain: Tu gain settings // Note GAIN_1 is the least sensitive setting which means we need to multiply the - // measured values by 8. Conversly GAIN_8 is the most sensitive and therefore only + // measured values by 8. Conversly GAIN_8 is the most sensitive and therefore // does not need a multiplier """ diff --git a/csxtools/image/dask.py b/csxtools/image/dask.py index 9fd0514..7a91ba8 100644 --- a/csxtools/image/dask.py +++ b/csxtools/image/dask.py @@ -23,9 +23,13 @@ def rotate90(images: DaskArray, sense: bool) -> DaskArray: """ # Rotate images. The axes (1, 2) specify the plane of rotation (y-x plane for each image). # k controls the direction and repetitions of the rotation. - if sense: + if sense != 0: + # Counter-clockwise k = 1 - elif sense: + elif sense == 0: + # Clockwise k = -1 + else: + raise ValueError(f"{sense = } is not a valid integer") rotated_images = da.rot90(images, k=k, axes=(-2, -1)) return rotated_images