ap-- / python-seabreeze

Python module for OceanOptics spectrometers
https://python-seabreeze.readthedocs.io
MIT License
209 stars 81 forks source link

Basic SR2 support #217

Closed avedillopy closed 11 months ago

avedillopy commented 11 months ago

spectrometer and system information

current problem

Hi! I have opened this issue as I am unfamiliar with creating a pull request and wish to share the modifications I made to pyseabreeze for basic support of the SR2 spectrometer. To achieve this, I adapted the SR4 classes due to their similarity with the spectrometer.

From the file /seabreze/pyseabreeze/devices.py I added the next piece of code:

class SR2(SeaBreezeDevice):
    model_name = "SR2"

    # communication config
    transport = (USBTransport,)
    usb_vendor_id = 0x0999
    usb_product_id = 0x1001
    usb_endpoint_map = EndPointMap(ep_out=0x01, highspeed_in=0x81)
    usb_protocol = OBP2Protocol

    # spectrometer config
    dark_pixel_indices = DarkPixelIndices.from_ranges((0,24))
    integration_time_min = 1  # 1 us
    integration_time_max = 2000000  # 2 s
    integration_time_base = 1
    spectrum_num_pixel = 2110
    spectrum_raw_length = (2110 * 2) + 32  # Metadata
    spectrum_max_value = 65535
    trigger_modes = TriggerMode.supported("OBP_NORMAL")

    # features
    feature_classes = (sbf.spectrometer.SeaBreezeSpectrometerFeatureSR2,)

From the file /seabreze/pyseabreeze/features/spectrometer.py I added the next piece of code:

class SeaBreezeSpectrometerFeatureSR2(SeaBreezeSpectrometerFeatureOBP):

    def _get_spectrum_raw(self) -> NDArray[np.uint8]:
        timeout = int(
            self._integration_time_max * 1e-3
            + self.protocol.transport.default_timeout_ms
        )
        datastring = self.protocol.query(0x000_01C_00, timeout_ms=timeout)  
        return numpy.frombuffer(datastring, dtype=numpy.uint8)  # type: ignore

    def get_intensities(self) -> NDArray[np.float_]:
        tmp = self._get_spectrum_raw()
        # 32byte metadata block at beginning
        ret = numpy.array(
            struct.unpack("<" + "H" *self._spectrum_length, tmp[32:]),
            dtype=numpy.double,
        )
        return ret * self._normalization_value

    def set_trigger_mode(self, mode: int) -> None:
        if mode in self._trigger_modes:
            self.protocol.send(0x000_00D_01, mode, request_ack=True)  
        else:
            raise SeaBreezeError("Only supports: %s" % str(self._trigger_modes))

    def set_integration_time_micros(self, integration_time_micros: int) -> None:
        t_min = self._integration_time_min
        t_max = self._integration_time_max
        if t_min <= integration_time_micros < t_max:
            i_time = int(integration_time_micros / self._integration_time_base)
            self.protocol.send(0x000_00C_01, i_time)  
        else:
            raise SeaBreezeError(f"Integration not in [{t_min:d}, {t_max:d}]")

    def get_wavelengths(self) -> NDArray[np.float_]:
        data = self.protocol.query(0x000_011_00)  
        num_coeffs = len(data) // 4
        assert len(data) % 4 == 0  # ???
        assert num_coeffs > 1  # ???
        coeffs = struct.unpack("<" + "f" * num_coeffs, data)[1:]
        # and generate the wavelength array
        indices = numpy.arange(self._spectrum_length, dtype=numpy.float64)
        return sum(wl * (indices**i) for i, wl in enumerate(coeffs))  # type: ignore

And to the 10-oceanoptics.rule I added:

ATTR{idVendor}=="0999", ATTR{idProduct}=="1001", SYMLINK+="sr2-%n", MODE:="0666"

To verify the proper functioning of both spectrometers, I compare the data acquired with the seabreeze library on a Raspberry Pi and the data acquired with OceanView on a Windows 11 PC.

The following comparison is conducted with the Electric Dark option activated in OceanView.

image

The following image is achieved by subtracting the mean of the first 24 pixels for the SR2 spectra and the first 17 pixels for the SR4 spectra manually, simulating the Electric Dark option.

image

As far as I have been able to test, the data obtained with the SR2 and SR4 spectrometers are practically the same as those obtained with OceanView.

ap-- commented 11 months ago

Hello @avedillopy

Thank you for opening the issue and sharing your code! This is beautifully executed and can be added to python-seabreeze as is.

Would you want me to walk you through on how to prepare a PR, or would you prefer I add this myself?

Thanks again for your contribution, Andreas 😃

ap-- commented 11 months ago

There should be a new release out soon with your code added to support SR2 spectrometers!

Thanks again! Andreas 😃