Measurements

The JII-MultispeQ package allows the direct connection to a MultispeQ using a python script, instead of using a separate application. In the following examples, it’s shown how to connect to a MultispeQ, use protocols or commands with the device and save the measurements to your local computer, as well as analyze them with custom functions and load them for a later analysis.

Important

The JII-MultispeQ package is for local measurements. If the measurements are intended to be saved with a project online, use the PhotosynQ, Inc. Desktop or Mobile app.

Connect a MultispeQ

Make sure your MultispeQ is powered on and connect it to your computer either using USB or Bluetooth®. After that, you have to open the connection by selecting the Serial port the device is connected to. Depending on the operating system used, the port names have different names.

Basic Serial Port Names depending on the OS

Operating system

USB

Bluetooth®

Windows

COM…

COM…

macOS

usbmodem…

Instrument-Name

Linux

ACM…

Not tested

Note

Connecting a Device

  1. When connecting the device via USB, make sure the mirco-USB cable is designed for data transfer, not just charging.

  2. When using Bluetooth®, the required pairing code is 1234.

Available Ports

Use the get_ports() to see the list of available ports on your computer. Make sure to use the port, not just the name when selecting your device’s port.

Example: Get a list of available serial ports and connected devices.
1import jii_multispeq.device as _device
2
3## View Ports
4_device.get_ports( )
Example: Output of the jii_multispeq.device.get_ports() function (on macOS).
Port                             Name                        Description    Manufacturer    Product
-------------------------------  --------------------------  -------------  --------------  ----------
/dev/cu.debug-console            cu.debug-console            n/a
/dev/cu.Bluetooth-Incoming-Port  cu.Bluetooth-Incoming-Port  n/a
/dev/cu.usbmodem42949672951      cu.usbmodem42949672951      USB Serial     Teensyduino     USB Serial
/dev/cu.MultiSpeQ                cu.MultiSpeQ                n/a

Establish a Connection

Once the serial port is identified, use the following code to open the connection to your MultispeQ device. The returned connection (serial.serialposix.Serial) will be used with all other functions.

Example: Open a connection to a MultispeQ device.
1import jii_multispeq.device as _device
2
3## Establish connection
4_connection = _device.connect( "/com1" )

Test the Connection

When the connection is established, it needs to be checked it the MultispeQ device is properly responding. The is_connected() checks if the connection is open and identifies the MultispeQ.

Example: Check if the connection is open and the MultispeQ is responding
1## Test if connection is open and device is communicating
2if _device.is_connected( _connection ):

Take a Measurement

If the MultispeQ device is connected and the connection is open, measurements can be run now. The example below shows how a single relative Chlorophyll (SPAD) measurement can be taken.

Example: Take a SPAD measurement with the MultispeQ
 1import jii_multispeq.measurement as _measurement
 2import jii_multispeq.device as _device
 3
 4## MultispeQ Protocol
 5spad_protocol = [{
 6  "spad": [1]
 7}]
 8
 9## MultispeQ Response Analysis
10def spad_fn( _data ):
11  output = {}
12  output["SPAD"] = _data["spad"][0]
13  return output
14
15## Establish connection
16_connection = _device.connect( "/com1" )
17
18## Test if connection is open and device is communicating
19if _device.is_connected( _connection ):
20
21  data, crc32 = _measurement.measure( _connection, spad_protocol, 
22                                     None, 
23                                     'Single SPAD meaurement' )
24
25  ## Run the analysis function
26  data = _measurement.analyze( data, spad_fn )
27
28  ## View response as a table
29  _measurement.view( data )
30
31## Close the connection
32_device.disconnect( _connection )
Example: Output from a SPAD measurement with the MultispeQ
Parameter        Value                             Type
---------------  --------------------------------  ---------------
created_at       2024-12-21T10:38:11.498360+01:00  <class 'str'>
device_battery   95                                <class 'int'>
device_firmware  2.3465                            <class 'float'>
device_id        01:12:53:##                       <class 'str'>
device_name      MultispeQ                         <class 'str'>
device_version   2                                 <class 'str'>
md5_measurement  3565bc573416002424c6072bdfb033f2  <class 'str'>
md5_protocol     b133a7f804cd1d1608da0577b3d89f72  <class 'str'>
notes            Single SPAD meaurement            <class 'str'>
protocol         n/a                               <class 'list'>
SPAD             24.548                            <class 'float'>

Save Measurement to Disk

The jii_multispeq.measurement.measure() allows to save the data from the MultispeQ to the disk as well. The example below shows how to only return the data, setting the filename to None or returning the data and saving the output to a file by specifying the file name and a directory. If the filename is not set, or defined as auto, a filename is generated based on date and time (YYYY-MM-DD_HHmm). In case the directory is not set, files will automatically saved to the ./local directory.

If a defined directory doesn’t exists it will be created. Files will not be overwritten, to prevent data loss. In case a filename already exists, a number will be appended to the filename.

Example: Take a SPAD measurement with the MultispeQ and save to disk
 1## Only return data
 2data, crc32 = _measurement.measure( _connection, spad_protocol,
 3                                  None,
 4                                  'Single SPAD meaurement' )
 5
 6## Save data to defined file and define directory
 7data, crc32 = _measurement.measure( _connection, spad_protocol,
 8                                  "SPAD",
 9                                  'Single SPAD meaurement',
10                                  './chlorophyll-measurements' )
11
12## Save data automatically
13data, crc32 = _measurement.measure( _connection, spad_protocol,
14                                  'Single SPAD meaurement' )

Note

Available Protocols

The JII-MultispeQ-Protocols package provides preset protocols for the MultispeQ. In addition, it provides a validation function for custom MultispeQ protocols, helping to identify potential issues with a protocol before running it.

Load Saved Measurement(s)

Saved measurement files can be loaded into a list of dictionaries (list[dict]) or they can be loaded into a pandas dataframe.

File Summary

It might be helpful to list files in the selected directory, to check what the content is without having to open individual files using the list_files() function of the measurement module. The filenames, the date and time the measurement was saved and also the notes are listed by individual directories.

Example: View all the files in a directory including all sub-directories
1import jii_multispeq.measurement as _measurement
2
3## View Files in Directories
4_measurement.list_files('./local')

Load Files

Example: View all the files in a directory including all sub-directories
1import jii_multispeq.measurement as _measurement
2
3## Load Files Into a List
4data = _measurement.load_files('./local')
5
6## Load Files Into a Dataframe
7df = _measurement.load_files_df('./local')

Recursive Loading

Example: View all the files in a directory including all sub-directories
1## Load Files Into a List Using Recursive Search
2data = _measurement.load_files('./local', True)
Example: Take a SPAD measurement with the MultispeQ and save to disk
MultispeQ/
├── local/
│   ├── Measurement.json
│   ├── Measurement - 1.json
│   ├── Measurement - 2.json
│   ├── Measurement - 3.json
│   ├── Measurement - 4.json
│   └── sub-directory/
       ├── Measurement.json
       ├── Measurement - 1.json
       └── Measurement - 2.json
└── script.py

Process Data

Example: View all the files in a directory including all sub-directories
1def fn( _data ):
2  ## Data Processing here
3  return _data
4
5## Load Files and Apply Function on File Content
6data = _measurement.load_files('./local', fn=fn)

TL;DR

The following functions are available in the applications and probably more convinient to use.

Send a Command

Example: Sending a command to a connected MultispeQ device.
 1import jii_multispeq.device as _device
 2
 3## Establish connection
 4_connection = _device.connect( "/com1" )
 5
 6## Test if connection is open and device is communicating
 7if _device.is_connected( _connection ):
 8
 9  ## Send the command for device settings (memory)
10  response = _device.send_command( _connection, "print_memory" )
11
12  ## View response
13  print( response )
14
15## Close the connection
16_device.disconnect( _connection )
Example: Response from connected MultispeQ device for command print_memory.
 1{
 2  "device_name": "MultispeQ",
 3  "device_version": 2,
 4  "device_id": "01:12:53:##",
 5  "firmware": 2.3465,
 6  "compiled": [ "Aug  2 2021", "16:00:28" ],
 7  "calTimes": [ 1627920106, 1065353216, 1627920743, 1627920666, 1627920788, 0, -1050673152, 0 ],
 8  "device_mod": 0,
 9  "mag_address": 14,
10  "mag_x_scale": 800.0,
11  "mag_y_scale": 837.0,
12  "mag_z_scale": 694.0,
13  "max_PAR": [ 351, 4095, 3054, 4095, 4095, 4095, 1859, 4095, 4095, 4095 ],
14  "light_slope_all": [ 0.362 ],
15  "light_slope_r": [ -0.235 ],
16  "light_slope_g": [ -0.136 ],
17  "light_slope_b": [ -0.01 ],
18  "light_yint": [ 0.0 ],
19  "bleed3": [
20    [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ],
21    [ 31.0, 173.0, 360.0, 401.0, 476.0, 726.0, 860.0, 986.0 ],
22    [ 7.0, 19.0, 33.0, 39.0, 63.0, 134.0, 178.0, 230.0 ],
23    [ 9.0, -48.0, -48.0, -46.0, -48.0, -141.0, -118.0, -123.0 ],
24    [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ],
25    [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ],
26    [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ],
27    [ -10.0, 44.0, 41.0, 84.0, 125.0, 206.0, 312.0, 420.0 ],
28    [ 6.0, 10.0, -2.0, -5.0, 1.0, 6.0, -11.0, 7.0 ],
29    [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ]
30  ],
31  "thickness_a": 68202.516,
32  "thickness_b": -6.395,
33  "thickness_c": 149665.172,
34  "mag_orientation": 0.0,
35  "open_position": 39965.0,
36  "closed_position": 41634.0,
37  "spad_scale": 45.829,
38  "spad_offset": -100.0,
39  "spad_yint": 6.805,
40  "blink_mode": 0,
41  "pilot_blink": 0,
42  "status_blink": 1,
43  "shutdown_time (s)": 1800,
44  "usb_on": 0,
45  "pix_pin": 14,
46  "par_map": [
47    [ 58, 95, 131, 203, 351, 351, 351 ],
48    [ 64, 101, 136, 206, 348, 771, 1477 ],
49    [ 39, 248, 455, 870, 1699, 3054, 3054 ],
50    [ 127, 371, 615, 1101, 2075, 4995, 9862 ],
51    [ 150, 177, 203, 257, 363, 683, 1217 ],
52    [ 150, 417, 683, 1217, 2283, 4095, 4095 ],
53    [ 73, 127, 180, 286, 497, 1132, 1859 ],
54    [ 150, 283, 417, 683, 1217, 2817, 4095 ],
55    [ 150, 417, 683, 1217, 2283, 4095, 4095 ],
56    [ 150, 283, 417, 683, 1217, 2817, 4095 ]
57  ],
58  "detector_offsets": [ 8.0, 0.0, -84.0, 0.0 ],
59  "par_tweak": 0.749
60}

Get Device Settings

The device’s settings can be viewed using the print_memory command like in the example above, but there is also a dedicated function for it. The code example below shows how to use this function.

In addition, the view() of the measurement module is used to print it out in a more convinient way.

Example: Getting the device settings from a connected MultispeQ device.
 1import jii_multispeq.device as _device
 2import jii_multispeq.measurement as _measurement
 3
 4## Establish connection
 5_connection = _device.connect( "/com1" )
 6
 7## Test if connection is open and device is communicating
 8if _device.is_connected( _connection ):
 9
10  ## Send the command for device settings (memory)
11  response, crc32 = _device.get_memory( _connection )
12
13  ## View response as a table
14  _measurement.view( response )
15
16## Close the connection
17_device.disconnect( _connection )
Example: Settings from a connected MultispeQ device.
 1Parameter          Value        Type
 2-----------------  -----------  ---------------
 3bleed3             n/a          <class 'list'>
 4blink_mode         0            <class 'int'>
 5calTimes           n/a          <class 'list'>
 6closed_position    41634.0      <class 'float'>
 7compiled           n/a          <class 'list'>
 8detector_offsets   n/a          <class 'list'>
 9device_id          01:12:53:##  <class 'str'>
10device_mod         0            <class 'int'>
11device_name        MultispeQ    <class 'str'>
12device_version     2            <class 'int'>
13firmware           2.3465       <class 'float'>
14light_slope_all    n/a          <class 'list'>
15light_slope_b      n/a          <class 'list'>
16light_slope_g      n/a          <class 'list'>
17light_slope_r      n/a          <class 'list'>
18light_yint         n/a          <class 'list'>
19mag_address        14           <class 'int'>
20mag_orientation    0.0          <class 'float'>
21mag_x_scale        800.0        <class 'float'>
22mag_y_scale        837.0        <class 'float'>
23mag_z_scale        694.0        <class 'float'>
24max_PAR            n/a          <class 'list'>
25open_position      39965.0      <class 'float'>
26par_map            n/a          <class 'list'>
27par_tweak          0.749        <class 'float'>
28pilot_blink        0            <class 'int'>
29pix_pin            14           <class 'int'>
30shutdown_time (s)  1800         <class 'int'>
31spad_offset        -100.0       <class 'float'>
32spad_scale         45.829       <class 'float'>
33spad_yint          6.805        <class 'float'>
34status_blink       1            <class 'int'>
35thickness_a        68202.516    <class 'float'>
36thickness_b        -6.395       <class 'float'>
37thickness_c        149665.172   <class 'float'>
38usb_on             0            <class 'int'>