Metadata-Version: 2.1
Name: aedat
Version: 2.0.2
Requires-Dist: numpy>=1
Summary: A fast AEDAT4 decoder with an underlying Rust implementation
Home-Page: https://github.com/neuromorphicsystems/aedat/
Author: International Centre for Neuromorphic Systems, Alexandre Marcireau
Requires-Python: >=3.7
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Source Code, https://github.com/neuromorphicsystems/aedat/

AEDAT is a fast AEDAT 4 python reader, with a Rust underlying implementation.

Run `pip install aedat` to install it.

- [Example](#example)
- [Process frames](#process-frames)
    - [Pillow (PIL)](#pillow-pil)
    - [OpenCV](#opencv)
- [Detailed example](#detailed-example)
- [Install from source](#install-from-source)
    - [Linux](#linux)
    - [macOS](#macos)
    - [Windows](#windows)
- [Contribute](#contribute)
- [Publish](#publish)

## Example

The `aedat` library provides a single class: `Decoder`. A decoder object is created by passing a file name to `Decoder`. The file name must be a [path-like object](https://docs.python.org/3/glossary.html#term-path-like-object).

```python
import aedat

decoder = aedat.Decoder("/path/to/file.aedat")
print(decoder.id_to_stream())

for packet in decoder:
    print(packet["stream_id"], end=": ")
    if "events" in packet:
        print("{} polarity events".format(len(packet["events"])))
    elif "frame" in packet:
        print("{} x {} frame".format(packet["frame"]["width"], packet["frame"]["height"]))
    elif "imus" in packet:
        print("{} IMU samples".format(len(packet["imus"])))
    elif "triggers" in packet:
        print("{} trigger events".format(len(packet["triggers"])))
```

## Process frames

### Pillow (PIL)

```py
import aedat
import PIL.Image # https://pypi.org/project/Pillow/

index = 0
for packet in decoder:
    if "frame" in packet:
        image = PIL.Image.fromarray(
            packet["frame"]["pixels"],
            mode=packet["frame"]["format"],
        )
        image.save(f"{index}.png")
        index += 1
```

### OpenCV

```py
import aedat
import cv2 # https://pypi.org/project/opencv-python/

index = 0
for packet in decoder:
    if "frame" in packet:
        image = packet["frame"]["pixels"]
        if packet["frame"]["format"] == "RGB":
            image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
        elif packet["frame"]["format"] == "RGBA":
            image = cv2.cvtColor(image, cv2.COLOR_RGBA2BGRA)
        cv2.imwrite(f"{index}.png", image)
        index += 1
```

## Detailed example

This is the same as the first example, with detailed comments:

```python
import aedat

decoder = aedat.Decoder("/path/to/file.aedat")
"""
decoder is a packet iterator with an additional method id_to_stream
id_to_stream returns a dictionary with the following structure:
{
    <int>: {
        "type": <str>,
    }
}
type is one of "events", "frame", "imus", "triggers"
if type is "events" or "frame", its parent dictionary has the following structure:
{
    "type": <str>,
    "width": <int>,
    "height": <int>,
}
"""
print(decoder.id_to_stream())

for packet in decoder:
    """
    packet is a dictionary with the following structure:
    {
        "stream_id": <int>,
    }
    packet also has exactly one of the following fields:
        "events", "frame", "imus", "triggers"
    """
    print(packet["stream_id"], end=": ")
    if "events" in packet:
        """
        packet["events"] is a structured numpy array with the following dtype:
            [
                ("t", "<u8"),
                ("x", "<u2"),
                ("y", "<u2"),
                ("on", "?"),
            ]
        """
        print("{} polarity events".format(len(packet["events"])))
    elif "frame" in packet:
        """
        packet["frame"] is a dictionary with the following structure:
            {
                "t": <int>,
                "begin_t": <int>,
                "end_t": <int>,
                "exposure_begin_t": <int>,
                "exposure_end_t": <int>,
                "format": <str>,
                "width": <int>,
                "height": <int>,
                "offset_x": <int>,
                "offset_y": <int>,
                "pixels": <numpy.array(shape=(height, width), dtype=uint8)>,
            }
        format is one of "L", "RGB", "RGBA"
        """
        print("{} x {} frame".format(packet["frame"]["width"], packet["frame"]["height"]))
    elif "imus" in packet:
        """
        packet["imus"] is a structured numpy array with the following dtype:
            [
                ("t", "<u8"),
                ("temperature", "<f4"),
                ("accelerometer_x", "<f4"),
                ("accelerometer_y", "<f4"),
                ("accelerometer_z", "<f4"),
                ("gyroscope_x", "<f4"),
                ("gyroscope_y", "<f4"),
                ("gyroscope_z", "<f4"),
                ("magnetometer_x", "<f4"),
                ("magnetometer_y", "<f4"),
                ("magnetometer_z", "<f4"),
            ]
        """
        print("{} IMU samples".format(len(packet["imus"])))
    elif "triggers" in packet:
        """
        packet["triggers"] is a structured numpy array with the following dtype:
            [
                ("t", "<u8"),
                ("source", "u1"),
            ]
        the source value has the following meaning:
            0: timestamp reset
            1: external signal rising edge
            2: external signal falling edge
            3: external signal pulse
            4: external generator rising edge
            5: external generator falling edge
            6: frame begin
            7: frame end
            8: exposure begin
            9: exposure end
        """
        print("{} trigger events".format(len(packet["triggers"])))
```

Because the lifetime of the file handle is managed by Rust, decoder objects are not compatible with the [with](https://docs.python.org/3/reference/compound_stmts.html#with) statement. To ensure garbage collection, point the decoder variable to something else, for example `None`, when you are done using it:

```py
import aedat

decoder = aedat.Decoder("/path/to/file.aedat")
# do something with decoder
decoder = None
```

## Install from source

This library requires [Python 3.x](https://www.python.org), x >= 5, and [NumPy](https://numpy.org). This guide assumes that they are installed on your machine.

Note for Windows users: this library requires the x86-64 version of Python. You can download it here: https://www.python.org/downloads/windows/ (the default installer contains the x86 version).

A Rust compiling toolchain is required during the installation (but can be removed afterwards).

### Linux

```sh
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
git clone https://github.com/neuromorphicsystems/aedat.git
cd aedat
cargo build --release
cp target/release/libaedat.so aedat.so
```

You can `import aedat` from python scripts in the same directory as _aedat.so_, which can be placed in any directory.

### macOS

```sh
brew install rustup
rustup-init
git clone https://github.com/neuromorphicsystems/aedat.git
cd aedat
cargo build --release
cp target/release/libaedat.dylib aedat.so
```

You can `import aedat` from python scripts in the same directory as _aedat.so_, which can be placed in any directory.

### Windows

1. install rustup (instructions availables at https://www.rust-lang.org/tools/install)
2. clone or download this repository
3. run in PowerShell from the _aedat_ directory:

```sh
cargo build --release
copy .\target\release\aedat.dll .\aedat.pyd
```

You can `import aedat` from python scripts in the same directory as _aedat.pyd_, which can be placed in any directory.

## Contribute

After changing any of the files in _framebuffers_, one must run:

```sh
flatc --rust -o src/ flatbuffers/*.fbs
```

To format the code, run:

```sh
cargo fmt
```

You may need to install rustfmt first with:

```sh
rustup component add rustfmt
```

# Publish

1. Bump the version number in _setup.py_.

2. Create a new release on GitHub.

