====================
File-Based Archivers
====================
:py:class:`~herostools.actor.archiver.ArrayArchiver`,
:py:class:`~herostools.actor.archiver.ZarrArchiver`, and
:py:class:`~herostools.actor.archiver.JsonArchiver` all write payloads to the
local filesystem. The output path (or store path) for each event is resolved at
runtime by rendering a `Jinja2 `_
template against the merged metadata.
Jinja2 path templates
---------------------
Every file-based archiver accepts one or more template strings. Templates are
rendered against a metadata dict that is the merge of ``default_metadata``
(static defaults set at configuration time) and the per-event metadata received
with each payload. ``default_metadata`` values act as fallbacks; per-event
values take precedence on key collision.
Any Python expression valid inside a Jinja2 ``{{ }}`` block can be used,
including arithmetic and filter pipelines:
.. code:: text
{{ file_path }}/shot-{{ '%04d' % (frame / 2) | round(0, 'floor') }}-{{ frame % 2 }}.npy
This produces paths such as::
/mnt/storage/shot-0000-0.npy
/mnt/storage/shot-0000-1.npy
/mnt/storage/shot-0001-0.npy
when ``file_path`` is ``/mnt/storage`` and ``frame`` is a running integer
supplied by the event source.
Parent directories are created automatically if they do not exist.
ArrayArchiver
-------------
Saves each numpy array payload as a ``.npy`` file.
**Parameters**
``save_template``
Jinja2 template rendered to the output file path.
``split_data_array`` *(default: False)*
When ``True``, each row of a 2-D array is written as a separate file.
The special variable ``{{ _split_index }}`` is injected into the metadata
before rendering so that row number can appear in the filename.
**BOSS configuration example**
.. code:: json
{
"_id": "my-camera-capturer",
"classname": "herostools.actor.archiver.ArrayArchiver",
"arguments": {
"object_selector": "my-camera",
"event_name": "acquisition_data",
"default_metadata": {
"file_path": "/mnt/storage/images"
},
"save_template": "{{ file_path }}/frame-{{ '%04d' % frame }}.npy"
}
}
ZarrArchiver
------------
Stores numpy array payloads inside a
`zarr `_ directory store. A zarr store is a
directory (e.g. ``shot_0001.zarr/``) — not a single file. Its internal
structure (chunk files, ``.zarray`` metadata) is managed by zarr and should be
treated as opaque. The store is opened in append mode (``'a'``) so successive
writes to the same store path do not erase existing arrays.
**Parameters**
``store_template``
Jinja2 template rendered to the zarr store directory path.
``array_path_template``
Jinja2 template rendered to the array key within the store. Nested keys
(e.g. ``sensors/spectrum``) create a group hierarchy inside the store.
**BOSS configuration example**
.. code:: json
{
"_id": "my-zarr-archiver",
"classname": "herostools.actor.archiver.ZarrArchiver",
"arguments": {
"object_selector": "my-instrument",
"event_name": "frame_data",
"default_metadata": {
"store_root": "/mnt/storage/zarr"
},
"store_template": "{{ store_root }}/{{ run_id }}.zarr",
"array_path_template": "{{ identifier }}/data"
}
}
With ``run_id = "run_01"`` and ``identifier = "shot_001"`` this writes the
array to ``/mnt/storage/zarr/run_01.zarr/shot_001/data``.
To read the array back:
.. code:: python
import zarr
group = zarr.open_group("/mnt/storage/zarr/run_01.zarr", mode="r")
arr = group["shot_001/data"][:]
JsonArchiver
------------
Saves each dict payload as a ``.json`` file.
**Parameters**
``save_template``
Jinja2 template rendered to the output file path.
``merge_metadata`` *(default: False)*
When ``True``, the metadata dict is embedded in the saved JSON under the
key ``"metadata"``.
**BOSS configuration example**
.. code:: json
{
"_id": "my-json-archiver",
"classname": "herostools.actor.archiver.JsonArchiver",
"arguments": {
"object_selector": "my-device",
"event_name": "result_data",
"default_metadata": {
"output_dir": "/mnt/storage/results"
},
"save_template": "{{ output_dir }}/{{ identifier }}.json",
"merge_metadata": true
}
}