Devices
Bases: type(ABC), type(QObject)
Metaclass enabling multiple inheritance of ABC and QObject.
Overview
Resolves metaclass conflicts so DeviceWorker can be both an ABC
and a Qt QObject.
- Combines behavior of
ABCandQtCore.QObjectmetatypes.
Usage Notes
Use this metaclass only for worker classes that inherit from QObject.
Source code in contracts/device_worker.py
class WorkerMeta(type(ABC), type(QtCore.QObject)):
"""
Metaclass enabling multiple inheritance of ABC and QObject.
Overview:
Resolves metaclass conflicts so `DeviceWorker` can be both an ABC
and a Qt QObject.
- Combines behavior of `ABC` and `QtCore.QObject` metatypes.
Usage Notes:
Use this metaclass only for worker classes that inherit from QObject.
"""
pass
Bases: ABC, QObject
Abstract base for device worker objects that run in a Qt context.
Overview
Defines the required API for workers that accept a DataSet, run a task, and accept data/processor types.
- Required methods: set_data, run, set_data_type, set_processor_type.
- Provides a short
identifierused for run labelling.
Usage Notes
Concrete workers should implement thread-safe run logic and emit signals.
Source code in contracts/device_worker.py
class DeviceWorker(ABC, QtCore.QObject, metaclass=WorkerMeta):
"""
Abstract base for device worker objects that run in a Qt context.
Overview:
Defines the required API for workers that accept a DataSet,
run a task, and accept data/processor types.
- Required methods: set_data, run, set_data_type, set_processor_type.
- Provides a short `identifier` used for run labelling.
Usage Notes:
Concrete workers should implement thread-safe run logic and emit signals.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.identifier = str(uuid.uuid4())[:4]
@abstractmethod
def set_data(self, dataset: DataSet):
pass
@abstractmethod
def run(self):
pass
@abstractmethod
def set_data_type(self, data_type):
pass
@abstractmethod
def set_processor_type(self, processor_type):
pass
Bases: DeviceWorker
Concrete worker base implementing common setup and progress handling.
Overview
Implements option handling, dataset instantiation and a simple run flow that prepares data processors, emits progress, and calls a plot.
- Manages
device,dataset,plot_type,options, anddata_processors. - Populates processors per-file and emits
progress/finishedsignals. set_data_type/set_processor_typeare simple setters.
Usage Notes
Subclasses provide plotting methods referenced by plot_type and may
extend run behaviour if needed.
Source code in contracts/device_worker.py
@decorate_class_with_logging(log_level=DEBUG_WORKER)
class DeviceWorkerCore(DeviceWorker):
"""
Concrete worker base implementing common setup and progress handling.
Overview:
Implements option handling, dataset instantiation and a simple run
flow that prepares data processors, emits progress, and calls a plot.
- Manages `device`, `dataset`, `plot_type`, `options`, and `data_processors`.
- Populates processors per-file and emits `progress`/`finished` signals.
- `set_data_type` / `set_processor_type` are simple setters.
Usage Notes:
Subclasses provide plotting methods referenced by `plot_type` and may
extend run behaviour if needed.
"""
finished = QtCore.pyqtSignal(dict)
progress = QtCore.pyqtSignal(int)
console_print = QtCore.pyqtSignal(str, str)
def __init__(self, device, dataset, plot_type, options: PlotterOptions):
super().__init__()
self.device = device
self.dataset = dataset
self.plot_type = plot_type
self.options = options
self.options.add_option(label="experiment_datetime", value = dataset.get_experiment_date())
self.data_processors = None
self.processor_type = None
self.data_type = None
def set_data_type(self, data_type):
if not issubclass(data_type, Data):
raise TypeError("data_type must be a subclass of Data")
self.data_type = data_type
def set_processor_type(self, processor_type):
if not issubclass(processor_type, DataProcessor):
raise TypeError("processor_type must be a subclass of DataProcessor")
self.processor_type = processor_type
def set_data(self, dataset: DataSet):
if not isinstance(dataset, DataSet):
raise TypeError("dataset must be an instance of DataSet")
# CHECK: Check that dataset and processor types have been set
# Initialise an empty dict and get the required filepaths
self.data_processors = {}
filepaths = dataset.get_filepaths()
colours = dataset.get_all_colours()
if colours is not None:
self.options.add_option(label="colours", value=colours)
# Progress housekeeping
nr_of_files = len(filepaths)
counter = 0
# Read the dataset and instantiate a processor for each file
for key in filepaths:
data = self.data_type(key)
data.read_file(filepaths[key])
self.data_processors[key] = self.processor_type(data)
# Emit progress signal
counter += 1
self.progress.emit(int(100*counter/nr_of_files))
def run(self):
try:
# Set the data
self.set_data(self.dataset)
# Grab the correct plot and execute it, including uuid in the title
title = f"{self.dataset.get_name()} (run {self.identifier})"
plot_type = getattr(self, self.plot_type)
plot_type(title=title)
self.finished.emit({"ok": True})
# Catchall is intentional
except Exception as e:
import traceback
self.finished.emit({"ok": False,"message": f"{e}" ,"traceback": traceback.format_exc()})