Source code for seabreeze.pyseabreeze.devices

"""device classes for pyseabreeze

Author: Andreas Poehlmann
"""
from __future__ import annotations

import enum
import itertools
from collections import defaultdict
from typing import Any
from typing import Iterable
from typing import Tuple
from typing import TypeVar

from seabreeze.pyseabreeze import features as sbf
from seabreeze.pyseabreeze.exceptions import SeaBreezeError
from seabreeze.pyseabreeze.features import SeaBreezeFeature
from seabreeze.pyseabreeze.protocol import ADCProtocol
from seabreeze.pyseabreeze.protocol import OBP2Protocol
from seabreeze.pyseabreeze.protocol import OBPProtocol
from seabreeze.pyseabreeze.protocol import OOIProtocol
from seabreeze.pyseabreeze.transport import USBTransport
from seabreeze.pyseabreeze.types import PySeaBreezeTransport
from seabreeze.types import SeaBreezeFeatureAccessor

# class registry for all spectrometer models

_model_class_registry: dict[str, type[SeaBreezeDevice]] = {}


class _SeaBreezeDeviceMeta(type):
    """metaclass for pyseabreeze devices"""

    def __new__(
        mcs: type[_SeaBreezeDeviceMeta],
        name: str,
        bases: tuple[Any],
        attr_dict: dict[str, Any],
    ) -> _SeaBreezeDeviceMeta:
        # This part of the metaclass magic is very opaque. It could be avoided by moving all
        # of this logic to custom __init__ methods of the Spectrometer classes, or to factory
        # functions. But I am using python-seabreeze as a playground to experiment with
        # different ways of implementing extendable interfaces, so please forgive these design
        # decisions ^^"
        #
        # I also like the concise way you can define the spectrometer functionality this way.
        # It allows you to get a quick overview of the functionality implemented in pyseabreeze,
        # without having to read through a lot of code. And I think that it's easy to understand
        # what a spectrometer can do, by just looking at the spectrometer class definition, even
        # if you don't understand the details of the implementation.
        #
        # Because it's quite opaque the code below tries hard to enforce strict typing in all
        # defined spectrometer classes to minimize the amount of errors you can make when adding
        # a new spectrometer to pyseabreeze.
        #
        if name != "SeaBreezeDevice":
            # This runs for all subclasses of SeaBreezeDevice, so for all defined spectrometers
            # What might be unintuitive to the user is, that all defined attributes in the
            # spectrometer classes are only used to configure the subclass and are NOT directly
            # available in the instances later. (look at how attr_dict is modified below).
            #
            if "model_name" not in attr_dict:
                raise AttributeError(f"'model_name' not provided for class '{name}'")
            model_name = attr_dict.pop("model_name")
            if not isinstance(model_name, str):
                raise TypeError(f"{name}.model_name not a str")

            new_attr_dict: dict[str, Any] = {
                "_model_name": model_name,
            }

            if any(base is SeaBreezeDevice for base in bases):
                # gather the transport classes defined on the class
                transport_classes = mcs._extract_transform_classes(
                    model_name, class_name=name, attr_dict=attr_dict
                )
                new_attr_dict["_transport_classes"] = transport_classes
            # gather the feature classes defined on the class
            feature_classes = mcs._extract_feature_classes(
                model_name, class_name=name, attr_dict=attr_dict
            )
            new_attr_dict["_feature_classes"] = feature_classes

            try:
                # move classmethod to new class for dynamic model class subsitution
                k = "_substitute_compatible_subclass"
                new_attr_dict[k] = attr_dict[k]
            except KeyError:
                pass

            if any(not attr.startswith("_") for attr in attr_dict):
                raise ValueError(
                    "can't define extra attrs on spectrometer classes: {}".format(
                        ", ".join(
                            attr for attr in attr_dict if not attr.startswith("_")
                        )
                    )
                )
            else:
                attr_dict = new_attr_dict

        return super().__new__(mcs, name, bases, attr_dict)

    def __init__(cls, name: str, bases: tuple[Any], attr_dict: dict[str, Any]) -> None:
        if name != "SeaBreezeDevice":
            # > model name
            model_name = getattr(cls, "_model_name")
            assert isinstance(model_name, str), "model name not a str"
            _model_class_registry[model_name] = cls  # type: ignore

        super().__init__(name, bases, attr_dict)

    @staticmethod
    def _extract_transform_classes(
        model_name: str, class_name: str, attr_dict: dict[str, Any]
    ) -> tuple[type[PySeaBreezeTransport[Any]], ...]:
        visited_attrs = set()
        transport_classes = []
        try:
            supported_transport_classes = attr_dict.pop("transport")
        except KeyError:
            raise AttributeError(f"{class_name}.transport not provided")
        if (
            not isinstance(supported_transport_classes, tuple)
            or not supported_transport_classes
        ):
            raise TypeError(f"{class_name}.transport not a tuple of len > 0")

        for idx, transport_cls in enumerate(supported_transport_classes):
            # for each supported transport of the spectrometer, gather the configuration from
            # the spectrometer class and specialize the transport_cls with the provided settings.
            #
            if not issubclass(transport_cls, PySeaBreezeTransport):
                raise TypeError(
                    "{}.transport[{:d}] '{}' does not derive from TransportInterface".format(
                        class_name, idx, transport_cls.__name__
                    )
                )
            # noinspection PyProtectedMember
            kwargs = transport_cls._required_init_kwargs
            transport_init_kwargs = {}
            for kw in kwargs:
                if kw not in attr_dict:
                    raise AttributeError(
                        "{}.{} not provided for class but '{}' requires it.".format(
                            class_name, kw, transport_cls.__name__
                        )
                    )
                transport_init_kwargs[kw] = attr_dict[kw]
            visited_attrs.update(kwargs)
            # specialize the transport class with the spectrometer's custom config
            specialized_transport_cls = transport_cls.specialize(
                model_name, **transport_init_kwargs
            )
            transport_classes.append(specialized_transport_cls)

        for attr in visited_attrs:
            del attr_dict[attr]

        return tuple(transport_classes)

    @staticmethod
    def _extract_feature_classes(
        model_name: str, class_name: str, attr_dict: dict[str, Any]
    ) -> dict[str, list[type[SeaBreezeFeature]]]:
        visited_attrs = set()
        feature_classes: defaultdict[str, list[type[SeaBreezeFeature]]] = defaultdict(
            list
        )
        try:
            supported_feature_classes = attr_dict.pop("feature_classes")
        except KeyError:
            raise AttributeError(f"{class_name}.feature_classes not provided")
        if (
            not isinstance(supported_feature_classes, tuple)
            or not supported_feature_classes
        ):
            raise TypeError(f"{class_name}.feature_classes not a tuple of len > 0")
        for idx, feature_cls in enumerate(supported_feature_classes):
            # for each supported feature of the spectrometer, gather the configuration
            # from the spectrometer class and subclass the feature_cls with the provided
            # settings. Also check if requirements are fulfilled, i.e. if the feature depends
            # on other features or on specific protocols
            #
            if not issubclass(feature_cls, SeaBreezeFeature):
                raise TypeError(
                    "{}.feature_classes[{:d}] '{}' does not derive from SeaBreezeFeature".format(
                        class_name, idx, feature_cls.__name__
                    )
                )
            # noinspection PyProtectedMember
            required = set(feature_cls._required_features)
            if not required.issubset(feature_classes):
                raise KeyError(
                    "{}.feature_classes[{:d}] '{}' requires '{}'. To fix, re-order or add.".format(
                        class_name,
                        idx,
                        feature_cls.__name__,
                        ", ".join(required - set(feature_classes)),
                    )
                )
            # noinspection PyProtectedMember
            kwargs = feature_cls._required_kwargs
            feature_attrs = {}
            for kw in kwargs:
                if kw not in attr_dict:
                    raise AttributeError(
                        "{}.{} not provided for class but '{}' requires it.".format(
                            class_name, kw, feature_cls.__name__
                        )
                    )
                feature_attrs[kw] = attr_dict[kw]
            visited_attrs.update(kwargs)
            # specialize the feature class with the spectrometer's custom config
            specialized_feature_cls = feature_cls.specialize(
                model_name, **feature_attrs
            )
            feature_classes[feature_cls.identifier].append(specialized_feature_cls)

        for attr in visited_attrs:
            del attr_dict[attr]

        return feature_classes


class EndPointMap:
    """internal endpoint map for spectrometer classes"""

    def __init__(
        self,
        ep_out: int | None = None,
        lowspeed_in: int | None = None,
        highspeed_in: int | None = None,
        highspeed_in2: int | None = None,
    ) -> None:
        self.primary_out = self.ep_out = ep_out
        self.primary_in = self.lowspeed_in = lowspeed_in
        self.secondary_out = ep_out
        self.secondary_in = self.highspeed_in = highspeed_in
        self.secondary_in2 = self.highspeed_in2 = highspeed_in2


class DarkPixelIndices(Tuple[int, ...]):
    """internal dark pixel range class"""

    def __new__(
        cls: type[DarkPixelIndices], indices: Iterable[int]
    ) -> DarkPixelIndices:
        """dark pixel indices

        Parameters
        ----------
        indices : iterable
            index of electric dark pixel
        """
        return super().__new__(DarkPixelIndices, sorted(set(indices)))  # type: ignore

    @classmethod
    def from_ranges(cls, *ranges: tuple[int, int]) -> DarkPixelIndices:
        """return dark pixes indices from ranges

        Parameters
        ----------
        *ranges : (`int`, `int`)
            ranges of electric dark pixels
        """
        dp = itertools.chain(*(range(low, high) for (low, high) in ranges))
        # noinspection PyArgumentList
        return cls(dp)


class TriggerMode(enum.IntEnum):
    """internal trigger modes enum"""

    NORMAL = 0x00
    SOFTWARE = 0x01
    LEVEL = 0x01
    SYNCHRONIZATION = 0x02
    HARDWARE = 0x03
    EDGE = 0x03
    SINGLE_SHOT = 0x04
    SELF_NORMAL = 0x80
    SELF_SOFTWARE = 0x81
    SELF_SYNCHRONIZATION = 0x82
    SELF_HARDWARE = 0x83
    DISABLED = 0xFF
    OBP_NORMAL = 0x00
    OBP_EXTERNAL = 0x01
    OBP_INTERNAL = 0x02
    OBP_EDGE = 0x01
    OBP_LEVEL = 0x03

    @classmethod
    def supported(cls, *mode_strings: str) -> set[TriggerMode]:
        return {getattr(cls, mode_string) for mode_string in mode_strings}


DT = TypeVar("DT", bound="SeaBreezeDevice")


[docs]class SeaBreezeDevice(metaclass=_SeaBreezeDeviceMeta): # internal attribute _model_name = None _serial_number = "?" _cached_features: dict[str, list[SeaBreezeFeature]] | None = None _transport_classes: tuple[type[PySeaBreezeTransport[Any]], ...] _feature_classes: dict[str, list[type[SeaBreezeFeature]]] def __new__(cls: type[DT], raw_device: Any = None) -> SeaBreezeDevice: if raw_device is None: raise SeaBreezeError( "Don't instantiate SeaBreezeDevice directly. Use `SeabreezeAPI.list_devices()`." ) for transport in {USBTransport}: supported_model = transport.supported_model(raw_device) if supported_model is not None: break else: raise TypeError("No transport supports device.") specialized_cls = _model_class_registry[supported_model] return super().__new__(specialized_cls) def __init__(self, raw_device: Any = None) -> None: if raw_device is None: raise SeaBreezeError( "Don't instantiate SeaBreezeDevice directly. Use `SeabreezeAPI.list_devices()`." ) self._raw_device = raw_device for transport in self._transport_classes: if transport.supported_model(self._raw_device) is not None: self._transport: PySeaBreezeTransport[Any] = transport() break else: raise TypeError("No transport supports device.") try: # sneakily switch in the correct subclass in case it's needed self.__class__ = self.__class__._substitute_compatible_subclass( self._transport ) except AttributeError: pass try: self._serial_number = self.get_serial_number() except SeaBreezeError: pass @property def model(self) -> str: if self._model_name is None: raise RuntimeError("model can't be None") return self._model_name @property def serial_number(self) -> str: return self._serial_number @classmethod def _substitute_compatible_subclass( cls: type[DT], transport: PySeaBreezeTransport[Any] ) -> type[DT]: return cls def __repr__(self) -> str: return f"<SeaBreezeDevice {self.model}:{self.serial_number}>"
[docs] def open(self) -> None: """open the spectrometer usb connection Returns ------- None """ self._transport.open_device(self._raw_device) # substitute subclass if needed self.__class__ = self.__class__._substitute_compatible_subclass(self._transport) # cache features self._cached_features = None _ = self.features # get serial self._serial_number = self.get_serial_number()
[docs] def close(self) -> None: """close the spectrometer usb connection Returns ------- None """ if self.is_open: self._transport.close_device()
@property def is_open(self) -> bool: """returns if the spectrometer device usb connection is opened Returns ------- bool """ return self._transport.is_open
[docs] def get_serial_number(self) -> str: """return the serial number string of the spectrometer Returns ------- serial_number: str """ try: protocol = self._transport.protocol except RuntimeError: raise SeaBreezeError("device not open") if isinstance(protocol, OOIProtocol) or isinstance(protocol, ADCProtocol): # The serial is stored in slot 0 return self.f.eeprom.eeprom_read_slot(0) elif isinstance(protocol, OBP2Protocol): serial_str = protocol.query(0x00000100).strip() return serial_str.decode("utf8") elif isinstance(protocol, OBPProtocol): serial_len = ord(protocol.query(0x00000101)) serial_str = protocol.query(0x00000100)[:serial_len] return serial_str.decode("utf8") else: raise NotImplementedError( f"No serial number for protocol class {type(protocol).__name__}" )
@property def features(self) -> dict[str, list[SeaBreezeFeature]]: """return a dictionary of all supported features this returns a dictionary with all supported Features of the spectrometer Returns ------- features : `dict` [`str`, `seabreeze.cseabreeze.SeaBreezeFeature`] """ if not self._cached_features: protocol = self._transport.protocol self._cached_features = {} for identifier in sbf.SeaBreezeFeature.get_feature_class_registry(): f_list = self._cached_features.setdefault(identifier, []) for feature_cls in self._feature_classes[identifier]: assert ( issubclass(feature_cls, sbf.SeaBreezeFeature) and identifier == feature_cls.identifier ) if not feature_cls.supports_protocol(protocol): continue f_list.append(feature_cls(self._transport.protocol, len(f_list))) return self._cached_features @property def f(self) -> SeaBreezeFeatureAccessor: """convenience access to features via attributes this allows you to access a feature like this:: # via .features device.features['spectrometer'][0].get_intensities() # via .f device.f.spectrometer.get_intensities() """ class FeatureAccessHandler: def __init__(self, feature_dict: dict[str, Any]) -> None: for identifier, features in feature_dict.items(): setattr( self, identifier, features[0] if features else None ) # TODO: raise FeatureNotAvailable? accessor: SeaBreezeFeatureAccessor = FeatureAccessHandler(self.features) # type: ignore return accessor
# SPECTROMETER DEFINITIONS # ======================== # class USB2000PLUS(SeaBreezeDevice): model_name = "USB2000PLUS" # communication config transport = (USBTransport,) usb_vendor_id = 0x2457 usb_product_id = 0x101E usb_endpoint_map = EndPointMap( ep_out=0x01, lowspeed_in=0x81, highspeed_in=0x82, highspeed_in2=0x86 ) usb_protocol = OOIProtocol # spectrometer config dark_pixel_indices = DarkPixelIndices.from_ranges((6, 21)) # as in seabreeze-3.0.9 integration_time_min = 1000 integration_time_max = 655350000 integration_time_base = 1 spectrum_num_pixel = 2048 spectrum_raw_length = (2048 * 2) + 1 spectrum_max_value = 65535 trigger_modes = TriggerMode.supported( "NORMAL", "SOFTWARE", "SYNCHRONIZATION", "HARDWARE", "SINGLE_SHOT" ) # features feature_classes = ( sbf.eeprom.SeaBreezeEEPromFeatureOOI, sbf.spectrometer.SeaBreezeSpectrometerFeatureUSB2000PLUS, sbf.nonlinearity.NonlinearityCoefficientsEEPromFeatureOOI, sbf.continuousstrobe.SeaBreezeContinuousStrobeFeatureOOI, sbf.rawusb.SeaBreezeRawUSBBusAccessFeature, ) @classmethod def _substitute_compatible_subclass( cls: type[DT], transport: PySeaBreezeTransport[Any] ) -> type[DT]: """return the correct subclass of the usb2000plus like model""" try: protocol = transport.protocol except RuntimeError: raise AttributeError("transport not opened") # noinspection PyUnresolvedReferences,PyProtectedMember from seabreeze.pyseabreeze.features.fpga import _FPGARegisterFeatureOOI fpga = _FPGARegisterFeatureOOI(protocol) if fpga.get_firmware_version()[0] >= 3: return FLAMES # type: ignore else: return USB2000PLUS # type: ignore class FLAMES(USB2000PLUS): model_name = "FLAMES" # spectrometer config dark_pixel_indices = DarkPixelIndices.from_ranges((6, 21)) # as in seabreeze-3.0.9 integration_time_min = 1000 integration_time_max = 655350000 integration_time_base = 1 spectrum_num_pixel = 2048 spectrum_raw_length = (2048 * 2) + 1 spectrum_max_value = 65535 trigger_modes = TriggerMode.supported( "NORMAL", "SOFTWARE", "SYNCHRONIZATION", "HARDWARE", "SINGLE_SHOT" ) # features feature_classes = ( sbf.eeprom.SeaBreezeEEPromFeatureOOI, sbf.spectrometer.SeaBreezeSpectrometerFeatureUSB2000PLUS, sbf.nonlinearity.NonlinearityCoefficientsEEPromFeatureOOI, sbf.continuousstrobe.SeaBreezeContinuousStrobeFeatureOOI, sbf.rawusb.SeaBreezeRawUSBBusAccessFeature, ) class USB2000(SeaBreezeDevice): model_name = "USB2000" # communication config transport = (USBTransport,) usb_vendor_id = 0x2457 usb_product_id = 0x1002 usb_endpoint_map = EndPointMap(ep_out=0x02, lowspeed_in=0x87, highspeed_in=0x82) usb_protocol = OOIProtocol # spectrometer config dark_pixel_indices = DarkPixelIndices.from_ranges((2, 24)) integration_time_min = 3000 integration_time_max = 655350000 integration_time_base = 1000 spectrum_num_pixel = 2048 spectrum_raw_length = (2048 * 2) + 1 spectrum_max_value = 4095 trigger_modes = TriggerMode.supported("NORMAL", "SOFTWARE", "HARDWARE") # features feature_classes = ( sbf.eeprom.SeaBreezeEEPromFeatureOOI, sbf.spectrometer.SeaBreezeSpectrometerFeatureUSB2000, sbf.rawusb.SeaBreezeRawUSBBusAccessFeature, ) class HR2000(SeaBreezeDevice): model_name = "HR2000" # communication config transport = (USBTransport,) usb_vendor_id = 0x2457 usb_product_id = 0x100A usb_endpoint_map = EndPointMap(ep_out=0x02, lowspeed_in=0x87, highspeed_in=0x82) usb_protocol = OOIProtocol # spectrometer config dark_pixel_indices = DarkPixelIndices.from_ranges((2, 24)) integration_time_min = 3000 integration_time_max = 655350000 integration_time_base = 1000 spectrum_num_pixel = 2048 spectrum_raw_length = (2048 * 2) + 1 spectrum_max_value = 4095 trigger_modes = TriggerMode.supported("NORMAL", "SOFTWARE", "HARDWARE") # features feature_classes = ( sbf.eeprom.SeaBreezeEEPromFeatureOOI, sbf.spectrometer.SeaBreezeSpectrometerFeatureHR2000, sbf.rawusb.SeaBreezeRawUSBBusAccessFeature, ) class HR4000(SeaBreezeDevice): model_name = "HR4000" # communication config transport = (USBTransport,) usb_vendor_id = 0x2457 usb_product_id = 0x1012 usb_endpoint_map = EndPointMap( ep_out=0x01, lowspeed_in=0x81, highspeed_in=0x82, highspeed_in2=0x86 ) usb_protocol = OOIProtocol # spectrometer config dark_pixel_indices = DarkPixelIndices.from_ranges((2, 13)) integration_time_min = 10 integration_time_max = 655350000 integration_time_base = 1 spectrum_num_pixel = 3840 spectrum_raw_length = (3840 * 2) + 1 spectrum_max_value = 16383 trigger_modes = TriggerMode.supported( "NORMAL", "SOFTWARE", "SYNCHRONIZATION", "HARDWARE" ) # features feature_classes = ( sbf.eeprom.SeaBreezeEEPromFeatureOOI, sbf.spectrometer.SeaBreezeSpectrometerFeatureHR4000, sbf.rawusb.SeaBreezeRawUSBBusAccessFeature, ) class HR2000PLUS(SeaBreezeDevice): model_name = "HR2000PLUS" # communication config transport = (USBTransport,) usb_vendor_id = 0x2457 usb_product_id = 0x1016 usb_endpoint_map = EndPointMap( ep_out=0x01, lowspeed_in=0x81, highspeed_in=0x82, highspeed_in2=0x86 ) usb_protocol = OOIProtocol # spectrometer config dark_pixel_indices = DarkPixelIndices.from_ranges((2, 24)) integration_time_min = 1000 integration_time_max = 655350000 integration_time_base = 1 spectrum_num_pixel = 2048 spectrum_raw_length = (2048 * 2) + 1 spectrum_max_value = 16383 trigger_modes = TriggerMode.supported( "NORMAL", "SOFTWARE", "SYNCHRONIZATION", "HARDWARE" ) # features feature_classes = ( sbf.eeprom.SeaBreezeEEPromFeatureOOI, sbf.spectrometer.SeaBreezeSpectrometerFeatureHR2000PLUS, sbf.rawusb.SeaBreezeRawUSBBusAccessFeature, ) class USB650(SeaBreezeDevice): model_name = "USB650" # communication config transport = (USBTransport,) usb_vendor_id = 0x2457 usb_product_id = 0x1014 usb_endpoint_map = EndPointMap(ep_out=0x02, lowspeed_in=0x87, highspeed_in=0x82) usb_protocol = OOIProtocol # spectrometer config dark_pixel_indices = DarkPixelIndices.from_ranges() integration_time_min = 3000 integration_time_max = 655350000 integration_time_base = 1000 spectrum_num_pixel = 2048 spectrum_raw_length = (2048 * 2) + 1 spectrum_max_value = 4095 trigger_modes = TriggerMode.supported("NORMAL", "SOFTWARE", "HARDWARE") # features feature_classes = ( sbf.eeprom.SeaBreezeEEPromFeatureOOI, sbf.spectrometer.SeaBreezeSpectrometerFeatureUSB650, sbf.rawusb.SeaBreezeRawUSBBusAccessFeature, sbf.continuousstrobe.SeaBreezeContinuousStrobeFeatureOOI, ) class QE65000(SeaBreezeDevice): model_name = "QE65000" # communication config transport = (USBTransport,) usb_vendor_id = 0x2457 usb_product_id = 0x1018 usb_endpoint_map = EndPointMap( ep_out=0x01, lowspeed_in=0x81, highspeed_in=0x82, highspeed_in2=0x86 ) usb_protocol = OOIProtocol # spectrometer config dark_pixel_indices = DarkPixelIndices.from_ranges( (0, 4), (1040, 1044) ) # as in seabreeze-3.0.5 integration_time_min = 8000 integration_time_max = 1600000000 integration_time_base = 1000 spectrum_num_pixel = 1280 spectrum_raw_length = (1024 + 256) * 2 + 1 spectrum_max_value = 65535 trigger_modes = TriggerMode.supported("NORMAL", "SOFTWARE", "HARDWARE") # features feature_classes = ( sbf.eeprom.SeaBreezeEEPromFeatureOOI, sbf.spectrometer.SeaBreezeSpectrometerFeatureQE65000, sbf.rawusb.SeaBreezeRawUSBBusAccessFeature, ) class USB4000(SeaBreezeDevice): model_name = "USB4000" # communication config transport = (USBTransport,) usb_vendor_id = 0x2457 usb_product_id = 0x1022 usb_endpoint_map = EndPointMap( ep_out=0x01, lowspeed_in=0x81, highspeed_in=0x82, highspeed_in2=0x86 ) usb_protocol = OOIProtocol # spectrometer config dark_pixel_indices = ( DarkPixelIndices.from_ranges((5, 16)), ) # as in seabreeze-3.0.9 integration_time_min = 10 integration_time_max = 65535000 integration_time_base = 1 spectrum_num_pixel = 3840 spectrum_raw_length = (3840 * 2) + 1 spectrum_max_value = 65535 trigger_modes = TriggerMode.supported( "NORMAL", "SOFTWARE", "SYNCHRONIZATION", "HARDWARE" ) # features feature_classes = ( sbf.eeprom.SeaBreezeEEPromFeatureOOI, sbf.spectrometer.SeaBreezeSpectrometerFeatureUSB4000, sbf.rawusb.SeaBreezeRawUSBBusAccessFeature, ) class NIRQUEST512(SeaBreezeDevice): model_name = "NIRQUEST512" # communication config transport = (USBTransport,) usb_vendor_id = 0x2457 usb_product_id = 0x1026 usb_endpoint_map = EndPointMap( ep_out=0x01, lowspeed_in=0x81, highspeed_in=0x82, highspeed_in2=0x86 ) usb_protocol = OOIProtocol # spectrometer config dark_pixel_indices = (DarkPixelIndices.from_ranges(),) # as in seabreeze-3.0.9 integration_time_min = 1000 integration_time_max = 1600000000 integration_time_base = 1000 spectrum_num_pixel = 512 spectrum_raw_length = (512 * 2) + 1 spectrum_max_value = 65535 trigger_modes = TriggerMode.supported( "NORMAL", "SOFTWARE", "SYNCHRONIZATION", "HARDWARE" ) # features feature_classes = ( sbf.eeprom.SeaBreezeEEPromFeatureOOI, sbf.spectrometer.SeaBreezeSpectrometerFeatureNIRQUEST512, sbf.rawusb.SeaBreezeRawUSBBusAccessFeature, ) class NIRQUEST256(SeaBreezeDevice): model_name = "NIRQUEST256" # communication config transport = (USBTransport,) usb_vendor_id = 0x2457 usb_product_id = 0x1028 usb_endpoint_map = EndPointMap( ep_out=0x01, lowspeed_in=0x81, highspeed_in=0x82, highspeed_in2=0x86 ) usb_protocol = OOIProtocol # spectrometer config dark_pixel_indices = (DarkPixelIndices.from_ranges(),) # as in seabreeze-3.0.9 integration_time_min = 1000 integration_time_max = 1600000000 integration_time_base = 1000 spectrum_num_pixel = 256 spectrum_raw_length = (256 * 2) + 1 spectrum_max_value = 65535 trigger_modes = TriggerMode.supported( "NORMAL", "SOFTWARE", "SYNCHRONIZATION", "HARDWARE" ) # features feature_classes = ( sbf.eeprom.SeaBreezeEEPromFeatureOOI, sbf.spectrometer.SeaBreezeSpectrometerFeatureNIRQUEST256, sbf.rawusb.SeaBreezeRawUSBBusAccessFeature, ) class MAYA2000PRO(SeaBreezeDevice): model_name = "MAYA2000PRO" # communication config transport = (USBTransport,) usb_vendor_id = 0x2457 usb_product_id = 0x102A usb_endpoint_map = EndPointMap( ep_out=0x01, lowspeed_in=0x81, highspeed_in=0x82, highspeed_in2=0x86 ) usb_protocol = OOIProtocol # spectrometer config dark_pixel_indices = DarkPixelIndices.from_ranges((0, 4), (2064, 2068)) integration_time_min = 7200 integration_time_max = 65000000 integration_time_base = 1 spectrum_num_pixel = 2304 spectrum_raw_length = (2304 * 2) + 1 spectrum_max_value = 64000 trigger_modes = TriggerMode.supported( "NORMAL", "SOFTWARE", "SYNCHRONIZATION", "HARDWARE" ) # features feature_classes = ( sbf.eeprom.SeaBreezeEEPromFeatureOOI, sbf.spectrometer.SeaBreezeSpectrometerFeatureMAYA2000PRO, sbf.rawusb.SeaBreezeRawUSBBusAccessFeature, ) class MAYA2000(SeaBreezeDevice): model_name = "MAYA2000" # communication config transport = (USBTransport,) usb_vendor_id = 0x2457 usb_product_id = 0x102C usb_endpoint_map = EndPointMap( ep_out=0x01, lowspeed_in=0x81, highspeed_in=0x82, highspeed_in2=0x86 ) usb_protocol = OOIProtocol # spectrometer config dark_pixel_indices = DarkPixelIndices.from_ranges((0, 8), (2072, 2080)) integration_time_min = 15000 integration_time_max = 1600000000 integration_time_base = 1 spectrum_num_pixel = 2304 spectrum_raw_length = (2304 * 2) + 1 spectrum_max_value = 65535 trigger_modes = TriggerMode.supported("NORMAL", "SOFTWARE", "HARDWARE") # features feature_classes = ( sbf.eeprom.SeaBreezeEEPromFeatureOOI, sbf.spectrometer.SeaBreezeSpectrometerFeatureMAYA2000, sbf.rawusb.SeaBreezeRawUSBBusAccessFeature, ) class TORUS(SeaBreezeDevice): model_name = "TORUS" # communication config transport = (USBTransport,) usb_vendor_id = 0x2457 usb_product_id = 0x1040 usb_endpoint_map = EndPointMap( ep_out=0x01, lowspeed_in=0x81, highspeed_in=0x82, highspeed_in2=0x86 ) usb_protocol = OOIProtocol # spectrometer config dark_pixel_indices = DarkPixelIndices.from_ranges() integration_time_min = 1000 integration_time_max = 655350000 integration_time_base = 1 spectrum_num_pixel = 2048 spectrum_raw_length = (2048 * 2) + 1 spectrum_max_value = 65535 trigger_modes = TriggerMode.supported( "NORMAL", "SOFTWARE", "SYNCHRONIZATION", "HARDWARE" ) # features feature_classes = ( sbf.eeprom.SeaBreezeEEPromFeatureOOI, sbf.spectrometer.SeaBreezeSpectrometerFeatureTORUS, sbf.rawusb.SeaBreezeRawUSBBusAccessFeature, ) class APEX(SeaBreezeDevice): model_name = "APEX" # communication config transport = (USBTransport,) usb_vendor_id = 0x2457 usb_product_id = 0x1044 usb_endpoint_map = EndPointMap( ep_out=0x01, lowspeed_in=0x81, highspeed_in=0x82, highspeed_in2=0x86 ) usb_protocol = OOIProtocol # spectrometer config dark_pixel_indices = DarkPixelIndices.from_ranges((0, 4), (2064, 2068)) integration_time_min = 15000 integration_time_max = 1600000000 integration_time_base = 1 spectrum_num_pixel = 2304 spectrum_raw_length = (2304 * 2) + 1 spectrum_max_value = 64000 trigger_modes = TriggerMode.supported( "NORMAL", ) # features feature_classes = ( sbf.eeprom.SeaBreezeEEPromFeatureOOI, sbf.spectrometer.SeaBreezeSpectrometerFeatureAPEX, sbf.rawusb.SeaBreezeRawUSBBusAccessFeature, ) class MAYALSL(SeaBreezeDevice): model_name = "MAYALSL" # communication config transport = (USBTransport,) usb_vendor_id = 0x2457 usb_product_id = 0x1046 usb_endpoint_map = EndPointMap( ep_out=0x01, lowspeed_in=0x81, highspeed_in=0x82, highspeed_in2=0x86 ) usb_protocol = OOIProtocol # spectrometer config dark_pixel_indices = DarkPixelIndices.from_ranges((0, 4), (2064, 2068)) integration_time_min = 7200 integration_time_max = 65000000 integration_time_base = 1 spectrum_num_pixel = 2304 spectrum_raw_length = (2304 * 2) + 1 spectrum_max_value = 64000 trigger_modes = TriggerMode.supported( "NORMAL", "SOFTWARE", "SYNCHRONIZATION", "HARDWARE" ) # features feature_classes = ( sbf.eeprom.SeaBreezeEEPromFeatureOOI, sbf.spectrometer.SeaBreezeSpectrometerFeatureMAYALSL, sbf.rawusb.SeaBreezeRawUSBBusAccessFeature, ) class JAZ(SeaBreezeDevice): model_name = "JAZ" # communication config transport = (USBTransport,) usb_vendor_id = 0x2457 usb_product_id = 0x2000 usb_endpoint_map = EndPointMap(ep_out=0x01, lowspeed_in=0x81, highspeed_in=0x82) usb_protocol = OOIProtocol # spectrometer config dark_pixel_indices = DarkPixelIndices.from_ranges((2, 24)) integration_time_min = 1000 integration_time_max = 655350000 integration_time_base = 1 spectrum_num_pixel = 2048 spectrum_raw_length = 2048 * 2 # XXX: No Sync byte! spectrum_max_value = 65535 trigger_modes = TriggerMode.supported( "NORMAL", "SOFTWARE", "SYNCHRONIZATION", "HARDWARE" ) # features feature_classes = ( sbf.eeprom.SeaBreezeEEPromFeatureOOI, sbf.spectrometer.SeaBreezeSpectrometerFeatureJAZ, sbf.rawusb.SeaBreezeRawUSBBusAccessFeature, ) class STS(SeaBreezeDevice): model_name = "STS" # communication config transport = (USBTransport,) usb_vendor_id = 0x2457 usb_product_id = 0x4000 usb_endpoint_map = EndPointMap( ep_out=0x01, lowspeed_in=0x81 ) # XXX: we'll ignore the alternative EPs usb_protocol = OBPProtocol # spectrometer config dark_pixel_indices = DarkPixelIndices.from_ranges() integration_time_min = 10 integration_time_max = 85000000 integration_time_base = 1 spectrum_num_pixel = 1024 spectrum_raw_length = 1024 * 2 spectrum_max_value = 16383 trigger_modes = TriggerMode.supported("OBP_NORMAL", "OBP_EXTERNAL", "OBP_INTERNAL") # features feature_classes = ( sbf.spectrometer.SeaBreezeSpectrometerFeatureSTS, sbf.rawusb.SeaBreezeRawUSBBusAccessFeature, ) class QEPRO(SeaBreezeDevice): model_name = "QEPRO" # communication config transport = (USBTransport,) usb_vendor_id = 0x2457 usb_product_id = 0x4004 usb_endpoint_map = EndPointMap( ep_out=0x01, lowspeed_in=0x81 ) # XXX: we'll ignore the alternative EPs usb_protocol = OBPProtocol # spectrometer config dark_pixel_indices = DarkPixelIndices.from_ranges((0, 4), (1040, 1044)) integration_time_min = 8000 integration_time_max = 1600000000 integration_time_base = 1 spectrum_num_pixel = 1044 spectrum_raw_length = (1044 * 4) + 32 # XXX: Metadata spectrum_max_value = (2**18) - 1 trigger_modes = TriggerMode.supported("NORMAL", "LEVEL", "SYNCHRONIZATION", "EDGE") # features feature_classes = ( sbf.spectrometer.SeaBreezeSpectrometerFeatureQEPRO, sbf.rawusb.SeaBreezeRawUSBBusAccessFeature, ) class VENTANA(SeaBreezeDevice): model_name = "VENTANA" # communication config transport = (USBTransport,) usb_vendor_id = 0x2457 usb_product_id = 0x5000 usb_endpoint_map = EndPointMap(ep_out=0x01, lowspeed_in=0x82) usb_protocol = OBPProtocol # spectrometer config dark_pixel_indices = DarkPixelIndices.from_ranges() integration_time_min = 22000 integration_time_max = 60000000 integration_time_base = 1 spectrum_num_pixel = 1024 spectrum_raw_length = 1024 * 2 # XXX: No Sync byte! spectrum_max_value = 65535 trigger_modes = TriggerMode.supported("NORMAL", "LEVEL", "SYNCHRONIZATION", "EDGE") # features feature_classes = ( sbf.spectrometer.SeaBreezeSpectrometerFeatureVENTANA, sbf.rawusb.SeaBreezeRawUSBBusAccessFeature, ) class SPARK(SeaBreezeDevice): model_name = "SPARK" # communication config transport = (USBTransport,) usb_vendor_id = 0x2457 usb_product_id = 0x4200 usb_endpoint_map = EndPointMap( ep_out=0x01, lowspeed_in=0x81 ) # XXX: we'll ignore the alternative EPs usb_protocol = OBPProtocol # spectrometer config dark_pixel_indices = DarkPixelIndices.from_ranges() integration_time_min = 10 integration_time_max = 85000000 integration_time_base = 1 spectrum_num_pixel = 1024 spectrum_raw_length = (1024 * 2) + 64 # XXX: Metadata spectrum_max_value = 16383 trigger_modes = TriggerMode.supported("OBP_NORMAL", "OBP_EXTERNAL", "OBP_INTERNAL") # features feature_classes = ( sbf.spectrometer.SeaBreezeSpectrometerFeatureSPARK, sbf.rawusb.SeaBreezeRawUSBBusAccessFeature, ) class HDX(SeaBreezeDevice): model_name = "HDX" # communication config transport = (USBTransport,) usb_vendor_id = 0x2457 usb_product_id = 0x2003 usb_endpoint_map = EndPointMap( ep_out=0x01, lowspeed_in=0x81, highspeed_in=0x82, highspeed_in2=0x86 ) usb_protocol = OBPProtocol # spectrometer config dark_pixel_indices = DarkPixelIndices.from_ranges() integration_time_min = 6000 integration_time_max = 10000000 integration_time_base = 1 spectrum_num_pixel = 2068 spectrum_raw_length = (2068 * 2) + 64 # XXX: Metadata spectrum_max_value = 65535 trigger_modes = TriggerMode.supported( "OBP_NORMAL", "OBP_LEVEL", "OBP_EDGE", "DISABLED" ) # features feature_classes = ( sbf.spectrometer.SeaBreezeSpectrometerFeatureHDX, sbf.rawusb.SeaBreezeRawUSBBusAccessFeature, sbf.nonlinearity.NonlinearityCoefficientsFeatureOBP, ) class ADC1000USB(SeaBreezeDevice): model_name = "ADC1000-USB" # communication config transport = (USBTransport,) usb_vendor_id = 0x2457 usb_product_id = 0x1004 usb_endpoint_map = EndPointMap(ep_out=0x02, lowspeed_in=0x87, highspeed_in=0x82) usb_protocol = ADCProtocol # spectrometer config dark_pixel_indices = DarkPixelIndices.from_ranges((2, 24)) integration_time_min = 5000 integration_time_max = 65535000 integration_time_base = 1000 spectrum_num_pixel = 2048 spectrum_raw_length = 2048 * 2 + 1 # Sync byte! spectrum_max_value = 65535 trigger_modes = TriggerMode.supported( "NORMAL", "SOFTWARE", "SYNCHRONIZATION", "HARDWARE" ) # features feature_classes = ( sbf.eeprom.SeaBreezeEEPromFeatureADC, sbf.spectrometer.SeaBreezeSpectrometerFeatureADC, sbf.rawusb.SeaBreezeRawUSBBusAccessFeature, ) class SR4(SeaBreezeDevice): model_name = "SR4" # communication config transport = (USBTransport,) usb_vendor_id = 0x0999 usb_product_id = 0x1002 usb_endpoint_map = EndPointMap(ep_out=0x01, highspeed_in=0x81) usb_protocol = OBP2Protocol # spectrometer config dark_pixel_indices = DarkPixelIndices.from_ranges() integration_time_min = 6000 # ??? integration_time_max = 10000000 # ??? integration_time_base = 1 spectrum_num_pixel = 3648 spectrum_raw_length = (3648 * 2) + 32 # XXX: Metadata spectrum_max_value = 65535 trigger_modes = TriggerMode.supported("OBP_NORMAL") # features feature_classes = (sbf.spectrometer.SeaBreezeSpectrometerFeatureSR4,) class ST(SeaBreezeDevice): model_name = "ST" # communication config transport = (USBTransport,) usb_vendor_id = 0x0999 usb_product_id = 0x1000 usb_endpoint_map = EndPointMap(ep_out=0x01, highspeed_in=0x81, highspeed_in2=0x82) usb_protocol = OBP2Protocol # spectrometer config dark_pixel_indices = DarkPixelIndices.from_ranges((1503, 1516)) integration_time_min = 1560 integration_time_max = 6000000 integration_time_base = 10 spectrum_num_pixel = 1516 spectrum_raw_length = 1516 * 2 # ?? spectrum_max_value = 16383 # Triggering (from Ocean ST Manual v10-5): Software, External Rising Edge trigger_modes = TriggerMode.supported("NORMAL", "SOFTWARE", "EDGE") # features feature_classes = ( # sbf.eeprom.SeaBreezeEEPromFeatureOOI, sbf.spectrometer.SeaBreezeSpectrometerFeatureST, sbf.rawusb.SeaBreezeRawUSBBusAccessFeature, sbf.nonlinearity.NonlinearityCoefficientsFeatureOBP2, )