[![Build Status](https://travis-ci.com/MideTechnology/idelib.svg?branch=main)](https://travis-ci.com/MideTechnology/idelib)


# _idelib_ README

_idelib_ is a Python API for accessing [enDAQ's](http://endaq.com) IDE recordings.  The IDE format is
an [EBML](http://matroska-org.github.io/libebml/specs.html) encoded file using a 
custom schemata.  This library utilizes our 
[ebmlite](https://github.com/MideTechnology/ebmlite) to parse the files, and 
provides classes that make reading data simple.

## IDE File Basics

### What's an IDE file?

An IDE file is a read-only hierarchical data format that stores recording 
information generated by an [enDAQ sensor device](http://endaq.com/collections/endaq-sensors-shock-vibration-s-series). It contains both time-indexed 
data from several different kinds of sensors (like acceleration, pressure, 
temperature, etc.), as well as metadata about the recording device (like device 
serial number, model number, device name, etc.) and recording settings.

### Accessing an IDE file

The top-level interface for an IDE file is the `Dataset` object, through which 
one can access all of the above-listed information. When you open a file for 
reading, for example, this is the type of object that is returned.

#### Opening an IDE File

You can open an IDE file like so:

```python
filename = "your_file.IDE"
with idelib.importFile(filename) as ds:
    print(type(ds))
```

**Note**: a `Dataset` object perfoms _lazy-loading_, meaning that it only loads 
information as is needed. As a result, it internally retains a handle to the 
source file which after use needs to be closed. This can be accomplished by 
either using `Dataset` as a _context manager_ (as seen above; this is the 
recommended method), or by using `Dataset` as a normal value and calling the 
`close()` method manually:

```python
filename = "your_file.IDE"
ds = idelib.importFile(filename)
# use `ds` here
ds.close()  # remember to close the file after use!
```

### Getting recording data

#### Channels and Subchannels



IDE files organize recording data into _channels_ and _subchannels_. A channel 
encapsulates data recorded by a particular individual sensor on the device 
(e.g., XYZ acceleration from the ADXL375 DC Accelerometer); a subchannel, if 
present, specifies a particular data stream within a channel (e.g., the 
X-coordinate acceleration from the ADXL375 DC Accelerometer).

At the top-level, a `Dataset` object has a `channels` member, which is a dict 
of all channels recorded in the file. The dict is keyed by channel id numbers, 
with `Channel` objects in the values.

Each `Channel` object has a `subchannels` member, which is a list of 
`Subchannel` objects. If the channel has no subchannels, this member will be `None`.

The below table lists current conventions for channels across all enDAQ sensors:

| (Abbreviated) Product No. | Description                                                                       | Example Product Nos.    |
|--------------------------:|-----------------------------------------------------------------------------------|-------------------------|
| S-D                       | enDAQ S-series devices with single digital accelerometers                         | S3-D16, S4-D40          |
| S-DD                      | enDAQ S-series devices with dual digital accelerometers                           | S1-D100D40, S2-D25D16   |
| S-ED                      | enDAQ S-series devices with an analog electrocapacitive and digital accelerometer | S5-E25D40, S4-E100D40   |
| S-RD                      | enDAQ S-series devices with an analog piezoresistive and digital accelerometer    | S4-R500D40, S5-R2000D40 |
| SSX                       | Midé Slam Stick X data recorders                                                  | SSX                     |
| SSC                       | Midé Slam Stick C data recorders                                                  | SSC                     |
| SSS                       | Midé Slam Stick S data recorders                                                  | SSS                     |

The below table lists channel ID numbers used in a recording file based on the 
recording device’s product number (device series numbers and accelerometer 
sensitivity ranges are omitted when applicable to all such devices):

| Sensor                | Channel | Valid Devices                      | Suchannels                                   |
|----------------------:|:--------|------------------------------------|----------------------------------------------|
| Main Accelerometer    | 8       | S-RD, S-ED, SSS, SSX               | X-, Y-, Z-axis Acceleration                  |
| 16/200g Accelerometer | 32      | S-DD, SSX, SSS, SSC, S-D16, S-D200 | X-, Y-, Z-axis Acceleration                  |
| 8/40g Accelerometer   | 80      | S-RD, S-DD, S-ED, S-D40, S-D8      | X-, Y-, Z-axis Acceleration                  |
| IMU Gyroscope         | 47      | All<sup>1</sup>                    | X-, Y-, Z-axis Rotation                      |
| Absolute Orientation  | 65      | All<sup>1</sup>                    | X-, Y-, Z-, W-axis Quaternion; Acc           |
| Relative Orientation  | 70      | All<sup>1</sup>                    | X-, Y-, Z-, W-axis Quaternion                |
| MPL3115               | 36      | All<sup>1</sup>                    | Pressure, Temperature <sup>2</sup>           |
| MS8607                | 59      | All<sup>1</sup>                    | Pressure, Temperature, Humidity <sup>3</sup> |
| SI1133                | 76      | All<sup>1</sup>                    | Lux, UV                                      |

<sup>1</sup> excluding early SSC/SSS/SSX models

<sup>2</sup> 1 Hz Internal Measurements

<sup>3</sup> 10 Hz Control Pad Measurements

To simply use all recording data, we can iterate through each subchannel in a dataset like so:

```python
for ch in ds.channels.values():
    for sch in ch.subchannels:
        print(sch)
```

#### EventArrays and raw data

Each `Channel` and `Subchannel` object has a `getSession()` method, which 
returns an `EventArray` object. `EventArray` is a wrapper around a channel's 
underlying recording data that loads data on demand from the source file. You 
can index an `EventArray` (e.g., `eventarray[i]` for some index `i`) to get a 
numpy `ndarray` of data. Data is organized in an n-dimensional array.

For subchannels, this will always be a 2-by-n array, where n is the number of 
samples recorded; `eventarray[1]` indexes the samples, `eventarray[0]` indexes 
the respective timestamps.

For channels, this will be a (c+1)-by-n array, where n is the number of samples 
recorded and c is the number of subchannels; `eventarray[1:]` indexes the 
samples, `eventarray[0]` indexes the respective timestamps.

### Getting metadta

`Dataset` makes available some basic metadata. Some useful pieces of information 
are stored directly as members:

```python
>>> ds.filename
'C:\\Users\\Public\\SSX09546_019.IDE'
```

Other data is stored in the dict member recorderInfo:

```python
>>> ds.recorderInfo['RecorderSerial']
9546
>>> ds.recorderInfo['PartNumber']
'S3-E500D40'
```

`EventArray` also stores some sample-specific metadata, like the data's units:

```python
>>> eventarray.units
('Acceleration', u'g')
```
