Skip to content

Commit

Permalink
Merge branch 'padalev-main'
Browse files Browse the repository at this point in the history
  • Loading branch information
ap-- committed Feb 21, 2024
2 parents 0be9fcb + 89a672f commit 39278ef
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 9 deletions.
4 changes: 2 additions & 2 deletions src/seabreeze/cseabreeze/c_seabreeze.pxd
Expand Up @@ -64,9 +64,9 @@ cdef extern from "api/seabreezeapi/SeaBreezeAPI.h":
unsigned long spectrometerGetMinimumIntegrationTimeMicros(long deviceID, long spectrometerFeatureID, int *errorCode)
unsigned long spectrometerGetMaximumIntegrationTimeMicros(long deviceID, long spectrometerFeatureID, int *errorCode)
double spectrometerGetMaximumIntensity(long deviceID, long spectrometerFeatureID, int *errorCode)
# int spectrometerGetUnformattedSpectrumLength(long deviceID, long spectrometerFeatureID, int *errorCode)
int spectrometerGetUnformattedSpectrumLength(long deviceID, long spectrometerFeatureID, int *errorCode)
# int spectrometerGetUnformattedSpectrum(long deviceID, long spectrometerFeatureID, int *errorCode, unsigned char *buffer, int bufferLength)
# int spectrometerGetFastBufferSpectrum(long deviceID, long spectrometerFeatureID, int *errorCode, unsigned char *dataBuffer, int dataMaxLength, unsigned int numberOfSampleToRetrieve) # currently 15 max
int spectrometerGetFastBufferSpectrum(long deviceID, long spectrometerFeatureID, int *errorCode, unsigned char *dataBuffer, int dataMaxLength, unsigned int numberOfSampleToRetrieve) # currently 15 max
int spectrometerGetFormattedSpectrumLength(long deviceID, long spectrometerFeatureID, int *errorCode)
int spectrometerGetFormattedSpectrum(long deviceID, long spectrometerFeatureID, int *errorCode, double *buffer, int bufferLength) nogil
int spectrometerGetWavelengths(long deviceID, long spectrometerFeatureID, int *errorCode, double *wavelengths, int length) nogil
Expand Down
119 changes: 112 additions & 7 deletions src/seabreeze/cseabreeze/c_seabreeze_wrapper.pyx
Expand Up @@ -10,7 +10,9 @@ from libcpp cimport bool as bool_t

cimport seabreeze.cseabreeze.c_seabreeze as csb

import struct
import weakref
from collections import namedtuple

import numpy as np

Expand All @@ -36,6 +38,23 @@ class _ErrorCode(object):
DEF _MAXBUFLEN = 32
DEF _MAXDBUFLEN = 256

# Define SpectrumMetadata structure for individual buffered measurements.
SpectrumMetadata = namedtuple(
"SpectrumMetadata",
[
"metadata_protocol_version",
"metadata_length",
"pixel_data_length",
"microsecond_counter",
"integration_time_micros",
"pixel_data_format",
"spectrum_count",
"last_spectrum_count",
"last_microsecond_count",
"scans_to_average"
],
)


# DO NOT DIRECTLY IMPORT EXCEPTIONS FROM HERE!
# ALWAYS IMPORT FROM `seabreeze.spectrometers`
Expand Down Expand Up @@ -637,9 +656,11 @@ cdef class SeaBreezeSpectrometerFeature(SeaBreezeFeature):
identifier = "spectrometer"

cdef readonly int _cached_spectrum_length
cdef readonly int _cached_raw_spectrum_length

def __cinit__(self, SeaBreezeDevice device, int feature_id):
self._cached_spectrum_length = -1
self._cached_raw_spectrum_length = -1

@classmethod
def _get_feature_ids_from_device(cls, SeaBreezeDevice device): # autogenerated
Expand Down Expand Up @@ -785,6 +806,23 @@ cdef class SeaBreezeSpectrometerFeature(SeaBreezeFeature):
self._cached_spectrum_length = int(spec_length)
return self._cached_spectrum_length

@property
def _raw_spectrum_length(self):
"""cached spectrum length
Returns
-------
spectrum_length: int
"""
cdef int error_code
cdef int spec_length
if self._cached_raw_spectrum_length < 0:
spec_length = self.sbapi.spectrometerGetUnformattedSpectrumLength(self.device_id, self.feature_id, &error_code)
if error_code != 0:
raise SeaBreezeError(error_code=error_code)
self._cached_raw_spectrum_length = int(spec_length)
return self._cached_raw_spectrum_length

@cython.boundscheck(False)
def get_wavelengths(self):
"""computes the wavelengths for the spectrometer
Expand Down Expand Up @@ -841,14 +879,81 @@ cdef class SeaBreezeSpectrometerFeature(SeaBreezeFeature):
# unsigned char *buffer, int bufferLength)
raise NotImplementedError("unformatted spectrum")

def get_fast_buffer_spectrum(self):
# TODO: requires wrapping of OBP command GetRawSpectrumWithMetadata
# which returns N raw spectra each with a 64 byte metadata prefix
# int spectrometerGetFastBufferSpectrum(long deviceID, long spectrometerFeatureID, int *errorCode,
# unsigned char *dataBuffer, int dataMaxLength,
# unsigned int numberOfSampleToRetrieve) # currently 15 max
raise NotImplementedError("Not yet supported via cseabreeze. Requires changes to libseabreeze.")
@cython.boundscheck(False)
def get_fast_buffer_spectrum(self, int number_of_samples):
"""acquires raw spectra with metadata from the buffer and returns the spectra with metadata as a list of namedtuples.
Parameters
----------
number_of_samples : int
the number of samples to be retrieved from the spectrometer buffer.
the maximum allowed number depends on the spectrometer (e.g. OceanFX: max. 15).
Returns
-------
list[tuple[SpectrumMetadata, np.ndarray]]
"""
cdef int error_code
cdef int bytes_written
cdef unsigned char* c_buffer = NULL
cdef int out_length
cdef int sample_number
sample_number = int(number_of_samples)

out_length = (64 + self._raw_spectrum_length + 4) * number_of_samples

c_buffer = <unsigned char*> PyMem_Malloc(out_length * sizeof(unsigned char))
if not c_buffer:
raise MemoryError("could not allocate memory for buffer")
try:
bytes_written = self.sbapi.spectrometerGetFastBufferSpectrum(
self.device_id,
self.feature_id,
&error_code,
&c_buffer[0],
out_length,
sample_number,
)
if error_code != 0:
raise SeaBreezeError(error_code=error_code)
data = c_buffer[:bytes_written]
finally:
PyMem_Free(c_buffer)

buffer_data = []
offset = 0
for _ in range(sample_number):
# decode metadata
sm = SpectrumMetadata(
*struct.unpack('HHIQIIIIQH', data[offset : offset + 42])
)

# determine pixel data format from metadata and decode spectrum
intensities_raw = data[
offset + sm.metadata_length
: offset + sm.metadata_length + sm.pixel_data_length
]
if sm.pixel_data_format == 1:
intensities = np.frombuffer(intensities_raw, dtype=np.uint16)
elif sm.pixel_data_format == 2:
# note:
# we could create a 32bit view and use stride tricks and
# bit-masking 0x00ffffff to get 24bit data into a 32bit array...
raise NotImplementedError("Numpy lacks support for 24bit unsigned integers")
elif sm.pixel_data_format == 3:
intensities = np.frombuffer(intensities_raw, dtype=np.uint32)
elif sm.pixel_data_format == 4:
intensities = np.frombuffer(intensities_raw, dtype=np.single)
else:
raise SeaBreezeError(f"Unknown Pixel Data Format {sm.pixel_data_format!r}")

# add data to return list
buffer_data.append((sm, intensities))

# depending on the individual Dataset length, add offset to next Dataset.
# There is 4 bytes of unused data after every spectrum for some reason. So add that as well.
offset += sm.metadata_length + sm.pixel_data_length + 4
return buffer_data

cdef class SeaBreezePixelBinningFeature(SeaBreezeFeature):

Expand Down

0 comments on commit 39278ef

Please sign in to comment.