diff --git a/cuvis/FileWriteSettings.py b/cuvis/FileWriteSettings.py index de72e9d..aa11f8d 100644 --- a/cuvis/FileWriteSettings.py +++ b/cuvis/FileWriteSettings.py @@ -37,18 +37,21 @@ def _get_internal(self): ge.add_fullscale_pan = int(self.add_fullscale_pan) ge.permissive = int(self.permissive) return ge - + @classmethod def _from_internal(cls, ge): return cls(export_dir=ge.export_dir, channel_selection=ge.channel_selection, spectra_multiplier=ge.spectra_multiplier, - pan_sharpening_interpolation_type=internal.__PanSharpeningInterpolationType__[ge.pan_interpolation_type], - pan_sharpening_algorithm=internal.__PanSharpeningAlgorithm__[ge.pan_algorithm], + pan_sharpening_interpolation_type=internal.__PanSharpeningInterpolationType__[ + ge.pan_interpolation_type], + pan_sharpening_algorithm=internal.__PanSharpeningAlgorithm__[ + ge.pan_algorithm], add_pan=bool(ge.add_pan), add_fullscale_pan=bool(ge.add_fullscale_pan), permissive=bool(ge.permissive)) + @dataclass class EnviExportSettings(GeneralExportSettings): @@ -56,12 +59,11 @@ def _get_internal(self): ge = super()._get_internal() es = None return ge, es - + @classmethod def _from_internal(cls, ge, es): ge = super()._from_internal(ge) return cls(**ge.__dict__) - @dataclass @@ -72,17 +74,20 @@ class TiffExportSettings(GeneralExportSettings): def _get_internal(self): ge = super()._get_internal() ts = cuvis_il.cuvis_export_tiff_settings_t() - ts.compression_mode = internal.__CuvisTiffCompressionMode__[self.compression_mode] + ts.compression_mode = internal.__CuvisTiffCompressionMode__[ + self.compression_mode] ts.format = internal.__CuvisTiffFormat__[self.format] return ge, ts - + @classmethod def _from_internal(cls, ge, ts): ge = super()._from_internal(ge) return cls(**ge.__dict__, - compression_mode=internal.__TiffCompressionMode__[ts.compression_mode], + compression_mode=internal.__TiffCompressionMode__[ + ts.compression_mode], format=internal.__TiffFormat__[ts.format]) + @dataclass(repr=False) class ViewExportSettings(GeneralExportSettings): userplugin: InitVar[str] = None @@ -98,8 +103,9 @@ def __post_init__(self, userplugin: str): with open(userplugin) as f: self._userplugin = "".join(f.readlines()) else: - raise SDKException('Error when validating plugin data. Please provide a valid plugin or a path to a plugin file') - + raise SDKException( + 'Error when validating plugin data. Please provide a valid plugin or a path to a plugin file') + @property def userplugin(self) -> str: return self._userplugin @@ -108,29 +114,28 @@ def userplugin(self) -> str: def userplugin(self, v: str) -> None: self.__post_init__(v) - def __repr__(self): def short_str(s: str, l: int) -> str: return (s[:l] + '...') if len(s) > l else s """Returns a string containing but shortens the userplugin field.""" s = ', '.join(list(f'{field.name}={getattr(self, field.name)}' - for field in fields(self)) + [f'userplugin={short_str(self._userplugin, 15)}']) + for field in fields(self)) + [f'userplugin={short_str(self._userplugin, 15)}']) return f'{type(self).__name__}({s})' - def _get_internal(self): ge = super()._get_internal() vs = cuvis_il.cuvis_export_view_settings_t() vs.userplugin = self.userplugin return ge, vs - + @classmethod def _from_internal(cls, ge, vs): ge = super()._from_internal(ge) - return cls(**ge.__dict__, + return cls(**ge.__dict__, userplugin=vs.userplugin) + @dataclass class SaveArgs(GeneralExportSettings): allow_overwrite: bool = False @@ -153,30 +158,33 @@ def _get_internal(self): sa.allow_drop = int(self.allow_drop) sa.allow_session_file = int(self.allow_session_file) sa.allow_info_file = int(self.allow_info_file) - sa.operation_mode = internal.__CuvisOperationMode__[self.operation_mode] + sa.operation_mode = internal.__CuvisOperationMode__[ + self.operation_mode] sa.fps = int(self.fps) sa.soft_limit = int(self.soft_limit) sa.hard_limit = int(self.hard_limit) sa.max_buftime = int(self.max_buftime) sa.full_export = int(self.full_export) return ge, sa - + @classmethod def _from_internal(cls, ge, sa): ge = super()._from_internal(ge) return cls(**ge.__dict__, - allow_overwrite = bool(sa.allow_overwrite), - allow_fragmentation = bool(sa.allow_fragmentation), - allow_drop = bool(sa.allow_drop), - allow_session_file = bool(sa.allow_session_file), - allow_info_file = bool(sa.allow_info_file), - operation_mode = internal.__OperationMode__[sa.operation_mode], - fps = sa.fps, - soft_limit = sa.soft_limit, - hard_limit = sa.hard_limit, - max_buftime = sa.max_buftime, - full_export = sa.full_export - ) + allow_overwrite=bool(sa.allow_overwrite), + allow_fragmentation=bool(sa.allow_fragmentation), + allow_drop=bool(sa.allow_drop), + allow_session_file=bool(sa.allow_session_file), + allow_info_file=bool(sa.allow_info_file), + operation_mode=internal.__OperationMode__[ + sa.operation_mode], + fps=sa.fps, + soft_limit=sa.soft_limit, + hard_limit=sa.hard_limit, + max_buftime=sa.max_buftime, + full_export=sa.full_export + ) + @dataclass class ProcessingArgs(object): @@ -186,27 +194,34 @@ class ProcessingArgs(object): def _get_internal(self): pa = cuvis_il.cuvis_proc_args_t() pa.allow_recalib = int(self.allow_recalib) - pa.processing_mode = int(internal.__CuvisProcessingMode__[self.processing_mode]) + pa.processing_mode = int(internal.__CuvisProcessingMode__[ + self.processing_mode]) return pa - + @classmethod def _from_internal(cls, pa): return cls(allow_recalib=bool(pa.allow_recalib), processing_mode=internal.__ProcessingMode__[pa.processing_mode]) + @dataclass class WorkerSettings(object): - worker_count: int = 0 - poll_intervall: int = 10 - hard_limit: int = 10 - soft_limit: int = 10 - can_drop: bool = True + input_queue_size: int = 0 + mandatory_queue_size: int = 4 + supplementary_queue_size: int = 4 + output_queue_size: int = 10 + can_skip_measurements: bool = False + can_skip_supplementary_steps: bool = True + can_drop_results: bool = True def _get_internal(self): wa = cuvis_il.cuvis_worker_settings_t() - wa.worker_count = int(self.worker_count) - wa.poll_interval = int(self.poll_intervall) - wa.worker_queue_hard_limit = int(self.hard_limit) - wa.worker_queue_soft_limit = int(self.soft_limit) - wa.can_drop = int(self.can_drop) + wa.input_queue_size = int(self.input_queue_size) + wa.mandatory_queue_size = int(self.mandatory_queue_size) + wa.supplementary_queue_size = int(self.supplementary_queue_size) + wa.output_queue_size = int(self.output_queue_size) + wa.can_skip_measurements = int(self.can_skip_measurements) + wa.can_skip_supplementary_steps = int( + self.can_skip_supplementary_steps) + wa.can_drop_results = int(self.can_drop_results) return wa diff --git a/cuvis/Worker.py b/cuvis/Worker.py index b2fcd09..4b30091 100644 --- a/cuvis/Worker.py +++ b/cuvis/Worker.py @@ -1,7 +1,7 @@ from ._cuvis_il import cuvis_il from .Measurement import Measurement from .Viewer import Viewer, ImageData -from .cuvis_aux import SDKException +from .cuvis_aux import SDKException, WorkerState from .AcquisitionContext import AcquisitionContext from .ProcessingContext import ProcessingContext from .Export import Exporter @@ -10,15 +10,18 @@ from .FileWriteSettings import WorkerSettings import asyncio as a +from .doc import copydoc from dataclasses import dataclass from typing import Callable, Awaitable, Tuple + @dataclass class WorkerResult: mesu: Measurement view: ImageData + class Worker(object): def __init__(self, args: WorkerSettings): self._exporter_set = False @@ -37,7 +40,8 @@ def __init__(self, args: WorkerSettings): self._handle = cuvis_il.p_int_value(_ptr) pass - def set_acquisition_context(self, base: AcquisitionContext=None) -> None: + @copydoc(cuvis_il.cuvis_worker_set_acq_cont) + def set_acquisition_context(self, base: AcquisitionContext = None) -> None: if base is not None: if cuvis_il.status_ok != cuvis_il.cuvis_worker_set_acq_cont( self._handle, base._handle): @@ -50,7 +54,8 @@ def set_acquisition_context(self, base: AcquisitionContext=None) -> None: self._acquisition_set = False pass - def set_processing_context(self, base: ProcessingContext=None) -> None: + @copydoc(cuvis_il.cuvis_worker_set_proc_cont) + def set_processing_context(self, base: ProcessingContext = None) -> None: if base is not None: if cuvis_il.status_ok != cuvis_il.cuvis_worker_set_proc_cont( self._handle, base._handle): @@ -63,7 +68,8 @@ def set_processing_context(self, base: ProcessingContext=None) -> None: self._processing_set = False pass - def set_exporter(self, base: Exporter=None) -> None: + @copydoc(cuvis_il.cuvis_worker_set_exporter) + def set_exporter(self, base: Exporter = None) -> None: if base is not None: if cuvis_il.status_ok != cuvis_il.cuvis_worker_set_exporter( self._handle, base._handle): @@ -76,7 +82,8 @@ def set_exporter(self, base: Exporter=None) -> None: self._exporter_set = False pass - def set_viewer(self, base: Viewer=None) -> None: + @copydoc(cuvis_il.cuvis_worker_set_viewer) + def set_viewer(self, base: Viewer = None) -> None: if base is not None: if cuvis_il.status_ok != cuvis_il.cuvis_worker_set_viewer( self._handle, base._handle): @@ -89,36 +96,30 @@ def set_viewer(self, base: Viewer=None) -> None: self._viewer_set = False pass - def set_session_file(self, base: SessionFile=None, skipDroppedFrames: bool=True) -> None: - if base is not None: - if cuvis_il.status_ok != cuvis_il.cuvis_worker_set_session_file( - self._handle, skipDroppedFrames, base._handle): - raise SDKException() - self._session_file_set = True - else: - if cuvis_il.status_ok != cuvis_il.cuvis_worker_set_session_file( - self._handle, skipDroppedFrames, 0): - raise SDKException() - self._session_file_set = False - pass + @copydoc(cuvis_il.cuvis_worker_ingest_session_file) + def ingest_session_file(self, session: SessionFile, frame_selection: str = 'all') -> None: + if cuvis_il.status_ok != cuvis_il.cuvis_worker_ingest_session_file( + self._handle, session._handle, frame_selection): + raise SDKException() + @copydoc(cuvis_il.cuvis_worker_ingest_mesu) def ingest_mesu(self, mesu: Measurement) -> None: if cuvis_il.status_ok != cuvis_il.cuvis_worker_ingest_mesu( self._handle, mesu): raise SDKException() pass - def query_session_progress(self): - frames_read = cuvis_il.new_p_int() - frames_total = cuvis_il.new_p_int() + @property + @copydoc(cuvis_il.cuvis_worker_query_session_progress) + def query_session_progress(self) -> float: + val = cuvis_il.new_p_double() if cuvis_il.status_ok != \ cuvis_il.cuvis_worker_query_session_progress(self._handle, - frames_read, - frames_total): + val): raise SDKException() - return {"frames_read": cuvis_il.p_int_value(frames_read), "frames_total": cuvis_il.p_int_value(frames_total)} - + return cuvis_il.p_double_value(val) + @copydoc(cuvis_il.cuvis_worker_has_next_result) def has_next_result(self) -> bool: val = cuvis_il.new_p_int() if cuvis_il.status_ok != cuvis_il.cuvis_worker_has_next_result( @@ -126,6 +127,7 @@ def has_next_result(self) -> bool: raise SDKException() return cuvis_il.p_int_value(val) != 0 + @copydoc(cuvis_il.cuvis_worker_get_next_result) def get_next_result(self, timeout) -> WorkerResult: this_mesu = cuvis_il.new_p_int() this_viewer = cuvis_il.new_p_int() @@ -150,7 +152,7 @@ async def get_next_result_async(self, timeout: int) -> WorkerResult: if self.has_next_result(): await a.sleep(0) if cuvis_il.status_ok != cuvis_il.cuvis_worker_get_next_result( - self._handle, this_mesu, this_viewer, 100): + self._handle, this_mesu, this_viewer, 100): raise SDKException() break else: @@ -164,51 +166,135 @@ async def get_next_result_async(self, timeout: int) -> WorkerResult: return WorkerResult(mesu, view) @property - def queue_limits(self) -> Tuple[int,int]: - val_hard = cuvis_il.new_p_int() - val_soft = cuvis_il.new_p_int() - if cuvis_il.status_ok != cuvis_il.cuvis_worker_get_queue_limits( - self._handle, val_hard, val_soft): + @copydoc(cuvis_il.cuvis_worker_get_input_queue_limit) + def input_queue_limit(self) -> int: + val = cuvis_il.new_p_ulong() + if cuvis_il.status_ok != cuvis_il.cuvis_worker_get_input_queue_limit( + self._handle, val): raise SDKException() - return (cuvis_il.p_int_value(val_hard), cuvis_il.p_int_value(val_soft)) + return cuvis_il.p_ulong_value(val) + @property + @copydoc(cuvis_il.cuvis_worker_get_mandatory_queue_limit) + def mandatory_queue_limit(self) -> int: + val = cuvis_il.new_p_ulong() + if cuvis_il.status_ok != cuvis_il.cuvis_worker_get_mandatory_queue_limit( + self._handle, val): + raise SDKException() + return cuvis_il.p_ulong_value(val) - @queue_limits.setter - def queue_limits(self, val: Tuple[int,int] ) -> None: - hard_limit, soft_limit = val - if cuvis_il.status_ok != cuvis_il.cuvis_worker_set_queue_limits( - self._handle, hard_limit, soft_limit): + @property + @copydoc(cuvis_il.cuvis_worker_get_supplementary_queue_limit) + def supplementary_queue_limit(self) -> int: + val = cuvis_il.new_p_ulong() + if cuvis_il.status_ok != cuvis_il.cuvis_worker_get_supplementary_queue_limit( + self._handle, val): raise SDKException() - pass + return cuvis_il.p_ulong_value(val) + + @property + @copydoc(cuvis_il.cuvis_worker_get_output_queue_limit) + def output_queue_limit(self) -> int: + val = cuvis_il.new_p_ulong() + if cuvis_il.status_ok != cuvis_il.cuvis_worker_get_output_queue_limit( + self._handle, val): + raise SDKException() + return cuvis_il.p_ulong_value(val) @property + @copydoc(cuvis_il.cuvis_worker_get_queue_used) def queue_used(self) -> int: val = cuvis_il.new_p_int() if cuvis_il.status_ok != cuvis_il.cuvis_worker_get_queue_used( self._handle, val): raise SDKException() return cuvis_il.p_int_value(val) - + @property - def drop_behaviour(self) -> bool: + @copydoc(cuvis_il.cuvis_worker_get_can_drop_results) + def can_drop_results(self) -> bool: val = cuvis_il.new_p_int() - if cuvis_il.status_ok != cuvis_il.cuvis_worker_get_drop_behavior( + if cuvis_il.status_ok != cuvis_il.cuvis_worker_get_can_drop_results( self._handle, val): raise SDKException() return bool(cuvis_il.p_int_value(val)) - @drop_behaviour.setter - def drop_behaviour(self, can_drop: bool) -> None: - if cuvis_il.status_ok != cuvis_il.cuvis_worker_set_drop_behavior( - self._handle, can_drop): + @property + @copydoc(cuvis_il.cuvis_worker_get_can_skip_measurements) + def can_skip_measurements(self) -> bool: + val = cuvis_il.new_p_int() + if cuvis_il.status_ok != cuvis_il.cuvis_worker_get_can_skip_measurements( + self._handle, val): raise SDKException() - pass + return bool(cuvis_il.p_int_value(val)) + + @property + @copydoc(cuvis_il.cuvis_worker_get_can_skip_supplementary) + def can_skip_supplementary(self) -> bool: + val = cuvis_il.new_p_int() + if cuvis_il.status_ok != cuvis_il.cuvis_worker_get_can_skip_supplementary( + self._handle, val): + raise SDKException() + return bool(cuvis_il.p_int_value(val)) + + @property + @copydoc(cuvis_il.cuvis_worker_is_processing_mandatory) + def is_processing_mandatory(self) -> bool: + val = cuvis_il.new_p_int() + if cuvis_il.status_ok != cuvis_il.cuvis_worker_is_processing_mandatory( + self._handle, val): + raise SDKException() + return bool(cuvis_il.p_int_value(val)) + @property + @copydoc(cuvis_il.cuvis_worker_is_processing) + def is_processing(self) -> bool: + val = cuvis_il.new_p_int() + if cuvis_il.status_ok != cuvis_il.cuvis_worker_is_processing( + self._handle, val): + raise SDKException() + return bool(cuvis_il.p_int_value(val)) + + @property + @copydoc(cuvis_il.cuvis_worker_get_threads_busy) + def threads_busy(self) -> int: + val = cuvis_il.new_p_int() + if cuvis_il.status_ok != cuvis_il.cuvis_worker_get_threads_busy( + self._handle, val): + raise SDKException() + return cuvis_il.p_int_value(val) + + @property + @copydoc(cuvis_il.cuvis_worker_get_state) + def state(self) -> WorkerState: + val = cuvis_il.cuvis_worker_state_t() + if cuvis_il.status_ok != cuvis_il.cuvis_worker_get_state( + self._handle, val): + raise SDKException() + return WorkerState._from_internal(val) + + @copydoc(cuvis_il.cuvis_worker_start) + def start_processing(self) -> None: + if cuvis_il.status_ok != cuvis_il.cuvis_worker_start( + self._handle): + raise SDKException() + + @copydoc(cuvis_il.cuvis_worker_stop) + def stop_processing(self) -> None: + if cuvis_il.status_ok != cuvis_il.cuvis_worker_stop( + self._handle): + raise SDKException() + + @copydoc(cuvis_il.cuvis_worker_drop_all_queued) + def drop_all_queued(self) -> None: + if cuvis_il.status_ok != cuvis_il.cuvis_worker_drop_all_queued( + self._handle): + raise SDKException() def register_worker_callback(self, callback: Callable[[WorkerResult], Awaitable[None]]) -> None: self.reset_worker_callback() poll_time = 0.001 - + async def _internal_worker_loop(): while True: if self.has_next_result(): @@ -220,12 +306,11 @@ async def _internal_worker_loop(): await a.sleep(poll_time) self._worker_poll_task = a.create_task(_internal_worker_loop()) - + def reset_worker_callback(self) -> None: if self._worker_poll_task is not None: self._worker_poll_task.cancel() self._worker_poll_task = None - def __del__(self): self.reset_worker_callback() diff --git a/cuvis/cuvis_aux.py b/cuvis/cuvis_aux.py index 81e9caf..22c7078 100644 --- a/cuvis/cuvis_aux.py +++ b/cuvis/cuvis_aux.py @@ -1,14 +1,12 @@ +from dataclasses import dataclass +import cuvis.cuvis_types as internal +from typing import List, Union +from ._cuvis_il import cuvis_il import logging import datetime base_datetime = datetime.datetime(1970, 1, 1) -from ._cuvis_il import cuvis_il -from typing import List, Union - -import cuvis.cuvis_types as internal - -from dataclasses import dataclass def _fn_bits(n): flaglist = [] @@ -24,6 +22,7 @@ def _bit_translate(n, translate_dict): return [key for key, vald in translate_dict.items() if vald in flags] + class SDKException(Exception): def __init__(self, *args): @@ -35,6 +34,7 @@ def __init__(self, *args): super().__init__(self.message) pass + @dataclass class SessionData(object): name: str @@ -46,6 +46,7 @@ def __repr__(self): self.session_number, self.sequence_number) + @dataclass class CalibrationInfo(object): model_name: str @@ -64,6 +65,7 @@ def __repr__(self): self.unique_id, self.file_path) + @dataclass class GPSData(object): longitude: float @@ -75,14 +77,15 @@ def __repr__(self): return "'GPS: lon./lat.: {} / {}; alt. {}, time {}'".format( self.longitude, self.latitude, self.altitude, self.time) - + @classmethod def _from_internal(cls, gps): return cls(longitude=gps.longitude, latitude=gps.latitude, altitude=gps.altitude, - time= base_datetime + datetime.timedelta( - milliseconds=gps.time)) + time=base_datetime + datetime.timedelta( + milliseconds=gps.time)) + @dataclass class SensorInfo(object): @@ -99,13 +102,33 @@ def _from_internal(cls, info): return cls(averages=info.averages, temperature=info.temperature, gain=info.gain, - readout_time= base_datetime + datetime.timedelta( - milliseconds=info.readout_time), - width=info.width, - height=info.height, - raw_frame_id=info.raw_frame_id) + readout_time=base_datetime + datetime.timedelta( + milliseconds=info.readout_time), + width=info.width, + height=info.height, + raw_frame_id=info.raw_frame_id) + + +@dataclass +class WorkerState(object): + measurementsInQueue: int + sessionFilesInQueue: int + framesInQueue: int + measurementsBeingProcessed: int + resultsInQueue: int + hasAcquisitionContext: bool + isProcessing: bool + + @classmethod + def _from_internal(cls, state): + return cls(measurementsInQueue=state.measurementsInQueue, + sessionFilesInQueue=state.sessionFilesInQueue, + framesInQueue=state.framesInQueue, + measurementsBeingProcessed=state.measurementsBeingProcessed, + resultsInQueue=state.resultsInQueue, + hasAcquisitionContext=bool(state.hasAcquisitionContext), + isProcessing=bool(state.isProcessing)) - class Bitset(object): _translation_dict = {} @@ -114,8 +137,8 @@ class Bitset(object): @classmethod def supremum(cls): """"Returns a bitset containing all possible members of the current Bitset class""" - return cls(sum([v for k,v in cls._translation_dict.items()])) - + return cls(sum([v for k, v in cls._translation_dict.items()])) + def all(self): """"Returns a bitset containing all possible members of the current Bitset class""" return type(self).supremum() @@ -130,19 +153,19 @@ def strings(self) -> List[str]: def __repr__(self): """"Returns the string representation of the current Bitset""" return f'{self.__class__.__name__}({self.strings()})' - + def __int__(self): """"Returns the internal integer value of the current Bitset """ return self._value - + def __len__(self): """"Returns the amount of members of the current Bitset """ return bin(self._value).count('1') - + def __iter__(self): """"Returns an iterator over the string values of the current member of the Bitset """ return _bit_translate(self._value, type(self)._translation_dict).__iter__() - + def __contains__(self, member): """"Returns True if the input value is part of the set. The value can be a string, an int or a similiar Bitset instance """ if isinstance(member, str): @@ -153,16 +176,17 @@ def __contains__(self, member): return (member & self._value) == member else: 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])) - + class MeasurementFlags(Bitset): _translation_dict = internal.__CuvisMeasurementFlag__ - _inverse_dict = internal.__MeasurementFlag__ + _inverse_dict = internal.__MeasurementFlag__ + def __init__(self, value: int): super().__init__(value) @@ -170,5 +194,6 @@ def __init__(self, value: int): class Capabilities(Bitset): _translation_dict = internal.__CuvisCapabilities__ _inverse_dict = internal.__Capabilities__ + def __init__(self, value: int): super().__init__(value)