Skip to content

Commit 6f957ea

Browse files
committed
Improve module documentation
1 parent b861634 commit 6f957ea

File tree

1 file changed

+56
-65
lines changed

1 file changed

+56
-65
lines changed

neo/rawio/alphaomegarawio.py

Lines changed: 56 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -4,52 +4,15 @@
44
This module expect default channel names from the AlphaOmega record system (RAW
55
###, SPK ###, LFP ###, AI ###,…).
66
7-
This module reads all *.lsx and *.mpx files in a directory (not recursively).
7+
This module reads all \*.lsx and \*.mpx files in a directory (not recursively).
88
Listing files
99
1010
The specifications are mostly extracted from the "AlphaRS User Manual V1.0.1.pdf"
1111
manual provided with the AlphaRS hardware. The specifications are described in
1212
the chapter 6: ALPHARS FILE FORMAT. See at the end of this file for file format
1313
blocks description.
1414
Some informations missing from the file specifications were kindly provided by
15-
AlphaOmega engineers:
16-
- strings are NULL terminated and encoded in ASCII
17-
- m_ResourceVersion (4 bytes) is the C++ version used to compile the logging
18-
(data recording) library: each byte is read separately as a char and then
19-
concatenated to create the original value (e.g.: b"\x00\x01\x00\x00' -> 100)
20-
- m_ModeSpike (2 bytes) is read as hex data: 0xMCCC:
21-
- M: 1=Master, 2=Slave
22-
- CCC: linked channel
23-
Be carefull here, the first byte cover MC and the second byte the last
24-
part of the linked channel CC
25-
- m_nSpikeCount: the total number of segments captured on this channel
26-
- m_SpikeColor: follows the COLORREF convention from Windef.h:
27-
hex value: 0x00bbggrr
28-
- for Type E Stream Data Block:
29-
- m_uTimeStamp is an unsigned int32 (unsigned long) counter that is set
30-
to 0 at hardware boot and advances at the sampling rate of the system
31-
- for analog/digital data channels:
32-
- m_FirstSampleNumber: this is the same as m_uTimeStamp. Meaning the
33-
value is the timestamp of the first sample in the same block which is
34-
the index of the sample from the HW boot
35-
- final block: the stop condition for reading MPX blocks is based on the
36-
length of the block: if the block has length 65535 (or -1 in signed integer
37-
value) we know we have reached the end of the file
38-
39-
Still questions/need info:
40-
- for digital data channels:
41-
- m_SampleNumber and m_Value seems inverted in data compared to
42-
specification
43-
- m_Value seems to have wrong type: it looks like ushort in data
44-
- for analog channels:
45-
- what is the m_Duration value?
46-
- for segmented channel:
47-
- how do we extract the waveforms?
48-
- for block type E:
49-
- how do we decode the stream data (we need S(t)?reamFormat.h header)
50-
51-
52-
Author: Thomas Perret <[email protected]>
15+
AlphaOmega engineers.
5316
5417
.. note::
5518
Not a lot of memory optimization effort was put into this module. You should
@@ -59,10 +22,13 @@
5922
1. First search TODO in this file.
6023
2. add IO class with :py:class:`neo.io.basefromrawio.BaseFromRaw`
6124
3. Make TTL events -> epochs? We could describe TTL 0/1 events as epochs:
62-
when the TTL goes to 1=beginning of epoch
63-
0=end of epoch
64-
4. Once we know how to decode Stream AlphaOmega events add them to an event
65-
channel
25+
when the TTL goes to 1=beginning of epoch 0=end of epoch
26+
4. decode stram data (block type E) using StreamFormat.h file (not provided
27+
with this code)
28+
5. (4.bis) Once we know how to decode Stream AlphaOmega events add them to
29+
an event channel
30+
31+
Author: Thomas Perret <[email protected]>
6632
"""
6733

6834
import io
@@ -89,15 +55,22 @@
8955

9056
class AlphaOmegaRawIO(BaseRawIO):
9157
"""
92-
Handles several blocks and segments.
58+
AlphaOmega MPX file format 4 reader. Handles several blocks and segments.
59+
60+
A block is a recording defined in a \*.lsx file. AlphaOmega record system
61+
creates such files every time the software is opened.
9362
94-
A block is a recording define in a *.lsx file. AlphaOmega record system
95-
creates a new file each time the software is closed then re-opened.
9663
A segment is a continuous record (when record starts/stops).
97-
If file are not referenced in a *.lsx file, they are put in the same block.
64+
If file are not referenced in a \*.lsx file, they are put in the same block.
65+
66+
:param dirname: folder from where to load the data
67+
:type dirname: str or Path-like
68+
:param prune_channels: if True removes the empty channels, defaults to True
69+
:type prune_channels: bool
9870
99-
Because channels must be gathered into coherent streams, channels names MUST
100-
be the default channel names in AlphaRS software (or Alpha LAB SNR).
71+
.. warning::
72+
Because channels must be gathered into coherent streams, channels names
73+
MUST be the default channel names in AlphaRS software (or Alpha LAB SNR).
10174
"""
10275

10376
extensions = ["lsx", "mpx"]
@@ -274,6 +247,12 @@ def _read_file_blocks(self, filename, prune_channels=True):
274247

275248
length, block_type = HeaderType.unpack(header_bytes)
276249
if length == 65535:
250+
# The stop condition for reading MPX blocks is base on the
251+
# length of the block: if the block has length 65535 (or -1
252+
# in signed integer value) we know we have reached the end
253+
# of the file
254+
# We could also check that we are at the end of the file
255+
# after this block
277256
break
278257

279258
if block_type == b"h":
@@ -313,11 +292,10 @@ def _read_file_blocks(self, filename, prune_channels=True):
313292
metadata["max_sample_rate"] = max(
314293
metadata["max_sample_rate"], sample_rate * 1000
315294
)
316-
if (
317-
amplitude <= 5
318-
and metadata["application_name"] == "ALab SNR"
319-
):
320-
self.logger.warning("This should be checked!")
295+
if amplitude <= 5:
296+
# This is true for any logging software for map
297+
# version >4 (specs say only for ALab SNR but AO
298+
# engineer says it's true for any software)
321299
amplitude = 1250000 / 2 ** 15
322300
if mode == 0:
323301
# continuous analog channel definition block
@@ -609,11 +587,12 @@ def _merge_segments(self, factor_period=1.5):
609587
- The two segment must also have the same record date
610588
(YEAR-MONTH-DAY). This could potentially lead to errors if
611589
recordings are longer than a day or run over the night
612-
- The next segment must have a datetime greater or equal thant the
613-
previous segment (datetime : YEAR-MONTH-DAT-HOUR-MINUTE-SECOND)
590+
- The next segment must have a datetime greater or equal than the
591+
previous segment (datetime : YEAR-MONTH-DAY-HOUR-MINUTE-SECOND)
614592
615593
:param factor_period: how many sample period to consider the next segment
616-
is part of the previous one. This should be 1 <= `factor_period` < 2
594+
is part of the previous one. This should be 1 <= `factor_period` < 2,
595+
defaults to 1.5
617596
:type factor_period: float
618597
"""
619598
for block in self._blocks:
@@ -1090,7 +1069,8 @@ def _rescale_epoch_duration(self, raw_duration, dtype, event_channel_index):
10901069

10911070

10921071
def decode_string(encoded_string):
1093-
"""All AlphaOmega strings are NULL terminated and ASCII encoded"""
1072+
"""According to AlphaOmega engineers, all strings are NULL terminated and
1073+
ASCII encoded"""
10941074
return encoded_string[: encoded_string.find(b"\x00")].decode("ascii")
10951075

10961076

@@ -1113,6 +1093,8 @@ def get_name(f, name_length):
11131093
Other blocks exist in the data but are ignored in this implementation as per the
11141094
specification: "Any block type other than the ones described below should be
11151095
ignored."
1096+
1097+
All data is little-endian (hence the '<' in Struct calls).
11161098
"""
11171099

11181100
SDataHeader = struct.Struct("<xlhBBBBBBHBxddlB10s4sxl")
@@ -1136,7 +1118,8 @@ def get_name(f, name_length):
11361118
- application_name (10-char string): name of the recording application.
11371119
Should be "ARS" for AlphaRS hardware or "ALab SNR" for AlphaLab SnR hardware
11381120
- resource_version (4-char string): C++ version used to compile recording
1139-
software
1121+
software: each byte is read separately as a char and concatenated and cast
1122+
into an int to create the original value (e.g.: b"\x00\x01\x00\x00" -> 100)
11401123
- alignment byte: ignore
11411124
- reserved (long): not used
11421125
"""
@@ -1155,7 +1138,8 @@ def get_name(f, name_length):
11551138
- is_analog (short): 0=Digital, 1=Analog
11561139
- is_input (short): 0=Output, 1=Input
11571140
- channel_number: the (unique) channel number identifier from the recording software
1158-
- alignment byte: ignore
1141+
- alignment byte: ignore, this and the following bytes are COLORREF
1142+
convention from Windef.h: hex value: 0x00bbggrr
11591143
- spike_color_blue (unsigned char)
11601144
- spike_color_green (unsigned char)
11611145
- spike_color_red (unsigned char)
@@ -1164,10 +1148,15 @@ def get_name(f, name_length):
11641148
"""
11651149
Then if is_analog and is_input:
11661150
- mode (short): 0=Continuous, 1=(Level or Segmented)
1167-
- amplitude (float): bit resolution
1151+
- amplitude (float): bit resolution. For MAP file version 4 if amplitude < 5
1152+
amplitude = 1_250_000/2**15
11681153
- sample_rate (float): in kHz (or more precisely in kilosample per seconds)
1169-
- spike_count (short): total number of segments captured in this channel
1170-
- mode_spike: see top docstring of this module
1154+
- spike_count (short): size of each data block (short) + timestamp (unsigned long)
1155+
- mode_spike (2 bytes): read as hex data 0xMCCC:
1156+
- M: 1=Master, 2=Slave
1157+
- CCC: linked channel
1158+
Be carefull here, the first byte cover MC and the second byte the last
1159+
part of the linked channel CC
11711160
"""
11721161
SDefContinAnalog = struct.Struct("<fh")
11731162
"""
@@ -1233,7 +1222,8 @@ def get_name(f, name_length):
12331222
Then for analog channels:
12341223
- sample_value (n-short): array of data values
12351224
- first_sample_number (ulong): for continuous channels: first sample number
1236-
in the channel records
1225+
in the channel records. This is the timestamp (see :attr:`SAOEvent`) of
1226+
the first sample in the data blocks
12371227
"""
12381228
SDataChannelDigital = struct.Struct("<Lh")
12391229
"""
@@ -1258,7 +1248,8 @@ def get_name(f, name_length):
12581248
SAOEvent = struct.Struct("<cL")
12591249
"""Type E: stream data block:
12601250
- type_event (char): event type only b"S" for now
1261-
- timestamp (ulong): see above
1251+
- timestamp (ulong): a counter initialized at 0 at hardware boot and that
1252+
advances at the sampling rate of the system
12621253
- stream_data (n-char): Stream Data - spec says: "Refer to SreamFormat.h or
12631254
use dll to decipher this stream of data"; n=length-8
12641255
"""

0 commit comments

Comments
 (0)