From 347e1a30574ae946f0a052ede1a25c257c341f8f Mon Sep 17 00:00:00 2001 From: Simon Birkholz Date: Tue, 19 Nov 2024 13:27:59 +0100 Subject: [PATCH 1/5] added slicing directly onto image data --- cuvis/Measurement.py | 63 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/cuvis/Measurement.py b/cuvis/Measurement.py index acc5680..7dfdbd8 100644 --- a/cuvis/Measurement.py +++ b/cuvis/Measurement.py @@ -2,6 +2,7 @@ from .FileWriteSettings import SaveArgs import datetime import os +import numpy as np from ._cuvis_il import cuvis_il from .cuvis_aux import SDKException, SessionData, Capabilities, MeasurementFlags, SensorInfo, GPSData @@ -239,3 +240,65 @@ def __init__(self, img_buf=None, dformat=None): else: raise TypeError( "Wrong data type for image buffer: {}".format(type(img_buf))) + + def __getitem__(self, key) -> np.ndarray | tuple[np.ndarray, np.ndarray] | object: + """ + Enables slicing and indexing of the image data. + Example: + pixel, wavelengths = image_data[100, 50] # Single pixel spectrum plus wavelengths + band_slice = image_data[:, :, 10:20] # Subset of Image and Bands results in a new ImageData object + single_channel = image_data[:,:,10] # Single Channel returns a normal numpy array + """ + if self.array is None: + raise ValueError("Image array is not initialized.") + sliced_array = self.array[key] + + if sliced_array.ndim == 1: + start_band, end_band = self._get_band_range(key) + return sliced_array, self.wavelength[start_band:end_band] + elif sliced_array.ndim == 2: + return sliced_array + elif sliced_array.ndim == 3 and sliced_array.shape[-1] > 1: + if self.wavelength is None: + raise ValueError("Wavelength data is not available.") + start_band, end_band = self._get_band_range(key) + sliced_wavelength = self.wavelength[start_band:end_band] + return ImageData.from_array( + sliced_array, + width=sliced_array.shape[1], + height=sliced_array.shape[0], + channels=sliced_array.shape[2], + wavelength=sliced_wavelength, + ) + + def _get_band_range(self, key): + """ + Helper method to determine the band range based on the slicing key. + """ + if isinstance(key, tuple) and len(key) == 3: + if isinstance(key[2], slice): + start = key[2].start or 0 + stop = key[2].stop or self.channels + return start, stop + elif isinstance(key[2], int): + return key[2], key[2] + 1 + return 0, self.channels + + def to_numpy(self) -> np.ndarray: + """ + Returns the spectral data as a NumPy array. + """ + return self.array + + @classmethod + def from_array(cls, array: np.ndarray, width: int, height: int, channels: int, wavelength=None): + """ + Creates an ImageData instance from a NumPy array and metadata. + """ + instance = cls() + instance.array = array + instance.width = width + instance.height = height + instance.channels = channels + instance.wavelength = wavelength + return instance From 7f7d1ef8259c3c25be6649662e3cd8722f4c98a0 Mon Sep 17 00:00:00 2001 From: Simon Birkholz Date: Tue, 19 Nov 2024 13:56:33 +0100 Subject: [PATCH 2/5] skip wrapping of view_tuple if only one result --- cuvis/Viewer.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cuvis/Viewer.py b/cuvis/Viewer.py index 0ea9f23..13160f6 100644 --- a/cuvis/Viewer.py +++ b/cuvis/Viewer.py @@ -25,7 +25,7 @@ def __init__(self, settings: Union[int, ViewerSettings]): type(settings))) pass - def _create_view_data(self, new_handle: int) -> Dict[str, ImageData]: + def _create_view_data(self, new_handle: int) -> dict[str, ImageData] | ImageData: _ptr = cuvis_il.new_p_int() if cuvis_il.status_ok != cuvis_il.cuvis_view_get_data_count( @@ -47,9 +47,11 @@ def _create_view_data(self, new_handle: int) -> Dict[str, ImageData]: dformat=view_data.data.format) else: raise SDKException("Unsupported viewer bit depth!") - # TODO when is a good point to release the view - # cuvis_il.cuvis_view_free(_ptr) - return view_array + if len(view_array.keys()) == 1: + # if only one value is available, do not wrap in dictionary + return list(view_array.values())[0] + else: + return view_array def apply(self, mesu: Measurement) -> Dict[str, ImageData]: _ptr = cuvis_il.new_p_int() From 88e6eed0160ff58c71fbdfc1277f01185f45a5b4 Mon Sep 17 00:00:00 2001 From: Simon Birkholz Date: Tue, 19 Nov 2024 14:05:47 +0100 Subject: [PATCH 3/5] changed typing to support python 3.9 --- cuvis/Async.py | 19 ++++++++++--------- cuvis/Measurement.py | 4 ++-- cuvis/Viewer.py | 6 +++--- cuvis/Worker.py | 2 +- cuvis/cuvis_aux.py | 6 +++--- 5 files changed, 19 insertions(+), 18 deletions(-) diff --git a/cuvis/Async.py b/cuvis/Async.py index 4c64570..dd2ed26 100644 --- a/cuvis/Async.py +++ b/cuvis/Async.py @@ -5,9 +5,10 @@ import asyncio as a -from typing import Tuple, Optional, Union +from typing import Optional, Union from datetime import timedelta + def _to_ms(value: Union[int, timedelta]) -> int: if isinstance(value, timedelta): return int(value / timedelta(milliseconds=1)) @@ -23,14 +24,15 @@ def __init__(self, handle): pass - def get(self, timeout_ms: Union[int, timedelta] ) -> Tuple[Optional[Measurement], AsyncResult]: + def get(self, timeout_ms: Union[int, timedelta]) -> tuple[Optional[Measurement], AsyncResult]: """ """ _ptr = cuvis_il.new_p_int() _pmesu = cuvis_il.new_p_int() cuvis_il.p_int_assign(_ptr, self._handle) - res = cuvis_il.cuvis_async_capture_get(_ptr, _to_ms(timeout_ms), _pmesu) + res = cuvis_il.cuvis_async_capture_get( + _ptr, _to_ms(timeout_ms), _pmesu) if res == cuvis_il.status_ok: return Measurement(cuvis_il.p_int_value(_pmesu)), AsyncResult.done @@ -42,14 +44,14 @@ def get(self, timeout_ms: Union[int, timedelta] ) -> Tuple[Optional[Measurement] return None, AsyncResult.timeout else: raise SDKException() - + # Python Magic Methods def __await__(self) -> Optional[Measurement]: async def _wait_for_return(): _status_ptr = cuvis_il.new_p_cuvis_status_t() while True: - if cuvis_il.status_ok != cuvis_il.cuvis_async_capture_status(self._handle,_status_ptr): + if cuvis_il.status_ok != cuvis_il.cuvis_async_capture_status(self._handle, _status_ptr): raise SDKException() status = cuvis_il.p_cuvis_status_t_value(_status_ptr) if status == cuvis_il.status_ok: @@ -57,7 +59,7 @@ async def _wait_for_return(): else: await a.sleep(10.0 / 1000) return _wait_for_return().__await__() - + def __del__(self): _ptr = cuvis_il.new_p_int() cuvis_il.p_int_assign(_ptr, self._handle) @@ -89,14 +91,13 @@ def get(self, timeout_ms: Union[int, timedelta]) -> AsyncResult: raise SDKException() pass - # Python Magic Methods def __await__(self) -> AsyncResult: async def _wait_for_return(): _status_ptr = cuvis_il.new_p_cuvis_status_t() while True: - if cuvis_il.status_ok != cuvis_il.cuvis_async_call_status(self._handle,_status_ptr): + if cuvis_il.status_ok != cuvis_il.cuvis_async_call_status(self._handle, _status_ptr): raise SDKException() status = cuvis_il.p_cuvis_status_t_value(_status_ptr) if status == cuvis_il.status_ok: @@ -104,7 +105,7 @@ async def _wait_for_return(): else: await a.sleep(10.0 / 1000) return _wait_for_return().__await__() - + def __del__(self): _ptr = cuvis_il.new_p_int() cuvis_il.p_int_assign(_ptr, self._handle) diff --git a/cuvis/Measurement.py b/cuvis/Measurement.py index 7dfdbd8..f47306f 100644 --- a/cuvis/Measurement.py +++ b/cuvis/Measurement.py @@ -1,4 +1,4 @@ -from typing import Union, List +from typing import Union from .FileWriteSettings import SaveArgs import datetime import os @@ -241,7 +241,7 @@ def __init__(self, img_buf=None, dformat=None): raise TypeError( "Wrong data type for image buffer: {}".format(type(img_buf))) - def __getitem__(self, key) -> np.ndarray | tuple[np.ndarray, np.ndarray] | object: + def __getitem__(self, key) -> Union[np.ndarray, tuple[np.ndarray, np.ndarray], object]: """ Enables slicing and indexing of the image data. Example: diff --git a/cuvis/Viewer.py b/cuvis/Viewer.py index 13160f6..66d1aa6 100644 --- a/cuvis/Viewer.py +++ b/cuvis/Viewer.py @@ -5,7 +5,7 @@ from .FileWriteSettings import ViewerSettings -from typing import Union, Dict +from typing import Union class Viewer(object): @@ -25,7 +25,7 @@ def __init__(self, settings: Union[int, ViewerSettings]): type(settings))) pass - def _create_view_data(self, new_handle: int) -> dict[str, ImageData] | ImageData: + def _create_view_data(self, new_handle: int) -> Union[dict[str, ImageData], ImageData]: _ptr = cuvis_il.new_p_int() if cuvis_il.status_ok != cuvis_il.cuvis_view_get_data_count( @@ -53,7 +53,7 @@ def _create_view_data(self, new_handle: int) -> dict[str, ImageData] | ImageData else: return view_array - def apply(self, mesu: Measurement) -> Dict[str, ImageData]: + def apply(self, mesu: Measurement) -> dict[str, ImageData]: _ptr = cuvis_il.new_p_int() if cuvis_il.status_ok != cuvis_il.cuvis_viewer_apply(self._handle, mesu._handle, _ptr): diff --git a/cuvis/Worker.py b/cuvis/Worker.py index b822797..34c2f87 100644 --- a/cuvis/Worker.py +++ b/cuvis/Worker.py @@ -13,7 +13,7 @@ from .doc import copydoc from dataclasses import dataclass -from typing import Callable, Awaitable, Tuple +from typing import Callable, Awaitable @dataclass diff --git a/cuvis/cuvis_aux.py b/cuvis/cuvis_aux.py index 5f918c6..c63c288 100644 --- a/cuvis/cuvis_aux.py +++ b/cuvis/cuvis_aux.py @@ -1,7 +1,7 @@ from dataclasses import dataclass import cuvis.cuvis_types as internal -from typing import List, Union +from typing import Union from ._cuvis_il import cuvis_il import logging import datetime @@ -150,7 +150,7 @@ def all(self): def __init__(self, value): self._value = value - def strings(self) -> List[str]: + def strings(self) -> list[str]: """"Returns a list containing the string values of the current members of the Bitset""" return _bit_translate(self._value, type(self)._translation_dict) @@ -182,7 +182,7 @@ def __contains__(self, member): raise ValueError(f'Cannot call operator with type {type(member)}') @classmethod - def from_strings(cls, *values: List[str]): + def from_strings(cls, *values: list[str]): """" Creates a Bitset from a list of strings """ return cls(sum([cls._translation_dict[v] for v in values])) From 13e5b7f78e97b05db0b857ee0cb103560a6efe82 Mon Sep 17 00:00:00 2001 From: Simon Birkholz Date: Tue, 19 Nov 2024 15:50:14 +0100 Subject: [PATCH 4/5] added cube getter that lazily tries to rebuild the cube if not saved in the measurement --- cuvis/Measurement.py | 146 ++++++++++++------------------------------- cuvis/SessionFile.py | 43 +++++++------ cuvis/__init__.py | 1 + cuvis/cube_utils.py | 111 ++++++++++++++++++++++++++++++++ 4 files changed, 174 insertions(+), 127 deletions(-) create mode 100644 cuvis/cube_utils.py diff --git a/cuvis/Measurement.py b/cuvis/Measurement.py index f47306f..e1baddd 100644 --- a/cuvis/Measurement.py +++ b/cuvis/Measurement.py @@ -7,6 +7,8 @@ from ._cuvis_il import cuvis_il from .cuvis_aux import SDKException, SessionData, Capabilities, MeasurementFlags, SensorInfo, GPSData from .cuvis_types import DataFormat, ProcessingMode, ReferenceType +from .cube_utils import ImageData + import cuvis.cuvis_types as internal base_datetime = datetime.datetime(1970, 1, 1) @@ -31,6 +33,7 @@ class Measurement(object): def __init__(self, base: Union[int, str]): self._handle = None + self._session = None if isinstance(base, int): self._handle = base @@ -130,6 +133,42 @@ def set_name(self, name: str) -> None: self._refresh_metadata() pass + @property + def cube(self) -> ImageData: + """ + Retrieves or processes the 'cube' data for this Measurement. + + This property prioritizes convenience over strict design principles: + - Attempts to retrieve the 'cube' from `self.data`. + - Lazily initializes a `ProcessingContext` if a session is available but uninitialized. + - May trigger expensive processing and modify internal state during property access. + + While functional, this approach introduces side effects and tight coupling, making it less + predictable and not the cleanest solution. Suitable for specific workflows where these + trade-offs are acceptable. + + Raises + ------ + ValueError + If the 'cube' is not available and processing is not possible. + + Returns + ------- + ImageData + The 'cube' data, either retrieved from `self.data` or generated through processing. + """ + if 'cube' in self.data: + return self.data.get('cube') + if self._session is not None: + # try fallback if session is known + if self._session._pc is None: + from .ProcessingContext import ProcessingContext + self._session._pc = ProcessingContext(self._session) + self._session._pc.apply(self) + return self.data.get('cube', None) + raise ValueError( + "This Measurement does not have a cube saved. Consider reprocessing with a Processing Context.") + @property def thumbnail(self): thumb = [val for key, val in self.data.items() if "view" in key] @@ -195,110 +234,3 @@ def __del__(self): cuvis_il.cuvis_measurement_free(_ptr) self._handle = cuvis_il.p_int_value(_ptr) pass - - -class ImageData(object): - def __init__(self, img_buf=None, dformat=None): - - if img_buf is None: - - self.width = None - self.height = None - self.channels = None - self.array = None - self.wavelength = None - - elif isinstance(img_buf, cuvis_il.cuvis_imbuffer_t): - - if dformat is None: - raise TypeError("Missing format for reading image buffer") - - if img_buf.format == 1: - self.array = cuvis_il.cuvis_read_imbuf_uint8(img_buf) - elif img_buf.format == 2: - self.array = cuvis_il.cuvis_read_imbuf_uint16(img_buf) - elif img_buf.format == 3: - self.array = cuvis_il.cuvis_read_imbuf_uint32(img_buf) - elif img_buf.format == 4: - self.array = cuvis_il.cuvis_read_imbuf_float32(img_buf) - else: - raise SDKException() - - self.width = img_buf.width - self.height = img_buf.height - self.channels = img_buf.channels - - if img_buf.wavelength is not None: - self.wavelength = [ - cuvis_il.p_unsigned_int_getitem( - img_buf.wavelength, z) for z - in - range(self.channels)] - - # print("got image of size {}.".format(self.array.shape)) - - else: - raise TypeError( - "Wrong data type for image buffer: {}".format(type(img_buf))) - - def __getitem__(self, key) -> Union[np.ndarray, tuple[np.ndarray, np.ndarray], object]: - """ - Enables slicing and indexing of the image data. - Example: - pixel, wavelengths = image_data[100, 50] # Single pixel spectrum plus wavelengths - band_slice = image_data[:, :, 10:20] # Subset of Image and Bands results in a new ImageData object - single_channel = image_data[:,:,10] # Single Channel returns a normal numpy array - """ - if self.array is None: - raise ValueError("Image array is not initialized.") - sliced_array = self.array[key] - - if sliced_array.ndim == 1: - start_band, end_band = self._get_band_range(key) - return sliced_array, self.wavelength[start_band:end_band] - elif sliced_array.ndim == 2: - return sliced_array - elif sliced_array.ndim == 3 and sliced_array.shape[-1] > 1: - if self.wavelength is None: - raise ValueError("Wavelength data is not available.") - start_band, end_band = self._get_band_range(key) - sliced_wavelength = self.wavelength[start_band:end_band] - return ImageData.from_array( - sliced_array, - width=sliced_array.shape[1], - height=sliced_array.shape[0], - channels=sliced_array.shape[2], - wavelength=sliced_wavelength, - ) - - def _get_band_range(self, key): - """ - Helper method to determine the band range based on the slicing key. - """ - if isinstance(key, tuple) and len(key) == 3: - if isinstance(key[2], slice): - start = key[2].start or 0 - stop = key[2].stop or self.channels - return start, stop - elif isinstance(key[2], int): - return key[2], key[2] + 1 - return 0, self.channels - - def to_numpy(self) -> np.ndarray: - """ - Returns the spectral data as a NumPy array. - """ - return self.array - - @classmethod - def from_array(cls, array: np.ndarray, width: int, height: int, channels: int, wavelength=None): - """ - Creates an ImageData instance from a NumPy array and metadata. - """ - instance = cls() - instance.array = array - instance.width = width - instance.height = height - instance.channels = channels - instance.wavelength = wavelength - return instance diff --git a/cuvis/SessionFile.py b/cuvis/SessionFile.py index 07eea1a..d6f7e8f 100644 --- a/cuvis/SessionFile.py +++ b/cuvis/SessionFile.py @@ -10,9 +10,11 @@ from typing import Union, Optional + class SessionFile(object): - def __init__(self, base: Union[Path,str]): + def __init__(self, base: Union[Path, str]): self._handle = None + self._pc = None if isinstance(Path(base), Path) and os.path.exists(base): _ptr = cuvis_il.new_p_int() if cuvis_il.status_ok != cuvis_il.cuvis_session_file_load(base, @@ -23,40 +25,41 @@ def __init__(self, base: Union[Path,str]): raise SDKException( "Could not open SessionFile File! File not found!") - pass - - def get_measurement(self, frameNo: int, itemtype: SessionItemType = SessionItemType.no_gaps) -> Optional[Measurement]: + def get_measurement(self, frameNo: int = 0, itemtype: SessionItemType = SessionItemType.no_gaps) -> Optional[Measurement]: _ptr = cuvis_il.new_p_int() - ret = cuvis_il.cuvis_session_file_get_mesu(self._handle, frameNo, internal.__CuvisSessionItemType__[itemtype], - _ptr) + ret = cuvis_il.cuvis_session_file_get_mesu(self._handle, frameNo, internal.__CuvisSessionItemType__[itemtype], + _ptr) if cuvis_il.status_no_measurement == ret: return None if cuvis_il.status_ok != ret: raise SDKException() - return Measurement(cuvis_il.p_int_value(_ptr)) - - def get_reference(self, frameNo: int, reftype: ReferenceType) -> Optional[Measurement]: + mesu = Measurement(cuvis_il.p_int_value(_ptr)) + mesu._session = self + return mesu + + def get_reference(self, frameNo: int, reftype: ReferenceType) -> Optional[Measurement]: _ptr = cuvis_il.new_p_int() ret = cuvis_il.cuvis_session_file_get_reference_mesu( - self._handle, frameNo, internal.__CuvisReferenceType__[reftype], - _ptr) + self._handle, frameNo, internal.__CuvisReferenceType__[reftype], + _ptr) if cuvis_il.status_no_measurement == ret: return None if cuvis_il.status_ok != ret: raise SDKException() return Measurement(cuvis_il.p_int_value(_ptr)) - - def get_thumbnail(self) -> ImageData: + + @property + def thumbnail(self) -> ImageData: thumbnail_data = cuvis_il.cuvis_view_data_t() if cuvis_il.status_ok != cuvis_il.cuvis_session_file_get_thumbnail(self, thumbnail_data): raise SDKException() - + if thumbnail_data.data.format == CUVIS_imbuffer_format["imbuffer_format_uint8"]: return ImageData(img_buf=thumbnail_data.data, - dformat=thumbnail_data.data.format) + dformat=thumbnail_data.data.format) else: raise SDKException("Unsupported viewer bit depth!") - + def get_size(self, itemtype: SessionItemType = SessionItemType.no_gaps) -> int: val = cuvis_il.new_p_int() if cuvis_il.status_ok != cuvis_il.cuvis_session_file_get_size( @@ -79,20 +82,20 @@ def operation_mode(self) -> OperationMode: self._handle, val): raise SDKException() return internal.__OperationMode__[cuvis_il.p_cuvis_operation_mode_t_value(val)] - + @property def hash(self) -> str: return cuvis_il.cuvis_session_file_get_hash_swig(self._handle) - + # Python Magic Methods def __iter__(self): for i in range(len(self)): yield self[i] pass - + def __len__(self): return self.get_size() - + def __getitem__(self, key: int) -> Measurement: return self.get_measurement(key) diff --git a/cuvis/__init__.py b/cuvis/__init__.py index bde3037..e20a2cb 100644 --- a/cuvis/__init__.py +++ b/cuvis/__init__.py @@ -14,6 +14,7 @@ from .Export import CubeExporter, EnviExporter, TiffExporter, ViewExporter from .Calibration import Calibration from .AcquisitionContext import AcquisitionContext +from .cube_utils import ImageData import os import platform import sys diff --git a/cuvis/cube_utils.py b/cuvis/cube_utils.py new file mode 100644 index 0000000..d0da2e4 --- /dev/null +++ b/cuvis/cube_utils.py @@ -0,0 +1,111 @@ +from typing import Union +from ._cuvis_il import cuvis_il +import numpy as np +from .cuvis_aux import SDKException + + +class ImageData(object): + def __init__(self, img_buf=None, dformat=None): + + if img_buf is None: + + self.width = None + self.height = None + self.channels = None + self.array = None + self.wavelength = None + + elif isinstance(img_buf, cuvis_il.cuvis_imbuffer_t): + + if dformat is None: + raise TypeError("Missing format for reading image buffer") + + if img_buf.format == 1: + self.array = cuvis_il.cuvis_read_imbuf_uint8(img_buf) + elif img_buf.format == 2: + self.array = cuvis_il.cuvis_read_imbuf_uint16(img_buf) + elif img_buf.format == 3: + self.array = cuvis_il.cuvis_read_imbuf_uint32(img_buf) + elif img_buf.format == 4: + self.array = cuvis_il.cuvis_read_imbuf_float32(img_buf) + else: + raise SDKException() + + self.width = img_buf.width + self.height = img_buf.height + self.channels = img_buf.channels + + if img_buf.wavelength is not None: + self.wavelength = [ + cuvis_il.p_unsigned_int_getitem( + img_buf.wavelength, z) for z + in + range(self.channels)] + + # print("got image of size {}.".format(self.array.shape)) + + else: + raise TypeError( + "Wrong data type for image buffer: {}".format(type(img_buf))) + + def __getitem__(self, key) -> Union[np.ndarray, tuple[np.ndarray, np.ndarray], object]: + """ + Enables slicing and indexing of the image data. + Example: + pixel, wavelengths = image_data[100, 50] # Single pixel spectrum plus wavelengths + band_slice = image_data[:, :, 10:20] # Subset of Image and Bands results in a new ImageData object + single_channel = image_data[:,:,10] # Single Channel returns a normal numpy array + """ + if self.array is None: + raise ValueError("Image array is not initialized.") + sliced_array = self.array[key] + + if sliced_array.ndim == 1: + start_band, end_band = self._get_band_range(key) + return sliced_array, self.wavelength[start_band:end_band] + elif sliced_array.ndim == 2: + return sliced_array + elif sliced_array.ndim == 3 and sliced_array.shape[-1] > 1: + if self.wavelength is None: + raise ValueError("Wavelength data is not available.") + start_band, end_band = self._get_band_range(key) + sliced_wavelength = self.wavelength[start_band:end_band] + return ImageData.from_array( + sliced_array, + width=sliced_array.shape[1], + height=sliced_array.shape[0], + channels=sliced_array.shape[2], + wavelength=sliced_wavelength, + ) + + def _get_band_range(self, key): + """ + Helper method to determine the band range based on the slicing key. + """ + if isinstance(key, tuple) and len(key) == 3: + if isinstance(key[2], slice): + start = key[2].start or 0 + stop = key[2].stop or self.channels + return start, stop + elif isinstance(key[2], int): + return key[2], key[2] + 1 + return 0, self.channels + + def to_numpy(self) -> np.ndarray: + """ + Returns the spectral data as a NumPy array. + """ + return self.array + + @classmethod + def from_array(cls, array: np.ndarray, width: int, height: int, channels: int, wavelength=None): + """ + Creates an ImageData instance from a NumPy array and metadata. + """ + instance = cls() + instance.array = array + instance.width = width + instance.height = height + instance.channels = channels + instance.wavelength = wavelength + return instance From 85012c967b3f3815e70b359e1bc200379a1ae4c0 Mon Sep 17 00:00:00 2001 From: Simon Birkholz Date: Tue, 19 Nov 2024 16:26:24 +0100 Subject: [PATCH 5/5] updated version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e12589f..21a324e 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ here = os.path.abspath(os.path.dirname(__file__)) NAME = 'cuvis' -VERSION = '3.3.0.post1' +VERSION = '3.3.1rc1' DESCRIPTION = 'CUVIS Python SDK.'