Wasatch.PY
Python application driver for Wasatch Photonics spectrometers
Loading...
Searching...
No Matches
wasatch.WasatchDevice.WasatchDevice Class Reference
Inheritance diagram for wasatch.WasatchDevice.WasatchDevice:
wasatch.InterfaceDevice.InterfaceDevice

Public Member Functions

 __init__ (self, device_id, message_queue=None, alert_queue=None)
 
 acquire_area_scan (self)
 
 acquire_data (self)
 
 acquire_spectrum (self)
 Generate one Reading from the spectrometer, including one optionally-averaged spectrum, device temperatures and other hardware status.
 
 acquire_spectrum_auto_raman (self)
 
 acquire_spectrum_standard (self)
 
 balance_acquisition (self, device=None, mode=None, intensity=45000, threshold=2500, pixel=None, max_integration_time_ms=5000, max_tries=20)
 
 connect (self)
 
 connect_feature_identification (self)
 Given a specified universal identifier, attempt to connect to the device using FID protocol.
 
 disconnect (self)
 
 handle_requests (self, requests)
 
 initialize_settings (self)
 
 monitor_memory (self)
 
 perform_optional_throwaways (self)
 It's unclear how many throwaways are really needed for a stable Raman spectrum, and whether they're really based on number of integrations (sensor stabilization) or time (laser warmup); I suspect both.
 
 set_take_one_request (self, tor)
 
 take_one_averaged_reading (self, label=None)
 
- Public Member Functions inherited from wasatch.InterfaceDevice.InterfaceDevice
 __init__ (self, device_id, message_queue=None, alert_queue=None)
 
 change_setting (self, setting, value=None)
 Request Handlers.
 
 check_alert (self, name)
 
 handle_cmd (self, cmd, arg=None, force=False)
 
 handle_request (self, request)
 
 queue_message (self, setting, value)
 Outgoing Sideline Messages.
 

Public Attributes

 auto_raman = None
 
bool connected = False
 
 hardware = None
 
bool immediate_mode = False
 
int last_battery_percentage = 0
 
 last_complete_acquisition = None
 
 last_memory_check = datetime.now()
 
 late_arriving_timestamp = datetime.now()
 
 process_id = os.getpid()
 
int session_reading_count = 0
 
 settings = SpectrometerSettings()
 
int sum_count = 0
 Aggregate scan averaging.
 
list summed_spectra = None
 Aggregate scan averaging.
 
 take_one_request = None
 
- Public Attributes inherited from wasatch.InterfaceDevice.InterfaceDevice
 alert_queue = alert_queue
 
 device_id = device_id
 
 message_queue = message_queue
 
dict process_f = {}
 
int remaining_throwaways = 0
 

Protected Member Functions

 _init_process_funcs (self)
 
- Protected Member Functions inherited from wasatch.InterfaceDevice.InterfaceDevice
 _refresh_alerts (self)
 Incoming Sideline Messages.
 

Additional Inherited Members

- Protected Attributes inherited from wasatch.InterfaceDevice.InterfaceDevice
 _alerts = set()
 
dict _supported_message_queue_types
 

Detailed Description

This is the top-level interface for controlling and communicating with
Wasatch Photonics USB 2.0 spectrometers using the FeatureInterfaceDevice 
(FID) protocol as defined in ENG-0001.

This class is essentially a clunky wrapper over FeatureInterfaceDevice, 
providing the higher-level InterfaceDevice interface making WasatchDevice a 
peer to AndorDevice, OceanDevice, TCPDevice, IDSDevice etc.

Several points will stand out:

1. WasatchDevice should probably be renamed to FIDDevice, because Wasatch 
   Photonics is also the manufacturer for spectrometers using AndorDevice,
   TCPDevice, IDSDevice etc.

2. This class could be simplified considerably, and possibly merged with
   FeatureIdentificationDevice.

ENLIGHTEN does not instantiate WasatchDevices directly, but instead uses
a WasatchDeviceWrapper to access a single WasatchDevice in a dedicated child
thread.  Other users of Wasatch.PY may of course instantiate a WasatchDevice 
directly, or go straight to a FeatureInterfaceDevice.

Main things this "wrapper" provides:

- Implements software-based, library-based scan averaging (badly). Although
  ARM-based spectrometers can do this in firmware, FX2-based models have to
  do it in software. Between Wasatch.PY and ENLIGHTEN, it seems preferable
  to have this in the Wasatch.PY library, where it is easier for customers
  to use.

- Distinguishes between normal, averaged, AutoRaman and AreaScan  
  acquisitions.

@par History

This class exists because it used to wrap TWO types of spectrometers:

- FeatureIdentificationDevices, which follow the ENG-0001 API and
  have EEPROMs

- StrokerDevices, which didn't have EEPROMs and didn't obey ENG-0001.

I don't know if we even have an ENG document specifying the protocol that the
old Stroker electronics used. It was already deprecated when I started, and 
was rapidly removed from Wasatch.PY...which kind of makes this class 
superfluous :-(

This class (and all who inherit InterfaceDevice) use a somewhat clunky
data-passing and error-handling mechanism via SpectrometerRequest and
SpectrometerResponse objects. That should probably be streamlined.

Part of the legacy "ugly-isms" in this class date back to when ENLIGHTEN
was multi-process rather than multi-threaded, and all data flows between
Wasatch.PY and ENLIGHTEN were via pickled queues :-(

Constructor & Destructor Documentation

◆ __init__()

wasatch.WasatchDevice.WasatchDevice.__init__ ( self,
device_id,
message_queue = None,
alert_queue = None )
@param device_id      a DeviceID instance OR string label thereof

Member Function Documentation

◆ _init_process_funcs()

wasatch.WasatchDevice.WasatchDevice._init_process_funcs ( self)
protected

◆ acquire_area_scan()

wasatch.WasatchDevice.WasatchDevice.acquire_area_scan ( self)
FeatureIdentificationDevice.get_area_scan returns a Reading because it
really needs to return both the AreaScanImage (whatever form that may 
take) and the vertically-binned spectrum (for live display).

@returns a SpectrometerResponse(data=Reading)

◆ acquire_data()

wasatch.WasatchDevice.WasatchDevice.acquire_data ( self)
Process all enqueued settings, then generate one Reading from the device
(a spectrum, followed by all the miscellaneous device metadata like 
temperatures, battery and laser state).

ENLIGHTEN calls this function in a continuous loop in a Thread via 
WrapperWorker.run().

The most important thing is at the end of the function, it calls 
acquire_spectrum() which generates the actual Reading.

@see Controller.acquire_reading

◆ acquire_spectrum()

wasatch.WasatchDevice.WasatchDevice.acquire_spectrum ( self)

Generate one Reading from the spectrometer, including one optionally-averaged spectrum, device temperatures and other hardware status.

This is normally called by self.acquire_data when that function decides it is time to perform an acquisition.

Scan Averaging

IF the driver is in free-running mode, AND performing scan averaging, THEN scan averaging is NOT encapsulated within a single call to this function. Instead, we let ths spectrometer run in free-running mode, collecting individual spectra as per normal, and returning each "partial" readings while "building up" to the final averaged measurement.

That is to say, if scan averaging is set to 10, then this function will get called 10 times, as ticked by the regular free-running timers, before the fully averaged spectrum is returned. A total of 10 (not 11) spectra will be generated and sent upstream: the first 9 "partial" (unaveraged) reads, and the final 10th spectrum which will contain the average of all 10 measurements.

This gives the user-facing GUI an opportunity to update the "collected X-of-Y" readout on-screen, and potentially even graph the traces of in-process partial readings.

HOWEVER, this doesn't make as much sense if we're not in free-running mode, i.e. the subprocess has been slaved to explicit control by the Controller (likely a feature object like BatchCollection), and is collecting exactly those measurements we're being commanded, as they're commanded.

THEREFORE, if the driver IS NOT in free-running mode, then we ONLY return the final averaged spectrum as one atomic operation.

In this case, if scan averaging is set to 10, then A SINGLE CALL to this function will "block" while the full 10 measurements are made, and then a single, fully-averaged spectrum will be returned upstream.

Returns
a Reading wrapped in a SpectrometerResponse
@returns a SpectrometerResponse(data=Reading)

◆ acquire_spectrum_auto_raman()

wasatch.WasatchDevice.WasatchDevice.acquire_spectrum_auto_raman ( self)
@returns a SpectrometerResponse(data=Reading)
@todo fold-in a lot of the post-reading sensor measurements 
      (temperature, interlock etc) provided by acquire_spectrum_standard

◆ acquire_spectrum_standard()

wasatch.WasatchDevice.WasatchDevice.acquire_spectrum_standard ( self)
@returns a SpectrometerResponse(data=Reading)

◆ balance_acquisition()

wasatch.WasatchDevice.WasatchDevice.balance_acquisition ( self,
device = None,
mode = None,
intensity = 45000,
threshold = 2500,
pixel = None,
max_integration_time_ms = 5000,
max_tries = 20 )

◆ connect()

wasatch.WasatchDevice.WasatchDevice.connect ( self)
 Attempt low level connection to the specified DeviceID 

◆ connect_feature_identification()

wasatch.WasatchDevice.WasatchDevice.connect_feature_identification ( self)

Given a specified universal identifier, attempt to connect to the device using FID protocol.

Todo
merge with the hardcoded list in DeviceFinderUSB

◆ disconnect()

wasatch.WasatchDevice.WasatchDevice.disconnect ( self)

◆ handle_requests()

wasatch.WasatchDevice.WasatchDevice.handle_requests ( self,
requests )
Override handle_requests to distinguish between requests handled by
WasatchDevice (CONNECT, ACQUIRE, DISCONNECT) and FeatureInterfaceDevice 
(everything else).

Reimplemented from wasatch.InterfaceDevice.InterfaceDevice.

◆ initialize_settings()

wasatch.WasatchDevice.WasatchDevice.initialize_settings ( self)

◆ monitor_memory()

wasatch.WasatchDevice.WasatchDevice.monitor_memory ( self)

◆ perform_optional_throwaways()

wasatch.WasatchDevice.WasatchDevice.perform_optional_throwaways ( self)

It's unclear how many throwaways are really needed for a stable Raman spectrum, and whether they're really based on number of integrations (sensor stabilization) or time (laser warmup); I suspect both.

Also note the potential need for sensor warm-up, but I think that's handled inside FW.

Optimal would probably be something like "As many integrations as it takes to span 2sec, but not fewer than two."

These are NOT the same throwaways added to smooth spectra over changes to integration time and gain. These are separate throwaways potentially required when waking the sensor from sleep. However, I'm using the same mechanism for tracking (self.remaining_throwaways) for commonality.

Todo
This shouldn't be required at all if we're in free-running mode, or if it's been less than a second since the last acquisition.
Returns
SpectrometerResponse IFF error occurred (normally None)

◆ set_take_one_request()

wasatch.WasatchDevice.WasatchDevice.set_take_one_request ( self,
tor )
This is in WasatchDevice rather than FID because it's WasatchDevice that
decides how to handle each acquire_data.

◆ take_one_averaged_reading()

wasatch.WasatchDevice.WasatchDevice.take_one_averaged_reading ( self,
label = None )
Okay, let's talk about averaging.  We only perform averaging as a 
blocking call in Wasatch.PY when given a TakeOneRequest with non-zero 
scans_to_average, typically from ENLIGHTEN's TakeOneFeature (perhaps via
VCRControls.step, possibly with AutoRaman, or BatchCollection).

Otherwise, normally this thread (the background thread owned by a 
WasatchDeviceWrapper object and running WrapperWorker.run) is in "free-
running" mode, taking spectra just as fast as it can in an endless loop,
feeding them back to the consumer (ENLIGHTEN) over a queue. To keep that 
pipeline "moving," generally we don't do heavy blocking operations down 
here in the background thread.

The new AutoRaman feature makes increased using of scan averaging,
and again kind of wants to be encapsulated down here. And by implication,
all TakeOneRequests should really support encapsulated, atomic averaging.
So the current design is that ATOMIC scans_to_average comes from 
TakeOneRequest, ENLIGHTEN-based averaging in SpectrometerState.

@returns Reading on success, true or false on "stop processing" conditions

Member Data Documentation

◆ auto_raman

wasatch.WasatchDevice.WasatchDevice.auto_raman = None

◆ connected

bool wasatch.WasatchDevice.WasatchDevice.connected = False

◆ hardware

wasatch.WasatchDevice.WasatchDevice.hardware = None

◆ immediate_mode

bool wasatch.WasatchDevice.WasatchDevice.immediate_mode = False

◆ last_battery_percentage

int wasatch.WasatchDevice.WasatchDevice.last_battery_percentage = 0

◆ last_complete_acquisition

wasatch.WasatchDevice.WasatchDevice.last_complete_acquisition = None

◆ last_memory_check

wasatch.WasatchDevice.WasatchDevice.last_memory_check = datetime.now()

◆ late_arriving_timestamp

wasatch.WasatchDevice.WasatchDevice.late_arriving_timestamp = datetime.now()

◆ process_id

wasatch.WasatchDevice.WasatchDevice.process_id = os.getpid()

◆ session_reading_count

int wasatch.WasatchDevice.WasatchDevice.session_reading_count = 0

◆ settings

wasatch.WasatchDevice.WasatchDevice.settings = SpectrometerSettings()

◆ sum_count

int wasatch.WasatchDevice.WasatchDevice.sum_count = 0

Aggregate scan averaging.

◆ summed_spectra

wasatch.WasatchDevice.WasatchDevice.summed_spectra = None

Aggregate scan averaging.

◆ take_one_request

wasatch.WasatchDevice.WasatchDevice.take_one_request = None

The documentation for this class was generated from the following file: