Skip to content

Commit

Permalink
Merge pull request #19 from aestream/cli
Browse files Browse the repository at this point in the history
Cli
  • Loading branch information
aMarcireau authored Nov 26, 2024
2 parents a61c907 + a22880e commit 965b7fc
Show file tree
Hide file tree
Showing 40 changed files with 3,416 additions and 545 deletions.
173 changes: 168 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,181 @@
![faery logo](faery_logo.png)

Faery sends event data from A to B.
It is both a command-line tool and a Python library, wrapping an optimized core written in Rust.
Faery converts neuromorphic event-based data between formats. It can also generate videos, spectrograms, and event rate curves.

## Usage
- [Using Faery from the command line](#using-faery-from-the-command-line)
- [Setup](#setup)
- [Examples](#examples)
- [Using Faery in a Python script](#using-faery-in-a-python-script)
- [Setup](#setup-1)
- [Examples](#examples-1)
- [Using Faery to render and analyze many recordings](#using-faery-to-render-and-analyze-many-recordings)
- [Setup](#setup-2)
- [Workflow](#workflow)
- [Local development](#local-development)
- [Setup the environment](#setup-the-environment)
- [Format and lint](#format-and-lint)
- [Test](#test)
- [Upload a new version](#upload-a-new-version)
- [Acknowledgements](#acknowledgements)


## Using Faery from the command line

### Setup

1. Install pipx (https://pipx.pypa.io/stable/installation/)

2. Install faery
2. Install Faery by running `pipx install faery`

3. Run `faery --help` to see a list of options, `faery list-filters` to list available filters, and `faery filter <filter> --help` for help on a specific filter

> Note: you can use a virtual environment instead of pipx, see [Use Faery in a script](#use-faery-in-a-script) for instructions.
### Examples

```sh
# Convert a Prophesee raw file (input.raw) to AEDAT (output.aedat)
faery input file input.raw output file output.aedat

# Render an event file (input.es) as a real-time video (output.mp4)
faery input file input.es filter regularize 60.0 filter envelope exponential 0.2 filter colorize starry_night output file output.mp4

# Render an event file (input.es) as a video 10 x slower than real-time (output.mp4)
# The envelope parameter (0.03) is the exponential decay constant.
# Slow-motion videos look better with shorter decays but it does not need to be scaled like regularize,
# which controls the playback speed.
faery input file input.es filter regularize 600.0 filter envelope exponential 0.03 filter colorize starry_night output file output.mp4

# Render an event file (input.es) as frames (frames/*.png)
faery input file input.es filter regularize 60.0 filter envelope exponential 0.2 filter colorize starry_night output files 'frames/{index:04}.png'

# Print ON events to the terminal
faery input file input.aedat filter remove-off-events

# Read event data from UDP and write it to a CSV file (output.csv)
faery input udp 0.0.0.0:3000 output file output.csv
```

## Using Faery in a Python script

### Setup

```sh
pipx install faery
python3 -m venv .venv
source .venv/bin/activate # 'venv\Scripts\activate' on Windows
pip install faery
```

### Examples

See _examples_ in this repository.

## Using Faery to render and analyze many recordings

### Setup

1. Install pipx (https://pipx.pypa.io/stable/installation/)

2. Install Faery by running `pipx install faery`

> Note: you can use a virtual environment instead of pipx, see [Use Faery in a script](#use-faery-in-a-script) for instructions.
### Workflow

1. Create a directory called _my-wonderful-project_ (or any other name), a subdirectory called _recordings_ (this must be called _recordings_), and move the files to analyze to _recordings_ (the file names do not matter). Faery supports AEDAT, RAW, DAT, ES, and CSV files.

```txt
my-wonderful-project
└── recordings
├── file_1.raw
├── file_2.raw
├── ...
└── file_n.raw
```
2. Generate a render and analysis script with Faery.
```sh
cd path/to/my-wonderful-project
faery init
```
Faery will read _recordings_, calculate the time range of each recording, and create the file _faery_script.py_. This can take a little while if there are many recordings or if they are large.
You can also use `faery init --generate-nicknames` to use easy-to-remember nicknames for the files instead of their original names.
3. Run the script.
```sh
faery run
```
Faery will execute the script, which generates assets in the _renders_ directory.
> Note: if you installed Faery in a virtual environment, you can also run the script directly with `python faery_script.py`
4. Edit the script.
You can modify _faery_script.py_ to analyze shorter time slices, use different colormaps, or generate slow motion videos. After editing the script, run it again with `faery run`.
Faery keeps track of completed renders, hence you do not need to delete past jobs before running the script again. For instance, if you run the default script once and find a time window of interest for which you wish to generate a slow motion video, we recommend proceeding as follows.
a. Add a slow motion video task to the script after `real_time_video`.
```py
@faery.task(suffix=".mp4", icon="🎬")
def real_time_video(...):
...
@faery.task(suffix=".mp4", icon="🎬")
def slow_motion_video(
input: pathlib.Path,
output: pathlib.Path,
start: faery.Time,
end: faery.Time,
):
(
faery.events_stream_from_file(input)
.time_slice(start=start, end=end)
.regularize(frequency_hz=6000.0) # 100 x slower than real time
.envelope(
decay="exponential",
tau="00:00:00.002000", # faster decay
)
.colorize(colormap=faery.colormaps.starry_night)
.scale()
.to_file(output, on_progress=faery.progress_bar_fold)
)
```
b. Add a new job at the end of the file, before `job_manager.run()`. You can use the same nickname as long as the time range is different.
```py
tasks: list[faery.Task] = [kinectograph, kinectograph_dense, real_time_video]
# original job (stays in the script)
job_manager.add(
faery.dirname / "recordings" / "dvs.es",
"00:00:00.000000",
"00:00:00.999001",
tasks,
nickname="optimistic-tarsier"
)
# new job (added to the script)
job_manager.add(
faery.dirname / "recordings" / "dvs.es",
"00:00:00.100000",
"00:00:00.200000",
[kinectograph, kinectograph_dense, slow_motion_video],
nickname="optimistic-tarsier"
)
job_manager.run()
```
Since we use `slow_motion_video` (100 x slower than real-time) on a 100 ms slice, the resulting video will be 10 seconds long.
## Local development
### Setup the environment
Expand Down
1 change: 0 additions & 1 deletion examples/dynamic_tasks.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import inspect
import pathlib

import faery
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ requires = ["maturin==1.7.4"]
build-backend = "maturin"

[tool.maturin]
include = ["python/faery/cli/faery_script.mustache"]
python-source = "python"
module-name = "faery.extension"
features = ["pyo3/extension-module"]
Expand Down
52 changes: 50 additions & 2 deletions python/faery/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,24 @@
from .display import (
progress_bar as progress_bar,
progress_bar_fold as progress_bar_fold,
format_bold as format_bold,
format_color as format_color,
)
from .enums import (
ColorblindnessType as ColorblindnessType,
Decay as Decay,
EventsFileCompression as EventsFileCompression,
EventsFileType as EventsFileType,
EventsFileVersion as EventsFileVersion,
FilterOrientation as FilterOrientation,
UdpFormat as UdpFormat,
ImageFileCompressionLevel as ImageFileCompressionLevel,
ImageFileType as ImageFileType,
ImageResizeSamplingFilter as ImageResizeSamplingFilter,
TransposeAction as TransposeAction,
VideoFilePreset as VideoFilePreset,
VideoFileProfile as VideoFileProfile,
VideoFileTune as VideoFileTune,
VideoFileType as VideoFileType,
)
from .events_input import (
Expand Down Expand Up @@ -90,9 +98,29 @@
)

if typing.TYPE_CHECKING:
from .types import aedat, csv, dat, event_stream, evt, image, job_metadata, mp4 # type: ignore
from .types import (
aedat, # type: ignore
csv, # type: ignore
dat, # type: ignore
event_stream, # type: ignore
evt, # type: ignore
image, # type: ignore
job_metadata, # type: ignore
mp4, # type: ignore
mustache, # type: ignore
)
else:
from .extension import aedat, csv, dat, event_stream, evt, image, job_metadata, mp4
from .extension import (
aedat,
csv,
dat,
event_stream,
evt,
image,
job_metadata,
mp4,
mustache,
)


def colormaps_list() -> list[Colormap]:
Expand All @@ -104,6 +132,15 @@ def colormaps_list() -> list[Colormap]:
]


def name_to_colormaps() -> dict[str, Colormap]:
return {
colormap.name: colormap
for _, colormap in inspect.getmembers(
colormaps, lambda member: isinstance(member, Colormap)
)
}


__all__ = [
"__version__",
"colormaps",
Expand All @@ -113,14 +150,22 @@ def colormaps_list() -> list[Colormap]:
"parse_color",
"progress_bar",
"progress_bar_fold",
"format_bold",
"format_color",
"ColorblindnessType",
"Decay",
"EventsFileCompression",
"EventsFileType",
"EventsFileVersion",
"FilterOrientation",
"UdpFormat",
"ImageFileCompressionLevel",
"ImageFileType",
"ImageResizeSamplingFilter",
"TransposeAction",
"VideoFilePreset",
"VideoFileProfile",
"VideoFileTune",
"VideoFileType",
"events_stream_from_array",
"events_stream_from_file",
Expand Down Expand Up @@ -162,6 +207,7 @@ def colormaps_list() -> list[Colormap]:
"CsvProperties",
"dirname",
"Task",
"UdpFormat",
"JobManager",
"task",
"Time",
Expand All @@ -176,4 +222,6 @@ def colormaps_list() -> list[Colormap]:
"image",
"job_metadata",
"mp4",
"colormaps_list",
"name_to_colormaps",
]
Loading

0 comments on commit 965b7fc

Please sign in to comment.