- Capitalize Rugnux as a proper noun throughout the prose; the command stays lowercase `rugnux` in code font. RUGNUX_OVERVIEW.md is retitled "What Rugnux does". - ACKNOWLEDGEMENT.md cites the raw-data repositories only: dataset counts and DOI prefixes moved out (EXTERNAL_TEST_DATA.md owns them), the ESRF data portal gains its citation (Dimper et al. 2019), and MXRDR remains name + link - it has no canonical citation paper. - RUGNUX_FORMATS.md: the CCD formats (marCCD, SMV) are supported as-is with very limited scope, and per-panel XFEL data is not read. - Fix wrong facts a reader would act on: nonexistent `make jfjoch` targets, invalid udev rules, PUSH sockets documented as PULL, swapped writer width/height, underload semantics, the transposed pixel-mask numpy example (the server checks width and height separately), the Durin/Neggia mask-bit table, FPGA threshold register addresses and the mailbox bit field, the I2C core's document number (PG090), an inverted MODEL_FIT_SIGMA formula, a self-inconsistent worked report example, and 11 cross-page anchors whose slugs carry MyST section numbers. - Unify CC1/2 spelling in prose; math notation and report keys unchanged. - Sweep grammar, typos and editing residue across the FPGA, deployment, streaming and analysis pages, including historical CHANGELOG typos. - rugnux_cli.cpp: the -S usage/error examples pair 96 with P43212; 92 names a different group. - Root THIRD_PARTY_NOTICES.md: scope the GPL-compatibility claim (CUDA EULA) and the vendored-table intro (traccc); the docs copy regenerates via update_version.sh. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
173 lines
8.9 KiB
Markdown
173 lines
8.9 KiB
Markdown
# jfjoch_writer
|
|
|
|
`jfjoch_writer` is a NeXus-compliant HDF5 file writer.
|
|
|
|
## Acknowledgements
|
|
Thanks to Zdenek Matej (MAX IV) and Felix Engelmann (MAX IV) for testing and multiple improvement
|
|
suggestions.
|
|
|
|
## Running directory
|
|
The writer needs to run in the base directory for writing files - `file_prefix` is always relative to the writer's running directory.
|
|
The writer detects and protects against basic security issues, like `file_prefix` starting with a slash, or starting with `../`, or containing `/../`.
|
|
|
|
## Usage
|
|
Writer needs to be started as a background service, with the following command:
|
|
```
|
|
jfjoch_writer {options} <address to connect via ZeroMQ to DCU>
|
|
|
|
Options:
|
|
-T | --tcp Use raw TCP/IP instead of ZeroMQ
|
|
-j<int> | --nproc=<int> Number of forks (only with -T)
|
|
-d<path> | --root_dir=<path> Root directory for file writing (-R is a deprecated alias)
|
|
-r<int> | --zmq_repub_port=<int> ZeroMQ port for PUSH socket to republish images
|
|
-f<int> | --zmq_file_port=<int> ZeroMQ port for PUB socket for notifications on finalized files
|
|
-w<int> | --rcv_watermark=<int> Receiving ZeroMQ socket watermark (default = 100)
|
|
-W<int> | --repub_watermark=<int> Republish ZeroMQ socket watermark (default = 1000)
|
|
-v | --verbose Verbose output
|
|
-h This message
|
|
```
|
|
for example:
|
|
```
|
|
jfjoch_writer -d /data tcp://dcu-address:5400
|
|
```
|
|
|
|
## Status and cancellation
|
|
When a data collection is finalized, each writer reports its outcome back to `jfjoch_broker` over
|
|
the writer notification socket — a ZeroMQ address the broker passes in the START message
|
|
(`writer_notification_socket` in the broker configuration) — as a JSON message with the socket
|
|
number, run name and number, processed image count, throughput, and on failure an error string.
|
|
That is how the broker learns that a writer could not write. On the TCP/IP image stream, failures
|
|
additionally come back in-band as negative acknowledgements
|
|
(see [Data streams](IMAGE_STREAM.md#tcpip-image-stream)).
|
|
|
|
To stop a writer, send it `SIGINT`, `SIGQUIT`, `SIGTERM` or `SIGHUP`: it closes the HDF5 files it is
|
|
writing and exits. This is only for the case where the broker was terminated or disconnected — it is
|
|
not the normal way to end a data collection, which the broker finishes on its own.
|
|
|
|
## Republish
|
|
Republish creates a PUSH socket on the writer, where all the messages are republished for further use by a data analysis pipeline.
|
|
Republish is non-blocking, so if there is no receiver on other end or the sending queue is full - images won't be republished.
|
|
In case of START/END messages republishing will attempt sending for 100 ms, but if send times out it won't be retried.
|
|
|
|
Republish functionality is optional, if republish port number is omitted this functionality is not enabled.
|
|
|
|
## Overwriting files
|
|
When `jfjoch_writer` creates a HDF5 file, it first adds suffix `.<random>.tmp`.
|
|
Random value depends on current time-stamp and likely will be different from each file of the particular series.
|
|
After file is all saved and closed, it is renamed to remove the suffix.
|
|
By default, renaming won't happen if this would overwrite existing file.
|
|
However, this behavior can be changed by setting `overwrite` parameter to true in the file writer configuration.
|
|
|
|
### When the overwrite conflict is reported
|
|
An existing output file is a fatal condition (unless `overwrite` is true). *When* it is detected
|
|
depends on whether the transport between the broker and the writer has a back-channel to report the
|
|
failure before acquisition starts:
|
|
|
|
* **Direct HDF5 pusher and TCP writer (back-channel available).** The conflict is detected at
|
|
**start**: the writer that owns the master file checks whether it already exists and refuses to
|
|
start. The direct pusher raises the error in-process; the TCP writer returns a START-failure
|
|
acknowledgement. Either way the broker learns immediately and aborts the data collection *before*
|
|
the detector is armed — no images are taken and nothing is written. Only the master file is
|
|
checked up front: in a multi-writer setup the per-image data files are staggered across writers,
|
|
and checking them at start would make each writer inspect files it never writes (and race the
|
|
writers that do). Data-file conflicts are instead caught by their owning writer at the final
|
|
rename, which for the TCP path surfaces as a write-failure acknowledgement to the broker.
|
|
* **ZeroMQ writer (no back-channel).** The ZeroMQ image stream is fire-and-forget: the writer has no
|
|
way to tell the broker to stop, and the broker would keep streaming images regardless. The writer
|
|
therefore does **not** fail at start. It writes the whole series to the `.<random>.tmp` files as
|
|
usual and only fails at the final rename, leaving the `.tmp` files on disk. This is deliberate: the
|
|
acquired images are preserved (in `.tmp` form) rather than being dropped by a writer that aborted
|
|
mid-stream. Rename the `.tmp` files by hand, or re-run with `overwrite` set, to recover them.
|
|
|
|
## Finalized files information
|
|
Creates PUB socket to inform about finalized data files. For each closed file, the socket will send a JSON message, with the following structure:
|
|
|
|
```
|
|
{
|
|
"filename": <string>: HDF5 data file name (relative to writer root directory),
|
|
"nimages": <int> number of images in the file (counting from 1!),
|
|
"file_number": <int> number of file within the acquisition,
|
|
"sample_name": <string> name of sample,
|
|
"run_name": <string> name of run,
|
|
"run_number": <int> number of run,
|
|
"experiment_group": <string> number of p-group / proposal (optional),
|
|
"user_data": <any json> user_data,
|
|
"beam_x_pxl": <float> beam center (X) in pixels,
|
|
"beam_y_pxl": <float> beam center (Y) in pixels,
|
|
"detector_distance_m": <float> detector distance in m,
|
|
"detector_height_pxl": <int> detector size (Y) in pixels,
|
|
"detector_width_pxl": <int> detector size (X) in pixels,
|
|
"incident_energy_eV": <float> photon energy of the X-ray beam,
|
|
"pixel_size_m": <float> pixel size in meter (assuming pixel X == Y),
|
|
"saturation": <int> this count and higher mean saturation,
|
|
"space_group_number": <int> space group number (optional),
|
|
"underload": <int> lowest valid count; anything below it is invalid,
|
|
"unit_cell": <optional> unit cell dimensions in Angstrom/degree {
|
|
"a": <float>, "b": <float>, "c": <float>,
|
|
"alpha": <float>, "beta": <float>, "gamma": <float>
|
|
},
|
|
}
|
|
```
|
|
`user_data` is defined as `header_appendix` in the `/start` operation in the `jfjoch_broker`.
|
|
Other metadata are also carried over from `/start` operation.
|
|
|
|
If the `header_appendix` is a string with valid JSON meaning, it will be embedded as JSON, otherwise it will be escaped as string.
|
|
For example `header_appendix` of `{"param1": "test1", "param2": ["test1", "test2"]}`, then the example message will look as follows:
|
|
```json
|
|
{
|
|
"filename": "dataset_name_data_000001.h5",
|
|
"nimages": 1000,
|
|
"file_number": 0,
|
|
"sample_name": "my_sample",
|
|
"run_name": "my_run",
|
|
"run_number": 25,
|
|
"experiment_group": "p00001",
|
|
"beam_x_pxl": 1200,
|
|
"beam_y_pxl": 1500,
|
|
"detector_distance_m": 0.155,
|
|
"detector_height_pxl": 2164,
|
|
"detector_width_pxl": 2068,
|
|
"incident_energy_eV": 12400.0,
|
|
"pixel_size_m": 7.5e-05,
|
|
"saturation": 32766,
|
|
"space_group_number": 96,
|
|
"underload": -32767,
|
|
"unit_cell": {
|
|
"a": 78.0,
|
|
"alpha": 90.0,
|
|
"b": 78.0,
|
|
"beta": 90.0,
|
|
"c": 39.0,
|
|
"gamma": 90.0
|
|
},
|
|
"user_data": {
|
|
"param1": "test1",
|
|
"param2": ["test1", "test2"]
|
|
}
|
|
}
|
|
```
|
|
|
|
Notifications for finalized files are optional, if notification port number is omitted this functionality is not enabled.
|
|
|
|
## HDF5 file structure
|
|
|
|
Jungfraujoch writes NXmx-compliant HDF5, with substantial derived metadata (spot finding, indexing,
|
|
integration, azimuthal integration, per-image statistics and timing) stored *beyond* the NXmx
|
|
standard. The complete file layout — master vs data files, the three format variants
|
|
(`NXmxLegacy`, `NXmxVDS`, `NXmxIntegrated`), every NXmx field that is populated and every
|
|
Jungfraujoch extension — is documented in [HDF5 / NeXus data format](HDF5.md).
|
|
|
|
If data collection was configured with a `header_appendix` containing a key `hdf5` whose value is a
|
|
JSON object of numbers and strings, those entries are written to `/entry/user`.
|
|
|
|
## Other formats (CBF and TIFF)
|
|
Earlier versions could also write Crystallographic Binary File (CBF, miniCBF) and TIFF images. These
|
|
writers have been removed: Jungfraujoch now writes only NXmx HDF5. The `CBF` and `TIFF` values are
|
|
retained in the file-format enum for wire back-compatibility, but a request to write either format
|
|
is rejected.
|
|
|
|
## No file option(s)
|
|
There are two options to disable writing of files by the writer:
|
|
* Setting `file_prefix` to empty string - this will disable sending files on ZeroMQ image socket.
|
|
* Setting file format to `NoFile` - files are streamed over ZeroMQ socket, but `jfjoch_writer` will not write anything.
|
|
This can be useful for debugging purposes, or if you only rely on republishing functionality of the `jfjoch_writer` |