diff --git a/datasets/colorado-boulder/README.md b/datasets/colorado-boulder/README.md index 60e1bee..42b5aeb 100644 --- a/datasets/colorado-boulder/README.md +++ b/datasets/colorado-boulder/README.md @@ -1,5 +1,6 @@ --- -pretty_name: "OpenH-RF — Tracked Swept Synthetic Aperture 3D Phantom Dataset" +name: colorado-boulder +pretty_name: "Tracked Swept Synthetic Aperture Ultrasound Datasets" license: cc-by-4.0 task_categories: - generalized-reconstruction @@ -14,63 +15,89 @@ size_categories: - n<1K --- -# Tracked Swept Synthetic Aperture 3D Phantom Ultrasound Dataset +# Tracked Swept Synthetic Aperture Ultrasound Datasets -## Dataset Description +| 2D ATS 539 phantom | 3D phantom | In-vivo quadriceps | +|:---:|:---:|:---:| +| ![Tracked SSA sweep through the 2D ATS 539 phantom](assets/ssa-sweep-phantom2d.gif) | ![Tracked SSA sweep through the 3D phantom](assets/ssa-sweep-phantom3d.gif) | ![Tracked SSA sweep along in-vivo quadriceps muscle](assets/ssa-sweep-invivo.gif) | +| [`Sub-dataset-1`](https://huggingface.co/datasets/nvidia/OpenH-RF/tree/main/colorado-boulder/Sub-dataset-1) | [`Sub-dataset-2`](https://huggingface.co/datasets/nvidia/OpenH-RF/tree/main/colorado-boulder/Sub-dataset-2) | [`Sub-dataset-3`](https://huggingface.co/datasets/nvidia/OpenH-RF/tree/main/colorado-boulder/Sub-dataset-3) | -This dataset contains tracked swept synthetic aperture (SSA) ultrasound acquisitions of a 3D ultrasound imaging phantom. The data were acquired using a Verasonics Vantage research ultrasound system with a P4-2 phased array transducer. +*Motion-compensated tracked SSA reconstructions, one per sub-dataset. Each frame beamforms the raw RF channel data with its own tracked probe pose; a 40 mm window of frames is then coherently summed to synthesise a larger effective aperture, and the window slides along the freehand sweep.* -The dataset includes raw RF channel data, acquisition parameters, probe geometry, transmit information, and frame-wise tracked probe pose metadata. Its purpose is to provide a reproducible example of motion-compensated SSA reconstruction from raw channel data using the zea/OpenH-RF data format. +## Dataset Description -This dataset contains phantom data only. It does not contain human subject data, animal data, or protected health information (PHI). +This dataset contains tracked swept synthetic aperture (SSA) ultrasound acquisitions from three targets: a 2D ATS 539 multipurpose imaging phantom, a 3D ultrasound imaging phantom, and in-vivo quadriceps muscle from healthy volunteer participants. -## Dataset Contributor(s) +The data were acquired using a Verasonics Vantage research ultrasound system with a P4-2 phased array transducer. The dataset includes raw RF channel data, acquisition parameters, probe geometry, transmit information, and frame-wise tracked probe pose metadata. -**Contributing organization:** University of Colorado Boulder, Bottenus Lab +The purpose of this dataset is to provide reproducible examples for generalized ultrasound reconstruction, with a particular focus on motion-compensated tracked SSA reconstruction using the zea/OpenH-RF data format. -**Contributors:** +The phantom acquisitions do not contain human subject data, animal data, or protected health information (PHI). The in-vivo acquisitions were collected from healthy volunteer participants and de-identified prior to release. -- Anet Sanchez -- Nick Bottenus +## Dataset Contributor(s) + +- Anet Sanchez (University of Colorado Boulder, Bottenus Lab) +- Nick Bottenus (University of Colorado Boulder, Bottenus Lab) ## Dataset Creation Date -06/24/2025 +Data were collected between 08/23/2024 and 06/30/2026. ## License / Terms of Use -This dataset is released under the Creative Commons Attribution 4.0 International License (CC BY 4.0). - -The contributed data consist exclusively of phantom ultrasound acquisitions and are cleared for release under CC BY 4.0. Patient consent, clinical data-use agreements, and PHI de-identification are not applicable because the dataset does not contain human subject data. +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage -This dataset is intended for research on generalized ultrasound reconstruction, with a particular focus on tracked swept synthetic aperture imaging, motion-compensated beamforming, coherent compounding, and ultrasound image-quality evaluation. +This dataset is intended for research on generalized ultrasound reconstruction, with a specific focus on tracked swept synthetic aperture imaging, motion-compensated beamforming, coherent compounding, and ultrasound image-quality evaluation. -For SSA reconstruction, each raw RF frame is beamformed using its corresponding tracked transducer pose. The resulting beamformed IQ frames are placed on a common reconstruction grid and coherently summed to synthesize a larger effective aperture. Because the reconstruction relies on coherent compounding, summation is performed before envelope detection, normalization, and log compression. +For SSA reconstruction, each raw RF frame is beamformed using its corresponding tracked transducer pose. The resulting beamformed frames are placed on a common reconstruction grid and coherently summed to synthesize a larger effective aperture. Because the reconstruction relies on coherent compounding, summation is performed before envelope detection, normalization, and log compression. ## Dataset Characterization -- **Data Collection Method:** Phantom ultrasound acquisition -- **Labeling Method:** N/A; no manual labels or segmentation masks are provided -- **Acquisition System:** Verasonics Vantage research ultrasound scanner with a Verasonics P4-2 phased array transducer +The dataset includes acquisitions from three targets: + +- 2D ATS 539 multipurpose imaging phantom +- 3D ultrasound imaging phantom +- In-vivo quadriceps muscle from healthy volunteer participants + +The ATS 539 multipurpose imaging phantom contains: + +- Wire targets +- Cylindrical lesions +- Tissue-mimicking speckle regions + +These targets may be used to assess spatial resolution, contrast, lesion visibility, and speckle characteristics. + +### Data Collection Method + +Each acquisition consisted of a freehand sweep in the lateral direciton of the transducer. All 64 array elements were used during receive. Diverging waves were generated using a negative virtual source while activating the 20 central array elements during transmit. For in-vivo targets the transducer was manually swept along the longitudinal direction of the quadriceps while transmitting diverging waves at 400 Hz. The transducer was optically tracked using an NDI Polaris Vega® XT optical tracking system manufactured by Northern Digital Inc., Ontario, Canada. + +### Labeling Method -### Acquisition Details +None (N/A). -The transducer was manually swept over the phantom field of view while diverging-wave transmissions were acquired at 400 Hz. All 64 array elements were used on receive. +### Acquisition System -Diverging waves were generated using a negative virtual source with the 20 central array elements active on transmit. +- Verasonics Vantage research ultrasound scanner +- Verasonics P4-2 phased array transducer +- 64 elements +- Reconstruction center frequency: 2.5 MHz +- Sampling frequency: 10 MHz +- Optical tracking: NDI Polaris Vega XT -The transducer was optically tracked using an NDI Polaris Vega® XT optical tracking system manufactured by Northern Digital Inc., Ontario, Canada. +## Processing the Dataset -### Probe and Geometry +The acquisitions can be processed with the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/colorado-boulder/reconstruct.py) as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF), together with the `pipeline.yaml` definition in this folder and the [zea library](https://github.com/tue-bmd/zea). The script streams the data from the Hugging Face Hub. -The acquisition used a Verasonics P4-2 phased array transducer with 64 elements. The center frequency stored in the acquisition and used for reconstruction is 2.5 MHz. +The script selects tracked frames at roughly 1 mm lateral spacing and writes the reconstruction to `ssa_bmode.png`. Point `ZEA_FILE` at any acquisition in the corpus to reconstruct it. [`assets/main_bmode.png`](./assets/main_bmode.png) was produced this way, compounding the full sweep into one image; the loops at the top of this card slide a shorter aperture window along the sweep instead. -The probe geometry, transmit origins, transmit delays, transmit apodization, and other acquisition parameters are stored in the zea/OpenH-RF file. Frame-wise probe translations and rotations are stored using the native `metadata/probe_pose` structure. +`reconstruct.py` defines the custom `apply_probe_pose` operation that `pipeline.yaml` refers to, which applies the frame-wise `metadata/probe_pose` to the probe geometry and transmit origins before beamforming. ## Dataset Format +[zea v0.1.7](https://github.com/tue-bmd/zea) + The dataset is distributed in the zea/OpenH-RF HDF5 format. Each file includes: @@ -84,20 +111,19 @@ Each file includes: - Frame-wise tracked probe translations - Frame-wise tracked probe rotations -The stored RF channel data have not been beamformed, envelope detected, normalized, or log compressed. The accompanying reconstruction pipeline performs these processing steps. +The stored RF channel data have not been beamformed, demodulated, envelope detected, normalized, or log compressed. These processing steps are performed by the accompanying reconstruction pipeline. ## Dataset Quantification -**Current OpenH-RF release:** 62 HDF5 files; 16.04 GB (16,037,117,952 bytes) stored; root `zea_version` **0.1.6**. Sizes include all HDF5 contents and use decimal units (MB = 10^6 bytes, GB = 10^9 bytes, TB = 10^12 bytes), not decoded-array memory or original-source download sizes. - -- **Number of phantom objects:** 1 -- **Number of acquisitions:** 10 -- **Number of RF frames per acquisition:** 1200 -- **Number of transmit events per frame:** 1 -- **Number of receive elements:** 64 -- **Number of active transmit elements:** 20 +- Number of phantom objects: 2 +- Number of volunteer participants: 7 +- Number of acquisitions: 62 +- Number of RF frames per acquisition: 1200–1600 +- Number of transmit events per frame: 1 +- Number of receive elements: 64 +- Number of active transmit elements: 20 -### Per-File Feature Summary +### Per-acquisition contents | Feature | Shape | Data type | Units | Description | |---|---:|---|---|---| @@ -111,64 +137,40 @@ The stored RF channel data have not been beamformed, envelope detected, normaliz ## Subject Metadata -### Metadata Schema Migration - -The zea 0.1.6 migration uses these approved metadata locations: - -| Legacy location | Canonical location | -|---|---| -| Dataset `metadata/subject_id` | Dataset `metadata/subject/id` | -| Dataset `metadata/subject_type` | Dataset `metadata/subject/type` | -| Dataset `metadata/us_machine` | Root HDF5 attribute `us_machine` | - -Read the machine name with `f.attrs["us_machine"]`, not `f["us_machine"]`. -Subject values and their existing attributes are preserved. The machine -string is preserved; migration stops for review if its legacy dataset has -attributes that cannot be represented without loss. No numerical arrays are -rescaled or otherwise changed by these relocations. - -The three-field pilot passed full array and metadata parity checks with the -approved description changes and `transmit_only=False` default. Full-release -migration is still pending. Replacement files are uploaded only after -per-file validation; readers supporting both revisions should check the -canonical locations first, then the legacy locations. +This dataset contains acquisitions from two ultrasound imaging phantoms and healthy volunteer participants. - -This dataset contains one 3D ultrasound imaging phantom. - -- **Subject type:** 3D phantom -- **Anatomical region:** Not applicable -- **Human participants:** None -- **Animal subjects:** None -- **Protected health information:** None -- **Scanner:** Verasonics Vantage -- **Probe:** Verasonics P4-2 phased array +- Subject types: 2D imaging phantom, 3D phantom, and in-vivo human ultrasound data +- 2D phantom model: ATS 539 +- 2D phantom target types: wires, cylindrical lesions, and tissue-mimicking speckle +- In-vivo anatomical region: quadriceps muscle +- Human participants: healthy volunteer participants for the in-vivo dataset +- Animal subjects: None +- Protected health information: None +- Scanner: Verasonics Vantage +- Probe: Verasonics P4-2 phased array ## Data Validation -The submission includes a `zea.Pipeline` that reconstructs a representative tracked SSA B-mode image from the raw RF channel data. +The submission includes a `zea.Pipeline` that reconstructs representative tracked SSA B-mode images from the raw RF channel data. The pipeline performs: 1. Frame-wise demodulation 2. Application of the tracked probe pose 3. Delay-and-sum beamforming onto a common reconstruction grid -4. Coherent summation of the beamformed IQ frames +4. Coherent SSA compounding 5. Envelope detection 6. Normalization 7. Log compression -The reconstruction is defined in `pipeline.yaml` and executed using `reconstruct.py`. A representative reconstructed B-mode image is included with the dataset. - ## Known Issues - Optical tracking measurements may contain small position and orientation uncertainties. - Reconstruction quality depends on tracking calibration accuracy and coherent alignment between frames. - +- The in-vivo dataset is intended for research use and has not been optimized for clinical workflows. ## Ethical Considerations -This dataset contains phantom ultrasound data only. It does not contain human participants, animal subjects, personal identifiers, clinical records, or protected health information. - -Human-subject consent and institutional review board approval are therefore not applicable. - +- **Phantom data:** phantom ultrasound acquisitions only — no human participants, animal subjects, personal identifiers or clinical records; consent and IRB approval are not applicable. +- **In-vivo data:** acquired from healthy volunteer participants with informed consent under IRB-approved protocol #24-0176, and de-identified prior to release. +- No protected health information (PHI) or participant-identifying metadata is included in the released files. diff --git a/datasets/colorado-boulder/assets/main_bmode.png b/datasets/colorado-boulder/assets/main_bmode.png new file mode 100644 index 0000000..58c52eb Binary files /dev/null and b/datasets/colorado-boulder/assets/main_bmode.png differ diff --git a/datasets/colorado-boulder/assets/ssa-sweep-invivo.gif b/datasets/colorado-boulder/assets/ssa-sweep-invivo.gif new file mode 100644 index 0000000..4163acb Binary files /dev/null and b/datasets/colorado-boulder/assets/ssa-sweep-invivo.gif differ diff --git a/datasets/colorado-boulder/assets/ssa-sweep-phantom2d.gif b/datasets/colorado-boulder/assets/ssa-sweep-phantom2d.gif new file mode 100644 index 0000000..1f0cb16 Binary files /dev/null and b/datasets/colorado-boulder/assets/ssa-sweep-phantom2d.gif differ diff --git a/datasets/colorado-boulder/assets/ssa-sweep-phantom3d.gif b/datasets/colorado-boulder/assets/ssa-sweep-phantom3d.gif new file mode 100644 index 0000000..ab26f61 Binary files /dev/null and b/datasets/colorado-boulder/assets/ssa-sweep-phantom3d.gif differ diff --git a/datasets/colorado-boulder/reconstruct.py b/datasets/colorado-boulder/reconstruct.py index e7a4def..f2e693e 100644 --- a/datasets/colorado-boulder/reconstruct.py +++ b/datasets/colorado-boulder/reconstruct.py @@ -51,35 +51,30 @@ # --- Inputs ----------------------------------------------------------------- # Defaults stream straight from the published corpus. Swap any of these for a # local path to run against your own copy. -DATA_FILE = "hf://nvidia/OpenH-RF/colorado-boulder/Sub-dataset-1/sub-phantom2d_01_tracked-ssa.hdf5" -PIPELINE_FILE = HERE / "pipeline.yaml" -OUTPUT_PNG = HERE / "ssa_bmode.png" - - -# ============================================================ -# Custom operation: ApplyProbePose -# ============================================================ -# Rotates/translates the probe geometry and transmit origin by the tracked -# probe pose of the current frame. Placed before Beamform in the pipeline. -# The per-frame pose (probe_translation, probe_rotation) is supplied by the -# enclosing Map, which slices them frame-by-frame alongside the data. -# These ops are defined here, not in zea: a pipeline.yaml naming them resolves -# only once this module is imported. See https://github.com/open-h/OpenH-RF +ZEA_FILE = "hf://nvidia/OpenH-RF/colorado-boulder/Sub-dataset-1/sub-phantom2d_01_tracked-ssa.hdf5" +CONFIG = HERE / "pipeline.yaml" # written by build_pipeline() below; this is what the run loads +OUT = HERE / "ssa_bmode.png" +HF_CONFIG = "hf://nvidia/OpenH-RF/colorado-boulder/pipeline.yaml" # where CONFIG is published +FRAME_SPACING = 1e-3 # select tracked frames every 1 mm along the sweep + + @ops_registry("apply_probe_pose") class ApplyProbePose(Operation): - """Apply the tracked probe pose of one frame before beamforming.""" + """Apply the tracked probe pose of one frame before beamforming. + + Rotates and translates the probe geometry and transmit origins by the pose + of the current frame, so that every frame shares one coordinate system. The + per-frame pose is sliced off ``probe_translation`` / ``probe_rotation`` by + the enclosing :class:`Map`. + + This operation lives here rather than in zea, so a ``pipeline.yaml`` naming + ``apply_probe_pose`` only resolves once this module has been imported. + """ def __init__(self, **kwargs): super().__init__(additional_output_keys=["probe_geometry", "transmit_origins"], **kwargs) - def call( - self, - probe_translation, - probe_rotation, - probe_geometry, - transmit_origins, - **kwargs, - ): + def call(self, probe_translation, probe_rotation, probe_geometry, transmit_origins, **kwargs): t = ops.reshape(ops.cast(probe_translation, "float32"), (3,)) q = ops.reshape(ops.cast(probe_rotation, "float32"), (4,)) q = q / (ops.sqrt(ops.sum(q * q)) + 1e-12) @@ -125,103 +120,81 @@ def call( } -# ============================================================ -# One single pipeline -# ============================================================ -# Map runs the frame-wise part (pose + beamforming) once per tracked frame, -# slicing data / probe_translation / probe_rotation along the frame axis. -# The stacked beamformed IQ frames are then coherently summed (Sum) before -# envelope detection, as required for SSA. -def create_and_save_pipeline(): - pipeline = Pipeline( +def build_pipeline() -> Pipeline: + """Define the tracked swept synthetic aperture beamforming pipeline in code. + + ``Map`` beamforms one tracked frame at a time, each with its own pose. The + stacked frames are coherently summed before envelope detection, which is + what makes this a synthetic aperture rather than incoherent compounding. + """ + return Pipeline( operations=[ Map( operations=[ - Squeeze(axis=0), # (1, n_tx=1, n_ax, n_el, n_ch) -> (n_tx=1, n_ax, n_el, n_ch) + Squeeze(axis=0), Cast(dtype="float32"), Demodulate(), ApplyProbePose(), Beamform(beamformer="delay_and_sum", enable_pfield=False), - ExpandDims(axis=0), # restore the frame axis so Map can stack frames + ExpandDims(axis=0), # restore the frame axis so Map can stack ], argnames=["data", "probe_translation", "probe_rotation"], - batch_size=1, # one tracked frame at a time (each frame has its own pose) + batch_size=1, ), - Sum(axis=0), # coherent compounding of the beamformed IQ frames + Sum(axis=0), EnvelopeDetect(), Normalize(), LogCompress(), ], with_batch_dim=False, ) - pipeline.to_yaml(str(PIPELINE_FILE)) -# ============================================================ -# Main reconstruction -# ============================================================ +def select_sweep_frames(x_positions, spacing): + """Indices of the frames closest to a uniform ``spacing`` along the sweep.""" + step = spacing * np.sign(x_positions[-1] - x_positions[0]) + targets = np.arange(x_positions[0], x_positions[-1] + 0.5 * step, step, dtype=np.float32) + return np.array([np.argmin(np.abs(x_positions - target)) for target in targets]) + + def main(): - zea.init_device(device="cpu", verbose=False) + zea.init_device(verbose=False) - create_and_save_pipeline() - pipeline = Pipeline.from_path(str(PIPELINE_FILE)) + # Define the pipeline in code, save it to pipeline.yaml, then load it back. + build_pipeline().to_yaml(str(CONFIG)) + pipeline = Pipeline.from_path(str(CONFIG)) - # ------------------------------------------------------------ - # Load zea parameters and raw data - # ------------------------------------------------------------ - with zea.File(str(DATA_FILE), mode="r") as file: - parameters = file.load_parameters( + # Load acquisition parameters, raw RF data and the tracked probe pose + with zea.File(ZEA_FILE) as f: + parameters = f.load_parameters( xlims=(-0.05, 0.05), zlims=(0.01, 0.13), ylims=(0.0, 0.0), f_number=0.75, dynamic_range=(-50, 0), ) - raw_data = np.array(file.data.raw_data).astype(np.float32) - probe_pose = file.metadata.probe_pose - probe_translation = np.array(probe_pose.translation).astype(np.float32) - probe_rotation = np.array(probe_pose.rotation).astype(np.float32) - - # ------------------------------------------------------------ - # Select frames based on approximately uniform x-spacing - # ------------------------------------------------------------ - desired_dx_m = 1e-3 # 1 mm spacing along x - x_motion = probe_translation[:, 0] # x position for each frame, in meters - direction = np.sign(x_motion[-1] - x_motion[0]) - dx = desired_dx_m * direction - desired_x_positions = np.arange(x_motion[0], x_motion[-1] + 0.5 * dx, dx, dtype=np.float32) - frame_indices = np.array( - [np.argmin(np.abs(x_motion - x_target)) for x_target in desired_x_positions] - ) - - # Center the selected sweep around the reconstruction grid - probe_translation = probe_translation - np.mean(probe_translation[frame_indices], axis=0) - - # ------------------------------------------------------------ - # Run the pipeline - # ------------------------------------------------------------ - inputs = pipeline.prepare_parameters(parameters) - output = pipeline( - data=raw_data[frame_indices], - probe_translation=probe_translation[frame_indices], - probe_rotation=probe_rotation[frame_indices], - **inputs, - ) - bmode = np.squeeze(ops.convert_to_numpy(output["data"])) - - # ------------------------------------------------------------ - # Save B-mode image - # ------------------------------------------------------------ - flatgrid_np = ops.convert_to_numpy(inputs["flatgrid"]) - x_plot = flatgrid_np[:, 0] * 1e3 - z_plot = flatgrid_np[:, 2] * 1e3 - + raw_data = f.data.raw_data[:] + probe_translation = f.metadata.probe_pose.translation[:] + probe_rotation = f.metadata.probe_pose.rotation[:] + + # Thin the sweep to a uniform spacing and centre it on the reconstruction grid + frames = select_sweep_frames(probe_translation[:, 0], FRAME_SPACING) + probe_translation = probe_translation - np.mean(probe_translation[frames], axis=0) + + bmode = pipeline( + data=raw_data[frames], + probe_translation=probe_translation[frames], + probe_rotation=probe_rotation[frames], + **pipeline.prepare_parameters(parameters), + )["data"] + + # Save PNG zea.visualize.set_mpl_style() plt.figure(figsize=(6, 7)) plt.imshow( - bmode, + np.squeeze(ops.convert_to_numpy(bmode)), cmap="gray", - extent=[np.min(x_plot), np.max(x_plot), np.max(z_plot), np.min(z_plot)], + extent=parameters.extent_imshow * 1e3, aspect="auto", ) plt.xlabel("x [mm]") @@ -230,9 +203,9 @@ def main(): cax = make_axes_locatable(plt.gca()).append_axes("right", size="5%", pad=0.05) plt.gcf().colorbar(plt.gci(), cax=cax, label="dB") plt.tight_layout() - plt.savefig(str(OUTPUT_PNG), dpi=200, bbox_inches="tight") + plt.savefig(str(OUT), dpi=200, bbox_inches="tight") plt.close() - print(f"\nSaved reconstruction to: {OUTPUT_PNG}") + print(f"Saved to {OUT}") if __name__ == "__main__": diff --git a/datasets/concordia/README.md b/datasets/concordia/README.md index 9ce90d4..e53b6e2 100644 --- a/datasets/concordia/README.md +++ b/datasets/concordia/README.md @@ -1,5 +1,6 @@ --- -pretty_name: "OpenH-RF — SynthUS-FSA" +name: concordia +pretty_name: "SynthUS-FSA" license: cc-by-4.0 task_categories: - image-to-image @@ -16,36 +17,24 @@ size_categories: - 1K M. Sharifzadeh, S. Goudarzi, A. Tang, H. Benali, and H. Rivaz, "Mitigating -> aberration-induced noise: A deep learning-based aberration-to-aberration approach," -> *IEEE Transactions on Medical Imaging*, vol. 43, no. 12, pp. 4380–4392, 2024. +SynthUS-FSA is a fully synthetic corpus of pre-beamformed full-synthetic-aperture (FSA) ultrasound channel-data captures, generated with the Field II simulator to model a 128-element L11-5v linear array. Each capture fires one transmit event per element (128 transmits, receive on all 128 elements), so every element-to-element combination is retained and any receive/transmit beamforming scheme (focused B-mode, multi-angle plane-wave compounding, diverging-wave, adaptive/compressive schemes) can be retrospectively synthesized from the same channel data. The dataset is packaged in [zea](https://zea.readthedocs.io) format — the OpenH-RF reference Python library for ultrasound file I/O and beamforming — which every `.hdf5` file and `reconstruct.py` depend on. The dataset targets three OpenH-RF task categories: (1) generalized reconstruction (FSA channel data paired with a synthesized full synthetic-aperture B-mode as ground truth), (2) compressed sensing / adaptive transmit (retrospective sub-selection of the 128 transmit events — derived from the same captures, no additional files), and (3) segmentation (binary echogenicity masks on a subset of phantoms). All data is simulated — no clinical, phantom-hardware, or animal acquisition is involved. + +A similar data-generation approach was used in Sharifzadeh et al. (2024). Users of this dataset are kindly requested to cite that work: + +> M. Sharifzadeh, S. Goudarzi, A. Tang, H. Benali, and H. Rivaz, "Mitigating aberration-induced noise: A deep learning-based aberration-to-aberration approach," *IEEE Transactions on Medical Imaging*, vol. 43, no. 12, pp. 4380–4392, 2024. ## Dataset Contributor(s) -Mostafa Sharifzadeh (mostafa.sharifzadeh@mail.concordia.ca) and Hassan Rivaz -(hassan.rivaz@concordia.ca), IMPACT Lab, Concordia University. +- Mostafa Sharifzadeh (IMPACT Lab, Concordia University) +- Hassan Rivaz (IMPACT Lab, Concordia University) ## Dataset Creation Date @@ -53,127 +42,68 @@ Mostafa Sharifzadeh (mostafa.sharifzadeh@mail.concordia.ca) and Hassan Rivaz ## License / Terms of Use -CC BY 4.0. - -- **Simulated channel data, phantom parameters, and reconstruction/segmentation - labels** are generated by the contributors (Field II simulation outputs) and - released under CC BY 4.0. -- **Segmentation mask shapes** (anechoic/hypoechoic/hyperechoic classes, 750 - captures) are sourced from [Open Images V7](https://storage.googleapis.com/openimages/web/factsfigures_v7.html)'s - animal-class segmentation annotations (Google LLC). Only the binary mask is used — never the underlying source photograph. Open Images V7 - licenses its annotations, including segmentation masks, under CC BY 4.0; the - source photographs themselves carry a separate CC BY 2.0 license but are not - used anywhere in this dataset. -- **Diverse-echogenicity amplitude-weight maps** (1,000 captures) are photographs - from Wikimedia Commons, restricted to the `CC-BY-4.0` category and verified - per-image against each file's `CC BY 4.0` license field before acceptance. - `wikimedia_commons_metadata.csv`, at the dataset root, credits each one: a row - per capture, keyed by `DatasetFile` for `image_0751.hdf5` through - `image_1750.hdf5`, giving the source URL, Commons file page, author, title and - license. No photograph appears in the dataset in its original form. Each was - converted to grayscale, resampled to the 400 × 450 weight-map grid, - histogram-equalized and rescaled to `[0, 1]`. Values above 0.9 were then set to - 1 and values below 0.1 to 0, producing the weight map stored as - `data/diverse_source_image`. This conversion is implemented in - `wikimedia_commons_postprocess.py`. -- **Point-target phantoms** (250 captures) use no external asset — purely - synthetic point scatterers. - -All channel data was generated with Field II (Jensen/DTU), distributed for free academic use. Field II's terms do not -restrict redistribution or licensing of simulation *output* data. Per Field II's terms, any use of this dataset should cite: -J.A. Jensen, "Field: A Program for Simulating Ultrasound Systems," *Med. Biol. -Eng. Comp.*, 1996; and J.A. Jensen and N.B. Svendsen, "Calculation of Pressure -Fields from Arbitrarily Shaped, Apodized, and Excited Ultrasound Transducers," -*IEEE Trans. Ultrason., Ferroelec., Freq. Contr.*, 1992. +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage -- **Generalized reconstruction** — learning a direct mapping from raw FSA channel - data to a B-mode image, and/or from FSA to arbitrary retrospective beamforming - targets (focused, multi-angle compounded, diverging-wave, adaptive). -- **Compressed sensing / adaptive transmit** — sparse-aperture and adaptive transmit - design research using retrospective sub-selection of the 128 FSA transmit events. -- **Segmentation** — pixel-aligned echogenicity-region segmentation (anechoic / - hypoechoic / hyperechoic) from raw channel data. -- **Scatterer-level analysis** — `data/scatterers` exposes the exact Field II - point-scatterer cloud (position + amplitude) used to simulate each capture, - for tasks that want ground truth finer than a pixel grid (e.g. quantitative - ultrasound, scatterer-density estimation). +- **Generalized reconstruction** — learning a direct mapping from raw FSA channel data to a B-mode image, and/or from FSA to arbitrary retrospective beamforming targets (focused, multi-angle compounded, diverging-wave, adaptive). +- **Compressed sensing / adaptive transmit** — sparse-aperture and adaptive transmit design research using retrospective sub-selection of the 128 FSA transmit events. +- **Segmentation** — pixel-aligned echogenicity-region segmentation (anechoic / hypoechoic / hyperechoic) from raw channel data. +- **Scatterer-level analysis** — `data/scatterers` exposes the exact Field II point-scatterer cloud (position + amplitude) used to simulate each capture, for tasks that want ground truth finer than a pixel grid (e.g. quantitative ultrasound, scatterer-density estimation). ## Dataset Characterization -- **Data Collection Method:** synthetic (Field II full-synthetic-aperture - simulation). +- **Data Collection Method:** synthetic (Field II full-synthetic-aperture simulation). - **Labeling Method:** synthetic ground truth. - - Segmentation masks (anechoic/hypoechoic/hyperechoic) are Open Images V7 - animal-class segmentation annotations, used for shape only. - - Diverse-echogenicity amplitude weighting uses a Wikimedia Commons photograph - as a continuous grayscale weight map, preserved as `data/diverse_source_image` - so an end user can see what pattern the capture was generated from. - - The reconstruction-target B-mode (`data/image`) is synthesized from the same - FSA capture (see *Data Validation* below), not an independent acquisition. -- **Acquisition system (simulated):** 128-element linear array (model: L11-5v), - center frequency 5.208 MHz, element width 0.27 mm, kerf 0.03 mm, pitch 0.3 mm, - element height 5 mm, elevation focus (Rfocus) 20 mm. Simulated at 104.16 MHz (a - high rate required for Field II's numerical precision), then decimated ×5 to a - delivered sampling rate of 20.832 MHz. Sound speed 1540 m/s. - Receive dynamic-focus reconstruction uses F-number 1.75 (baked into - `reconstruct.py`'s `F_NUMBER` constant — zea's own default is 1.0, so this must be - supplied explicitly rather than relying on the file alone to reproduce the - reference images). -- **Transmit pulse:** Hann-windowed 2.5-cycle tone burst at the 5.208 MHz center - frequency, sampled at the native 104.16 MHz simulation rate. −6dB fractional - bandwidth ≈ 76.5% (stored as `probe/probe_bandwidth_percent`). -- **Coordinate convention:** x = lateral, y = elevation (always 0 for this 2D - acquisition), z = axial/depth — the standard zea convention, applied throughout - `probe_geometry`, `transmit_origins`, and every map's `coordinates` field. -- **Frame timing:** each file is a single static frame (`n_frames=1`) with no - simulated motion or pulse-repetition-frequency concept, so `scan/time_to_next_transmit` - is intentionally omitted rather than populated with a fabricated value. Likewise, no - time-gain-compensation was applied to this synthetic data, so `scan/tgc_gain_curve` - is omitted. + - Segmentation masks (anechoic/hypoechoic/hyperechoic) are Open Images V7 animal-class segmentation annotations, used for shape only. + - Diverse-echogenicity amplitude weighting uses a Wikimedia Commons photograph as a continuous grayscale weight map, preserved as `data/diverse_source_image` so an end user can see what pattern the capture was generated from. + - The reconstruction-target B-mode (`data/image`) is synthesized from the same FSA capture (see *Data Validation* below), not an independent acquisition. +- **Acquisition system (simulated):** 128-element linear array (model: L11-5v), center frequency 5.208 MHz, element width 0.27 mm, kerf 0.03 mm, pitch 0.3 mm, element height 5 mm, elevation focus (Rfocus) 20 mm. Simulated at 104.16 MHz (a high rate required for Field II's numerical precision), then decimated ×5 to a delivered sampling rate of 20.832 MHz. Sound speed 1540 m/s. Receive dynamic-focus reconstruction uses F-number 1.75 (baked into `reconstruct.py`'s `F_NUMBER` constant — zea's own default is 1.0, so this must be supplied explicitly rather than relying on the file alone to reproduce the reference images). +- **Transmit pulse:** Hann-windowed 2.5-cycle tone burst at the 5.208 MHz center frequency, sampled at the native 104.16 MHz simulation rate. −6dB fractional bandwidth ≈ 76.5% (stored as `probe/probe_bandwidth_percent`). +- **Coordinate convention:** x = lateral, y = elevation (always 0 for this 2D acquisition), z = axial/depth — the standard zea convention, applied throughout `probe_geometry`, `transmit_origins`, and every map's `coordinates` field. +- **Frame timing:** each file is a single static frame (`n_frames=1`) with no simulated motion or pulse-repetition-frequency concept, so `scan/time_to_next_transmit` is intentionally omitted rather than populated with a fabricated value. Likewise, no time-gain-compensation was applied to this synthetic data, so `scan/tgc_gain_curve` is omitted. + +## Source Attribution + +The dataset as a whole is released under CC BY 4.0. Its components come from the following sources: + +- **Simulated channel data, phantom parameters, and reconstruction/segmentation labels** are generated by the contributors (Field II simulation outputs) and released under CC BY 4.0. +- **Segmentation mask shapes** (anechoic/hypoechoic/hyperechoic classes, 750 captures) are sourced from [Open Images V7](https://storage.googleapis.com/openimages/web/factsfigures_v7.html)'s animal-class segmentation annotations (Google LLC). Only the binary mask is used — never the underlying source photograph. Open Images V7 licenses its annotations, including segmentation masks, under CC BY 4.0; the source photographs themselves carry a separate CC BY 2.0 license but are not used anywhere in this dataset. +- **Diverse-echogenicity amplitude-weight maps** (1,000 captures) are photographs from Wikimedia Commons, restricted to the `CC-BY-4.0` category and verified per-image against each file's `CC BY 4.0` license field before acceptance. `wikimedia_commons_metadata.csv`, at the dataset root, credits each one: a row per capture, keyed by `DatasetFile` for `image_0751.hdf5` through `image_1750.hdf5`, giving the source URL, Commons file page, author, title and license. No photograph appears in the dataset in its original form. Each was converted to grayscale, resampled to the 400 × 450 weight-map grid, histogram-equalized and rescaled to `[0, 1]`. Values above 0.9 were then set to 1 and values below 0.1 to 0, producing the weight map stored as `data/diverse_source_image`. This conversion is implemented in `wikimedia_commons_postprocess.py`. +- **Point-target phantoms** (250 captures) use no external asset — purely synthetic point scatterers. + +All channel data was generated with Field II (Jensen/DTU), distributed for free academic use. Field II's terms do not restrict redistribution or licensing of simulation *output* data. Per Field II's terms, any use of this dataset should cite: J.A. Jensen, "Field: A Program for Simulating Ultrasound Systems," *Med. Biol. Eng. Comp.*, 1996; and J.A. Jensen and N.B. Svendsen, "Calculation of Pressure Fields from Arbitrarily Shaped, Apodized, and Excited Ultrasound Transducers," *IEEE Trans. Ultrason., Ferroelec., Freq. Contr.*, 1992. + +## Processing the Dataset + +The acquisitions can be processed with the `pipeline.yaml` definition in this folder and the [zea library](https://github.com/tue-bmd/zea). + +`zea` streams the data from the Hugging Face Hub and processes it according to the pipeline. You can try it out with the following command: + +```bash +zea process \ + --dataset hf://nvidia/OpenH-RF/concordia/data/image_0005.hdf5 \ + --config hf://nvidia/OpenH-RF/concordia/pipeline.yaml \ + --n-frames 1 \ + --save-as png +``` + +Alternatively, you can use the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/concordia/reconstruct.py) as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF). ## Dataset Format -The dataset is 2,000 individual zea HDF5 files (one acquisition per file) under -`data/` (zea format; `zea_version` 0.1.6). Reference figures are in -`examples/`; `reconstruct.py`, `pipeline.yaml`, and this card sit -at the repository root. Per file: - -- `data/raw_data` — full FSA channel data, `int16`, quantized from the native - simulated (float32) values by per-file peak-scaling into the int16 range - (~5e-5 relative quantization step — well below any physically meaningful signal - feature, equivalent to how real ultrasound hardware ADCs store raw channel data). -- `data/image` — the reference B-mode: a synthetic transmit aperture (STA) - reconstruction that delay-and-sums all 128 single-element FSA transmits with - zea's default DAS pipeline over the intended imaging FOV (see *Subject - Metadata*). This is exactly what `reconstruct.py` reproduces from `data/raw_data`. -- `data/segmentation` — boolean mask with labels `["background", ]`, - present only for anechoic/hypoechoic/hyperechoic phantoms (750 captures) — - the Open Images V7 mask directly. Not produced for diverse or point-target - phantoms (no defined region to segment in either case). -- `data/echogenicity_multiplier` — present alongside `data/segmentation` - (same 750 captures): the scalar amplitude multiplier Field II applied to - every scatterer inside that region, e.g. "hypoechoic at 0.23×," not just - "hypoechoic." A single number per file, so it's stored without a - `coordinates` grid (optional per zea's `Map` spec — this isn't spatial). -- `data/diverse_source_image` — present only for diverse-class phantoms - (1,000 captures): the grayscale natural-image echogenicity weight map - (continuous `[0, 1]` values) each capture's scatterer amplitudes were - generated from. Not a mask — just the reference image, so an end user can - see what pattern produced the capture. -- `data/scatterers` — the exact Field II point-scatterer cloud used to - simulate the capture (every capture, all classes): `values` = per-scatterer - amplitude (a.u.), `coordinates` = per-scatterer `(x, y, z)` position in - metres. ~275,000 scatterers per capture, stored via zea's custom-data - extension mechanism as a `(1, n_scatterers, 1)` / `(n_scatterers, 1, 3)` - pair rather than a regular pixel grid (a point cloud has no grid to speak - of). This is the ground truth Field II actually simulated from — finer than - any pixel-grid label derived from it. - -Pre-processing applied before packaging: anti-alias FIR decimation (factor 5, -104.16 MHz → 20.832 MHz) and int16 quantization, both described above. zea's -The original submission used lossless `lzf` HDF5 compression; re-saving may change storage compression without changing the RF representation. +[zea v0.1.6](https://github.com/tue-bmd/zea) + +The dataset is 2,000 individual zea HDF5 files (one acquisition per file) under `data/` (zea format; `zea_version` 0.1.6). Reference figures are in `examples/`; `reconstruct.py`, `pipeline.yaml`, and this card sit at the repository root. Per file: + +- `data/raw_data` — full FSA channel data, `int16`, quantized from the native simulated (float32) values by per-file peak-scaling into the int16 range (~5e-5 relative quantization step — well below any physically meaningful signal feature, equivalent to how real ultrasound hardware ADCs store raw channel data). +- `data/image` — the reference B-mode: a synthetic transmit aperture (STA) reconstruction that delay-and-sums all 128 single-element FSA transmits with zea's default DAS pipeline over the intended imaging FOV (see *Subject Metadata*). This is exactly what `reconstruct.py` reproduces from `data/raw_data`. +- `data/segmentation` — boolean mask with labels `["background", ]`, present only for anechoic/hypoechoic/hyperechoic phantoms (750 captures) — the Open Images V7 mask directly. Not produced for diverse or point-target phantoms (no defined region to segment in either case). +- `data/echogenicity_multiplier` — present alongside `data/segmentation` (same 750 captures): the scalar amplitude multiplier Field II applied to every scatterer inside that region, e.g. "hypoechoic at 0.23×," not just "hypoechoic." A single number per file, so it's stored without a `coordinates` grid (optional per zea's `Map` spec — this isn't spatial). +- `data/diverse_source_image` — present only for diverse-class phantoms (1,000 captures): the grayscale natural-image echogenicity weight map (continuous `[0, 1]` values) each capture's scatterer amplitudes were generated from. Not a mask — just the reference image, so an end user can see what pattern produced the capture. +- `data/scatterers` — the exact Field II point-scatterer cloud used to simulate the capture (every capture, all classes): `values` = per-scatterer amplitude (a.u.), `coordinates` = per-scatterer `(x, y, z)` position in metres. ~275,000 scatterers per capture, stored via zea's custom-data extension mechanism as a `(1, n_scatterers, 1)` / `(n_scatterers, 1, 3)` pair rather than a regular pixel grid (a point cloud has no grid to speak of). This is the ground truth Field II actually simulated from — finer than any pixel-grid label derived from it. + +Pre-processing applied before packaging: anti-alias FIR decimation (factor 5, 104.16 MHz → 20.832 MHz) and int16 quantization, both described above. zea's The original submission used lossless `lzf` HDF5 compression; re-saving may change storage compression without changing the RF representation. ## Dataset Quantification @@ -189,16 +119,12 @@ This dataset comprises 2,000 FSA captures across five echogenicity classes: | Diverse (natural-image-derived) | 1,000 | 0751–1750 | No | `data/diverse_source_image` | | Point-target | 250 | 1751–2000 | No | — | -`data/scatterers` (the raw point-scatterer cloud) is present for all 2,000 -captures regardless of class. +`data/scatterers` (the raw point-scatterer cloud) is present for all 2,000 captures regardless of class. -Each capture's class label is stored in its file metadata -(`metadata/annotations/label`). +Each capture's class label is stored in its file metadata (`metadata/annotations/label`). - **Stored HDF5 size:** 82.39 GB total; 41.20 MB per file on average. RF remains int16 and post-decimation. -- **Train / validation / test split:** none predefined — the corpus is released - as a single set for users to partition as their task requires (class membership - and index ranges are given above). +- **Train / validation / test split:** none predefined — the corpus is released as a single set for users to partition as their task requires (class membership and index ranges are given above). - **Per-sample feature table:** | Field | Shape | Dtype | Units | Description | @@ -220,73 +146,24 @@ Each capture's class label is stored in its file metadata ## Subject Metadata -Not applicable — no human or animal subjects. Each "subject" is a simulated -phantom of ≈275,000 scatterers. The reconstructed image FOV is 45 mm (lateral) × -40 mm (axial), starting 10 mm from the transducer face. The scatterer field is -deliberately larger than this FOV (≈49.5 mm wide, extending to 54 mm deep) so -that beamforming at the FOV edges is fully supported by surrounding scatterers -and free of edge-truncation artifacts; only the reconstruction grid is cropped -to the FOV, while `data/raw_data` and `data/scatterers` retain the full extent. -Per-class amplitude weighting inside the mask/weight-map region: +Not applicable — no human or animal subjects. Each "subject" is a simulated phantom of ≈275,000 scatterers. The reconstructed image FOV is 45 mm (lateral) × 40 mm (axial), starting 10 mm from the transducer face. The scatterer field is deliberately larger than this FOV (≈49.5 mm wide, extending to 54 mm deep) so that beamforming at the FOV edges is fully supported by surrounding scatterers and free of edge-truncation artifacts; only the reconstruction grid is cropped to the FOV, while `data/raw_data` and `data/scatterers` retain the full extent. Per-class amplitude weighting inside the mask/weight-map region: - **Anechoic:** scatterer amplitude zeroed. -- **Hypoechoic:** amplitude × U[0.07, 0.50]; the exact per-capture draw is - preserved in `data/echogenicity_multiplier`. -- **Hyperechoic:** amplitude × U[2, 8]; same per-capture preservation via - `data/echogenicity_multiplier`. -- **Diverse:** continuous grayscale weight map from a Wikimedia Commons - photograph (histogram-equalized, normalized to [0, 1], clipped at the - extremes) — preserved as `data/diverse_source_image`. +- **Hypoechoic:** amplitude × U[0.07, 0.50]; the exact per-capture draw is preserved in `data/echogenicity_multiplier`. +- **Hyperechoic:** amplitude × U[2, 8]; same per-capture preservation via `data/echogenicity_multiplier`. +- **Diverse:** continuous grayscale weight map from a Wikimedia Commons photograph (histogram-equalized, normalized to [0, 1], clipped at the extremes) — preserved as `data/diverse_source_image`. - **Point-target:** 10–20 point targets per phantom (count ~ U[10, 20], rounded to an integer), each target's amplitude an independent U[15, 35] draw. The targets consist of adjacent groups of high-amplitude scatterers within `data/scatterers`. -All classes except point-target additionally include 2–5 extra bright point -scatterers (amplitude 18–22) scattered at valid random positions, for realism. +All classes except point-target additionally include 2–5 extra bright point scatterers (amplitude 18–22) scattered at valid random positions, for realism. ## Data Validation -**Setup:** `reconstruct.py` requires `zea>=0.1.1`, `matplotlib`, -and `KERAS_BACKEND=jax` (or `torch`/`tensorflow`) set before import. This submission -was built and verified against the [OpenH-RF repo](https://github.com/open-h/OpenH-RF)'s environment (`uv sync` inside a clone of that repo installs `zea` and every other -dependency this script needs — see that repo's README for the exact commands). - -`reconstruct.py` (+ `pipeline.yaml`, zea's default DAS pipeline: Cast → ApplyWindow → -Demodulate → Beamform → EnvelopeDetect → Normalize → LogCompress; each stage is -explained in `reconstruct.py`'s own module docstring) reconstructs a synthetic -transmit aperture (STA) B-mode from `data/raw_data` using all 128 transmits, over -the same field of view as the stored `data/image` reference. It renders that -reconstruction on physical mm axes next to the capture's class-specific label -— segmentation foreground for anechoic/hypoechoic/hyperechoic, -`data/diverse_source_image` for diverse, none for point-target — and the -`data/scatterers` cloud coloured by |amplitude|, all on shared equal-aspect mm -axes, confirming the label, reconstruction, and scatterer field are spatially -registered. `examples/` holds one such figure for a representative capture of -each of the five classes. +`reconstruct.py` (+ `pipeline.yaml`, zea's default DAS pipeline: Cast → ApplyWindow → Demodulate → Beamform → EnvelopeDetect → Normalize → LogCompress; each stage is explained in `reconstruct.py`'s own module docstring) reconstructs a synthetic transmit aperture (STA) B-mode from `data/raw_data` using all 128 transmits, over the same field of view as the stored `data/image` reference. It renders that reconstruction on physical mm axes next to the capture's class-specific label — segmentation foreground for anechoic/hypoechoic/hyperechoic, `data/diverse_source_image` for diverse, none for point-target — and the `data/scatterers` cloud coloured by |amplitude|, all on shared equal-aspect mm axes, confirming the label, reconstruction, and scatterer field are spatially registered. [`assets/reference_capture.png`](assets/reference_capture.png) is one such figure. ## Known Issues -- **`data/image` is a log-compressed dB B-mode, not raw beamformed RF** — - This is a minor packaging note, not a coverage - gap, precisely because the data is FSA: `data/raw_data` retains every - element-to-element transmit/receive combination (128 single-element transmit - firings × 128-element receive aperture on the L11-5v), which is a more - general representation than any one fixed beamformed product could be: by - delay-and-sum with the appropriate per-element transmit delays and apodization - (linear superposition over the 128 single-element firings), it is a sufficient - basis to retrospectively synthesize other transmit/receive schemes — multi-angle - plane-wave compounding at arbitrary steering angles, diverging-wave imaging from - an arbitrary virtual source behind the array, conventional focused/multi-line - transmit — with no re-acquisition. `reconstruct.py` as shipped only implements - one of these (the full 128-transmit STA beamforming used to produce - `data/image`, via zea's default DAS pipeline, `pipeline.yaml`); it does not take - a scheme argument. Reconstructing a different scheme means modifying - `reconstruct.py` accordingly — supplying the corresponding transmit - delays/apodization to zea's `Beamform` op. +- **`data/image` is a log-compressed dB B-mode, not raw beamformed RF** — This is a minor packaging note, not a coverage gap, precisely because the data is FSA: `data/raw_data` retains every element-to-element transmit/receive combination (128 single-element transmit firings × 128-element receive aperture on the L11-5v), which is a more general representation than any one fixed beamformed product could be: by delay-and-sum with the appropriate per-element transmit delays and apodization (linear superposition over the 128 single-element firings), it is a sufficient basis to retrospectively synthesize other transmit/receive schemes — multi-angle plane-wave compounding at arbitrary steering angles, diverging-wave imaging from an arbitrary virtual source behind the array, conventional focused/multi-line transmit — with no re-acquisition. `reconstruct.py` as shipped only implements one of these (the full 128-transmit STA beamforming used to produce `data/image`, via zea's default DAS pipeline, `pipeline.yaml`); it does not take a scheme argument. Reconstructing a different scheme means modifying `reconstruct.py` accordingly — supplying the corresponding transmit delays/apodization to zea's `Beamform` op. ## Ethical Considerations -Entirely synthetic data. No human or animal subjects are involved, and no IRB/ethics -approval is required or applicable. The "animal-class" Open Images V7 segmentation -annotations and Wikimedia Commons photographs referenced elsewhere in this card -contribute only geometric silhouette shapes and grayscale texture patterns, -respectively — no live animal or human subject, tissue, or imagery of either is -used anywhere in this dataset. +Entirely synthetic data. No human or animal subjects are involved, and no IRB/ethics approval is required or applicable. The "animal-class" Open Images V7 segmentation annotations and Wikimedia Commons photographs referenced elsewhere in this card contribute only geometric silhouette shapes and grayscale texture patterns, respectively — no live animal or human subject, tissue, or imagery of either is used anywhere in this dataset. diff --git a/datasets/concordia/assets/classes.png b/datasets/concordia/assets/classes.png new file mode 100644 index 0000000..67fe09b Binary files /dev/null and b/datasets/concordia/assets/classes.png differ diff --git a/datasets/concordia/assets/main.png b/datasets/concordia/assets/main.png new file mode 100644 index 0000000..5f31cfe Binary files /dev/null and b/datasets/concordia/assets/main.png differ diff --git a/datasets/concordia/assets/reference_capture.png b/datasets/concordia/assets/reference_capture.png new file mode 100644 index 0000000..9bd38b5 Binary files /dev/null and b/datasets/concordia/assets/reference_capture.png differ diff --git a/datasets/concordia/pipeline.yaml b/datasets/concordia/pipeline.yaml index 3ea552e..622e926 100644 --- a/datasets/concordia/pipeline.yaml +++ b/datasets/concordia/pipeline.yaml @@ -1,3 +1,24 @@ +parameters: + # Imaging FOV: 45 mm lateral x 40 mm axial, starting 10 mm from the + # transducer face. This is a reconstruction-time choice, not part of the zea + # scan spec, so without it zea falls back to an aperture-wide, full-depth + # default grid. + xlims: + - -0.0225 + - 0.0225 + zlims: + - 0.01 + - 0.05 + # Isotropic pixels (~74 um both ways). data/image is stored on a 542 x 384 + # anisotropic grid; rendering that grid as square pixels stretches the + # phantom vertically. + grid_size_x: 610 + grid_size_z: 542 + # zea's default is 1.0, which is not what produced the reference images. + f_number: 1.75 + dynamic_range: + - -60 + - 0 pipeline: operations: - name: keras.ops.cast diff --git a/datasets/concordia/reconstruct.py b/datasets/concordia/reconstruct.py index 8ae6f30..faf5864 100644 --- a/datasets/concordia/reconstruct.py +++ b/datasets/concordia/reconstruct.py @@ -59,9 +59,9 @@ # --- Inputs ----------------------------------------------------------------- # Defaults stream straight from the published corpus. Swap any of these for a # local path to run against your own copy. -ZEA_FILE = "hf://nvidia/OpenH-RF/concordia/data/image_1808.hdf5" +ZEA_FILE = "hf://nvidia/OpenH-RF/concordia/data/image_0753.hdf5" FRAME = 0 # Frame index (default: 0) -OUT = HERE / "reference_capture.png" +OUT = HERE / "assets" / "reference_capture.png" SAVE_YAML = None # Optionally write the pipeline to a reusable pipeline.yaml @@ -260,6 +260,7 @@ def main(): _label(axs, f"data/scatterers (|amplitude|, {int(m.sum()):,} pts in FOV)") fig.tight_layout() + Path(OUT).parent.mkdir(parents=True, exist_ok=True) fig.savefig(OUT, dpi=150, bbox_inches="tight") print(f"Saved reconstruction to {OUT}") diff --git a/datasets/dartmouth-uct/README.md b/datasets/dartmouth-uct/README.md index 527e00a..14630c1 100644 --- a/datasets/dartmouth-uct/README.md +++ b/datasets/dartmouth-uct/README.md @@ -1,37 +1,47 @@ -# OpenH-RF Sub-Dataset: 2D Ring-Array USCT Waveforms from 2D- and 3D-k-Wave Simulations +--- +name: dartmouth-uct +pretty_name: "2D Ring-Array USCT Waveforms from 2D- and 3D-k-Wave Simulations" +license: cc-by-4.0 +task_categories: + - other +tags: + - ultrasound + - rf + - openh-rf + - usct + - sound-speed-estimation + - attenuation + - breast + - simulation +language: + - en +size_categories: + - 1K (primary point of contact) +- Geoffrey P. Luke (PI) +- Thayer School of Engineering, Dartmouth College +- University of Rochester Medical Center ## Dataset Creation Date @@ -39,27 +49,20 @@ are involved. ## License / Terms of Use -**CC BY 4.0.** All contributed data are cleared for this license. The data are -fully synthetic (no patient data, no consent or IRB requirements). The digital -breast phantoms derive from the publicly available VICTRE model (U.S. FDA / NCI, -public domain). +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage - **Sound-speed estimation / imaging** (quantitative SOS reconstruction). - **Acoustic-attenuation imaging.** -- **Ultrasound computed tomography (USCT)** reconstruction from full-ring - channel data. -- **Compressed sensing** — sub-sampling along the transmit - (Tx) or receive (Rx) axis and recovering missing channels. +- **Ultrasound computed tomography (USCT)** reconstruction from full-ring channel data. +- **Compressed sensing** — sub-sampling along the transmit (Tx) or receive (Rx) axis and recovering missing channels. ## Dataset Characterization - **Data Collection Method:** synthetic (k-Wave simulation). -- **Labeling Method:** synthetic ground truth (voxel-level SOS and attenuation - maps are the exact simulation inputs; no annotation error). -- **Acquisition system** (the label denotes the k-Wave simulation dimensionality; - the recorded data are 2D in both cases): +- **Labeling Method:** synthetic ground truth (voxel-level SOS and attenuation maps are the exact simulation inputs; no annotation error). +- **Acquisition system** (the label denotes the k-Wave simulation dimensionality; the recorded data are 2D in both cases): **2D-sim** (RF from 2D k-Wave) @@ -91,50 +94,32 @@ public domain). | Time samples (T) | 2161 | | Record length | ~180 µs | +## Processing the Dataset + +The acquisitions can be processed with the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/dartmouth-uct/reconstruct.py) as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF), together with the `pipeline.yaml` definition in this folder and the [zea library](https://github.com/tue-bmd/zea). The script streams the data from the Hugging Face Hub. + +Set `ZEA_FILE` at the top of the script and run `python reconstruct.py`; the figure at the top of this card is its output. `SOS_MAP = True` swaps the constant-sound-speed delays for a straight-ray integral through the ground-truth map. + ## Dataset Format -All data are packaged in the **`zea` HDF5 file format** (one `.hdf5` file per -acquisition), written entirely through `zea.File.create`. Each file stores the -raw channel data under `tracks/track_0/data/raw_data`, the acquisition parameters -under `tracks/track_0/scan`, the ring geometry under `probe` (in the **XZ imaging -plane**, y = elevation), and the voxel-level ground truth as native zea map fields -`tracks/track_0/data/sos_map` and `tracks/track_0/data/attenuation_map` (each with -per-pixel `coordinates`). Tissue class is stored in `metadata/annotations` -(`anatomy`, `label`); only fields with no standard spec home (z-plane indices, -element focus) are zea `CustomElement`s under the top-level `custom/` group. Files -are laid out by simulation type: `data/2d/` (2D-sim) and `data/3d/` (3D-sim), -produced by -[`convert_2d_to_zea.py`](convert_2d_to_zea.py) and -[`convert_3d_to_zea.py`](convert_3d_to_zea.py) respectively. +[zea v0.1.6](https://github.com/tue-bmd/zea) + +All data are packaged in the **`zea` HDF5 file format** (one `.hdf5` file per acquisition), written entirely through `zea.File.create`. Each file stores the raw channel data under `tracks/track_0/data/raw_data`, the acquisition parameters under `tracks/track_0/scan`, the ring geometry under `probe` (in the **XZ imaging plane**, y = elevation), and the voxel-level ground truth as native zea map fields `tracks/track_0/data/sos_map` and `tracks/track_0/data/attenuation_map` (each with per-pixel `coordinates`). Tissue class is stored in `metadata/annotations` (`anatomy`, `label`); only fields with no standard spec home (z-plane indices, element focus) are zea `CustomElement`s under the top-level `custom/` group. Files are laid out by simulation type: `data/2d/` (2D-sim) and `data/3d/` (3D-sim). **Pre-processing applied before packaging:** -- 2D-sim: temporal decimation by 3× (20 MHz → 6.67 MHz native simulation rate). - 3D-sim: saved at the native 12 MHz, no decimation. -- Time-zero is carried in `scan/initial_times`; the per-sample time vector is - `initial_times[tx] + n / sampling_frequency`. In **both** sets `t = 0` is the - emission centroid: `initial_times = -2.15e-6` (2D-sim) and `-1.625e-6` (3D-sim, - the 5-cycle toneburst centroid). The 3D simulation saved its time vector from - the pulse onset, so this centroid offset is applied during conversion. -- Single-element transmit events are described by `scan/tx_apodizations` with zero - `scan/t0_delays`. **2D-sim:** all 256 elements fire (identity matrix). - **3D-sim:** 64 events fire every 4th element (a `(64, 256)` stride-4 matrix). -- Per-acquisition channel data stored as `float32` (zea's `raw_data` spec allows - only `float32` or `int16`). The 2D-sim RF was decimated and intermediately - cached at fp16 precision, so its stored `float32` is bit-faithful to that source - rather than carrying extra precision; the 3D-sim RF is the native `float32` - simulation output. +- 2D-sim: temporal decimation by 3× (20 MHz → 6.67 MHz native simulation rate). 3D-sim: saved at the native 12 MHz, no decimation. +- Time-zero is carried in `scan/initial_times`; the per-sample time vector is `initial_times[tx] + n / sampling_frequency`. In **both** sets `t = 0` is the emission centroid: `initial_times = -2.15e-6` (2D-sim) and `-1.625e-6` (3D-sim, the 5-cycle toneburst centroid). The 3D simulation saved its time vector from the pulse onset, so this centroid offset is applied during conversion. +- Single-element transmit events are described by `scan/tx_apodizations` with zero `scan/t0_delays`. **2D-sim:** all 256 elements fire (identity matrix). **3D-sim:** 64 events fire every 4th element (a `(64, 256)` stride-4 matrix). +- Per-acquisition channel data stored as `float32` (zea's `raw_data` spec allows only `float32` or `int16`). The 2D-sim RF was decimated and intermediately cached at fp16 precision, so its stored `float32` is bit-faithful to that source rather than carrying extra precision; the 3D-sim RF is the native `float32` simulation output. - No demodulation or beamforming is applied — data are raw RF channel signals. ## Dataset Quantification **Current OpenH-RF release:** 2,625 HDF5 files; 257.62 GB (257,619,787,776 bytes) stored; root `zea_version` **0.1.6**. Sizes include all HDF5 contents and use decimal units (MB = 10^6 bytes, GB = 10^9 bytes, TB = 10^12 bytes), not decoded-array memory or original-source download sizes. -- **Number of acquisitions:** **2,149 (2D-sim)** (1,859 dense + 290 fatty) + - **476 (3D-sim)** = **2,625 acquisitions**. -- **Single-Tx channel-capture frames:** 2,149 × 256 (2D-sim) + 476 × 64 (3D-sim) - ≈ **5.8 × 10⁵ frames**. -- **Train / val / test split:** suggested 80 / 10 / 10 by **source phantom** - (so slices/z-planes from one phantom never cross splits — prevents leakage). +- **Number of acquisitions:** **2,149 (2D-sim)** (1,859 dense + 290 fatty) + **476 (3D-sim)** = **2,625 acquisitions**. +- **Single-Tx channel-capture frames:** 2,149 × 256 (2D-sim) + 476 × 64 (3D-sim) ≈ **5.8 × 10⁵ frames**. +- **Train / val / test split:** suggested 80 / 10 / 10 by **source phantom** (so slices/z-planes from one phantom never cross splits — prevents leakage). - **Stored HDF5 size:** 257.62 GB total; 198.10 GB (2D-sim) + 59.52 GB (3D-sim). **Per-sample feature table — 2D-sim** (HDF5 keys per `.hdf5` acquisition): @@ -157,9 +142,7 @@ produced by | `metadata/annotations` | – | str | – | `anatomy="breast"`, `label="dense"`/`"fatty"` | | `custom/z_slice` | scalar | int | – | Phantom z-slice index | -Attenuation is stored in the zea base unit **dB/m/Hz** (`1 dB/cm/MHz = 1e-4 -dB/m/Hz`); the `sos_map`/`attenuation_map` `coordinates` carry the physical grid -(so `dx` is implicit), and `scan/sound_speed` holds the water reference. +Attenuation is stored in the zea base unit **dB/m/Hz** (`1 dB/cm/MHz = 1e-4 dB/m/Hz`); the `sos_map`/`attenuation_map` `coordinates` carry the physical grid (so `dx` is implicit), and `scan/sound_speed` holds the water reference. **Per-sample feature table — 3D-sim** (HDF5 keys per `.hdf5` acquisition): @@ -182,66 +165,32 @@ dB/m/Hz`); the `sos_map`/`attenuation_map` `coordinates` carry the physical grid | `metadata/annotations` | – | str | – | `anatomy="breast"` (no dense/fatty label for the 3D set) | | `custom/z_off`, `custom/phantom_z_idx`, `custom/element_focus` | scalar | int / float | – / m | Ring z-offset, phantom z-slice index, element focus (0.075 m) | -Attenuation is in the zea base unit **dB/m/Hz**; the map `coordinates` carry the -physical grid (XZ plane, y = 0). +Attenuation is in the zea base unit **dB/m/Hz**; the map `coordinates` carry the physical grid (XZ plane, y = 0). ## Subject Metadata Not applicable — all data are synthetic. Aggregate phantom statistics: -- **Anatomical region:** breast cross-sections. The 3D-sim set samples several - z-planes per phantom (one 2D cross-section each). -- **Tissue classes represented:** fat, glandular, skin/connective, with - continuous SOS/attenuation/density assignments. +- **Anatomical region:** breast cross-sections. The 3D-sim set samples several z-planes per phantom (one 2D cross-section each). +- **Tissue classes represented:** fat, glandular, skin/connective, with continuous SOS/attenuation/density assignments. - **No PHI.** ## Data Validation -A single reference reconstruction, [`reconstruct.py`](reconstruct.py), serves -**both** sub-datasets. It builds a `zea.Pipeline` whose beamforming stage is -zea's dedicated `zea.ops.USCTReflectivityDAS` — a round-trip time-of-flight -Delay-And-Sum that, for every pixel, coherently sums over all transmit/receive -pairs, rejects the direct through-transmission arrival, and apodizes to keep only -backscatter geometries. The pipeline is saved to [`pipeline.yaml`](pipeline.yaml). - -The same code reconstructs the 2D-sim (256 transmits) and 3D-sim (64 transmits) -files because everything it needs is read **back from the zea file**: element -positions (`probe/probe_geometry`), the transmit selection (`scan/tx_apodizations`), -sampling rate (`scan/sampling_frequency`), time-zero (`scan/initial_times`), and -the imaging grid (from the ground-truth `coordinates`). The ring is stored in the -XZ imaging plane, so `zea.File.load_parameters` + `pipeline.prepare_parameters` -drive the reconstruction directly. A resulting image whose bright skin boundary -traces the ground-truth contour confirms the geometry, timing, and transmit -parameters were recorded correctly. - -``` -python reconstruct.py --input data/2d/phantom_xxx.hdf5 -python reconstruct.py --input data/3d/phantom_xxx.hdf5 -``` +A single reference reconstruction, `reconstruct.py` (see *Processing the Dataset*), serves **both** sub-datasets. It builds a `zea.Pipeline` whose beamforming stage is zea's dedicated `zea.ops.USCTReflectivityDAS` — a round-trip time-of-flight Delay-And-Sum that, for every pixel, coherently sums over all transmit/receive pairs, rejects the direct through-transmission arrival, and apodizes to keep only backscatter geometries. The pipeline is saved to [`pipeline.yaml`](pipeline.yaml). + +The same code reconstructs the 2D-sim (256 transmits) and 3D-sim (64 transmits) files because everything it needs is read **back from the zea file**: element positions (`probe/probe_geometry`), the transmit selection (`scan/tx_apodizations`), sampling rate (`scan/sampling_frequency`), time-zero (`scan/initial_times`), and the imaging grid (from the ground-truth `coordinates`). The ring is stored in the XZ imaging plane, so `zea.File.load_parameters` + `pipeline.prepare_parameters` drive the reconstruction directly. A resulting image whose bright skin boundary traces the ground-truth contour confirms the geometry, timing, and transmit parameters were recorded correctly. ## Known Issues -- **2D-sim vs 3D-sim differ in geometry and sampling** — 60 mm vs 110.9 mm ring; - 256 vs 64 transmits; 867 @ 6.67 MHz (130 µs) vs 2161 @ 12 MHz (~180 µs); 230² - (0.30 mm) vs 800² (0.29 mm) GT maps. Users combining both must resample to a - common grid. -- **2D-sim point elements vs 3D-sim focused elements:** the 2D-sim set idealizes - elements as points (no element directivity), while the 3D-sim set models finite - focused elements (0.558 mm × 19 mm, 75 mm elevation focus) and out-of-plane - propagation. -- **Attenuation units:** stored in the zea base unit `dB/m/Hz` (converted from the - simulation's `dB/cm/MHz`; `1 dB/cm/MHz = 1e-4 dB/m/Hz`). The k-Wave `alpha_power` - is the `attenuation_map/gamma` field (α(f)=α₀·fᵞ, γ=1.01). -- **Time-zero convention:** in both sets `t = 0` is the emission centroid (carried - in `scan/initial_times`), not the first recorded sample — important for - time-of-flight methods. +- **2D-sim vs 3D-sim differ in geometry and sampling** — 60 mm vs 110.9 mm ring; 256 vs 64 transmits; 867 @ 6.67 MHz (130 µs) vs 2161 @ 12 MHz (~180 µs); 230² (0.30 mm) vs 800² (0.29 mm) GT maps. Users combining both must resample to a common grid. +- **2D-sim point elements vs 3D-sim focused elements:** the 2D-sim set idealizes elements as points (no element directivity), while the 3D-sim set models finite focused elements (0.558 mm × 19 mm, 75 mm elevation focus) and out-of-plane propagation. +- **Attenuation units:** stored in the zea base unit `dB/m/Hz` (converted from the simulation's `dB/cm/MHz`; `1 dB/cm/MHz = 1e-4 dB/m/Hz`). The k-Wave `alpha_power` is the `attenuation_map/gamma` field (α(f)=α₀·fᵞ, γ=1.01). +- **Time-zero convention:** in both sets `t = 0` is the emission centroid (carried in `scan/initial_times`), not the first recorded sample — important for time-of-flight methods. ## Ethical Considerations -Fully synthetic dataset. No human or animal subjects; no consent, IRB, or -de-identification requirements apply. The VICTRE breast phantoms are publicly -released by the U.S. FDA/NCI. No usage caveats beyond the CC BY 4.0 attribution -requirement. +Fully synthetic dataset. No human or animal subjects; no consent, IRB, or de-identification requirements apply. The VICTRE breast phantoms are publicly released by the U.S. FDA/NCI. No usage caveats beyond the CC BY 4.0 attribution requirement. --- @@ -249,9 +198,5 @@ requirement. If you use this sub-dataset, please cite: -- Badano A, Graff CG, Badal A, Sharma D, Zeng R, Samuelson FW, Glick SJ, - Myers KJ. *Evaluation of Digital Breast Tomosynthesis as Replacement of - Full-Field Digital Mammography Using an In Silico Imaging Trial.* - JAMA Network Open. 2018;1(7):e185474. doi:10.1001/jamanetworkopen.2018.5474 -- Treeby BE, Cox BT. *k-Wave: MATLAB toolbox for the simulation and - reconstruction of photoacoustic wave fields.* J Biomed Opt. 2010;15(2):021314. +- Badano A, Graff CG, Badal A, Sharma D, Zeng R, Samuelson FW, Glick SJ, Myers KJ. *Evaluation of Digital Breast Tomosynthesis as Replacement of Full-Field Digital Mammography Using an In Silico Imaging Trial.* JAMA Network Open. 2018;1(7):e185474. doi:10.1001/jamanetworkopen.2018.5474 +- Treeby BE, Cox BT. *k-Wave: MATLAB toolbox for the simulation and reconstruction of photoacoustic wave fields.* J Biomed Opt. 2010;15(2):021314. diff --git a/datasets/dartmouth-uct/assets/main.png b/datasets/dartmouth-uct/assets/main.png new file mode 100644 index 0000000..3b4ff97 Binary files /dev/null and b/datasets/dartmouth-uct/assets/main.png differ diff --git a/datasets/dartmouth-uct/assets/phantom_179604449_z200.png b/datasets/dartmouth-uct/assets/phantom_179604449_z200.png new file mode 100644 index 0000000..918bc11 Binary files /dev/null and b/datasets/dartmouth-uct/assets/phantom_179604449_z200.png differ diff --git a/datasets/dartmouth-uct/pipeline.yaml b/datasets/dartmouth-uct/pipeline.yaml index 4a6e14e..70b2b5b 100644 --- a/datasets/dartmouth-uct/pipeline.yaml +++ b/datasets/dartmouth-uct/pipeline.yaml @@ -5,9 +5,7 @@ pipeline: dtype: float32 - name: patched_grid operations: - - name: usct_reflectivity_das - params: - compounding: coherent + - usct_reflectivity_das params: with_batch_dim: false jit_options: null diff --git a/datasets/dartmouth-uct/reconstruct.py b/datasets/dartmouth-uct/reconstruct.py index a6acf3e..d832681 100644 --- a/datasets/dartmouth-uct/reconstruct.py +++ b/datasets/dartmouth-uct/reconstruct.py @@ -64,7 +64,7 @@ # --- Inputs ----------------------------------------------------------------- # Defaults stream straight from the published corpus. Swap any of these for a # local path to run against your own copy. -INPUT = "hf://nvidia/OpenH-RF/dartmouth-uct/data/2d/phantom_179604449_z200.hdf5" +ZEA_FILE = "hf://nvidia/OpenH-RF/dartmouth-uct/data/2d/phantom_179604449_z200.hdf5" FOV = None # square field of view [m] (default: the ground-truth map footprint) NUM_PIXELS = None # output image is num_pixels x num_pixels (default: ground-truth resolution) SOS_MAP = False # use the ground-truth sound-speed map for straight-ray corrected delays @@ -182,12 +182,12 @@ def crop_to_grid(gt, grid): def main(): suffix = "_sos.png" if SOS_MAP else ".png" - output_path = Path(Path(INPUT).stem + suffix) + output_path = HERE / "assets" / (Path(ZEA_FILE).stem + suffix) zea.init_device(device=DEVICE, verbose=True) # Load file: acquisition parameters (with config overrides) and raw RF data. - with File(str(INPUT)) as f: + with File(str(ZEA_FILE)) as f: check_ring_in_imaging_plane(f) gt_full = ground_truth(f) grid = grid_limits(gt_full, ring_radius(f), FOV, NUM_PIXELS) @@ -244,8 +244,9 @@ def main(): # Tie the colorbar axes to the image axes so it matches the panel height. cax = make_axes_locatable(ax).append_axes("right", size="5%", pad=0.05) fig.colorbar(handle, cax=cax) - fig.suptitle(Path(INPUT).name) + fig.suptitle(Path(ZEA_FILE).name) fig.tight_layout() + Path(output_path).parent.mkdir(parents=True, exist_ok=True) plt.savefig(str(output_path), bbox_inches="tight", dpi=100) print(f"Reconstructed : {recon.shape}") diff --git a/datasets/kaist-snubh-barreleye/README.md b/datasets/kaist-snubh-barreleye/README.md index 1efe650..a41ece6 100644 --- a/datasets/kaist-snubh-barreleye/README.md +++ b/datasets/kaist-snubh-barreleye/README.md @@ -1,17 +1,42 @@ -# Breast OpenH-RF - -*An in-vivo human breast plane-wave raw-channel ultrasound sub-dataset for the OpenH-RF foundation initiative.* +--- +name: kaist-snubh-barreleye +pretty_name: "KAIST–SNUBH In-vivo Breast Plane-Wave RF" +license: cc-by-4.0 +task_categories: + - image-classification + - other +tags: + - ultrasound + - rf + - openh-rf + - breast + - in-vivo + - plane-wave + - sound-speed-estimation +language: + - en +size_categories: + - n<1K +--- + +# KAIST–SNUBH In-vivo Breast Plane-Wave RF + +![DAS B-mode reconstruction of a biopsy-proven invasive ductal carcinoma (S01_D1)](assets/main.png) + +*Delay-and-sum reconstruction of a biopsy-proven invasive ductal carcinoma, [`data/S01_D1.hdf5`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/kaist-snubh-barreleye/data/S01_D1.hdf5).* ## Dataset Description -Breast OpenH-RF contains pre-beamformed RF channel-capture data from in-vivo breast ultrasound exams performed on a clinical, FDA-cleared scanner. Every acquisition is a 9-angle plane-wave compounding sequence with a 192-element linear array, paired with a B-mode reference image and a clinically verified diagnostic label. The intended research contribution is two-fold: (1) provide a clinically-grounded benchmark for **sound-speed and attenuation imaging** (Section 6.3 of the RFP) on real human breast tissue with biopsy-proven outcomes and (2) supply a high-quality plane-wave compounding corpus for **generalized reconstruction** research (Section 6.1: super-resolution, aberration correction, adaptive transmit design). Pathology and BI-RADS labels additionally enable benchmarking of **ultrasound interpretation** (Section 6.5). +This dataset contains pre-beamformed RF channel-capture data from in-vivo breast ultrasound exams performed on a clinical, FDA-cleared scanner. Every acquisition is a 9-angle plane-wave compounding sequence with a 192-element linear array, paired with a B-mode reference image and a clinically verified diagnostic label. The intended research contribution is two-fold: (1) provide a clinically-grounded benchmark for **sound-speed and attenuation imaging** (Section 6.3 of the RFP) on real human breast tissue with biopsy-proven outcomes and (2) supply a high-quality plane-wave compounding corpus for **generalized reconstruction** research (Section 6.1: super-resolution, aberration correction, adaptive transmit design). Pathology and BI-RADS labels additionally enable benchmarking of **ultrasound interpretation** (Section 6.5). ## Dataset Contributor(s) -- **Lead PI:** Prof. Hyeon-Min Bae — KAIST, School of Electrical Engineering -- **Co-investigators (KAIST / Barreleye Inc.):** Seok-Hwan Oh, Myeong-Gee Kim, Young-Min Kim, HyeonJik Lee -- **Clinical co-investigator:** Hyuk-sool Kwon — Seoul National University Bundang Hospital (SNUBH) -- **Primary point of contact:** Seok-Hwan Oh (Barreleye Inc., Korea) — shoh@barreleye.co.kr +- Hyeon-Min Bae (lead PI; KAIST, School of Electrical Engineering) +- Seok-Hwan Oh (primary point of contact; KAIST / Barreleye Inc.) +- Myeong-Gee Kim (KAIST / Barreleye Inc.) +- Young-Min Kim (KAIST / Barreleye Inc.) +- HyeonJik Lee (KAIST / Barreleye Inc.) +- Hyuk-sool Kwon (clinical co-investigator; Seoul National University Bundang Hospital, SNUBH) ## Dataset Creation Date @@ -19,7 +44,7 @@ Breast OpenH-RF contains pre-beamformed RF channel-capture data from in-vivo bre ## License / Terms of Use -**CC BY 4.0** (see [`LICENCE`](LICENCE)). +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage @@ -37,8 +62,28 @@ Quantitative imaging (sound-speed / attenuation estimation), generalized reconst - ADC sampling: **62.5 MHz**, exported as float32. - Transmit: 9-angle plane-wave compounding at **[-15, -10, -5, -2.5, 0, +2.5, +5, +10, +15]°**. +## Processing the Dataset + +The acquisitions can be processed with the `pipeline.yaml` definition in this folder and the [zea library](https://github.com/tue-bmd/zea). + +`zea` streams the data from the Hugging Face Hub and processes it according to the pipeline. You can try it out with the following command: + +```bash +zea process \ + --dataset hf://nvidia/OpenH-RF/kaist-snubh-barreleye/data/S01_D1.hdf5 \ + --config hf://nvidia/OpenH-RF/kaist-snubh-barreleye/pipeline.yaml \ + --n-frames 1 \ + --save-as png +``` + +Alternatively, you can use the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/kaist-snubh-barreleye/reconstruct.py) as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF). + +This is a single-frame acquisition, so `zea process` outputs a `.png` rather than a `.gif` — this requires a `zea` build newer than the currently pinned 0.1.6 (single-frame PNG output landed after that release). + ## Dataset Format +[zea v0.1.6](https://github.com/tue-bmd/zea) + All data is delivered in the **zea HDF5** format (OpenH-RF spec). One HDF5 file per acquisition; one image track per file. ``` @@ -52,7 +97,6 @@ hdf5/ - **Channel reordering** of the scanner's raw export into the OpenH-RF convention `(n_frames=1, n_tx=9, n_ax=Ns, n_el=192, n_ch=1)`. - No demodulation, decimation, band-pass filtering, or value clipping — `raw_data` is bit-faithful to the scanner export. - ## Dataset Quantification **Current OpenH-RF release:** 70 HDF5 files; 802.10 MB (802,095,104 bytes) stored; root `zea_version` **0.1.6**. Sizes include all HDF5 contents and use decimal units (MB = 10^6 bytes, GB = 10^9 bytes, TB = 10^12 bytes), not decoded-array memory or original-source download sizes. @@ -91,7 +135,7 @@ Per-acquisition feature table (one row per HDF5): ## Subject Metadata -Per-file metadata follows the **HIPAA Safe-Harbor** approach: only **de-identified subject ID, sex, anatomy, binary label, BI-RADS, pathology subtype** are stored. Free-text identifiers, exact age, exact lesion size, and exam dates are deliberately **omitted from the HDF5 files**. +Per-file metadata follows the **HIPAA Safe-Harbor** approach: only **de-identified subject ID, sex, anatomy, binary label, BI-RADS, pathology subtype** are stored. Free-text identifiers, exact age, exact lesion size, and exam dates are deliberately **omitted from the HDF5 files**. - **Number of subjects:** 35 - **Sex distribution:** 100% female (35/35) @@ -142,20 +186,15 @@ Plane-wave transmit beamforming is used, with all 192 elements activated on each ## Data Validation -A reference reconstruction is provided in [`reconstruct.py`](reconstruct.py). It defines the DAS pipeline directly in code (via zea ops) and applies, to the raw RF channel data: +`reconstruct.py` reconstructs a B-mode from `raw_data` using the `zea.Pipeline` defined in [`pipeline.yaml`](pipeline.yaml): ``` cast(float32) → band-pass filter (1–12 MHz) → demodulate → DAS beamform → envelope detect → normalize → log compression ``` -The 1–12 MHz band-pass rejects a persistent sub-MHz band before coherent beamforming, which allows to produce a clean B-mode directly from the raw RF. Run it to reproduce a reference B-mode from any delivered file: - -```bash -python reconstruct.py --input hdf5/original/S01_D1.hdf5 # single file -python reconstruct.py --compare S01_D1 # DAS vs. scanner reference B-mode -``` +The 1–12 MHz band-pass rejects a persistent sub-MHz band before coherent beamforming, which allows it to produce a clean B-mode directly from the raw RF. -`python reconstruct.py --save-yaml` exports the pipeline as `pipeline.yaml` if a shareable recipe is needed (the script itself does not load it). +Reference output: `main.png` — `data/S01_D1.hdf5` (biopsy-proven invasive ductal carcinoma), shown above. ## Known Issues @@ -167,5 +206,5 @@ python reconstruct.py --compare S01_D1 # DAS vs. scanner re - **Consent status:** All subjects gave informed consent under SNUBH IRB protocol **B-2401-876-301**. - **De-identification:** No direct identifiers (name, full exam date, free-text clinical notes) are stored. Age is decade-binned at the dataset level (not stored per file); exact lesion size and exam dates are not stored per file; only the acquisition year (2024) is reported. Subject IDs are coded (`S01`…`S35`). -- **IRB approval:** SNUBH IRB **B-2401-876-301** +- **IRB approval:** SNUBH IRB **B-2401-876-301** - **Animal welfare (ARRIVE 2.0):** Not applicable — human-only dataset. diff --git a/datasets/kaist-snubh-barreleye/assets/main.png b/datasets/kaist-snubh-barreleye/assets/main.png new file mode 100644 index 0000000..794dd6d Binary files /dev/null and b/datasets/kaist-snubh-barreleye/assets/main.png differ diff --git a/datasets/kaist-snubh-barreleye/pipeline.yaml b/datasets/kaist-snubh-barreleye/pipeline.yaml index dd41858..86f063e 100644 --- a/datasets/kaist-snubh-barreleye/pipeline.yaml +++ b/datasets/kaist-snubh-barreleye/pipeline.yaml @@ -1,3 +1,15 @@ +parameters: + xlims: + - -0.0191 + - 0.0191 + zlims: + - 0.002 + - 0.04 + dynamic_range: + - -50 + - 0 + apply_lens_correction: false + f_number: 1.5 pipeline: operations: - name: keras.ops.cast diff --git a/datasets/kaist-snubh-barreleye/reconstruct.py b/datasets/kaist-snubh-barreleye/reconstruct.py index 6b8b032..f45a392 100644 --- a/datasets/kaist-snubh-barreleye/reconstruct.py +++ b/datasets/kaist-snubh-barreleye/reconstruct.py @@ -4,8 +4,9 @@ Dataset link: https://huggingface.co/datasets/nvidia/OpenH-RF/tree/main/kaist-snubh-barreleye B-mode reconstruction of 9-angle plane-wave compounded in-vivo breast channel data. - -The reconstruction is written as a single B-mode PNG. +The 1-12 MHz band-pass rejects a persistent sub-MHz band before coherent +beamforming, which allows the raw RF (n_ch=1) to demodulate and beamform +cleanly. Requires zea>=0.1.6 (https://github.com/tue-bmd/zea), the library that does the ultrasound processing here, together with one of its Keras backends (JAX, @@ -16,147 +17,60 @@ python reconstruct.py """ -from __future__ import annotations - import os os.environ.setdefault("KERAS_BACKEND", "jax") os.environ.setdefault("MPLBACKEND", "Agg") - from pathlib import Path +import keras import matplotlib.pyplot as plt import numpy as np import zea -from zea import File, Pipeline -from zea.ops import ( - BandPassFilter, - Beamform, - Cast, - Demodulate, - EnvelopeDetect, - LogCompress, - Normalize, -) - -_MM = plt.FuncFormatter(lambda v, _: f"{v * 1e3:.0f}") -_HERE = Path(__file__).resolve().parent - -# Imaging grid & display parameters (shared by both representations). -PARAMETERS = { - "grid_size_x": 512, # lateral pixels - "grid_size_z": 768, # axial pixels - "xlims": [-0.0191, 0.0191], # metres (lateral_length = 3.82 cm) - "zlims": [0.002, 0.04], # metres — skip first 2 mm dead-zone / TX-pulse residue - "dynamic_range": [-50, 0], - "apply_lens_correction": False, - "f_number": 1.5, -} +from zea import Config, File, Pipeline + +HERE = Path(__file__).parent # --- Inputs ----------------------------------------------------------------- # Defaults stream straight from the published corpus. Swap any of these for a # local path to run against your own copy. -INPUT = "hf://nvidia/OpenH-RF/kaist-snubh-barreleye/data/S32_D2.hdf5" -SAVE_YAML = False # write pipeline.yaml / pipeline_simple.yaml from the pipelines below -OUTPUT = _HERE / "reconstruct.png" - - -def build_pipeline(n_ch: int, filtered: bool = True) -> Pipeline: - """Pipeline by representation, built directly from zea ops. - - Raw RF (n_ch=1) gets band-pass + demodulate; already-demodulated IQ - (n_ch=2) goes straight to beamforming. - """ - ops = [Cast(dtype="float32")] - if n_ch == 1: - if filtered: - # The proposed one-op filter, applied to the raw RF before - # demodulation. Low edge 1 MHz: the filter ablation - # (evaluation/*.md) shows the entire benefit of the upstream - # depth-adaptive filter is rejecting a persistent sub-MHz band - # before coherent beamforming. High edge 12 MHz: passes the full - # fundamental around fc = 10 MHz (Nyquist = 31.25 MHz). 255 taps - # keep the ~0.25 MHz transition well-resolved at the 1 MHz edge - # (fs = 62.5 MHz). - ops.append(BandPassFilter(passband=(1e6, 12e6), num_taps=255)) - ops.append(Demodulate()) - ops += [ - Beamform(beamformer="delay_and_sum"), - EnvelopeDetect(), - Normalize(), - LogCompress(), - ] - return Pipeline(operations=ops) - - -def save_yaml() -> None: - """Export the code-defined pipeline as a shareable YAML recipe.""" - out = _HERE / "pipeline.yaml" - build_pipeline(n_ch=1).to_yaml(str(out)) - print(f"Saved : {out}") - - -def reconstruct_file(path: Path, filtered: bool = True) -> dict: - """Beamform one .hdf5 file. Returns bmode, extent and metadata.""" - with File(str(path)) as f: +ZEA_FILE = "hf://nvidia/OpenH-RF/kaist-snubh-barreleye/data/S01_D1.hdf5" +CONFIG = "hf://nvidia/OpenH-RF/kaist-snubh-barreleye/pipeline.yaml" +OUT = HERE / "assets" / "reconstruction.png" + + +def main(): + zea.init_device() + config = Config.from_path(CONFIG) + + with File(str(ZEA_FILE)) as f: + parameters = f.load_parameters(**config.parameters) raw = f.data.raw_data[:] - parameters = f.load_parameters(**PARAMETERS) patient = f.metadata.subject.id label = f.metadata.annotations.label - n_ch = raw.shape[-1] - print( - f" {Path(path).parent.name}/{Path(path).name}: n_ch={n_ch} ({'RF' if n_ch == 1 else 'IQ'})" - ) - pipeline = build_pipeline(n_ch, filtered) + pipeline = Pipeline.from_config(config) inputs = pipeline.prepare_parameters(parameters) - recon = np.array(pipeline(data=raw, **inputs)["data"]) - return { - "bmode": zea.display.to_8bit(recon[0]), - "extent": parameters.extent_imshow, - "patient": patient, - "label": label, - "n_ch": n_ch, - } - - -def _imshow_recon(ax, bmode, extent, title): - ax.imshow(bmode, cmap="gray", aspect="auto", extent=extent) - ax.set_title(title) - ax.set_xlabel("x [mm]") - ax.set_ylabel("z [mm]") - ax.xaxis.set_major_formatter(_MM) - ax.yaxis.set_major_formatter(_MM) - - -def run_single() -> int: - print(f"Input : {INPUT}") - zea.init_device() - r = reconstruct_file(INPUT) + outputs = pipeline(data=raw, **inputs) + recon = keras.ops.convert_to_numpy(outputs[pipeline.output_key])[0] - Path(OUTPUT).parent.mkdir(parents=True, exist_ok=True) zea.visualize.set_mpl_style() - plt.figure(figsize=(5, 7)) - _imshow_recon( - plt.gca(), - r["bmode"], - r["extent"], - f"DAS reconstruction — patient {r['patient']} ({r['label']})", + fig, ax = plt.subplots(figsize=(5.5, 6)) + ax.imshow( + zea.display.to_8bit(recon, dynamic_range=parameters.dynamic_range), + cmap="gray", + extent=np.asarray(parameters.extent_imshow) * 1e3, + aspect="equal", ) - plt.tight_layout() - plt.savefig(str(OUTPUT), dpi=130, bbox_inches="tight") - plt.close() - print(f"Saved : {OUTPUT}") - return 0 - - -def main() -> int: - if SAVE_YAML: - save_yaml() - return 0 - return run_single() + ax.set_xlabel("lateral [mm]") + ax.set_ylabel("depth [mm]") + ax.set_title(f"{Path(ZEA_FILE).stem} — patient {patient} ({label})") + fig.tight_layout() + Path(OUT).parent.mkdir(parents=True, exist_ok=True) + fig.savefig(str(OUT), dpi=130, bbox_inches="tight") + print(f"raw {raw.shape} -> B-mode {recon.shape}; saved {OUT}") if __name__ == "__main__": - raise SystemExit(main()) + main() diff --git a/datasets/mosaic-intelligence/README.md b/datasets/mosaic-intelligence/README.md index 50bb51c..0b0405c 100644 --- a/datasets/mosaic-intelligence/README.md +++ b/datasets/mosaic-intelligence/README.md @@ -1,5 +1,6 @@ --- -name: "OpenH-RF — Mosaic Intelligence / NuevoSono IVUS" +name: mosaic-intelligence +pretty_name: "Mosaic Intelligence / NuevoSono IVUS" license: cc-by-4.0 task_categories: - image-segmentation @@ -9,38 +10,27 @@ tags: - openh-rf - IVUS - tracked-ultrasound +language: + - en --- -# Data Card — Mosaic Intelligence / NuevoSono in-vivo IVUS +# Mosaic Intelligence / NuevoSono In-vivo IVUS -This data card consists of a collection of in-vivo intravascular ultrasound (IVUS) acquisitions from a porcine study. Each acquisition is saved in the *zea* file format. The general dataset sections below (contributors, license, characterization, etc.) apply to all acquisitions, with individual subsections detailing each dataset's specific dimensions and which optional groups are present. +![IVUS pullback: B-mode, segmentation overlay and pullback trajectory](assets/pullback.gif) -There are 9 acquisitions in total: - -**Group 1: `15_*` acquisitions (untracked, counterclockwise rotation):** -- `15_10_18_21` -- `15_10_50_19` -- `15_16_45_06` - -**Group 2: `22_*` acquisitions (tracked, linear encoder pullback, clockwise rotation):** -- `22_12_10_52` -- `22_12_29_46` -- `22_12_38_45` -- `22_13_10_16` -- `22_13_56_43` -- `22_14_29_54` +*Pullback through [`data/22_12_10_52.hdf5`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/mosaic-intelligence/data/22_12_10_52.hdf5), reconstructed from the raw channel data. Left to right: B-mode, the same frame with the lumen and intima-media segmentation, and the linear-encoder pullback position with the current frame marked.* ## Dataset Description -Each dataset is an IVUS acquisition collected in a porcine animal study. The source data consists of raw IVUS RF frames and a pre-computed (scan-converted) B-mode image. Six of the acquisitions have time-sampled linear encoder pullback positions of the IVUS probe at each frame. For each acquisition and for every frame, per-class segmentation masks are provided for the vessel lumen, intima-media, and guidewire. +Nine in-vivo intravascular ultrasound (IVUS) acquisitions from a porcine study, each in the *zea* file format with per-frame lumen, intima-media and guidewire segmentation. Per-acquisition dimensions are in [Acquisitions](#acquisitions). -## Dataset Contributors +Each dataset is an IVUS acquisition collected in a porcine animal study. The source data consists of raw IVUS RF frames and a pre-computed (scan-converted) B-mode image. Six of the acquisitions have time-sampled linear encoder pullback positions of the IVUS probe at each frame. For each acquisition and for every frame, per-class segmentation masks are provided for the vessel lumen, intima-media, and guidewire. -Mosaic Intelligence Labs in collaboration with NuevoSono +## Dataset Contributor(s) -Primary points of contact: -Brian Boitnott (1): brian@mosaicintelligence.xyz -Ali Mackanic (1): ali@mosaicintelligence.xyz +- Brian Boitnott (Mosaic Intelligence Labs; primary point of contact) +- Ali Mackanic (Mosaic Intelligence Labs; primary point of contact) +- Mosaic Intelligence Labs, in collaboration with NuevoSono ## Dataset Creation Date @@ -48,31 +38,31 @@ Ali Mackanic (1): ali@mosaicintelligence.xyz ## License / Terms of Use -All contributed data, labels, and metadata is released by Mosaic Intelligence Labs -and NuevoSono under CC BY 4.0. -Pre-existing hardware, software, simulation, and platform intellectual property -remain the property of their respective owners. The team agrees to comply with -OpenH-RF governance, publication, and data-sharing policies. The license is also -recorded in each file's `metadata/credit` field. +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage -Intended for IVUS tracking and segmentation applications, including lesion -detection and image-guided intervention. +Intended for IVUS tracking and segmentation applications, including lesion detection and image-guided intervention. ## Dataset Characterization - **Data Collection Method:** Porcine (in-vivo animal study) - **Labeling Method:** derived tracking metadata, semi-automated labeling -- **Acquisition System:** Single-element IVUS, center frequency 30 MHz, - sampling rate 1 GHz +- **Acquisition System:** Single-element IVUS, center frequency 30 MHz, sampling rate 1 GHz + +## Processing the Dataset + +The acquisitions can be processed with the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/mosaic-intelligence/reconstruct.py) as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF), together with the `pipeline.yaml` definition in this folder and the [zea library](https://github.com/tue-bmd/zea). The script streams the data from the Hugging Face Hub. + +The script overlays the segmentation masks on frames spread evenly across the pullback: + +![Five frames across the pullback with lumen, intima-media and guidewire overlays](assets/22_12_10_52/overview_5_frames.png) ## Dataset Format -All acquisitions are submitted in the *zea* file format. The RF data is stored as -a rotational sequence of A-lines (`n_tx` transmits per frame, one element/channel); -the accompanying B-mode `image` and `segmentation` masks are pre-computed, -scan-converted Cartesian frames sharing a per-pixel coordinate grid. +[zea v0.1.6](https://github.com/tue-bmd/zea) + +All acquisitions are submitted in the *zea* file format. The RF data is stored as a rotational sequence of A-lines (`n_tx` transmits per frame, one element/channel); the accompanying B-mode `image` and `segmentation` masks are pre-computed, scan-converted Cartesian frames sharing a per-pixel coordinate grid. ### Shared per-sample schema @@ -97,111 +87,29 @@ Fields that are in every acquisition: | `metadata/rotation/{samples, sampling_frequency, start_time_offset}` | `[1]` / scalar / scalar | float32 | — / Hz / s | Transducer rotation: `samples = +1` clockwise, `-1` counterclockwise | | `metadata/pullback_position/{samples, sampling_frequency, start_time_offset}` | `[n_frames]` / scalar / scalar | float32 | m / Hz / s | Linear encoder pullback position per frame (**tracked acquisitions only**) | -## Per-dataset details - -One data card per contributed sub-dataset. All fields follow the shared schema above. `n_ax = 8192`, `n_el = 1`, and `n_labels = 4` (`background`, `lumen`, -`intima_media`, `guidewire`) for every acquisition. - -### `15_10_18_21` (untracked, counterclockwise) - -- Dimensions: `n_frames = 100`, `n_tx = 540`, `H = 985`, `W = 986` -- Frame rate: ~3.33 Hz -- Rotation: `metadata/rotation/samples = -1` (counterclockwise); `metadata/text_report = "Transducer rotation direction: counterclockwise."` -- Tracking: **none** — no `metadata/pullback_position` group (source has no `frameAttributes.txt`) -- **Stored HDF5 size:** 1.46 GB (1,456,209,920 bytes). -- Note: the image/mask grid is non-square (`985 x 986`) - -![15_10_18_21 reconstruction overview](../outputs/15_10_18_21/overview_5_frames.png) - -### `15_10_50_19` (untracked, counterclockwise) - -- Dimensions: `n_frames = 60`, `n_tx = 360`, `H = 985`, `W = 986` -- Frame rate: ~3.33 Hz -- Rotation: `metadata/rotation/samples = -1` (counterclockwise) -- Tracking: **none** — no `metadata/pullback_position` group -- **Stored HDF5 size:** 592.12 MB (592,117,760 bytes). -- Note: the image/mask grid is non-square (`985 x 986`) - -![15_10_50_19 reconstruction overview](../outputs/15_10_50_19/overview_5_frames.png) - -### `15_16_45_06` (untracked, counterclockwise) - -- Dimensions: `n_frames = 100`, `n_tx = 540`, `H = 985`, `W = 986` -- Frame rate: ~3.33 Hz -- Rotation: `metadata/rotation/samples = -1` (counterclockwise) -- Tracking: **none** — no `metadata/pullback_position` group -- **Stored HDF5 size:** 1.46 GB (1,456,275,456 bytes). -- Note: the image/mask grid is non-square (`985 x 986`) +## Acquisitions -![15_16_45_06 reconstruction overview](../outputs/15_16_45_06/overview_5_frames.png) +Nine acquisitions sharing the schema above: `n_ax = 8192`, `n_el = 1`, and four segmentation labels (`background`, `lumen`, `intima_media`, `guidewire`). The `15_*` acquisitions are untracked, rotate counterclockwise and use a non-square image grid; the `22_*` are tracked with a linear-encoder pullback and rotate clockwise. -### `22_12_10_52` (tracked, clockwise) - -- Dimensions: `n_frames = 150`, `n_tx = 540`, `H = W = 2048` -- Frame rate: ~3.33 Hz -- Rotation: `metadata/rotation/samples = +1` (clockwise) -- Tracking: includes `metadata/pullback_position` `[150]` (linear encoder position per frame) -- **Stored HDF5 size:** 2.36 GB (2,356,936,704 bytes). - -![22_12_10_52 reconstruction overview](../outputs/22_12_10_52/overview_5_frames.png) - -### `22_12_29_46` (tracked, clockwise) - -- Dimensions: `n_frames = 100`, `n_tx = 540`, `H = W = 2048` -- Frame rate: ~8.33 Hz -- Rotation: `metadata/rotation/samples = +1` (clockwise) -- Tracking: includes `metadata/pullback_position` `[100]` -- **Stored HDF5 size:** 1.60 GB (1,602,945,024 bytes). - -![22_12_29_46 reconstruction overview](../outputs/22_12_29_46/overview_5_frames.png) - -### `22_12_38_45` (tracked, clockwise) - -- Dimensions: `n_frames = 150`, `n_tx = 360`, `H = W = 2048` -- Frame rate: ~8.33 Hz -- Rotation: `metadata/rotation/samples = +1` (clockwise) -- Tracking: includes `metadata/pullback_position` `[150]` -- **Stored HDF5 size:** 1.58 GB (1,576,337,408 bytes). - -![22_12_38_45 reconstruction overview](../outputs/22_12_38_45/overview_5_frames.png) - -### `22_13_10_16` (tracked, clockwise) - -- Dimensions: `n_frames = 100`, `n_tx = 540`, `H = W = 2048` -- Frame rate: ~8.33 Hz -- Rotation: `metadata/rotation/samples = +1` (clockwise) -- Tracking: includes `metadata/pullback_position` `[100]` -- **Stored HDF5 size:** 1.54 GB (1,535,311,872 bytes). - -![22_13_10_16 reconstruction overview](../outputs/22_13_10_16/overview_5_frames.png) - -### `22_13_56_43` (tracked, clockwise) - -- Dimensions: `n_frames = 100`, `n_tx = 540`, `H = W = 2048` -- Frame rate: ~8.33 Hz -- Rotation: `metadata/rotation/samples = +1` (clockwise) -- Tracking: includes `metadata/pullback_position` `[100]` -- **Stored HDF5 size:** 1.53 GB (1,527,119,872 bytes). - -![22_13_56_43 reconstruction overview](../outputs/22_13_56_43/overview_5_frames.png) - -### `22_14_29_54` (tracked, clockwise) - -- Dimensions: `n_frames = 150`, `n_tx = 360`, `H = W = 2048` -- Frame rate: ~3.33 Hz -- Rotation: `metadata/rotation/samples = +1` (clockwise); `metadata/text_report = "Transducer rotation direction: clockwise."` -- Tracking: includes `metadata/pullback_position` `[150]` -- **Stored HDF5 size:** 1.65 GB (1,653,080,064 bytes). - -![22_14_29_54 reconstruction overview](../outputs/22_14_29_54/overview_5_frames.png) +| Acquisition | Tracked | Frames | Transmits | Image grid | Frame rate | Size | +|---|---|---:|---:|---|---:|---:| +| `15_10_18_21` | no | 100 | 540 | 985x986 | 3.33 Hz | 1.46 GB | +| `15_10_50_19` | no | 60 | 360 | 985x986 | 3.33 Hz | 592.12 MB | +| `15_16_45_06` | no | 100 | 540 | 985x986 | 3.33 Hz | 1.46 GB | +| `22_12_10_52` | yes | 150 | 540 | 2048x2048 | 3.33 Hz | 2.36 GB | +| `22_12_29_46` | yes | 100 | 540 | 2048x2048 | 8.33 Hz | 1.60 GB | +| `22_12_38_45` | yes | 150 | 360 | 2048x2048 | 8.33 Hz | 1.58 GB | +| `22_13_10_16` | yes | 100 | 540 | 2048x2048 | 8.33 Hz | 1.54 GB | +| `22_13_56_43` | yes | 100 | 540 | 2048x2048 | 8.33 Hz | 1.53 GB | +| `22_14_29_54` | yes | 150 | 360 | 2048x2048 | 3.33 Hz | 1.65 GB | ## Dataset Quantification -**Current OpenH-RF release:** 9 HDF5 files; 13.76 GB (13,756,334,080 bytes) stored; root `zea_version` **0.1.6**. Sizes include all HDF5 contents and use decimal units (MB = 10^6 bytes, GB = 10^9 bytes, TB = 10^12 bytes), not decoded-array memory or original-source download sizes. +9 HDF5 files; 13.76 GB (13,756,334,080 bytes) stored; root `zea_version` **0.1.6**. Sizes include all HDF5 contents and use decimal units (MB = 10^6 bytes, GB = 10^9 bytes, TB = 10^12 bytes), not decoded-array memory or original-source download sizes. - Acquisitions: 9 (3 untracked `15_*`, 6 tracked `22_*`) - Frames per acquisition: 60–150 (1010 frames total across all acquisitions) -- Frame rate: ~3.33 Hz or ~8.33 Hz depending on acquisition (see per-dataset table) +- Frame rate: ~3.33 Hz or ~8.33 Hz depending on acquisition (see the table above) - Train / validation / test split: N/A - **Stored HDF5 size:** 13.76 GB (13,756,334,080 bytes). @@ -223,17 +131,14 @@ Animal study data — no human subjects. ## Data Validation -A `zea.Pipeline` reconstructs the IVUS B-mode from the raw channel data -(RF → envelope → normalization → log compression → scan conversion). See -[reconstruct.py](../reconstruct.py) and [pipeline.yaml](../pipeline.yaml). Linear encoder position (when applicable) and segmentation masks are overlayed on the B-modes. +A `zea.Pipeline` reconstructs the IVUS B-mode from the raw channel data (RF → envelope → normalization → log compression → scan conversion), as defined in `pipeline.yaml` and run by `reconstruct.py`. Linear encoder position (when applicable) and segmentation masks are overlayed on the B-modes. ## Known Issues -- Untracked (`15_*`) acquisitions have no `pullback_position`, so the pullback - trajectory panel is omitted during reconstruction. +- Untracked (`15_*`) acquisitions have no `pullback_position`, so the pullback trajectory panel is omitted during reconstruction. ## Ethical Considerations -Porcine animal study data only; contains no human subjects or PHI. Collected and -released in compliance with applicable institutional animal care approvals and -OpenH-RF governance and data-sharing policies. +Porcine animal study data only; contains no human subjects or PHI. Collected and released in compliance with applicable institutional animal care approvals and OpenH-RF governance and data-sharing policies. + +All contributed data, labels and metadata are released by Mosaic Intelligence Labs and NuevoSono; pre-existing hardware, software, simulation and platform intellectual property remains the property of the respective owners. The license is also recorded in each file's `metadata/credit` field. diff --git a/datasets/mosaic-intelligence/assets/15_10_18_21/overview_5_frames.png b/datasets/mosaic-intelligence/assets/15_10_18_21/overview_5_frames.png new file mode 100644 index 0000000..d0ae5f0 Binary files /dev/null and b/datasets/mosaic-intelligence/assets/15_10_18_21/overview_5_frames.png differ diff --git a/datasets/mosaic-intelligence/assets/15_10_50_19/overview_5_frames.png b/datasets/mosaic-intelligence/assets/15_10_50_19/overview_5_frames.png new file mode 100644 index 0000000..1a6690d Binary files /dev/null and b/datasets/mosaic-intelligence/assets/15_10_50_19/overview_5_frames.png differ diff --git a/datasets/mosaic-intelligence/assets/15_16_45_06/overview_5_frames.png b/datasets/mosaic-intelligence/assets/15_16_45_06/overview_5_frames.png new file mode 100644 index 0000000..1414fd0 Binary files /dev/null and b/datasets/mosaic-intelligence/assets/15_16_45_06/overview_5_frames.png differ diff --git a/datasets/mosaic-intelligence/assets/22_12_10_52/overview_5_frames.png b/datasets/mosaic-intelligence/assets/22_12_10_52/overview_5_frames.png new file mode 100644 index 0000000..80fc654 Binary files /dev/null and b/datasets/mosaic-intelligence/assets/22_12_10_52/overview_5_frames.png differ diff --git a/datasets/mosaic-intelligence/assets/22_12_29_46/overview_5_frames.png b/datasets/mosaic-intelligence/assets/22_12_29_46/overview_5_frames.png new file mode 100644 index 0000000..5ca5312 Binary files /dev/null and b/datasets/mosaic-intelligence/assets/22_12_29_46/overview_5_frames.png differ diff --git a/datasets/mosaic-intelligence/assets/22_12_38_45/overview_5_frames.png b/datasets/mosaic-intelligence/assets/22_12_38_45/overview_5_frames.png new file mode 100644 index 0000000..fda0124 Binary files /dev/null and b/datasets/mosaic-intelligence/assets/22_12_38_45/overview_5_frames.png differ diff --git a/datasets/mosaic-intelligence/assets/22_13_10_16/overview_5_frames.png b/datasets/mosaic-intelligence/assets/22_13_10_16/overview_5_frames.png new file mode 100644 index 0000000..b4ea13a Binary files /dev/null and b/datasets/mosaic-intelligence/assets/22_13_10_16/overview_5_frames.png differ diff --git a/datasets/mosaic-intelligence/assets/22_13_56_43/overview_5_frames.png b/datasets/mosaic-intelligence/assets/22_13_56_43/overview_5_frames.png new file mode 100644 index 0000000..32da49e Binary files /dev/null and b/datasets/mosaic-intelligence/assets/22_13_56_43/overview_5_frames.png differ diff --git a/datasets/mosaic-intelligence/assets/22_14_29_54/overview_5_frames.png b/datasets/mosaic-intelligence/assets/22_14_29_54/overview_5_frames.png new file mode 100644 index 0000000..4f8e6d2 Binary files /dev/null and b/datasets/mosaic-intelligence/assets/22_14_29_54/overview_5_frames.png differ diff --git a/datasets/mosaic-intelligence/assets/main.png b/datasets/mosaic-intelligence/assets/main.png new file mode 100644 index 0000000..0c9cef3 Binary files /dev/null and b/datasets/mosaic-intelligence/assets/main.png differ diff --git a/datasets/mosaic-intelligence/assets/pullback.gif b/datasets/mosaic-intelligence/assets/pullback.gif new file mode 100644 index 0000000..d1746a9 Binary files /dev/null and b/datasets/mosaic-intelligence/assets/pullback.gif differ diff --git a/datasets/mosaic-intelligence/reconstruct.py b/datasets/mosaic-intelligence/reconstruct.py index 48bc9ee..6ea71d0 100644 --- a/datasets/mosaic-intelligence/reconstruct.py +++ b/datasets/mosaic-intelligence/reconstruct.py @@ -39,7 +39,6 @@ from zea.ops import Downsample HERE = Path(__file__).resolve().parent -DEFAULT_PIPELINE = HERE / "pipeline.yaml" OUTPUT_PX = None # reconstruction side length; None -> derive from segmentation mask dims @@ -56,11 +55,11 @@ # --- Inputs ----------------------------------------------------------------- # Defaults stream straight from the published corpus. Swap any of these for a # local path to run against your own copy. -INPUT = "hf://nvidia/OpenH-RF/mosaic-intelligence/data/15_10_50_19.hdf5" -PIPELINE = "hf://nvidia/OpenH-RF/mosaic-intelligence/pipeline.yaml" +ZEA_FILE = "hf://nvidia/OpenH-RF/mosaic-intelligence/data/22_12_10_52.hdf5" +CONFIG = "hf://nvidia/OpenH-RF/mosaic-intelligence/pipeline.yaml" NUM_FRAMES = 5 # Number of frames to overlay, spaced evenly across the pullback FRAMES = None # Explicit frame indices to overlay (overrides NUM_FRAMES) -OUTPUT = None +OUT = None BANDWIDTH = 30e6 DYNAMIC_RANGE = (-43.0, -0.0) SIZE = OUTPUT_PX # Square canvas side length (px) for the reconstruction @@ -203,10 +202,10 @@ def render_overview(panels, frames, labels, position_mm, frame_rate_hz, alpha, o def main(): zea.init_device() - pipeline = zea.Pipeline.from_path(str(PIPELINE)) + pipeline = zea.Pipeline.from_path(str(CONFIG)) print(f"Pipeline: {pipeline}") - with zea.File(str(INPUT)) as f: + with zea.File(str(ZEA_FILE)) as f: parameters = f.load_parameters() n_frames = int(f.data.raw_data.shape[0]) frames = select_frames(n_frames, FRAMES, NUM_FRAMES) @@ -280,7 +279,7 @@ def main(): ) panels.append((frame, recon_gray, mask)) - output = OUTPUT or (HERE / "outputs" / Path(INPUT).stem / f"overview_{len(frames)}_frames.png") + output = OUT or (HERE / "assets" / Path(ZEA_FILE).stem / f"overview_{len(frames)}_frames.png") render_overview(panels, frames, labels, position_mm, frame_rate_hz, ALPHA, output) diff --git a/datasets/nv-raw2insights-us/README.md b/datasets/nv-raw2insights-us/README.md index 1e02e03..549d6be 100644 --- a/datasets/nv-raw2insights-us/README.md +++ b/datasets/nv-raw2insights-us/README.md @@ -1,5 +1,6 @@ --- -pretty_name: "OpenH-RF — NV-Raw2Insights-US (simulated FSA, sound-speed / aberration / segmentation)" +name: nv-raw2insights-us +pretty_name: "NV-Raw2Insights-US (simulated FSA, sound-speed / aberration / segmentation)" license: cc-by-4.0 task_categories: - other @@ -21,28 +22,23 @@ size_categories: # NV-Raw2Insights-US — Simulated FSA Channel Data with Sound-Speed, Aberration, and Segmentation Ground Truth +![DBUA B-mode reconstruction and estimated sound speed using NV-Raw2Insights-US data](assets/dbua-reconstruction.gif) + +*DBUA results using synthetic validation sample [`data/nv_r2i_us_validation_0084.hdf5`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/nv-raw2insights-us/data/nv_r2i_us_validation_0084.hdf5): B-mode (left) and estimated sound speed (right). Bulk-speed calibration is followed by 400 spatial-refinement iterations, with fixed display scales. These are DBUA reconstructions, not predictions from an NV-Raw2Insights-US model.* + + + ## Dataset Description -NV-Raw2Insights-US is a **simulated full-synthetic-aperture (FSA)** ultrasound -dataset for training and evaluating neural networks on **sound-speed -estimation**, **phase-aberration correction**, **tissue segmentation**, and -**learned image reconstruction** from raw pre-beamformed channel data. +NV-Raw2Insights-US is a **simulated full-synthetic-aperture (FSA)** ultrasound dataset for training and evaluating neural networks on **sound-speed estimation**, **phase-aberration correction**, **tissue segmentation**, and **learned image reconstruction** from raw pre-beamformed channel data. -Each sample is a single-frame FSA acquisition from a **180-element linear -array** simulated with **k-Wave** over a heterogeneous tissue phantom -containing cysts. Every acquisition provides raw baseband **IQ channel data** -alongside co-registered ground truth: a **speed-of-sound map**, a **binary cyst -segmentation mask**, a **DAS B-mode** and a **focused-transmit B-mode**, and a -per-sample **phase-aberration** value. All data are **synthetic**; no human or -animal subjects are involved. +Each sample is a single-frame FSA acquisition from a **180-element linear array** simulated with **k-Wave** over a heterogeneous tissue phantom containing cysts. Every acquisition provides raw baseband **IQ channel data** alongside co-registered ground truth: a **speed-of-sound map**, a **binary cyst segmentation mask**, a **DAS B-mode** and a **focused-transmit B-mode**, and a per-sample **phase-aberration** value. All data are **synthetic**; no human or animal subjects are involved. -This is the **zea-format (OpenH-RF) release**; the same simulations are also -published in a Hugging Face `datasets`/Arrow build at -[`nvidia/NV-Raw2Insights-US`](https://huggingface.co/datasets/nvidia/NV-Raw2Insights-US). +This is the **zea-format (OpenH-RF) release**; the same simulations are also published in a Hugging Face `datasets`/Arrow build at [`nvidia/NV-Raw2Insights-US`](https://huggingface.co/datasets/nvidia/NV-Raw2Insights-US). ## Dataset Contributor(s) -NVIDIA Corporation. +- NVIDIA Corporation ## Dataset Creation Date @@ -50,33 +46,27 @@ NVIDIA Corporation. ## License / Terms of Use -[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). -Retain attribution and identify modifications when reusing the data. +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage -Developing and benchmarking methods that operate on raw ultrasound channel data: -learned/adaptive beamforming, sound-speed estimation, phase-aberration -correction, and tissue segmentation. Because the sound-speed map, segmentation -mask, and aberration value are exact simulation ground truth, the dataset -supports fully-supervised training of channel-data-to-insight models. +Developing and benchmarking methods that operate on raw ultrasound channel data: learned/adaptive beamforming, sound-speed estimation, phase-aberration correction, and tissue segmentation. Because the sound-speed map, segmentation mask, and aberration value are exact simulation ground truth, the dataset supports fully-supervised training of channel-data-to-insight models. ## Dataset Characterization -- **Data Collection Method:** Synthetic — k-Wave acoustic simulation of a - 180-element linear array over heterogeneous tissue phantoms with cysts. -- **Labeling Method:** Synthetic — ground truth taken directly from the - simulation parameters (sound-speed map, cyst segmentation, aberration). -- **Acquisition model:** Full synthetic aperture (multi-static) — 180 - single-element transmit events per frame, all 180 elements received; - transmit centre frequency 6.5 MHz, baseband IQ sampled at 13.3 MHz, - background sound speed 1540 m/s. +- **Data Collection Method:** Synthetic — k-Wave acoustic simulation of a 180-element linear array over heterogeneous tissue phantoms with cysts. +- **Labeling Method:** Synthetic — ground truth taken directly from the simulation parameters (sound-speed map, cyst segmentation, aberration). +- **Acquisition model:** Full synthetic aperture (multi-static) — 180 single-element transmit events per frame, all 180 elements received; transmit centre frequency 6.5 MHz, baseband IQ sampled at 13.3 MHz, background sound speed 1540 m/s. + +## Processing the Dataset + +The acquisitions can be processed with the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/nv-raw2insights-us/reconstruct.py) as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF), which uses the [zea library](https://github.com/tue-bmd/zea) and streams the data from the Hugging Face Hub. It reconstructs both a plain DAS B-mode (the `pipeline.yaml` in this folder) and a sound-speed-corrected B-mode using the ground-truth `sos_map`. ## Dataset Format -Packaged in the **zea** file format (`zea_version` 0.1.6), **one HDF5 file per -sample**, all under `data/`. The train/validation split is encoded in each -filename (`nv_r2i_us_train_XXXX.hdf5`, `nv_r2i_us_validation_XXXX.hdf5`). +[zea v0.1.6](https://github.com/tue-bmd/zea) + +Packaged in the **zea** file format (`zea_version` 0.1.6), **one HDF5 file per sample**, all under `data/`. The train/validation split is encoded in each filename (`nv_r2i_us_train_XXXX.hdf5`, `nv_r2i_us_validation_XXXX.hdf5`). Per-file contents: @@ -99,45 +89,33 @@ Per-file contents: **Current OpenH-RF release:** 923 HDF5 files; 214.13 GB (214,133,178,368 bytes) stored; root `zea_version` **0.1.6**. Sizes include all HDF5 contents and use decimal units (MB = 10^6 bytes, GB = 10^9 bytes, TB = 10^12 bytes), not decoded-array memory or original-source download sizes. -- **923 samples (830 train / 93 validation)**, one HDF5 per sample, all in - `data/`; the split is carried in the filename. +- **923 samples (830 train / 93 validation)**, one HDF5 per sample, all in `data/`; the split is carried in the filename. - Each sample is a single-frame FSA acquisition (frame index always 0). ## Subject Metadata -- **Synthetic only** — no human or animal subjects. Each sample is a k-Wave - simulation over a digital tissue phantom containing cysts. +- **Synthetic only** — no human or animal subjects. Each sample is a k-Wave simulation over a digital tissue phantom containing cysts. - No PHI; `subject/type` is a simulation label, not a patient identifier. ## Data Validation -Each file's `tracks/track_0/data/image` is a DAS B-mode reconstructed from -`raw_data` by the zea pipeline (see `pipeline.yaml`), so the reconstruction is -reproducible from the raw channel data and the recorded transmit description. +Each file's `tracks/track_0/data/image` is a DAS B-mode reconstructed from `raw_data` by the zea pipeline (see `pipeline.yaml`), so the reconstruction is reproducible from the raw channel data and the recorded transmit description. ## Known Issues -- `sos_map` is a coarse `32 × 32` grid while the B-mode and segmentation are on - the fine `507 × 456` grid; each carries its own `coordinates` for alignment. -- Cysts that touch the edge of the B-mode frame are not included in the - segmentation mask. -- Some regions of gross reverberation artifact can be incorrectly segmented as - cysts. +- `sos_map` is a coarse `32 × 32` grid while the B-mode and segmentation are on the fine `507 × 456` grid; each carries its own `coordinates` for alignment. +- Cysts that touch the edge of the B-mode frame are not included in the segmentation mask. +- Some regions of gross reverberation artifact can be incorrectly segmented as cysts. ## Ethical Considerations -The data are entirely synthetic (k-Wave simulation of digital phantoms). There -are no human participants, animal subjects, patient identifiers, or protected -health information. Released under CC BY 4.0 for research and technical -evaluation, not clinical decision-making. +The data are entirely synthetic (k-Wave simulation of digital phantoms). There are no human participants, animal subjects, patient identifiers, or protected health information. Released under CC BY 4.0 for research and technical evaluation, not clinical decision-making. ## Source Dataset & Citation -This is the **zea-format conversion** of the original **NV-Raw2Insights-US** -dataset published by NVIDIA on Hugging Face: +This is the **zea-format conversion** of the original **NV-Raw2Insights-US** dataset published by NVIDIA on Hugging Face: . The raw -simulations, ground-truth labels, and normalization statistics originate there; -please cite the original dataset: +simulations, ground-truth labels, and normalization statistics originate there; please cite the original dataset: ```bibtex @misc{nv_raw2insights_us_2026, diff --git a/datasets/nv-raw2insights-us/assets/dbua-reconstruction.gif b/datasets/nv-raw2insights-us/assets/dbua-reconstruction.gif new file mode 100644 index 0000000..351a735 Binary files /dev/null and b/datasets/nv-raw2insights-us/assets/dbua-reconstruction.gif differ diff --git a/datasets/nv-raw2insights-us/assets/main.png b/datasets/nv-raw2insights-us/assets/main.png new file mode 100644 index 0000000..8931711 Binary files /dev/null and b/datasets/nv-raw2insights-us/assets/main.png differ diff --git a/datasets/nv-raw2insights-us/assets/main_uncorrect.png b/datasets/nv-raw2insights-us/assets/main_uncorrect.png new file mode 100644 index 0000000..18935d6 Binary files /dev/null and b/datasets/nv-raw2insights-us/assets/main_uncorrect.png differ diff --git a/datasets/nv-raw2insights-us/assets/nv_raw2insights_us_reconstructed.png b/datasets/nv-raw2insights-us/assets/nv_raw2insights_us_reconstructed.png new file mode 100644 index 0000000..a356643 Binary files /dev/null and b/datasets/nv-raw2insights-us/assets/nv_raw2insights_us_reconstructed.png differ diff --git a/datasets/nv-raw2insights-us/pipeline.yaml b/datasets/nv-raw2insights-us/pipeline.yaml new file mode 100644 index 0000000..e471e37 --- /dev/null +++ b/datasets/nv-raw2insights-us/pipeline.yaml @@ -0,0 +1,23 @@ +# Reconstruction pipeline recipe for the NV-Raw2Insights-US example. +# +# This file is written by reconstruct.py (pipeline.to_yaml) as a shareable +# artifact; the pipeline is defined in code, then saved here and loaded back +# in, so this file is provably what reconstruct.py actually runs. It records +# the DAS -> envelope -> normalize -> log-compress operation chain; the +# acquisition/grid parameters are derived from the data file at run time, not +# stored here. +# +# This is the plain DAS recipe, without sound-speed (aberration) correction. +# reconstruct.py's SoS-corrected panel runs this same pipeline a second time, +# passing sos_map/sos_grid_x/sos_grid_z to the "beamform" op at call time. + +pipeline: + operations: + - name: beamform + - envelope_detect + - name: normalize + params: + output_range: + - 0.0 + - 1.0 + - log_compress diff --git a/datasets/nv-raw2insights-us/reconstruct.py b/datasets/nv-raw2insights-us/reconstruct.py index 2199b55..53817f7 100644 --- a/datasets/nv-raw2insights-us/reconstruct.py +++ b/datasets/nv-raw2insights-us/reconstruct.py @@ -9,7 +9,10 @@ against it directly. The figure puts seven panels side by side: IQ energy, the stored DAS B-mode, the focused-transmit B-mode, the zea reconstruction, a sound-speed-corrected zea reconstruction, the ground-truth speed-of-sound map -and the segmentation. +and the segmentation. The DAS -> envelope -> normalize -> log-compress pipeline +is defined in code and saved to pipeline.yaml as a shareable recipe; it is the +plain, non-SoS-corrected reconstruction. The SoS-corrected panel reruns the +same pipeline with sos_map/sos_grid_x/sos_grid_z supplied at call time. Requires zea>=0.1.6 (https://github.com/tue-bmd/zea), the library that does the ultrasound processing here, together with one of its Keras backends (JAX, @@ -36,8 +39,9 @@ # --- Inputs ----------------------------------------------------------------- # Defaults stream straight from the published corpus. Swap any of these for a # local path to run against your own copy. -ZEA_FILE = "hf://nvidia/OpenH-RF/nv-raw2insights-us/data/nv_r2i_us_train_0000.hdf5" -OUTPUT = HERE / "nv_raw2insights_us_reconstructed.png" +ZEA_FILE = "hf://nvidia/OpenH-RF/nv-raw2insights-us/data/nv_r2i_us_validation_0084.hdf5" +OUT = HERE / "assets" / "nv_raw2insights_us_reconstructed.png" +CONFIG = HERE / "pipeline.yaml" def coords_to_imshow_mm(coords): @@ -48,6 +52,18 @@ def coords_to_imshow_mm(coords): return [x.min(), x.max(), z.max(), z.min()] +def build_pipeline() -> "zea.Pipeline": + """Define the DAS beamforming pipeline in code (no SoS correction).""" + return zea.Pipeline( + operations=[ + Beamform(beamformer="delay_and_sum"), + EnvelopeDetect(), + Normalize(), + LogCompress(), + ] + ) + + def main(): zea.init_device() @@ -67,14 +83,10 @@ def main(): print(f"raw_data: {raw.shape}") - pipeline = zea.Pipeline( - operations=[ - Beamform(beamformer="delay_and_sum"), - EnvelopeDetect(), - Normalize(), - LogCompress(), - ] - ) + build_pipeline().to_yaml(str(CONFIG)) + config = zea.Config.from_path(str(CONFIG)) + pipeline = zea.Pipeline.from_config(config) + params = pipeline.prepare_parameters(parameters) outputs = pipeline(**{pipeline.key: raw}, **params) recon = keras.ops.convert_to_numpy(outputs[pipeline.output_key])[0] @@ -172,8 +184,9 @@ def main(): fig.suptitle(f"openh-rf sample (phase error: {phase_err[0]:.2f} rad)", fontsize=14, y=1.02) plt.tight_layout() - plt.savefig(OUTPUT, dpi=150, bbox_inches="tight") - print(f"Saved {OUTPUT}") + Path(OUT).parent.mkdir(parents=True, exist_ok=True) + plt.savefig(OUT, dpi=150, bbox_inches="tight") + print(f"Saved {OUT}") if __name__ == "__main__": diff --git a/datasets/oslo/A_cardiac/README.md b/datasets/oslo/A_cardiac/README.md index a98aef5..86348d2 100644 --- a/datasets/oslo/A_cardiac/README.md +++ b/datasets/oslo/A_cardiac/README.md @@ -1,4 +1,5 @@ --- +name: oslo-a-cardiac pretty_name: "USTB - In-vivo Cardiac (Verasonics P4-2)" license: cc-by-4.0 task_categories: @@ -17,18 +18,23 @@ size_categories: # USTB - In-vivo Cardiac (Verasonics P4-2) -Part of the **UltraSound ToolBox (USTB) Channel Capture Collection** contributed to the -[OpenH-RF](https://github.com/open-h/OpenH-RF) initiative. All acquisitions are stored in the -*zea* HDF5 file format (zea_version 0.1.6) and contain raw pre-beamformed -channel data (`/data/raw_data`). +![Cine loop of a parasternal long-axis view](assets/hero.gif) + +*All 50 frames of [`Verasonics_P2-4_parasternal_long_subject_1.hdf5`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/oslo/A_cardiac/Verasonics_P2-4_parasternal_long_subject_1.hdf5), reconstructed from the raw channel data with `reconstruct.py`.* ## Dataset Description +Part of the **UltraSound ToolBox (USTB) Channel Capture Collection** (see the [collection card](../README.md)). + In-vivo human cardiac channel-capture data acquired with a Verasonics Vantage 256 research scanner and a P4-2 phased-array probe. The collection contains parasternal long-axis and apical four-chamber views recorded with focused transmit beams (sector scan). The data is pre-beamformed RF channel data intended for research into generalized beamforming, adaptive imaging and cardiac reconstruction. -## Dataset Contributors +## Dataset Contributor(s) -University of Oslo (UiO), Department of Informatics. Primary contact: Ole Marius Hoel Rindal (omrindal@ifi.uio.no). Team: Ole Marius Hoel Rindal, Yucel Karabiyik, Sven Peter Nasholm, Andreas Austeng. +- Ole Marius Hoel Rindal (primary contact) +- Yucel Karabiyik +- Sven Peter Nasholm +- Andreas Austeng +- University of Oslo (UiO), Department of Informatics ## Dataset Creation Date @@ -36,9 +42,7 @@ University of Oslo (UiO), Department of Informatics. Primary contact: Ole Marius ## License / Terms of Use -Released under **Creative Commons Attribution 4.0 International (CC BY 4.0)** — see the `LICENCE` -file at the submission root (this license is also declared in the YAML frontmatter above). The -contributed data is cleared for this license. The UltraSound ToolBox (USTB) Channel Capture Collection, University of Oslo. Contributed to OpenH-RF. Zenodo record 20261898. +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage @@ -48,18 +52,27 @@ Generalized reconstruction and adaptive beamforming of cardiac ultrasound (RFP t - **Data Collection Method:** clinical - **Labeling Method:** N/A (raw channel data; no annotations). -- **Acquisition system:** probe(s) P4-2; - element positions stored in `/probe/probe_geometry` (meters); center frequency, sampling - frequency and sound speed stored per acquisition in `/scan` (see per-sample feature table). +- **Acquisition system:** probe(s) P4-2; element positions stored in `/probe/probe_geometry` (meters); center frequency, sampling frequency and sound speed stored per acquisition in `/scan` (see per-sample feature table). + +## Processing the Dataset + +The example acquisition can be processed with the `pipeline.yaml` definition in this folder and the [zea library](https://github.com/tue-bmd/zea). + +`zea` streams the data from the Hugging Face Hub and processes it according to the pipeline. You can try it out with the following command: + +```bash +zea process \ + --dataset hf://nvidia/OpenH-RF/oslo/A_cardiac/Verasonics_P2-4_parasternal_long_subject_1.hdf5 \ + --config hf://nvidia/OpenH-RF/oslo/A_cardiac/pipeline.yaml +``` + +This `pipeline.yaml` holds the pipeline, display window and sector limits of that acquisition. Alternatively, all acquisitions can be processed with the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/oslo/reconstruct.py) at the root of this collection, as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF), together with the `pipeline*.yaml` definitions at the collection root and the [zea library](https://github.com/tue-bmd/zea). The script streams the data from the Hugging Face Hub; `parameters.yaml` picks the pipeline, display window and dynamic range per acquisition (see the [collection card](../README.md#processing-the-dataset)). ## Dataset Format -All acquisitions are stored in the **zea** HDF5 file format. Each `.hdf5` file is a single -acquisition with raw channel data `/data/raw_data` of shape -`(n_frames, n_tx, n_ax, n_el, n_ch)` and a fully populated `/scan` group describing the transmit -sequence (delays, focus distances, steering angles, apodization, timing). Data type: RF (n_ch=1). -No demodulation or decimation was applied during packaging beyond conversion from the USTB -Ultrasound File Format (UFF) to zea; RF data is demodulated inside the reconstruction pipeline. +[zea v0.1.6](https://github.com/tue-bmd/zea) + +All acquisitions are stored in the **zea** HDF5 file format. Each `.hdf5` file is a single acquisition with raw channel data `/data/raw_data` of shape `(n_frames, n_tx, n_ax, n_el, n_ch)` and a fully populated `/scan` group describing the transmit sequence (delays, focus distances, steering angles, apodization, timing). Data type: RF (n_ch=1). No demodulation or decimation was applied during packaging beyond conversion from the USTB Ultrasound File Format (UFF) to zea; RF data is demodulated inside the reconstruction pipeline. ## Dataset Quantification @@ -100,20 +113,9 @@ Healthy adult volunteer(s). Anatomy: heart (parasternal long-axis, apical four-c ## Data Validation -A Delay-And-Sum `zea.Pipeline` is provided in the **`pipeline.yaml` at the submission root** and -run by the single **`reconstruct.py` at the submission root**: -`cast -> demodulate -> delay-and-sum beamform -> envelope detect -> normalize -> log compress` -(RF is demodulated in-pipeline; IQ uses a baseband pipeline). The script is geometry-driven and -recurses into every sub-dataset folder; running `python reconstruct.py` from the root reconstructs -every `.hdf5` in the collection (or pass a folder-qualified path for a single acquisition) and writes -`_zea_bmode.png` next to each file as a portable check that the recorded geometry and timing -are correct. - -The reference B-mode images committed alongside the data (`_bmode.png`) are produced with the -UltraSound ToolBox (USTB) MATLAB Delay-And-Sum beamformer — the exact per-dataset reconstruction -used in the public USTB dataset catalog (https://unioslo.github.io/USTB/datasets.html), with -scanline transmit apodization for focused/sector acquisitions and correct sector-scan geometry. -These are the recommended reference reconstructions for visual verification. +A Delay-And-Sum `zea.Pipeline` (`cast -> demodulate -> delay-and-sum beamform -> envelope detect -> normalize -> log compress`; RF is demodulated in-pipeline, IQ uses a baseband pipeline) reconstructs every acquisition as a portable check that the recorded geometry and timing are correct (see *Processing the Dataset*). + +The reference B-mode images committed alongside the data (`_bmode.png`) are produced with the UltraSound ToolBox (USTB) MATLAB Delay-And-Sum beamformer — the exact per-dataset reconstruction used in the public USTB dataset catalog (https://unioslo.github.io/USTB/datasets.html), with scanline transmit apodization for focused/sector acquisitions and correct sector-scan geometry. These are the recommended reference reconstructions for visual verification. ## Known Issues @@ -122,3 +124,7 @@ Focused sector acquisition: lateral resolution and field of view follow the tran ## Ethical Considerations In-vivo data recorded from healthy adult volunteers at the University of Oslo with written informed consent for research use and data sharing, and approval from the Regional Committee for Medical and Health Research Ethics (REK), Norway. The files contain only backscattered RF channel data and acquisition parameters - no patient name, identifier, date of birth, acquisition date, facial image, or any other HHS Safe Harbor identifier is present (de-identified by construction). + +## Citation + +Please cite the UltraSound ToolBox (USTB) Channel Capture Collection, University of Oslo (Zenodo record 20261898). diff --git a/datasets/oslo/A_cardiac/assets/hero.gif b/datasets/oslo/A_cardiac/assets/hero.gif new file mode 100644 index 0000000..5607620 Binary files /dev/null and b/datasets/oslo/A_cardiac/assets/hero.gif differ diff --git a/datasets/oslo/A_cardiac/assets/main.png b/datasets/oslo/A_cardiac/assets/main.png new file mode 100644 index 0000000..e738821 Binary files /dev/null and b/datasets/oslo/A_cardiac/assets/main.png differ diff --git a/datasets/oslo/A_cardiac/pipeline.yaml b/datasets/oslo/A_cardiac/pipeline.yaml new file mode 100644 index 0000000..806d58a --- /dev/null +++ b/datasets/oslo/A_cardiac/pipeline.yaml @@ -0,0 +1,34 @@ +# zea pipeline for A_cardiac/Verasonics_P2-4_parasternal_long_subject_1.hdf5, the example acquisition of this sub-dataset. +# The shared ../pipeline_sector.yaml plus the per-acquisition settings that +# ../reconstruct.py takes from ../parameters.yaml (display window in metres, +# dynamic range, polar_limits), so `zea process` renders it standalone. + +parameters: + f_number: 0 + grid_type: polar + grid_size_x: 400 + grid_size_z: 600 + dynamic_range: [-60, 0] + selected_transmits: all + focal_region_length: 0.002 + pfield_kwargs: + interpolation: bilinear + percentile: 70 + alpha: 15 + zlims: [0.001, 0.11] + polar_limits: [-0.6552720665931702, 0.6552720665931702] +pipeline: + operations: + - name: keras.ops.cast + params: + dtype: float32 + - name: apply_window + - name: demodulate + - name: beamform + params: + beamformer: delay_and_sum + enable_pfield: true + - name: envelope_detect + - name: normalize + - name: log_compress + - name: scan_convert diff --git a/datasets/oslo/B_carotid/README.md b/datasets/oslo/B_carotid/README.md index edb95b8..da265fd 100644 --- a/datasets/oslo/B_carotid/README.md +++ b/datasets/oslo/B_carotid/README.md @@ -1,4 +1,5 @@ --- +name: oslo-b-carotid pretty_name: "USTB - In-vivo Carotid (Verasonics L7-4)" license: cc-by-4.0 task_categories: @@ -18,18 +19,23 @@ size_categories: # USTB - In-vivo Carotid (Verasonics L7-4) -Part of the **UltraSound ToolBox (USTB) Channel Capture Collection** contributed to the -[OpenH-RF](https://github.com/open-h/OpenH-RF) initiative. All acquisitions are stored in the -*zea* HDF5 file format (zea_version 0.1.6) and contain raw pre-beamformed -channel data (`/data/raw_data`). +![Focused B-mode of a carotid cross-section](assets/hero.png) + +*First frame of [`L7_FI_carotid_cross_1.hdf5`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/oslo/B_carotid/L7_FI_carotid_cross_1.hdf5), reconstructed from the raw channel data with `reconstruct.py`.* ## Dataset Description +Part of the **UltraSound ToolBox (USTB) Channel Capture Collection** (see the [collection card](../README.md)). + In-vivo human carotid-artery channel-capture data acquired with a Verasonics Vantage 256 and an L7-4 linear-array probe, cross-sectional views, focused transmit imaging. Pre-beamformed RF channel data for vascular imaging and beamforming research. -## Dataset Contributors +## Dataset Contributor(s) -University of Oslo (UiO), Department of Informatics. Primary contact: Ole Marius Hoel Rindal (omrindal@ifi.uio.no). Team: Ole Marius Hoel Rindal, Yucel Karabiyik, Sven Peter Nasholm, Andreas Austeng. +- Ole Marius Hoel Rindal (primary contact) +- Yucel Karabiyik +- Sven Peter Nasholm +- Andreas Austeng +- University of Oslo (UiO), Department of Informatics ## Dataset Creation Date @@ -37,9 +43,7 @@ University of Oslo (UiO), Department of Informatics. Primary contact: Ole Marius ## License / Terms of Use -Released under **Creative Commons Attribution 4.0 International (CC BY 4.0)** — see the `LICENCE` -file at the submission root (this license is also declared in the YAML frontmatter above). The -contributed data is cleared for this license. The UltraSound ToolBox (USTB) Channel Capture Collection, University of Oslo. Contributed to OpenH-RF. Zenodo record 20261898. +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage @@ -49,18 +53,27 @@ Generalized reconstruction and adaptive beamforming of vascular ultrasound (RFP - **Data Collection Method:** clinical - **Labeling Method:** N/A (raw channel data; no annotations). -- **Acquisition system:** probe(s) L7-4; - element positions stored in `/probe/probe_geometry` (meters); center frequency, sampling - frequency and sound speed stored per acquisition in `/scan` (see per-sample feature table). +- **Acquisition system:** probe(s) L7-4; element positions stored in `/probe/probe_geometry` (meters); center frequency, sampling frequency and sound speed stored per acquisition in `/scan` (see per-sample feature table). + +## Processing the Dataset + +The example acquisition can be processed with the `pipeline.yaml` definition in this folder and the [zea library](https://github.com/tue-bmd/zea). + +`zea` streams the data from the Hugging Face Hub and processes it according to the pipeline. You can try it out with the following command: + +```bash +zea process \ + --dataset hf://nvidia/OpenH-RF/oslo/B_carotid/L7_FI_carotid_cross_1.hdf5 \ + --config hf://nvidia/OpenH-RF/oslo/B_carotid/pipeline.yaml +``` + +This `pipeline.yaml` holds the pipeline and display window of that acquisition. Alternatively, all acquisitions can be processed with the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/oslo/reconstruct.py) at the root of this collection, as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF), together with the `pipeline*.yaml` definitions at the collection root and the [zea library](https://github.com/tue-bmd/zea). The script streams the data from the Hugging Face Hub; `parameters.yaml` picks the pipeline, display window and dynamic range per acquisition (see the [collection card](../README.md#processing-the-dataset)). ## Dataset Format -All acquisitions are stored in the **zea** HDF5 file format. Each `.hdf5` file is a single -acquisition with raw channel data `/data/raw_data` of shape -`(n_frames, n_tx, n_ax, n_el, n_ch)` and a fully populated `/scan` group describing the transmit -sequence (delays, focus distances, steering angles, apodization, timing). Data type: RF (n_ch=1). -No demodulation or decimation was applied during packaging beyond conversion from the USTB -Ultrasound File Format (UFF) to zea; RF data is demodulated inside the reconstruction pipeline. +[zea v0.1.6](https://github.com/tue-bmd/zea) + +All acquisitions are stored in the **zea** HDF5 file format. Each `.hdf5` file is a single acquisition with raw channel data `/data/raw_data` of shape `(n_frames, n_tx, n_ax, n_el, n_ch)` and a fully populated `/scan` group describing the transmit sequence (delays, focus distances, steering angles, apodization, timing). Data type: RF (n_ch=1). No demodulation or decimation was applied during packaging beyond conversion from the USTB Ultrasound File Format (UFF) to zea; RF data is demodulated inside the reconstruction pipeline. ## Dataset Quantification @@ -102,20 +115,9 @@ Healthy adult volunteer(s). Anatomy: carotid artery (cross-section). Scanner: Ve ## Data Validation -A Delay-And-Sum `zea.Pipeline` is provided in the **`pipeline.yaml` at the submission root** and -run by the single **`reconstruct.py` at the submission root**: -`cast -> demodulate -> delay-and-sum beamform -> envelope detect -> normalize -> log compress` -(RF is demodulated in-pipeline; IQ uses a baseband pipeline). The script is geometry-driven and -recurses into every sub-dataset folder; running `python reconstruct.py` from the root reconstructs -every `.hdf5` in the collection (or pass a folder-qualified path for a single acquisition) and writes -`_zea_bmode.png` next to each file as a portable check that the recorded geometry and timing -are correct. - -The reference B-mode images committed alongside the data (`_bmode.png`) are produced with the -UltraSound ToolBox (USTB) MATLAB Delay-And-Sum beamformer — the exact per-dataset reconstruction -used in the public USTB dataset catalog (https://unioslo.github.io/USTB/datasets.html), with -scanline transmit apodization for focused/sector acquisitions and correct sector-scan geometry. -These are the recommended reference reconstructions for visual verification. +A Delay-And-Sum `zea.Pipeline` (`cast -> demodulate -> delay-and-sum beamform -> envelope detect -> normalize -> log compress`; RF is demodulated in-pipeline, IQ uses a baseband pipeline) reconstructs every acquisition as a portable check that the recorded geometry and timing are correct (see *Processing the Dataset*). + +The reference B-mode images committed alongside the data (`_bmode.png`) are produced with the UltraSound ToolBox (USTB) MATLAB Delay-And-Sum beamformer — the exact per-dataset reconstruction used in the public USTB dataset catalog (https://unioslo.github.io/USTB/datasets.html), with scanline transmit apodization for focused/sector acquisitions and correct sector-scan geometry. These are the recommended reference reconstructions for visual verification. ## Known Issues @@ -124,3 +126,7 @@ Single/few-frame acquisitions; focused linear imaging. ## Ethical Considerations In-vivo data recorded from healthy adult volunteers at the University of Oslo with written informed consent for research use and data sharing, and approval from the Regional Committee for Medical and Health Research Ethics (REK), Norway. Files contain only RF channel data and acquisition parameters; no HHS Safe Harbor identifiers are present. + +## Citation + +Please cite the UltraSound ToolBox (USTB) Channel Capture Collection, University of Oslo (Zenodo record 20261898). diff --git a/datasets/oslo/B_carotid/assets/hero.png b/datasets/oslo/B_carotid/assets/hero.png new file mode 100644 index 0000000..7827f62 Binary files /dev/null and b/datasets/oslo/B_carotid/assets/hero.png differ diff --git a/datasets/oslo/B_carotid/assets/main.png b/datasets/oslo/B_carotid/assets/main.png new file mode 100644 index 0000000..9f8793a Binary files /dev/null and b/datasets/oslo/B_carotid/assets/main.png differ diff --git a/datasets/oslo/B_carotid/pipeline.yaml b/datasets/oslo/B_carotid/pipeline.yaml new file mode 100644 index 0000000..c707412 --- /dev/null +++ b/datasets/oslo/B_carotid/pipeline.yaml @@ -0,0 +1,41 @@ +# zea pipeline for B_carotid/L7_FI_carotid_cross_1.hdf5, the example acquisition of this sub-dataset. +# The shared ../pipeline_scanline.yaml plus the per-acquisition settings that +# ../reconstruct.py takes from ../parameters.yaml (display window in metres, +# dynamic range), so `zea process` renders it standalone. +# zea process writes the pixel array as-is, so the grid is sized for square +# pixels (scanline: resized after log compression). + +parameters: + f_number: 1.75 + dynamic_range: [-60, 0] + enable_scanline: true + grid_type: cartesian + selected_transmits: all + grid_size_z: 700 + zlims: [0.001, 0.06] + xlims: [-0.019, 0.019] +pipeline: + operations: + - name: keras.ops.cast + params: + dtype: float32 + - name: apply_window + - name: demodulate + - name: beamform + params: + beamformer: delay_and_sum + enable_aligned_apodization: true + enable_pfield: false + - name: envelope_detect + - name: normalize + - name: log_compress + - name: keras.ops.expand_dims + params: + axis: -1 + - name: keras.ops.image.resize + params: + size: [700, 449] + interpolation: bilinear + - name: keras.ops.squeeze + params: + axis: -1 diff --git a/datasets/oslo/C_verasonics_phantom/README.md b/datasets/oslo/C_verasonics_phantom/README.md index f34d8f8..0a73e2c 100644 --- a/datasets/oslo/C_verasonics_phantom/README.md +++ b/datasets/oslo/C_verasonics_phantom/README.md @@ -1,4 +1,5 @@ --- +name: oslo-c-verasonics-phantom pretty_name: "USTB - Phantom (Verasonics L7-4 / P4)" license: cc-by-4.0 task_categories: @@ -17,18 +18,23 @@ size_categories: # USTB - Phantom (Verasonics L7-4 / P4) -Part of the **UltraSound ToolBox (USTB) Channel Capture Collection** contributed to the -[OpenH-RF](https://github.com/open-h/OpenH-RF) initiative. All acquisitions are stored in the -*zea* HDF5 file format (zea_version 0.1.6) and contain raw pre-beamformed -channel data (`/data/raw_data`). +![Phased-array sector B-mode of a cyst phantom](assets/hero.png) + +*First frame of [`FI_P4_cysts_center.hdf5`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/oslo/C_verasonics_phantom/FI_P4_cysts_center.hdf5), reconstructed from the raw channel data with `reconstruct.py`.* ## Dataset Description +Part of the **UltraSound ToolBox (USTB) Channel Capture Collection** (see the [collection card](../README.md)). + Tissue-mimicking and table-top phantom channel-capture data acquired on a Verasonics Vantage 256 with L7-4 linear and P4 phased-array probes. The collection spans coherent plane-wave compounding (CPWC), focused imaging (FI), synthetic transmit aperture (STA) and diverging-wave (DW) sequences for resolution, contrast, dynamic-range and point-spread-function evaluation, including CIRS tissue-mimicking phantom targets. -## Dataset Contributors +## Dataset Contributor(s) -University of Oslo (UiO), Department of Informatics. Primary contact: Ole Marius Hoel Rindal (omrindal@ifi.uio.no). Team: Ole Marius Hoel Rindal, Yucel Karabiyik, Sven Peter Nasholm, Andreas Austeng. +- Ole Marius Hoel Rindal (primary contact) +- Yucel Karabiyik +- Sven Peter Nasholm +- Andreas Austeng +- University of Oslo (UiO), Department of Informatics ## Dataset Creation Date @@ -36,9 +42,7 @@ University of Oslo (UiO), Department of Informatics. Primary contact: Ole Marius ## License / Terms of Use -Released under **Creative Commons Attribution 4.0 International (CC BY 4.0)** — see the `LICENCE` -file at the submission root (this license is also declared in the YAML frontmatter above). The -contributed data is cleared for this license. The UltraSound ToolBox (USTB) Channel Capture Collection, University of Oslo. Contributed to OpenH-RF. Zenodo record 20261898. +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage @@ -48,18 +52,27 @@ Generalized reconstruction, image-quality assessment (resolution, contrast, dyna - **Data Collection Method:** phantom - **Labeling Method:** Derived (known phantom geometry, e.g. CIRS Model 040GSE where applicable). -- **Acquisition system:** probe(s) L7-4, P4-1, P4-2; - element positions stored in `/probe/probe_geometry` (meters); center frequency, sampling - frequency and sound speed stored per acquisition in `/scan` (see per-sample feature table). +- **Acquisition system:** probe(s) L7-4, P4-1, P4-2; element positions stored in `/probe/probe_geometry` (meters); center frequency, sampling frequency and sound speed stored per acquisition in `/scan` (see per-sample feature table). + +## Processing the Dataset + +The example acquisition can be processed with the `pipeline.yaml` definition in this folder and the [zea library](https://github.com/tue-bmd/zea). + +`zea` streams the data from the Hugging Face Hub and processes it according to the pipeline. You can try it out with the following command: + +```bash +zea process \ + --dataset hf://nvidia/OpenH-RF/oslo/C_verasonics_phantom/FI_P4_cysts_center.hdf5 \ + --config hf://nvidia/OpenH-RF/oslo/C_verasonics_phantom/pipeline.yaml +``` + +This `pipeline.yaml` holds the pipeline, display window and sector limits of that acquisition. Alternatively, all acquisitions can be processed with the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/oslo/reconstruct.py) at the root of this collection, as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF), together with the `pipeline*.yaml` definitions at the collection root and the [zea library](https://github.com/tue-bmd/zea). The script streams the data from the Hugging Face Hub; `parameters.yaml` picks the pipeline, display window and dynamic range per acquisition (see the [collection card](../README.md#processing-the-dataset)). ## Dataset Format -All acquisitions are stored in the **zea** HDF5 file format. Each `.hdf5` file is a single -acquisition with raw channel data `/data/raw_data` of shape -`(n_frames, n_tx, n_ax, n_el, n_ch)` and a fully populated `/scan` group describing the transmit -sequence (delays, focus distances, steering angles, apodization, timing). Data type: RF (n_ch=1). -No demodulation or decimation was applied during packaging beyond conversion from the USTB -Ultrasound File Format (UFF) to zea; RF data is demodulated inside the reconstruction pipeline. +[zea v0.1.6](https://github.com/tue-bmd/zea) + +All acquisitions are stored in the **zea** HDF5 file format. Each `.hdf5` file is a single acquisition with raw channel data `/data/raw_data` of shape `(n_frames, n_tx, n_ax, n_el, n_ch)` and a fully populated `/scan` group describing the transmit sequence (delays, focus distances, steering angles, apodization, timing). Data type: RF (n_ch=1). No demodulation or decimation was applied during packaging beyond conversion from the USTB Ultrasound File Format (UFF) to zea; RF data is demodulated inside the reconstruction pipeline. ## Dataset Quantification @@ -113,20 +126,9 @@ No human or animal subjects. Targets: CIRS tissue-mimicking phantoms and lab pha ## Data Validation -A Delay-And-Sum `zea.Pipeline` is provided in the **`pipeline.yaml` at the submission root** and -run by the single **`reconstruct.py` at the submission root**: -`cast -> demodulate -> delay-and-sum beamform -> envelope detect -> normalize -> log compress` -(RF is demodulated in-pipeline; IQ uses a baseband pipeline). The script is geometry-driven and -recurses into every sub-dataset folder; running `python reconstruct.py` from the root reconstructs -every `.hdf5` in the collection (or pass a folder-qualified path for a single acquisition) and writes -`_zea_bmode.png` next to each file as a portable check that the recorded geometry and timing -are correct. - -The reference B-mode images committed alongside the data (`_bmode.png`) are produced with the -UltraSound ToolBox (USTB) MATLAB Delay-And-Sum beamformer — the exact per-dataset reconstruction -used in the public USTB dataset catalog (https://unioslo.github.io/USTB/datasets.html), with -scanline transmit apodization for focused/sector acquisitions and correct sector-scan geometry. -These are the recommended reference reconstructions for visual verification. +A Delay-And-Sum `zea.Pipeline` (`cast -> demodulate -> delay-and-sum beamform -> envelope detect -> normalize -> log compress`; RF is demodulated in-pipeline, IQ uses a baseband pipeline) reconstructs every acquisition as a portable check that the recorded geometry and timing are correct (see *Processing the Dataset*). + +The reference B-mode images committed alongside the data (`_bmode.png`) are produced with the UltraSound ToolBox (USTB) MATLAB Delay-And-Sum beamformer — the exact per-dataset reconstruction used in the public USTB dataset catalog (https://unioslo.github.io/USTB/datasets.html), with scanline transmit apodization for focused/sector acquisitions and correct sector-scan geometry. These are the recommended reference reconstructions for visual verification. ## Known Issues @@ -135,3 +137,7 @@ Mixed transmit schemes across files (CPWC / FI / STA / DW). Single-frame acquisi ## Ethical Considerations Phantom / table-top acquisitions; no human or animal subjects are involved. No ethical considerations beyond standard laboratory practice. + +## Citation + +Please cite the UltraSound ToolBox (USTB) Channel Capture Collection, University of Oslo (Zenodo record 20261898). diff --git a/datasets/oslo/C_verasonics_phantom/assets/hero.png b/datasets/oslo/C_verasonics_phantom/assets/hero.png new file mode 100644 index 0000000..e327e62 Binary files /dev/null and b/datasets/oslo/C_verasonics_phantom/assets/hero.png differ diff --git a/datasets/oslo/C_verasonics_phantom/assets/main.png b/datasets/oslo/C_verasonics_phantom/assets/main.png new file mode 100644 index 0000000..709cc8e Binary files /dev/null and b/datasets/oslo/C_verasonics_phantom/assets/main.png differ diff --git a/datasets/oslo/C_verasonics_phantom/pipeline.yaml b/datasets/oslo/C_verasonics_phantom/pipeline.yaml new file mode 100644 index 0000000..3545b0d --- /dev/null +++ b/datasets/oslo/C_verasonics_phantom/pipeline.yaml @@ -0,0 +1,34 @@ +# zea pipeline for C_verasonics_phantom/FI_P4_cysts_center.hdf5, the example acquisition of this sub-dataset. +# The shared ../pipeline_sector.yaml plus the per-acquisition settings that +# ../reconstruct.py takes from ../parameters.yaml (display window in metres, +# dynamic range, polar_limits), so `zea process` renders it standalone. + +parameters: + f_number: 0 + grid_type: polar + grid_size_x: 400 + grid_size_z: 600 + dynamic_range: [-60, 0] + selected_transmits: all + focal_region_length: 0.002 + pfield_kwargs: + interpolation: bilinear + percentile: 70 + alpha: 15 + zlims: [0.001, 0.115] + polar_limits: [-0.7853981852531433, 0.7853981852531433] +pipeline: + operations: + - name: keras.ops.cast + params: + dtype: float32 + - name: apply_window + - name: demodulate + - name: beamform + params: + beamformer: delay_and_sum + enable_pfield: true + - name: envelope_detect + - name: normalize + - name: log_compress + - name: scan_convert diff --git a/datasets/oslo/D_alpinion_phantom/README.md b/datasets/oslo/D_alpinion_phantom/README.md index 8f8a90e..9f47e6e 100644 --- a/datasets/oslo/D_alpinion_phantom/README.md +++ b/datasets/oslo/D_alpinion_phantom/README.md @@ -1,4 +1,5 @@ --- +name: oslo-d-alpinion-phantom pretty_name: "USTB - Phantom (Alpinion L3-8)" license: cc-by-4.0 task_categories: @@ -17,18 +18,23 @@ size_categories: # USTB - Phantom (Alpinion L3-8) -Part of the **UltraSound ToolBox (USTB) Channel Capture Collection** contributed to the -[OpenH-RF](https://github.com/open-h/OpenH-RF) initiative. All acquisitions are stored in the -*zea* HDF5 file format (zea_version 0.1.6) and contain raw pre-beamformed -channel data (`/data/raw_data`). +![Plane-wave compounded B-mode of hypoechoic cysts](assets/hero.png) + +*First frame of [`Alpinion_L3-8_CPWC_hypoechoic.hdf5`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/oslo/D_alpinion_phantom/Alpinion_L3-8_CPWC_hypoechoic.hdf5), reconstructed from the raw channel data with `reconstruct.py`.* ## Dataset Description +Part of the **UltraSound ToolBox (USTB) Channel Capture Collection** (see the [collection card](../README.md)). + Phantom channel-capture data acquired on an Alpinion E-Cube 12R research scanner with an L3-8 linear-array probe. Hypoechoic and hyperechoic targets imaged with focused (FI) and coherent plane-wave compounding (CPWC) sequences. -## Dataset Contributors +## Dataset Contributor(s) -University of Oslo (UiO), Department of Informatics. Primary contact: Ole Marius Hoel Rindal (omrindal@ifi.uio.no). Team: Ole Marius Hoel Rindal, Yucel Karabiyik, Sven Peter Nasholm, Andreas Austeng. +- Ole Marius Hoel Rindal (primary contact) +- Yucel Karabiyik +- Sven Peter Nasholm +- Andreas Austeng +- University of Oslo (UiO), Department of Informatics ## Dataset Creation Date @@ -36,9 +42,7 @@ University of Oslo (UiO), Department of Informatics. Primary contact: Ole Marius ## License / Terms of Use -Released under **Creative Commons Attribution 4.0 International (CC BY 4.0)** — see the `LICENCE` -file at the submission root (this license is also declared in the YAML frontmatter above). The -contributed data is cleared for this license. The UltraSound ToolBox (USTB) Channel Capture Collection, University of Oslo. Contributed to OpenH-RF. Zenodo record 20261898. +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage @@ -48,18 +52,27 @@ Generalized reconstruction and image-quality assessment on a second hardware pla - **Data Collection Method:** phantom - **Labeling Method:** Derived (known phantom target types). -- **Acquisition system:** probe(s) L3-8; - element positions stored in `/probe/probe_geometry` (meters); center frequency, sampling - frequency and sound speed stored per acquisition in `/scan` (see per-sample feature table). +- **Acquisition system:** probe(s) L3-8; element positions stored in `/probe/probe_geometry` (meters); center frequency, sampling frequency and sound speed stored per acquisition in `/scan` (see per-sample feature table). + +## Processing the Dataset + +The example acquisition can be processed with the `pipeline.yaml` definition in this folder and the [zea library](https://github.com/tue-bmd/zea). + +`zea` streams the data from the Hugging Face Hub and processes it according to the pipeline. You can try it out with the following command: + +```bash +zea process \ + --dataset hf://nvidia/OpenH-RF/oslo/D_alpinion_phantom/Alpinion_L3-8_CPWC_hypoechoic.hdf5 \ + --config hf://nvidia/OpenH-RF/oslo/D_alpinion_phantom/pipeline.yaml +``` + +This `pipeline.yaml` holds the pipeline and display window of that acquisition. Alternatively, all acquisitions can be processed with the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/oslo/reconstruct.py) at the root of this collection, as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF), together with the `pipeline*.yaml` definitions at the collection root and the [zea library](https://github.com/tue-bmd/zea). The script streams the data from the Hugging Face Hub; `parameters.yaml` picks the pipeline, display window and dynamic range per acquisition (see the [collection card](../README.md#processing-the-dataset)). ## Dataset Format -All acquisitions are stored in the **zea** HDF5 file format. Each `.hdf5` file is a single -acquisition with raw channel data `/data/raw_data` of shape -`(n_frames, n_tx, n_ax, n_el, n_ch)` and a fully populated `/scan` group describing the transmit -sequence (delays, focus distances, steering angles, apodization, timing). Data type: RF (n_ch=1). -No demodulation or decimation was applied during packaging beyond conversion from the USTB -Ultrasound File Format (UFF) to zea; RF data is demodulated inside the reconstruction pipeline. +[zea v0.1.6](https://github.com/tue-bmd/zea) + +All acquisitions are stored in the **zea** HDF5 file format. Each `.hdf5` file is a single acquisition with raw channel data `/data/raw_data` of shape `(n_frames, n_tx, n_ax, n_el, n_ch)` and a fully populated `/scan` group describing the transmit sequence (delays, focus distances, steering angles, apodization, timing). Data type: RF (n_ch=1). No demodulation or decimation was applied during packaging beyond conversion from the USTB Ultrasound File Format (UFF) to zea; RF data is demodulated inside the reconstruction pipeline. ## Dataset Quantification @@ -102,20 +115,9 @@ No human or animal subjects. Targets: hypoechoic/hyperechoic phantom inclusions. ## Data Validation -A Delay-And-Sum `zea.Pipeline` is provided in the **`pipeline.yaml` at the submission root** and -run by the single **`reconstruct.py` at the submission root**: -`cast -> demodulate -> delay-and-sum beamform -> envelope detect -> normalize -> log compress` -(RF is demodulated in-pipeline; IQ uses a baseband pipeline). The script is geometry-driven and -recurses into every sub-dataset folder; running `python reconstruct.py` from the root reconstructs -every `.hdf5` in the collection (or pass a folder-qualified path for a single acquisition) and writes -`_zea_bmode.png` next to each file as a portable check that the recorded geometry and timing -are correct. - -The reference B-mode images committed alongside the data (`_bmode.png`) are produced with the -UltraSound ToolBox (USTB) MATLAB Delay-And-Sum beamformer — the exact per-dataset reconstruction -used in the public USTB dataset catalog (https://unioslo.github.io/USTB/datasets.html), with -scanline transmit apodization for focused/sector acquisitions and correct sector-scan geometry. -These are the recommended reference reconstructions for visual verification. +A Delay-And-Sum `zea.Pipeline` (`cast -> demodulate -> delay-and-sum beamform -> envelope detect -> normalize -> log compress`; RF is demodulated in-pipeline, IQ uses a baseband pipeline) reconstructs every acquisition as a portable check that the recorded geometry and timing are correct (see *Processing the Dataset*). + +The reference B-mode images committed alongside the data (`_bmode.png`) are produced with the UltraSound ToolBox (USTB) MATLAB Delay-And-Sum beamformer — the exact per-dataset reconstruction used in the public USTB dataset catalog (https://unioslo.github.io/USTB/datasets.html), with scanline transmit apodization for focused/sector acquisitions and correct sector-scan geometry. These are the recommended reference reconstructions for visual verification. ## Known Issues @@ -124,3 +126,7 @@ Single-frame acquisitions; two transmit schemes (FI, CPWC). ## Ethical Considerations Phantom acquisitions; no human or animal subjects. No ethical considerations beyond standard laboratory practice. + +## Citation + +Please cite the UltraSound ToolBox (USTB) Channel Capture Collection, University of Oslo (Zenodo record 20261898). diff --git a/datasets/oslo/D_alpinion_phantom/assets/hero.png b/datasets/oslo/D_alpinion_phantom/assets/hero.png new file mode 100644 index 0000000..bd41cd8 Binary files /dev/null and b/datasets/oslo/D_alpinion_phantom/assets/hero.png differ diff --git a/datasets/oslo/D_alpinion_phantom/assets/main.png b/datasets/oslo/D_alpinion_phantom/assets/main.png new file mode 100644 index 0000000..bbcdad4 Binary files /dev/null and b/datasets/oslo/D_alpinion_phantom/assets/main.png differ diff --git a/datasets/oslo/D_alpinion_phantom/pipeline.yaml b/datasets/oslo/D_alpinion_phantom/pipeline.yaml new file mode 100644 index 0000000..84214fe --- /dev/null +++ b/datasets/oslo/D_alpinion_phantom/pipeline.yaml @@ -0,0 +1,29 @@ +# zea pipeline for D_alpinion_phantom/Alpinion_L3-8_CPWC_hypoechoic.hdf5, the example acquisition of this sub-dataset. +# The shared ../pipeline.yaml plus the per-acquisition settings that +# ../reconstruct.py takes from ../parameters.yaml (display window in metres, +# dynamic range), so `zea process` renders it standalone. +# zea process writes the pixel array as-is, so the grid is sized for square +# pixels. + +parameters: + f_number: 1.75 + grid_size_x: 507 + grid_size_z: 600 + dynamic_range: [-60, 0] + selected_transmits: all + zlims: [0.005, 0.05] + xlims: [-0.019, 0.019] +pipeline: + operations: + - name: keras.ops.cast + params: + dtype: float32 + - name: apply_window + - name: demodulate + - name: beamform + params: + beamformer: delay_and_sum + enable_pfield: false + - name: envelope_detect + - name: normalize + - name: log_compress diff --git a/datasets/oslo/E_simulation/README.md b/datasets/oslo/E_simulation/README.md index d64110d..0d671c2 100644 --- a/datasets/oslo/E_simulation/README.md +++ b/datasets/oslo/E_simulation/README.md @@ -1,4 +1,5 @@ --- +name: oslo-e-simulation pretty_name: "USTB - Simulation (Field II)" license: cc-by-4.0 task_categories: @@ -18,18 +19,23 @@ size_categories: # USTB - Simulation (Field II) -Part of the **UltraSound ToolBox (USTB) Channel Capture Collection** contributed to the -[OpenH-RF](https://github.com/open-h/OpenH-RF) initiative. All acquisitions are stored in the -*zea* HDF5 file format (zea_version 0.1.6) and contain raw pre-beamformed -channel data (`/data/raw_data`). +![B-mode of the PICMUS numerical phantom](assets/hero.png) + +*First frame of [`PICMUS_numerical_calib_v2.hdf5`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/oslo/E_simulation/PICMUS_numerical_calib_v2.hdf5), reconstructed from the raw channel data with `reconstruct.py`.* ## Dataset Description +Part of the **UltraSound ToolBox (USTB) Channel Capture Collection** (see the [collection card](../README.md)). + Physics-based synthetic channel-capture data generated with the Field II ultrasound simulation framework. The collection covers point scatterers, cysts, speckle, dynamic-range targets and blocked-array (aperture-apodized) configurations, using linear (L7-4-like) and phased (P4-like) virtual probes with CPWC, FI and STA sequences. Because the scattering medium is fully defined, exact ground-truth scatterer positions and medium parameters are known. One numerical calibration acquisition (PICMUS_numerical_calib_v2) was created in collaboration with our group as part of the PICMUS effort; it is included here while the other PICMUS datasets are excluded (see Known Issues). -## Dataset Contributors +## Dataset Contributor(s) -University of Oslo (UiO), Department of Informatics. Primary contact: Ole Marius Hoel Rindal (omrindal@ifi.uio.no). Team: Ole Marius Hoel Rindal, Yucel Karabiyik, Sven Peter Nasholm, Andreas Austeng. +- Ole Marius Hoel Rindal (primary contact) +- Yucel Karabiyik +- Sven Peter Nasholm +- Andreas Austeng +- University of Oslo (UiO), Department of Informatics ## Dataset Creation Date @@ -37,9 +43,7 @@ University of Oslo (UiO), Department of Informatics. Primary contact: Ole Marius ## License / Terms of Use -Released under **Creative Commons Attribution 4.0 International (CC BY 4.0)** — see the `LICENCE` -file at the submission root (this license is also declared in the YAML frontmatter above). The -contributed data is cleared for this license. The UltraSound ToolBox (USTB) Channel Capture Collection, University of Oslo. Contributed to OpenH-RF. Zenodo record 20261898. +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage @@ -49,18 +53,27 @@ Generalized reconstruction, beamformer development and validation with known gro - **Data Collection Method:** synthetic - **Labeling Method:** Synthetic ground truth (known scatterer positions and medium parameters). -- **Acquisition system:** probe(s) L7-4, P4-1; - element positions stored in `/probe/probe_geometry` (meters); center frequency, sampling - frequency and sound speed stored per acquisition in `/scan` (see per-sample feature table). +- **Acquisition system:** probe(s) L7-4, P4-1; element positions stored in `/probe/probe_geometry` (meters); center frequency, sampling frequency and sound speed stored per acquisition in `/scan` (see per-sample feature table). + +## Processing the Dataset + +The example acquisition can be processed with the `pipeline.yaml` definition in this folder and the [zea library](https://github.com/tue-bmd/zea). + +`zea` streams the data from the Hugging Face Hub and processes it according to the pipeline. You can try it out with the following command: + +```bash +zea process \ + --dataset hf://nvidia/OpenH-RF/oslo/E_simulation/PICMUS_numerical_calib_v2.hdf5 \ + --config hf://nvidia/OpenH-RF/oslo/E_simulation/pipeline.yaml +``` + +This `pipeline.yaml` holds the pipeline and display window of that acquisition. Alternatively, all acquisitions can be processed with the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/oslo/reconstruct.py) at the root of this collection, as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF), together with the `pipeline*.yaml` definitions at the collection root and the [zea library](https://github.com/tue-bmd/zea). The script streams the data from the Hugging Face Hub; `parameters.yaml` picks the pipeline, display window and dynamic range per acquisition (see the [collection card](../README.md#processing-the-dataset)). ## Dataset Format -All acquisitions are stored in the **zea** HDF5 file format. Each `.hdf5` file is a single -acquisition with raw channel data `/data/raw_data` of shape -`(n_frames, n_tx, n_ax, n_el, n_ch)` and a fully populated `/scan` group describing the transmit -sequence (delays, focus distances, steering angles, apodization, timing). Data type: RF/IQ (n_ch in [1, 2]). -No demodulation or decimation was applied during packaging beyond conversion from the USTB -Ultrasound File Format (UFF) to zea; RF data is demodulated inside the reconstruction pipeline. +[zea v0.1.6](https://github.com/tue-bmd/zea) + +All acquisitions are stored in the **zea** HDF5 file format. Each `.hdf5` file is a single acquisition with raw channel data `/data/raw_data` of shape `(n_frames, n_tx, n_ax, n_el, n_ch)` and a fully populated `/scan` group describing the transmit sequence (delays, focus distances, steering angles, apodization, timing). Data type: RF/IQ (n_ch in [1, 2]). No demodulation or decimation was applied during packaging beyond conversion from the USTB Ultrasound File Format (UFF) to zea; RF data is demodulated inside the reconstruction pipeline. ## Dataset Quantification @@ -110,20 +123,9 @@ No human or animal subjects. Synthetic media simulated with Field II. Virtual pr ## Data Validation -A Delay-And-Sum `zea.Pipeline` is provided in the **`pipeline.yaml` at the submission root** and -run by the single **`reconstruct.py` at the submission root**: -`cast -> demodulate -> delay-and-sum beamform -> envelope detect -> normalize -> log compress` -(RF is demodulated in-pipeline; IQ uses a baseband pipeline). The script is geometry-driven and -recurses into every sub-dataset folder; running `python reconstruct.py` from the root reconstructs -every `.hdf5` in the collection (or pass a folder-qualified path for a single acquisition) and writes -`_zea_bmode.png` next to each file as a portable check that the recorded geometry and timing -are correct. - -The reference B-mode images committed alongside the data (`_bmode.png`) are produced with the -UltraSound ToolBox (USTB) MATLAB Delay-And-Sum beamformer — the exact per-dataset reconstruction -used in the public USTB dataset catalog (https://unioslo.github.io/USTB/datasets.html), with -scanline transmit apodization for focused/sector acquisitions and correct sector-scan geometry. -These are the recommended reference reconstructions for visual verification. +A Delay-And-Sum `zea.Pipeline` (`cast -> demodulate -> delay-and-sum beamform -> envelope detect -> normalize -> log compress`; RF is demodulated in-pipeline, IQ uses a baseband pipeline) reconstructs every acquisition as a portable check that the recorded geometry and timing are correct (see *Processing the Dataset*). + +The reference B-mode images committed alongside the data (`_bmode.png`) are produced with the UltraSound ToolBox (USTB) MATLAB Delay-And-Sum beamformer — the exact per-dataset reconstruction used in the public USTB dataset catalog (https://unioslo.github.io/USTB/datasets.html), with scanline transmit apodization for focused/sector acquisitions and correct sector-scan geometry. These are the recommended reference reconstructions for visual verification. ## Known Issues @@ -132,3 +134,7 @@ PICMUS calibration: 'PICMUS_numerical_calib_v2' was created in collaboration wit ## Ethical Considerations Fully synthetic data generated with the Field II simulation framework; no human or animal subjects. No personal data is present. The included numerical calibration file was produced in collaboration with our group as part of the PICMUS effort (IEEE IUS 2016) and is released here under CC BY 4.0. + +## Citation + +Please cite the UltraSound ToolBox (USTB) Channel Capture Collection, University of Oslo (Zenodo record 20261898). diff --git a/datasets/oslo/E_simulation/assets/hero.png b/datasets/oslo/E_simulation/assets/hero.png new file mode 100644 index 0000000..2a616b4 Binary files /dev/null and b/datasets/oslo/E_simulation/assets/hero.png differ diff --git a/datasets/oslo/E_simulation/assets/main.png b/datasets/oslo/E_simulation/assets/main.png new file mode 100644 index 0000000..7a32796 Binary files /dev/null and b/datasets/oslo/E_simulation/assets/main.png differ diff --git a/datasets/oslo/E_simulation/pipeline.yaml b/datasets/oslo/E_simulation/pipeline.yaml new file mode 100644 index 0000000..ea22f69 --- /dev/null +++ b/datasets/oslo/E_simulation/pipeline.yaml @@ -0,0 +1,28 @@ +# zea pipeline for E_simulation/PICMUS_numerical_calib_v2.hdf5, the example acquisition of this sub-dataset. +# The shared ../pipeline_iq.yaml plus the per-acquisition settings that +# ../reconstruct.py takes from ../parameters.yaml (display window in metres, +# dynamic range), so `zea process` renders it standalone. +# zea process writes the pixel array as-is, so the grid is sized for square +# pixels. + +parameters: + f_number: 1.75 + grid_size_x: 507 + grid_size_z: 600 + dynamic_range: [-60, 0] + selected_transmits: all + zlims: [0.005, 0.05] + xlims: [-0.019, 0.019] +pipeline: + operations: + - name: keras.ops.cast + params: + dtype: float32 + - name: apply_window + - name: beamform + params: + beamformer: delay_and_sum + enable_pfield: false + - name: envelope_detect + - name: normalize + - name: log_compress diff --git a/datasets/oslo/F_motion/README.md b/datasets/oslo/F_motion/README.md index f8380ee..dd2757d 100644 --- a/datasets/oslo/F_motion/README.md +++ b/datasets/oslo/F_motion/README.md @@ -1,4 +1,5 @@ --- +name: oslo-f-motion pretty_name: "USTB - Motion Estimation (SWE / ARFI, Verasonics L7-4)" license: cc-by-4.0 task_categories: @@ -18,18 +19,23 @@ size_categories: # USTB - Motion Estimation (SWE / ARFI, Verasonics L7-4) -Part of the **UltraSound ToolBox (USTB) Channel Capture Collection** contributed to the -[OpenH-RF](https://github.com/open-h/OpenH-RF) initiative. All acquisitions are stored in the -*zea* HDF5 file format (zea_version 0.1.6) and contain raw pre-beamformed -channel data (`/data/raw_data`). +![Shear waves travelling outward after an acoustic push](assets/hero.gif) + +*First push-track sequence (frames 1-49) of [`SWE_L7_type_III.hdf5`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/oslo/F_motion/SWE_L7_type_III.hdf5): B-mode (left) and axial displacement between consecutive frames (right), a lag-one autocorrelation (Kasai) estimate on the IQ beamformed with `pipeline.yaml`. The displacement estimate is not part of the reference pipeline.* ## Dataset Description +Part of the **UltraSound ToolBox (USTB) Channel Capture Collection** (see the [collection card](../README.md)). + Shear-wave elastography (SWE) and acoustic-radiation-force-impulse (ARFI) push-tracking phantom acquisitions on a Verasonics Vantage 256 with an L7-4 linear array. Each acquisition contains many high-frame-rate tracking frames following an acoustic push, suitable for tissue-motion and shear-wave-velocity estimation. -## Dataset Contributors +## Dataset Contributor(s) -University of Oslo (UiO), Department of Informatics. Primary contact: Ole Marius Hoel Rindal (omrindal@ifi.uio.no). Team: Ole Marius Hoel Rindal, Yucel Karabiyik, Sven Peter Nasholm, Andreas Austeng. +- Ole Marius Hoel Rindal (primary contact) +- Yucel Karabiyik +- Sven Peter Nasholm +- Andreas Austeng +- University of Oslo (UiO), Department of Informatics ## Dataset Creation Date @@ -37,9 +43,7 @@ University of Oslo (UiO), Department of Informatics. Primary contact: Ole Marius ## License / Terms of Use -Released under **Creative Commons Attribution 4.0 International (CC BY 4.0)** — see the `LICENCE` -file at the submission root (this license is also declared in the YAML frontmatter above). The -contributed data is cleared for this license. The UltraSound ToolBox (USTB) Channel Capture Collection, University of Oslo. Contributed to OpenH-RF. Zenodo record 20261898. +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage @@ -49,18 +53,27 @@ Motion estimation (RFP task 6.4): shear-wave velocity estimation, ARFI displacem - **Data Collection Method:** phantom - **Labeling Method:** Derived (elastography phantom of known/typical stiffness classes). -- **Acquisition system:** probe(s) L7-4; - element positions stored in `/probe/probe_geometry` (meters); center frequency, sampling - frequency and sound speed stored per acquisition in `/scan` (see per-sample feature table). +- **Acquisition system:** probe(s) L7-4; element positions stored in `/probe/probe_geometry` (meters); center frequency, sampling frequency and sound speed stored per acquisition in `/scan` (see per-sample feature table). + +## Processing the Dataset + +The example acquisition can be processed with the `pipeline.yaml` definition in this folder and the [zea library](https://github.com/tue-bmd/zea). + +`zea` streams the data from the Hugging Face Hub and processes it according to the pipeline. You can try it out with the following command: + +```bash +zea process \ + --dataset hf://nvidia/OpenH-RF/oslo/F_motion/SWE_L7_type_III.hdf5 \ + --config hf://nvidia/OpenH-RF/oslo/F_motion/pipeline.yaml +``` + +This `pipeline.yaml` holds the pipeline and display window of that acquisition. Alternatively, all acquisitions can be processed with the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/oslo/reconstruct.py) at the root of this collection, as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF), together with the `pipeline*.yaml` definitions at the collection root and the [zea library](https://github.com/tue-bmd/zea). The script streams the data from the Hugging Face Hub; `parameters.yaml` picks the pipeline, display window and dynamic range per acquisition (see the [collection card](../README.md#processing-the-dataset)). These plane-wave tracking acquisitions use the non-focused `compound` pipeline, and the reference reconstruction shows a single tracking frame. ## Dataset Format -All acquisitions are stored in the **zea** HDF5 file format. Each `.hdf5` file is a single -acquisition with raw channel data `/data/raw_data` of shape -`(n_frames, n_tx, n_ax, n_el, n_ch)` and a fully populated `/scan` group describing the transmit -sequence (delays, focus distances, steering angles, apodization, timing). Data type: RF (n_ch=1). -No demodulation or decimation was applied during packaging beyond conversion from the USTB -Ultrasound File Format (UFF) to zea; RF data is demodulated inside the reconstruction pipeline. +[zea v0.1.6](https://github.com/tue-bmd/zea) + +All acquisitions are stored in the **zea** HDF5 file format. Each `.hdf5` file is a single acquisition with raw channel data `/data/raw_data` of shape `(n_frames, n_tx, n_ax, n_el, n_ch)` and a fully populated `/scan` group describing the transmit sequence (delays, focus distances, steering angles, apodization, timing). Data type: RF (n_ch=1). No demodulation or decimation was applied during packaging beyond conversion from the USTB Ultrasound File Format (UFF) to zea; RF data is demodulated inside the reconstruction pipeline. ## Dataset Quantification @@ -103,20 +116,9 @@ No human or animal subjects. Elastography phantoms. Scanner: Verasonics Vantage ## Data Validation -A Delay-And-Sum `zea.Pipeline` is provided in the **`pipeline.yaml` at the submission root** and -run by the single **`reconstruct.py` at the submission root**: -`cast -> demodulate -> delay-and-sum beamform -> envelope detect -> normalize -> log compress` -(RF is demodulated in-pipeline; IQ uses a baseband pipeline). The script is geometry-driven and -recurses into every sub-dataset folder; running `python reconstruct.py` from the root reconstructs -every `.hdf5` in the collection (or pass a folder-qualified path for a single acquisition) and writes -`_zea_bmode.png` next to each file as a portable check that the recorded geometry and timing -are correct. These plane-wave tracking acquisitions use the non-focused `compound` pipeline, and the -reference reconstruction shows a single tracking frame. - -The reference B-mode images committed alongside the data (`_bmode.png`) are produced with the -UltraSound ToolBox (USTB) MATLAB Delay-And-Sum beamformer — the exact per-dataset reconstruction -used in the public USTB dataset catalog (https://unioslo.github.io/USTB/datasets.html). These are -the recommended reference reconstructions for visual verification. +A Delay-And-Sum `zea.Pipeline` (`cast -> demodulate -> delay-and-sum beamform -> envelope detect -> normalize -> log compress`; RF is demodulated in-pipeline, IQ uses a baseband pipeline) reconstructs every acquisition as a portable check that the recorded geometry and timing are correct (see *Processing the Dataset*). + +The reference B-mode images committed alongside the data (`_bmode.png`) are produced with the UltraSound ToolBox (USTB) MATLAB Delay-And-Sum beamformer — the exact per-dataset reconstruction used in the public USTB dataset catalog (https://unioslo.github.io/USTB/datasets.html). These are the recommended reference reconstructions for visual verification. ## Known Issues @@ -125,3 +127,7 @@ Push-track sequences contain many frames at a high frame rate; the B-mode refere ## Ethical Considerations Phantom acquisitions; no human or animal subjects. No ethical considerations beyond standard laboratory practice. + +## Citation + +Please cite the UltraSound ToolBox (USTB) Channel Capture Collection, University of Oslo (Zenodo record 20261898). diff --git a/datasets/oslo/F_motion/assets/hero.gif b/datasets/oslo/F_motion/assets/hero.gif new file mode 100644 index 0000000..840b229 Binary files /dev/null and b/datasets/oslo/F_motion/assets/hero.gif differ diff --git a/datasets/oslo/F_motion/assets/main.png b/datasets/oslo/F_motion/assets/main.png new file mode 100644 index 0000000..78bb0a1 Binary files /dev/null and b/datasets/oslo/F_motion/assets/main.png differ diff --git a/datasets/oslo/F_motion/pipeline.yaml b/datasets/oslo/F_motion/pipeline.yaml new file mode 100644 index 0000000..55e9280 --- /dev/null +++ b/datasets/oslo/F_motion/pipeline.yaml @@ -0,0 +1,29 @@ +# zea pipeline for F_motion/SWE_L7_type_III.hdf5, the example acquisition of this sub-dataset. +# The shared ../pipeline.yaml plus the per-acquisition settings that +# ../reconstruct.py takes from ../parameters.yaml (display window in metres, +# dynamic range), so `zea process` renders it standalone. +# zea process writes the pixel array as-is, so the grid is sized for square +# pixels. + +parameters: + f_number: 1.75 + grid_size_x: 585 + grid_size_z: 600 + dynamic_range: [-60, 0] + selected_transmits: all + zlims: [0.001, 0.04] + xlims: [-0.019, 0.019] +pipeline: + operations: + - name: keras.ops.cast + params: + dtype: float32 + - name: apply_window + - name: demodulate + - name: beamform + params: + beamformer: delay_and_sum + enable_pfield: false + - name: envelope_detect + - name: normalize + - name: log_compress diff --git a/datasets/oslo/README.md b/datasets/oslo/README.md index 5176f0a..f034e93 100644 --- a/datasets/oslo/README.md +++ b/datasets/oslo/README.md @@ -1,5 +1,6 @@ --- -pretty_name: "OpenH-RF — UltraSound ToolBox (USTB) Channel Capture Collection" +name: oslo +pretty_name: "UltraSound ToolBox (USTB) Channel Capture Collection" license: cc-by-4.0 task_categories: - image-to-image @@ -15,25 +16,17 @@ size_categories: - n<1K --- -# UltraSound ToolBox (USTB) Channel Capture Collection — OpenH-RF Submission +# UltraSound ToolBox (USTB) Channel Capture Collection -Contributed by the **University of Oslo (UiO), Department of Informatics** (the USTB team) -to the [OpenH-RF](https://github.com/open-h/OpenH-RF) initiative. All data is pre-beamformed -(raw channel-capture) ultrasound stored in the **zea** HDF5 format (`zea_version 0.1.6`, -meeting the OpenH-RF minimum format version `0.1.4`) and released under **CC BY 4.0**. +![B-modes from five of the six sub-datasets: in-vivo cardiac, in-vivo carotid, Verasonics and Alpinion phantoms, and simulation](assets/hero.png) -## OpenH-RF Release Inventory +*First frames reconstructed from the raw channel data with `reconstruct.py`. Top: [`A_cardiac/Verasonics_P2-4_parasternal_long_subject_1`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/oslo/A_cardiac/Verasonics_P2-4_parasternal_long_subject_1.hdf5), [`A_cardiac/Verasonics_P2-4_apical_four_chamber_subject_1`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/oslo/A_cardiac/Verasonics_P2-4_apical_four_chamber_subject_1.hdf5), [`C_verasonics_phantom/FI_P4_cysts_center`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/oslo/C_verasonics_phantom/FI_P4_cysts_center.hdf5). Bottom: [`B_carotid/L7_FI_carotid_cross_1`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/oslo/B_carotid/L7_FI_carotid_cross_1.hdf5), [`C_verasonics_phantom/L7_FI_Verasonics_CIRS`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/oslo/C_verasonics_phantom/L7_FI_Verasonics_CIRS.hdf5), [`D_alpinion_phantom/Alpinion_L3-8_CPWC_hypoechoic`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/oslo/D_alpinion_phantom/Alpinion_L3-8_CPWC_hypoechoic.hdf5), [`E_simulation/PICMUS_numerical_calib_v2`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/oslo/E_simulation/PICMUS_numerical_calib_v2.hdf5). Panels are scaled to a common height per row, not to a common physical scale.* -**Current OpenH-RF release:** 39 HDF5 files; 8.61 GB (8,613,593,088 bytes) stored; root `zea_version` **0.1.6**. Sizes include all HDF5 contents and use decimal units (MB = 10^6 bytes, GB = 10^9 bytes, TB = 10^12 bytes), not decoded-array memory or original-source download sizes. +## Dataset Description -## Contents +Contributed by the **University of Oslo (UiO), Department of Informatics** (the USTB team) to the [OpenH-RF](https://github.com/open-h/OpenH-RF) initiative. All data is pre-beamformed (raw channel-capture) ultrasound stored in the **zea** HDF5 format and released under **CC BY 4.0**. -39 acquisitions packaged into six application sub-datasets. Each sub-dataset folder contains its -zea `.hdf5` files, a Hugging Face–style `README.md` data card, and one reference B-mode PNG per -acquisition. The **`reconstruct.py`, `pipeline.yaml`, and the CC BY 4.0 `LICENCE` live once at the -submission root** — the reconstruction reconstructs every sub-dataset (the pipeline is identical -across folders), and the single LICENCE covers the whole collection (each data card also declares -`license: cc-by-4.0` in its YAML frontmatter). +39 acquisitions packaged into six application sub-datasets. Each sub-dataset folder contains its zea `.hdf5` files, a Hugging Face–style `README.md` data card, and one reference B-mode PNG per acquisition. The **`reconstruct.py`, `pipeline.yaml`, and the CC BY 4.0 `LICENCE` live once at the submission root** — the reconstruction reconstructs every sub-dataset (the pipeline is identical across folders), and the single LICENCE covers the whole collection (each data card also declares `license: cc-by-4.0` in its YAML frontmatter). | Folder | Sub-dataset | Tier | RFP task | Acq. | |---|---|---|---|---| @@ -46,23 +39,37 @@ across folders), and the single LICENCE covers the whole collection (each data c Total: **39 acquisitions**, 8.61 GB of stored HDF5 data. -## How to reconstruct +## Dataset Contributor(s) + +- Ole Marius Hoel Rindal (primary contact) +- Yucel Karabiyik +- Sven Peter Nasholm +- Andreas Austeng +- University of Oslo (UiO), Department of Informatics + +## Dataset Creation Date + +06/23/2026 (packaging date; original acquisitions/simulations were produced between 2016 and 2023). + +## License / Terms of Use + +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. + +## Processing the Dataset + +Every acquisition is reconstructable from the file alone — all acquisition parameters live in the zea `/scan` and `/probe` groups. The acquisitions can be processed with the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/oslo/reconstruct.py), as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF), together with the `pipeline*.yaml` and `parameters.yaml` definitions in this folder and the [zea library](https://github.com/tue-bmd/zea). The script streams the data from the Hugging Face Hub: set `PATHS` to the acquisition to reconstruct (default: the apical four-chamber cardiac scan above) and `REFOCUS = True` to also emit the REFoCUS variant. See [Reconstruction Details](#reconstruction-details) for how each acquisition's pipeline is chosen. Each sub-dataset folder also holds a `pipeline.yaml` for its example acquisition, so it can be rendered with a single `zea process` command (see the sub-dataset cards). -Every acquisition is reconstructable from the file alone — all acquisition parameters live in the -zea `/scan` and `/probe` groups. A single `reconstruct.py` at the submission root reconstructs -every acquisition in every sub-dataset folder: +## Dataset Format -```bash -export KERAS_BACKEND=jax -python reconstruct.py # standard recon for every .hdf5 -python reconstruct.py --refocus # also emit REFoCUS where enabled -python reconstruct.py A_cardiac/.hdf5 # a single acquisition -``` +[zea v0.1.6](https://github.com/tue-bmd/zea) -All reconstruction choices live in **`parameters.yaml`** — one entry per acquisition giving its -`pipeline` (which `zea.Pipeline` to use), display window (`zlims`/`xlims` in mm) and `dynamic_range`. -Every acquisition uses the same workflow (load parameters → run the pipeline → plot); there is no -per-file logic in the script. Each `pipeline` value maps to a `zea.Pipeline` YAML at the root: +## Dataset Quantification + +**Current OpenH-RF release:** 39 HDF5 files; 8.61 GB (8,613,593,088 bytes) stored; root `zea_version` **0.1.6**. Sizes include all HDF5 contents and use decimal units (MB = 10^6 bytes, GB = 10^9 bytes, TB = 10^12 bytes), not decoded-array memory or original-source download sizes. + +## Reconstruction Details + +All reconstruction choices live in **`parameters.yaml`** — one entry per acquisition giving its `pipeline` (which `zea.Pipeline` to use), display window (`zlims`/`xlims` in mm) and `dynamic_range`. Every acquisition uses the same workflow (load parameters → run the pipeline → plot); there is no per-file logic in the script. Each `pipeline` value maps to a `zea.Pipeline` YAML at the root: | `pipeline` | Used for | `zea.Pipeline` | |---|---|---| @@ -71,44 +78,18 @@ per-file logic in the script. Each `pipeline` value maps to a `zea.Pipeline` YAM | `iq` | baseband IQ (`n_ch == 2`, e.g. PICMUS) | `pipeline_iq.yaml` (no demodulation step). | | `compound` | non-focused linear (plane-wave / diverging / STA / SWE-ARFI) | `pipeline.yaml`: coherent compounding, no pfield (these insonify the whole field of view). | -The pipelines share the same shape as zea's regular B-mode pipeline (`cast → apply_window → -demodulate → beamform → envelope → normalize → log_compress`). Scanline imaging -uses the same `beamform` op with a scanline grid plus receive apodization rather than a dedicated -scanline-specific operation. Two zea features used in this submission are scanline receive -apodization (`enable_receive_apodization`) and `focal_region_length` (focal-region delay blending, -Rindal et al., IUS 2018). - -An acquisition may also set `refocus: true` in `parameters.yaml`. When `reconstruct.py` is run -with the `--refocus` flag (off by default), it additionally runs a REFoCUS reconstruction for -those acquisitions — transmit-encoding recovery (Bottenus, 2018) that -inverts the transmit-encoding matrix to recover the multistatic (full-matrix-capture) dataset -before pressure-field-weighted DAS — and writes a `_zea_refocus_bmode.png` alongside the -standard reconstruction. REFoCUS is enabled for **every acquisition with enough encoded transmits -for the inversion to be well posed (≥ 8 transmit events)** — coherent plane-wave compounding -(CPWC), diverging waves (DW), and focused linear (FI) and phased-array sector scans. Phased-array -`sector` acquisitions use `pipeline_refocus_sector.yaml` (polar grid + scan conversion, preserving -the sector geometry); every other geometry uses the linear/cartesian `pipeline_refocus.yaml`. -REFoCUS is **not** enabled where the inversion is ill-posed or undefined: single/few-transmit -acquisitions (the `n_tx = 1` plane-wave tracking sets and single-angle simulations, PICMUS's -5-angle IQ) and synthetic transmit aperture (STA) acquisitions, which are already multistatic. +The pipelines share the same shape as zea's regular B-mode pipeline (`cast → apply_window → demodulate → beamform → envelope → normalize → log_compress`). Scanline imaging uses the same `beamform` op with a scanline grid plus receive apodization rather than a dedicated scanline-specific operation. Two zea features used in this submission are scanline receive apodization (`enable_receive_apodization`) and `focal_region_length` (focal-region delay blending, Rindal et al., IUS 2018). + +An acquisition may also set `refocus: true` in `parameters.yaml`. With `REFOCUS = True` (off by default), `reconstruct.py` additionally runs a REFoCUS reconstruction for those acquisitions — transmit-encoding recovery (Bottenus, 2018) that inverts the transmit-encoding matrix to recover the multistatic (full-matrix-capture) dataset before pressure-field-weighted DAS — and writes a `_zea_refocus_bmode.png` alongside the standard reconstruction. REFoCUS is enabled for **every acquisition with enough encoded transmits for the inversion to be well posed (≥ 8 transmit events)** — coherent plane-wave compounding (CPWC), diverging waves (DW), and focused linear (FI) and phased-array sector scans. Phased-array `sector` acquisitions use `pipeline_refocus_sector.yaml` (polar grid + scan conversion, preserving the sector geometry); every other geometry uses the linear/cartesian `pipeline_refocus.yaml`. REFoCUS is **not** enabled where the inversion is ill-posed or undefined: single/few-transmit acquisitions (the `n_tx = 1` plane-wave tracking sets and single-angle simulations, PICMUS's 5-angle IQ) and synthetic transmit aperture (STA) acquisitions, which are already multistatic. ## Reference images Each acquisition ships with: - `_zea_bmode.png` — produced by the root `reconstruct.py` (zea); the reproducible reference. - `_zea_bmode_old.png` — the initial v2 zea reconstruction (kept for comparison). -- `_bmode.png` — the canonical USTB MATLAB Delay-And-Sum reconstruction from the public - [USTB dataset catalog](https://unioslo.github.io/USTB/datasets.html), for cross-validation. -- `_zea_refocus_bmode.png` — REFoCUS variant, for acquisitions with `refocus: true` - (CPWC / DW / focused FI / sector with ≥ 8 transmits; not STA or single/few-transmit sets). - -## Licensing & attribution - -Released under **CC BY 4.0**. Please cite the UltraSound ToolBox (USTB), University of Oslo -(Zenodo record 20261898). `PICMUS_numerical_calib_v2` (group E) was created **in collaboration -with our group** as part of the PICMUS effort and is included on that basis; the other PICMUS -datasets are excluded. +- `_bmode.png` — the canonical USTB MATLAB Delay-And-Sum reconstruction from the public [USTB dataset catalog](https://unioslo.github.io/USTB/datasets.html), for cross-validation. +- `_zea_refocus_bmode.png` — REFoCUS variant, for acquisitions with `refocus: true` (CPWC / DW / focused FI / sector with ≥ 8 transmits; not STA or single/few-transmit sets). -## Contact +## Citation -Ole Marius Hoel Rindal — omrindal@ifi.uio.no (UiO, USTB). +Please cite the UltraSound ToolBox (USTB) Channel Capture Collection, University of Oslo (Zenodo record 20261898). `PICMUS_numerical_calib_v2` (group E) was created **in collaboration with our group** as part of the PICMUS effort and is included on that basis; the other PICMUS datasets are excluded. diff --git a/datasets/oslo/assets/Verasonics_P2-4_apical_four_chamber_subject_1_zea_bmode.png b/datasets/oslo/assets/Verasonics_P2-4_apical_four_chamber_subject_1_zea_bmode.png new file mode 100644 index 0000000..649ec48 Binary files /dev/null and b/datasets/oslo/assets/Verasonics_P2-4_apical_four_chamber_subject_1_zea_bmode.png differ diff --git a/datasets/oslo/assets/hero.png b/datasets/oslo/assets/hero.png new file mode 100644 index 0000000..01324e7 Binary files /dev/null and b/datasets/oslo/assets/hero.png differ diff --git a/datasets/oslo/assets/main.png b/datasets/oslo/assets/main.png new file mode 100644 index 0000000..db908bf Binary files /dev/null and b/datasets/oslo/assets/main.png differ diff --git a/datasets/oslo/reconstruct.py b/datasets/oslo/reconstruct.py index d53f262..2fe65fa 100644 --- a/datasets/oslo/reconstruct.py +++ b/datasets/oslo/reconstruct.py @@ -40,6 +40,7 @@ from pathlib import Path +import keras import matplotlib.pyplot as plt import numpy as np import zea @@ -95,7 +96,7 @@ def reconstruct( outputs = pipeline(data=data, **pipeline.prepare_parameters(parameters)) image = np.array( zea.display.to_8bit( - np.squeeze(np.array(outputs["data"])), + np.squeeze(keras.ops.convert_to_numpy(outputs["data"])), dynamic_range=parameters.dynamic_range, ) ) @@ -121,7 +122,8 @@ def reconstruct( ax.set_ylabel("z [mm]") ax.set_title(Path(path).stem, fontsize=8) - out_path = HERE / (Path(path).stem + suffix) + out_path = HERE / "assets" / (Path(path).stem + suffix) + Path(out_path).parent.mkdir(parents=True, exist_ok=True) fig.savefig(str(out_path), bbox_inches="tight", dpi=110) plt.close(fig) print(f"{Path(path).parent.name}/{Path(path).name} -> {out_path.name}") diff --git a/datasets/politorino/README.md b/datasets/politorino/README.md index f3c70bd..5be1344 100644 --- a/datasets/politorino/README.md +++ b/datasets/politorino/README.md @@ -1,4 +1,5 @@ --- +name: politorino pretty_name: High Frame Rate Fascicle Tracking - PoliTO license: cc-by-4.0 task_categories: @@ -16,15 +17,30 @@ size_categories: - n<1K --- -# Data Card — High Frame Rate Fascicle Tracking - PoliTO - Torino, Italy +# PoliTO High Frame Rate Fascicle Tracking + +![Reconstructed cineloop from PAT02_w1.hdf5](assets/PAT02_w1.gif) + +*Cine loop of treadmill walking at 2 km/h, [`data/PAT02/PAT02_w1.hdf5`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/politorino/data/PAT02/PAT02_w1.hdf5), reconstructed from the raw channel data with the `pipeline.yaml` in this folder.* ## Dataset Description The dataset comprises in-vivo human musculoskeletal raw ultrasound data during dynamic tasks. It includes the acquisition of the medial gastrocnemius on 5 healthy volunteers during heel raises and treadmill walking. The data is acquired with a Verasonics Vantage 256 research platform and a 128-element linear array probe (L11-5v) at 500 fps for 9.6 s per acquisition. Each subject was imaged during six task conditions: cyclical heel raises and drops at a fixed frequency provided by a metronome at 60 (hr1), 90 (hr2), and 120 bpm (hr3) and walking at 2 (w1), 4 (w2), and 5 (w3) km/h. We provide the raw data, the beamformed DAS and FDMAS images, and the automated fascicle tracking obtained with UltraTimTrack (https://github.com/timvanderzee/UltraTimTrack). The tracking data was obtained on the first 9s of sub-sampled .mp4 videos at three frame rates: 25 fps, 50 fps and 125 fps. The tracking data is provided for each frame rate and a Python code is provided to correctly visualize the data on the reconstructed images. **Data type: in-vivo** -## Dataset Contributors +## Dataset Contributor(s) + +Personnel involved in raw data acquisition, beamforming, annotation, dataset preparation and analysis: -Contributors include the personnel involved in raw data acquisition, beamforming, annotation, dataset preparation and analysis: E. Cesti, M. Carbonaro, M. Boccardo, F. Truscello, S. Seoni, G.L. Cerone, K.M. Meiburger, G. Bardoscia, B.J. Raiteri, and A. Botter. +- E. Cesti +- M. Carbonaro +- M. Boccardo +- F. Truscello +- S. Seoni +- G.L. Cerone +- K.M. Meiburger +- G. Bardoscia +- B.J. Raiteri +- A. Botter ## Dataset Creation Date @@ -32,8 +48,7 @@ Contributors include the personnel involved in raw data acquisition, beamforming ## License / Terms of Use -[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). -The data is cleared for this license (see ethical considerations below). +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage @@ -43,21 +58,24 @@ This data was employed for an initial study evaluating fascicle tracking algorit - **Data Collection Method:** in-vivo human musculoskeletal - **Labeling Method:** derived fascicle tracking from UltraTimTrack -- **Acquisition system:** 128-element linear array (L11-5v), - sampling 31.25 MHz, center frequency 7.6 MHz +- **Acquisition system:** 128-element linear array (L11-5v), sampling 31.25 MHz, center frequency 7.6 MHz -## Dataset Format +## Processing the Dataset + +The acquisitions can be processed with the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/politorino/reconstruct.py) as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF), together with the `pipeline.yaml` definition in this folder and the [zea library](https://github.com/tue-bmd/zea). The script streams the data from the Hugging Face Hub. -Submitted in the [`zea` file format](https://zea.readthedocs.io/en/latest/) -(one HDF5 file per acquisition). -`reconstruct.py` reconstructs a B-mode from the raw channel data and overlays -the stored fascicle tracking on it, writing a `.png`. An example output is -provided. The constants at the top of the script select what is drawn: +The script reconstructs a B-mode from the raw channel data and overlays the stored fascicle tracking on it, writing a `.png`. The constants at the top of the script select what is drawn: - `FRAME` -- index of the acquisition frame to reconstruct - `FPS` -- which stored tracking rate to overlay (25, 50 or 125 fps) - `TRACK_INDEX` -- index of the tracking sample within that rate +## Dataset Format + +[zea v0.1.6](https://github.com/tue-bmd/zea) + +Submitted in the [`zea` file format](https://zea.readthedocs.io/en/latest/) (one HDF5 file per acquisition). + Per-sample contents of the HDF5: | Group / field | Shape | Dtype | Units | Description | @@ -69,7 +87,6 @@ Per-sample contents of the HDF5: | `probe` | -- | -- | -- | element width, probe_center_frequency, probe_geometry | | `metadata/tracking_` | -- | -- | -- | Tracking data as saved by UltraTimTrack | - ## Dataset Quantification **Current OpenH-RF release:** 30 HDF5 files; 169.95 GB (169,950,314,496 bytes) stored; root `zea_version` **0.1.6**. Sizes include all HDF5 contents and use decimal units (MB = 10^6 bytes, GB = 10^9 bytes, TB = 10^12 bytes), not decoded-array memory or original-source download sizes. @@ -79,25 +96,21 @@ Per-sample contents of the HDF5: ## Subject Metadata -Five healthy subjects (2 men, 3 women; (mean ± SD) age: 25.6 ± 1.3 yr, height: 1.78 ± 0.06 m, weight: 67 ± 11 kg) were recruited. All data were acquired with the same Verasonics system and probe. +Five healthy subjects (2 men, 3 women; (mean ± SD) age: 25.6 ± 1.3 yr, height: 1.78 ± 0.06 m, weight: 67 ± 11 kg) were recruited. All data were acquired with the same Verasonics system and probe. ## Data Validation -[`reconstruct.py`](reconstruct.py) builds a `zea.Pipeline` of DAS beamforming → -envelope detection → normalization → log-compression **in code** and reconstructs -a B-mode directly from `raw_data` — showing the raw-to-image flow without any -config file. It contains also the code employed to view the plotting of the tracking data. It also saves the pipeline to [`pipeline.yaml`](pipeline.yaml) as a -shareable recipe. +`reconstruct.py` builds a `zea.Pipeline` of DAS beamforming → envelope detection → normalization → log-compression **in code** and reconstructs a B-mode directly from `raw_data` — showing the raw-to-image flow without any config file. It contains also the code employed to view the plotting of the tracking data. It also saves the pipeline to `pipeline.yaml` as a shareable recipe. ## Known Issues -- The tracking data for three subjects (PAT01, PAT04, PAT05) were obtained on the reconstructed images after a horizontal flip. This is noted in the file and the provided code to view the tracking data automatically checks for this and flips the data, if needed. +- The tracking data for three subjects (PAT01, PAT04, PAT05) were obtained on the reconstructed images after a horizontal flip. This is noted in the file and the provided code to view the tracking data automatically checks for this and flips the data, if needed. - The tracking data covers the first 9s out of the provided 9.6s of the raw data. - There is a small offset in depth between the reconstructed data used for the tracking and the raw data. The `reconstruct.py` code provides the correction at lines 233-240. ## Ethical Considerations -The study was conducted in accordance with the Declaration of Helsinki and the procedure approved by the Institutional Ethics Committee of Politecnico di Torino (reference number: 2772/2025). Informed consent was obtained from all participants after receiving detailed explanation of the study procedures and before participating in the study. +The study was conducted in accordance with the Declaration of Helsinki and the procedure approved by the Institutional Ethics Committee of Politecnico di Torino (reference number: 2772/2025). Informed consent was obtained from all participants after receiving detailed explanation of the study procedures and before participating in the study. ## Citation diff --git a/datasets/politorino/assets/PAT02_w1.gif b/datasets/politorino/assets/PAT02_w1.gif new file mode 100644 index 0000000..4ac2f57 Binary files /dev/null and b/datasets/politorino/assets/PAT02_w1.gif differ diff --git a/datasets/politorino/assets/PAT02_w1_reconstructed.png b/datasets/politorino/assets/PAT02_w1_reconstructed.png new file mode 100644 index 0000000..47b0655 Binary files /dev/null and b/datasets/politorino/assets/PAT02_w1_reconstructed.png differ diff --git a/datasets/politorino/assets/main.png b/datasets/politorino/assets/main.png new file mode 100644 index 0000000..15f388f Binary files /dev/null and b/datasets/politorino/assets/main.png differ diff --git a/datasets/politorino/reconstruct.py b/datasets/politorino/reconstruct.py index 1fd9681..06dd0a3 100644 --- a/datasets/politorino/reconstruct.py +++ b/datasets/politorino/reconstruct.py @@ -44,7 +44,7 @@ # Defaults stream straight from the published corpus. Swap any of these for a # local path to run against your own copy. ZEA_FILE = "hf://nvidia/OpenH-RF/politorino/data/PAT02/PAT02_w1.hdf5" -OUT = HERE / "PAT02_w1_reconstructed.png" +OUT = HERE / "assets" / "PAT02_w1_reconstructed.png" SAVE_PIPELINE = None # Optionally write the pipeline to a reusable pipeline.yaml FRAME = 0 FPS = 50 # tracking rate to overlay: 25, 50 or 125 @@ -191,6 +191,7 @@ def row_to_mm(row): ax.set_ylabel("Axial depth [mm]") ax.legend(loc="upper right", fontsize=8) fig.tight_layout() + Path(OUT).parent.mkdir(parents=True, exist_ok=True) fig.savefig(str(OUT), dpi=150, bbox_inches="tight") print(f"Saved {OUT}") diff --git a/datasets/resolvestroke/README.md b/datasets/resolvestroke/README.md index 2aea793..3aa5ca8 100644 --- a/datasets/resolvestroke/README.md +++ b/datasets/resolvestroke/README.md @@ -1,24 +1,67 @@ -# OpenH-RF - Resolve Stroke datasets - -[Resolve Stroke](https://www.resolvestroke.com/) develops SYLVER, a -software-driven ultrasound platform, that supports clinical assessment in -patients suspected of, or at risk for, cerebral perfusion abnormalities by -improving visualization of cerebral vasculature and providing complementary -information on cerebral perfusion. -SYLVER images with a 32×32 matrix array probe at 2 MHz -using diverging-wave transmits. CE and FDA clearances are expected in 2026. -This directory holds the data Resolve Stroke contributes to OpenH-RF: pre-beamformed -RF/IQ channel data from an imaging phantom, a flow phantom, and in-vivo transcranial -acquisitions, all de-identified and released under CC BY 4.0. The in-vivo data comes -from the CPP-approved SCULPT clinical study. Two transmit sequences are included: a -saddle sequence that images a 2D plane for real-time B-mode, and a 4 kHz -single-aperture volume sequence for CEUS and blood-flow measurement. - -Each dataset directory is a self-contained data card: an HDF5 file, a -B-mode preview PNG, a `reconstruct.py` + `pipeline.yaml` beamforming recipe, a -`README.md`, and a `LICENCE` (CC BY 4.0). - -## OpenH-RF Release Inventory +--- +name: resolvestroke +pretty_name: "Resolve Stroke Transcranial CEUS, Flow and Imaging Phantom Channel Data" +license: cc-by-4.0 +task_categories: + - other +tags: + - ultrasound + - iq + - openh-rf + - 3d + - matrix-probe + - diverging-wave + - contrast-enhanced + - power-doppler + - transcranial + - clinical + - phantom +language: + - en +size_categories: + - 100KTranscranial contrast-enhanced power Doppler of a human subject (SP03-Left, bolus +10 s), x-z maximum-intensity projection + +*3D power Doppler of the transcranial CEUS acquisition [`clinical/SP03-Left/SP03-Left.hdf5`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/resolvestroke/clinical/SP03-Left/SP03-Left.hdf5) at bolus +10 s, x-z maximum-intensity projection, reconstructed from the released channel data.* + +## Dataset Description + +[Resolve Stroke](https://www.resolvestroke.com/) develops SYLVER, a software-driven ultrasound platform that supports clinical assessment in patients suspected of, or at risk for, cerebral perfusion abnormalities by improving visualization of cerebral vasculature and providing complementary information on cerebral perfusion. SYLVER images with a 32×32 matrix array probe at 2 MHz using diverging-wave transmits. CE and FDA clearances are expected in 2026. This directory holds the data Resolve Stroke contributes to OpenH-RF: pre-beamformed RF/IQ channel data from an imaging phantom, a flow phantom, and in-vivo transcranial acquisitions, all de-identified and released under CC BY 4.0. The in-vivo data comes from the CPP-approved SCULPT clinical study. Two transmit sequences are included: a saddle sequence that images a 2D plane for real-time B-mode, and a 4 kHz single-aperture volume sequence for CEUS and blood-flow measurement. + +Each dataset directory holds its data card (`README.md`), a `reconstruct.py` + `pipeline.yaml` beamforming recipe and, for the CEUS datasets, a power-Doppler reconstruction; the HDF5 files are on the Hub and the preview images in [`assets/`](assets/). + +## Dataset Contributor(s) + +- Aitana Waelbroeck +- Carl Ferlay +- Arthur Chavignon +- Maxence Reberol (contact) +- Vincent Hingot +- Resolve Stroke, 29 Rue du Faubourg Saint-Jacques, 75014 Paris + +## Dataset Creation Date + +01/23/2026 – 07/09/2026 (per sub-dataset; see each data card). + +## License / Terms of Use + +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. + +## Processing the Dataset + +Each sub-dataset has its own `reconstruct.py` and `pipeline.yaml`, as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF/tree/main/datasets/resolvestroke), built on the [zea library](https://github.com/tue-bmd/zea); the CEUS datasets add a power-Doppler reconstruction. See the data card of each sub-dataset for how to run them. + +The Python scripts carry their own `SPDX-License-Identifier: Apache-2.0` header; the dataset itself is CC BY 4.0. + +## Dataset Format + +[zea v0.1.6](https://github.com/tue-bmd/zea) + +## Dataset Quantification **Current OpenH-RF release:** 43 HDF5 files; 110.62 GB (110,619,394,048 bytes) stored; root `zea_version` **0.1.6**. Sizes include all HDF5 contents and use decimal units (MB = 10^6 bytes, GB = 10^9 bytes, TB = 10^12 bytes), not decoded-array memory or original-source download sizes. @@ -26,15 +69,59 @@ B-mode preview PNG, a `reconstruct.py` + `pipeline.yaml` beamforming recipe, a | Path | What's inside | |------|---------------| -| `clinical/` | 20 clinical transcranial CEUS acquisitions (SCULPT study, subjects `SP01`–`SP10`, Left/Right). Each subdirectory holds one acquisition's HDF5 (five 1 s bolus wash-in clips), reconstruction scripts, and B-mode / power-Doppler / reference montages. | -| `phantom_flow/` | Flow-phantom CEUS (CIRS 769 + ATS523A) with microbubble contrast: five 1 s clips capturing a flow-on/flow-off bolus wash-in. Single combined HDF5 + 3D power-Doppler reconstruction. | -| `phantom_mp/` | Multi-tissue imaging phantom (CIRS 040GSE), pre-beamformed channel data, 1000 volumetric frames of the same static scene (wire targets, cysts, tissue-mimicking background). | -| `saddle/` | Single-frame anatomical reference B-modes (21 files: the 20 clinical acquisitions + 1 imaging phantom) from the wide-angle "saddle" sequence. These are the structural companion to the clinical CEUS clips. | +| [`clinical/`](clinical/) | 20 clinical transcranial CEUS acquisitions (SCULPT study, subjects `SP01`–`SP10`, Left/Right), one HDF5 per acquisition (five 1 s bolus wash-in clips) under `clinical//`. All 20 share the same probe, sequence and layout, so one data card, the reconstruction scripts and the pipeline YAMLs live at the `clinical/` root (pick the acquisition with `SUBJECT` at the top of each script). | +| [`phantom_flow/`](phantom_flow/) | Flow-phantom CEUS (CIRS 769 + ATS523A) with microbubble contrast: five 1 s clips capturing a flow-on/flow-off bolus wash-in. Single combined HDF5 + 3D power-Doppler reconstruction. | +| [`phantom_mp/`](phantom_mp/) | Multi-tissue imaging phantom (CIRS 040GSE), pre-beamformed channel data, 1000 volumetric frames of the same static scene (wire targets, cysts, tissue-mimicking background). | +| [`saddle/`](saddle/) | Single-frame anatomical reference B-modes (21 files: the 20 clinical acquisitions + 1 imaging phantom) from the wide-angle "saddle" sequence. These are the structural companion to the clinical CEUS clips. | + +## `clinical/` — transcranial CEUS, 20 acquisitions + +Ten human subjects of the SCULPT study, two acquisitions each (other side and/or second session), imaged through the temporal acoustic window during a microbubble bolus. Each file holds 20 000 frames of diverging-wave IQ channel data at 4 kHz: five 1 s clips taken before the bolus and at +5, +10, +15 and +20 s. Every file also carries reference maps computed by SYLVER from the complete bolus passage (`custom/computed_references/`: microvascular image and radial velocities on a 0.6 mm Cartesian grid), provided as targets, not as a clinical ground truth. + +Power Doppler reconstructed from the released channel data, one x-z projection per acquisition at bolus +10 s: + +Power Doppler x-z MIP at bolus +10 s for the 20 clinical acquisitions + +The reference microvascular image of the same 20 acquisitions, x-z projection: + +Reference microvascular image (mvi), x-z MIP, for the 20 clinical acquisitions + +The [data card](clinical/README.md) has the inventory of the 20 acquisitions with the five-clip power Doppler and the reference maps of each, the field table, and the reconstruction scripts. + +## `phantom_flow/` — flow phantom CEUS + +A CIRS 769 flow phantom with an ATS523A pump, microbubbles flowing through two tubes (4 mm and 2 mm), same sequence and clip structure as the clinical files. The file also carries the tube mask (known geometry, an actual ground truth) and the same computed reference maps as the clinical data. + +phantom_flow B-mode, two perpendicular sectors + +*Figure: B-mode of one frame, x-z and y-z sectors.* + +phantom_flow power Doppler, five clips, next to the reference mvi + +*Figure: power Doppler of the five clips, with the reference `mvi` in the last column.* + +phantom_flow computed reference maps + +*Figure: computed reference maps (tube mask, `mvi`, radial velocities).* + +See the [data card](phantom_flow/README.md). + +## `phantom_mp/` — multi-tissue imaging phantom + +A CIRS 040GSE phantom, 1000 volumetric frames of the same static scene with the 4 kHz diverging-wave sequence: wire targets, cysts and tissue-mimicking background, for beamforming and resolution studies. + +phantom_mp B-mode, two perpendicular sectors + +*Figure: B-mode of one frame, x-z and y-z sectors.* + +See the [data card](phantom_mp/README.md). -## Contributors +## `saddle/` — anatomical reference B-modes -Aitana Waelbroeck\*, Carl Ferlay\*, Arthur Chavignon\*, Maxence Reberol\*, Vincent Hingot\* +One wide-angle diverging-wave frame per acquisition from the "saddle" sequence (9 transmits steered −24° to +24°; each transmit is repeated for four consecutive 256-element receive sub-apertures, so the frame carries the full 1024-element aperture), for the 20 clinical acquisitions and the imaging phantom: the anatomical view at the same probe placement as the CEUS clips. -_\*Resolve Stroke, 29 Rue du Faubourg Saint-Jacques, 75014 Paris_ +Saddle-array B-modes for the 21 files +*Figure: the 21 saddle B-modes.* +See the [data card](saddle/README.md). diff --git a/datasets/resolvestroke/assets/clinical/SP01-Left-1_pd_clips.png b/datasets/resolvestroke/assets/clinical/SP01-Left-1_pd_clips.png new file mode 100644 index 0000000..3b3fd4b Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP01-Left-1_pd_clips.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP01-Left-1_references.png b/datasets/resolvestroke/assets/clinical/SP01-Left-1_references.png new file mode 100644 index 0000000..8ea41df Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP01-Left-1_references.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP01-Left-2_pd_clips.png b/datasets/resolvestroke/assets/clinical/SP01-Left-2_pd_clips.png new file mode 100644 index 0000000..fd5ebab Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP01-Left-2_pd_clips.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP01-Left-2_references.png b/datasets/resolvestroke/assets/clinical/SP01-Left-2_references.png new file mode 100644 index 0000000..7198003 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP01-Left-2_references.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP02-Left-1_pd_clips.png b/datasets/resolvestroke/assets/clinical/SP02-Left-1_pd_clips.png new file mode 100644 index 0000000..076b199 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP02-Left-1_pd_clips.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP02-Left-1_references.png b/datasets/resolvestroke/assets/clinical/SP02-Left-1_references.png new file mode 100644 index 0000000..219c0f6 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP02-Left-1_references.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP02-Left-2_pd_clips.png b/datasets/resolvestroke/assets/clinical/SP02-Left-2_pd_clips.png new file mode 100644 index 0000000..33e6b97 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP02-Left-2_pd_clips.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP02-Left-2_references.png b/datasets/resolvestroke/assets/clinical/SP02-Left-2_references.png new file mode 100644 index 0000000..6cbd2de Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP02-Left-2_references.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP03-Left_pd_clips.png b/datasets/resolvestroke/assets/clinical/SP03-Left_pd_clips.png new file mode 100644 index 0000000..87a6164 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP03-Left_pd_clips.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP03-Left_references.png b/datasets/resolvestroke/assets/clinical/SP03-Left_references.png new file mode 100644 index 0000000..fdd3301 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP03-Left_references.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP03-Right_pd_clips.png b/datasets/resolvestroke/assets/clinical/SP03-Right_pd_clips.png new file mode 100644 index 0000000..6d4c355 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP03-Right_pd_clips.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP03-Right_references.png b/datasets/resolvestroke/assets/clinical/SP03-Right_references.png new file mode 100644 index 0000000..7b2ed84 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP03-Right_references.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP04-Left-1_pd_clips.png b/datasets/resolvestroke/assets/clinical/SP04-Left-1_pd_clips.png new file mode 100644 index 0000000..7a0b645 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP04-Left-1_pd_clips.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP04-Left-1_references.png b/datasets/resolvestroke/assets/clinical/SP04-Left-1_references.png new file mode 100644 index 0000000..ccc6347 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP04-Left-1_references.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP04-Left-2_pd_clips.png b/datasets/resolvestroke/assets/clinical/SP04-Left-2_pd_clips.png new file mode 100644 index 0000000..f53652b Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP04-Left-2_pd_clips.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP04-Left-2_references.png b/datasets/resolvestroke/assets/clinical/SP04-Left-2_references.png new file mode 100644 index 0000000..6eef596 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP04-Left-2_references.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP05-Left_pd_clips.png b/datasets/resolvestroke/assets/clinical/SP05-Left_pd_clips.png new file mode 100644 index 0000000..ff1dd86 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP05-Left_pd_clips.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP05-Left_references.png b/datasets/resolvestroke/assets/clinical/SP05-Left_references.png new file mode 100644 index 0000000..3153a7b Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP05-Left_references.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP05-Right_pd_clips.png b/datasets/resolvestroke/assets/clinical/SP05-Right_pd_clips.png new file mode 100644 index 0000000..60f2ce5 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP05-Right_pd_clips.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP05-Right_references.png b/datasets/resolvestroke/assets/clinical/SP05-Right_references.png new file mode 100644 index 0000000..8d13d07 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP05-Right_references.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP06-Left_pd_clips.png b/datasets/resolvestroke/assets/clinical/SP06-Left_pd_clips.png new file mode 100644 index 0000000..807e2cb Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP06-Left_pd_clips.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP06-Left_references.png b/datasets/resolvestroke/assets/clinical/SP06-Left_references.png new file mode 100644 index 0000000..4e58759 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP06-Left_references.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP06-Right_pd_clips.png b/datasets/resolvestroke/assets/clinical/SP06-Right_pd_clips.png new file mode 100644 index 0000000..4d4ecfd Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP06-Right_pd_clips.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP06-Right_references.png b/datasets/resolvestroke/assets/clinical/SP06-Right_references.png new file mode 100644 index 0000000..e27b763 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP06-Right_references.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP07-Left_pd_clips.png b/datasets/resolvestroke/assets/clinical/SP07-Left_pd_clips.png new file mode 100644 index 0000000..e9bec7c Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP07-Left_pd_clips.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP07-Left_references.png b/datasets/resolvestroke/assets/clinical/SP07-Left_references.png new file mode 100644 index 0000000..1bdc362 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP07-Left_references.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP07-Right_pd_clips.png b/datasets/resolvestroke/assets/clinical/SP07-Right_pd_clips.png new file mode 100644 index 0000000..180c7b0 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP07-Right_pd_clips.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP07-Right_references.png b/datasets/resolvestroke/assets/clinical/SP07-Right_references.png new file mode 100644 index 0000000..1592319 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP07-Right_references.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP08-Left_pd_clips.png b/datasets/resolvestroke/assets/clinical/SP08-Left_pd_clips.png new file mode 100644 index 0000000..1194423 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP08-Left_pd_clips.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP08-Left_references.png b/datasets/resolvestroke/assets/clinical/SP08-Left_references.png new file mode 100644 index 0000000..e857156 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP08-Left_references.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP08-Right_pd_clips.png b/datasets/resolvestroke/assets/clinical/SP08-Right_pd_clips.png new file mode 100644 index 0000000..aeab634 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP08-Right_pd_clips.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP08-Right_references.png b/datasets/resolvestroke/assets/clinical/SP08-Right_references.png new file mode 100644 index 0000000..fbb258e Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP08-Right_references.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP09-Left-1_pd_clips.png b/datasets/resolvestroke/assets/clinical/SP09-Left-1_pd_clips.png new file mode 100644 index 0000000..dec7d32 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP09-Left-1_pd_clips.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP09-Left-1_references.png b/datasets/resolvestroke/assets/clinical/SP09-Left-1_references.png new file mode 100644 index 0000000..5dafa9c Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP09-Left-1_references.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP09-Left-2_pd_clips.png b/datasets/resolvestroke/assets/clinical/SP09-Left-2_pd_clips.png new file mode 100644 index 0000000..cad62db Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP09-Left-2_pd_clips.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP09-Left-2_references.png b/datasets/resolvestroke/assets/clinical/SP09-Left-2_references.png new file mode 100644 index 0000000..c1ea0b9 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP09-Left-2_references.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP10-Left_pd_clips.png b/datasets/resolvestroke/assets/clinical/SP10-Left_pd_clips.png new file mode 100644 index 0000000..b9b298c Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP10-Left_pd_clips.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP10-Left_references.png b/datasets/resolvestroke/assets/clinical/SP10-Left_references.png new file mode 100644 index 0000000..15dcf3a Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP10-Left_references.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP10-Right_pd_clips.png b/datasets/resolvestroke/assets/clinical/SP10-Right_pd_clips.png new file mode 100644 index 0000000..0c99c73 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP10-Right_pd_clips.png differ diff --git a/datasets/resolvestroke/assets/clinical/SP10-Right_references.png b/datasets/resolvestroke/assets/clinical/SP10-Right_references.png new file mode 100644 index 0000000..ef7023b Binary files /dev/null and b/datasets/resolvestroke/assets/clinical/SP10-Right_references.png differ diff --git a/datasets/resolvestroke/assets/clinical_SP02-Left-2_bmode.png b/datasets/resolvestroke/assets/clinical_SP02-Left-2_bmode.png new file mode 100644 index 0000000..513d761 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical_SP02-Left-2_bmode.png differ diff --git a/datasets/resolvestroke/assets/clinical_SP02-Left-2_references_montage.png b/datasets/resolvestroke/assets/clinical_SP02-Left-2_references_montage.png new file mode 100644 index 0000000..0e9ecb0 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical_SP02-Left-2_references_montage.png differ diff --git a/datasets/resolvestroke/assets/clinical_SP03-Left_PD_montage.png b/datasets/resolvestroke/assets/clinical_SP03-Left_PD_montage.png new file mode 100644 index 0000000..671bae3 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical_SP03-Left_PD_montage.png differ diff --git a/datasets/resolvestroke/assets/clinical_mvi_montage.png b/datasets/resolvestroke/assets/clinical_mvi_montage.png new file mode 100644 index 0000000..a4d90d1 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical_mvi_montage.png differ diff --git a/datasets/resolvestroke/assets/clinical_pd_montage.png b/datasets/resolvestroke/assets/clinical_pd_montage.png new file mode 100644 index 0000000..6b87262 Binary files /dev/null and b/datasets/resolvestroke/assets/clinical_pd_montage.png differ diff --git a/datasets/resolvestroke/assets/main_image.png b/datasets/resolvestroke/assets/main_image.png new file mode 100644 index 0000000..7ac5715 Binary files /dev/null and b/datasets/resolvestroke/assets/main_image.png differ diff --git a/datasets/resolvestroke/assets/main_image_small.png b/datasets/resolvestroke/assets/main_image_small.png new file mode 100644 index 0000000..1db20ee Binary files /dev/null and b/datasets/resolvestroke/assets/main_image_small.png differ diff --git a/datasets/resolvestroke/assets/phantom_flow_PD_montage.png b/datasets/resolvestroke/assets/phantom_flow_PD_montage.png new file mode 100644 index 0000000..623f646 Binary files /dev/null and b/datasets/resolvestroke/assets/phantom_flow_PD_montage.png differ diff --git a/datasets/resolvestroke/assets/phantom_flow_bmode.png b/datasets/resolvestroke/assets/phantom_flow_bmode.png new file mode 100644 index 0000000..736d6af Binary files /dev/null and b/datasets/resolvestroke/assets/phantom_flow_bmode.png differ diff --git a/datasets/resolvestroke/assets/phantom_flow_references_montage.png b/datasets/resolvestroke/assets/phantom_flow_references_montage.png new file mode 100644 index 0000000..7da9a0d Binary files /dev/null and b/datasets/resolvestroke/assets/phantom_flow_references_montage.png differ diff --git a/datasets/resolvestroke/assets/phantom_mp_bmode.png b/datasets/resolvestroke/assets/phantom_mp_bmode.png new file mode 100644 index 0000000..ce35a56 Binary files /dev/null and b/datasets/resolvestroke/assets/phantom_mp_bmode.png differ diff --git a/datasets/resolvestroke/assets/saddle_bmode_montage.png b/datasets/resolvestroke/assets/saddle_bmode_montage.png new file mode 100644 index 0000000..2394d93 Binary files /dev/null and b/datasets/resolvestroke/assets/saddle_bmode_montage.png differ diff --git a/datasets/resolvestroke/clinical/NOTE.md b/datasets/resolvestroke/clinical/NOTE.md deleted file mode 100644 index d84c334..0000000 --- a/datasets/resolvestroke/clinical/NOTE.md +++ /dev/null @@ -1,10 +0,0 @@ -# Note on `clinical/` - -The full submission holds **20 clinical acquisitions** (`SP01`–`SP10`, Left/Right), as described -in the parent `README.md`. This repository tracks only the **non-data files** of a submission -(see `datasets/README.md`), and the reconstruction scripts are identical across all -20 subject directories. - -Only one representative subject — **`SP02-Left-2`** — is therefore included here, rather than 19 -further copies of the same scripts. The remaining acquisitions, along with all `.hdf5` data and -`.png` previews, are distributed with the dataset itself. diff --git a/datasets/resolvestroke/clinical/README.md b/datasets/resolvestroke/clinical/README.md new file mode 100644 index 0000000..0b24417 --- /dev/null +++ b/datasets/resolvestroke/clinical/README.md @@ -0,0 +1,220 @@ +--- +name: resolvestroke-clinical +pretty_name: "Resolve Stroke Clinical Transcranial CEUS (SCULPT, 20 acquisitions)" +license: cc-by-4.0 +task_categories: + - other +tags: + - ultrasound + - iq + - openh-rf + - 3d + - matrix-probe + - contrast-enhanced + - ceus + - transcranial + - clinical +language: + - en +size_categories: + - 100K + +*Reference microvascular image (`mvi`, x-z maximum-intensity projection of the computed reference) for the 20 acquisitions in [`clinical/`](https://huggingface.co/datasets/nvidia/OpenH-RF/tree/main/resolvestroke/clinical).* + +## Dataset Description + +Contrast-enhanced ultrasound (CEUS) transcranial brain acquisitions of 10 human subjects (20 acquisitions), acquired with SYLVER, Resolve Stroke's ultrasound device, using a 32×32 matrix probe with diverging-wave transmits at 4 kHz frame rate. Each acquisition captures a microbubble contrast-agent bolus wash-in: microbubbles are injected, perfuse the brain vasculature through the temporal acoustic window, and gradually wash out. + +For each acquisition, five 1-second clips (4000 frames each, 20000 total) are extracted at intervals relative to the end-of-baseline marker: + +| Clip | Time offset | Description | +|------|-------------|-------------| +| `baseline_minus1s` | baseline − 1 s | Before microbubble arrival | +| `baseline_plus5s` | baseline + 5 s | Early wash-in | +| `baseline_plus10s` | baseline + 10 s | Mid wash-in | +| `baseline_plus15s` | baseline + 15 s | Late wash-in | +| `baseline_plus20s` | baseline + 20 s | Plateau / early wash-out | + +The anatomical companion of every acquisition, a single wide-angle B-mode frame taken at the same probe placement, is in the sibling [`saddle/`](../saddle/) dataset under the same name. + +## Dataset Contributor(s) + +- Aitana Waelbroeck +- Carl Ferlay +- Arthur Chavignon +- Maxence Reberol (contact) +- Vincent Hingot +- Resolve Stroke, 29 Rue du Faubourg Saint-Jacques, 75014 Paris + +## Dataset Creation Date + +07/09/2026 + +## License / Terms of Use + +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. + +## Intended Usage + +Contrast-enhanced ultrasound flow/perfusion quantification, microbubble detection and tracking, power-Doppler and CEUS transcranial imaging, and matrix-probe diverging-wave beamforming research (RFP task group 6.2, Blood Flow). + +## Dataset Characterization + +- Data collection method: 10 human subjects, 20 acquisitions (2 per subject: the other side and/or a second session), transcranial CEUS with microbubble contrast agent (bolus injection), acquired through the temporal acoustic window. +- Labeling method: Automatic, no manual annotation. Per-frame clip labels (`metadata/annotations/label`, one of the five clip names above) are assigned from the acquisition time relative to the end-of-baseline marker. Per-voxel reference maps (`mvi`, radial velocities) are generated by SYLVER's processing pipeline from the complete bolus passage (see [Computed References](#computed-references)). +- Acquisition system: SYLVER (Resolve Stroke's ultrasound device). 32×32 matrix probe, 0.50 mm pitch, 0.30 mm kerf; transmit center frequency ≈ 2.031 MHz, sound speed 1540 m/s. Diverging-wave transmits; receive sub-apertures flattened into 256 virtual elements. Channel data is DDC (baseband) IQ, so `sampling_frequency` (≈ 2.031 MHz) is the post-decimation IQ rate and equals `demodulation_frequency`. + +## Processing the Dataset + +The acquisitions can be processed with the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/resolvestroke/clinical/reconstruct.py) as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF), together with the `pipeline.yaml` definition in this folder and the [zea library](https://github.com/tue-bmd/zea). The script streams the data from the Hugging Face Hub. + +The reconstruction recipe is identical for all 20 acquisitions, so the scripts and pipeline YAMLs live once in this directory. Each script has a `SUBJECT` constant at the top (default `"SP02-Left-2"`); set it to any acquisition name from the inventory, e.g. `SUBJECT = "SP07-Right"`, and the `hf://` input path `hf://nvidia/OpenH-RF/resolvestroke/clinical//.hdf5` and the output filenames follow from it. Swap `ZEA_FILE` (`INPUT` in `reconstruct_PD_3d.py`) for a local path to run against your own copy. + +```bash +uv run --project /path/to/OpenH-RF python reconstruct.py +``` + +`reconstruct_PD_3d.py` (with `pipeline_PD_3d.yaml`) renders the power-Doppler reconstruction and `display_references.py` the computed reference maps; both are described below. + +The Python scripts carry their own `SPDX-License-Identifier: Apache-2.0` header; the dataset itself is CC BY 4.0. + +## Dataset Format + +[zea v0.1.6](https://github.com/tue-bmd/zea) + +One zea HDF5 file per acquisition, `/.hdf5`, containing all 5 clips concatenated. Files are named `-[-]` (anonymized subject code `SP01`–`SP10`, imaging side, and a sequential index when a subject has two acquisitions on the same side). Gaps between clips are encoded in `scan/time_to_next_transmit`. Per-frame clip labels are stored in `metadata/annotations/label`. + +Computed reference maps (derived from the SYLVER processed exam) are stored in the zea `custom` group under `custom/computed_references/`: `mvi`, `velocity_radial_avg` / `_min` / `_max`, and a shared `coordinates` array (per-voxel `[x, y, z]` in metres), all on one 0.6 mm Cartesian grid `(192, 171, 171)`. Read them via `zea.File(...).dataset("custom/computed_references/")`. + +The files are in the zea HDF5 format, root `zea_version` 0.1.6. `data/raw_data` is DDC IQ (last axis [I, Q]). The hardware time-gain compensation is baked into `raw_data`; `scan/tgc_gain_curve` is the applied (non-linear) gain per axial sample; divide by it to recover true channel amplitudes. The reconstruction scripts divide `raw_data` by this curve before beamforming (the `zea.Pipeline` itself stays standard; the reversal is a plain array step). + +## Dataset Quantification + +**Current OpenH-RF release:** 20 HDF5 files; 105.16 GB (105,159,196,672 bytes) stored; root `zea_version` **0.1.6**. Sizes include all HDF5 contents and use decimal units (MB = 10^6 bytes, GB = 10^9 bytes, TB = 10^12 bytes), not decoded-array memory or original-source download sizes. + +- Acquisitions: 20 (10 subjects × 2) +- Frames per acquisition: 20000 (5 clips × 4000 frames) +- Frame rate: 4000 Hz (within each clip) +- Clip duration: 1 s each +- **Stored HDF5 size:** ≈ 5.26 GB per acquisition, 105.16 GB in total. + +| Field | Shape | dtype | Units | Description | +|---|---|---|---|---| +| `data/raw_data` | `(20000, 1, 320, 256, 2)` | int16 | n/a | IQ channel data: frames × tx × axial × elements × {I, Q} | +| `probe/probe_geometry` | `(256, 3)` | float32 | m | Virtual element positions | +| `scan/t0_delays` | `(1, 256)` | float32 | s | Per-element transmit delays | +| `scan/tx_apodizations` | `(1, 256)` | float32 | n/a | Per-element transmit weights | +| `scan/focus_distances` | `(1,)` | float32 | m | Virtual-source distance (diverging wave, negative) | +| `scan/polar_angles` | `(1,)` | float32 | rad | Transmit steering (0) | +| `scan/azimuth_angles` | `(1,)` | float32 | rad | Transmit steering (0) | +| `scan/transmit_origins` | `(1, 3)` | float32 | m | Transmit origin | +| `scan/initial_times` | `(1,)` | float32 | s | ADC start time | +| `scan/time_to_next_transmit` | `(20000, 1)` | float32 | s | Inter-frame timing (encodes clip gaps: 5, 4, 4, 4 s) | +| `scan/tgc_gain_curve` | `(320,)` | float32 | n/a | Hardware TGC applied to `raw_data`; divide by it to undo (`reconstruct*.py` does this before beamforming) | +| `scan/sampling_frequency` | scalar | float32 | Hz | 2031250 (post-DDC IQ rate) | +| `scan/center_frequency` | scalar | float32 | Hz | 2031250 | +| `scan/demodulation_frequency` | scalar | float32 | Hz | 2031250 | +| `scan/sound_speed` | scalar | float32 | m/s | 1540 | + +### Inventory + +One row per acquisition. Power Doppler: x-z maximum-intensity projection of each of the five clips (baseline, +5, +10, +15, +20 s), display window set per acquisition from the 90th and 99.95th percentiles of its dB values. Reference maps: `mvi` (microvascular image) and `velocity_radial_avg`, each as x-z and y-z projections. + + + + + + + + + + + + + + + + + + + + + + + + + +
AcquisitionSubjectSidePower Doppler, five clips (x-z MIP)Reference maps (mvi · velocity_radial_avg, x-z and y-z)
SP01-Left-1SP01LeftSP01-Left-1 power Doppler of the five clipsSP01-Left-1 reference maps
SP01-Left-2SP01LeftSP01-Left-2 power Doppler of the five clipsSP01-Left-2 reference maps
SP02-Left-1SP02LeftSP02-Left-1 power Doppler of the five clipsSP02-Left-1 reference maps
SP02-Left-2SP02LeftSP02-Left-2 power Doppler of the five clipsSP02-Left-2 reference maps
SP03-LeftSP03LeftSP03-Left power Doppler of the five clipsSP03-Left reference maps
SP03-RightSP03RightSP03-Right power Doppler of the five clipsSP03-Right reference maps
SP04-Left-1SP04LeftSP04-Left-1 power Doppler of the five clipsSP04-Left-1 reference maps
SP04-Left-2SP04LeftSP04-Left-2 power Doppler of the five clipsSP04-Left-2 reference maps
SP05-LeftSP05LeftSP05-Left power Doppler of the five clipsSP05-Left reference maps
SP05-RightSP05RightSP05-Right power Doppler of the five clipsSP05-Right reference maps
SP06-LeftSP06LeftSP06-Left power Doppler of the five clipsSP06-Left reference maps
SP06-RightSP06RightSP06-Right power Doppler of the five clipsSP06-Right reference maps
SP07-LeftSP07LeftSP07-Left power Doppler of the five clipsSP07-Left reference maps
SP07-RightSP07RightSP07-Right power Doppler of the five clipsSP07-Right reference maps
SP08-LeftSP08LeftSP08-Left power Doppler of the five clipsSP08-Left reference maps
SP08-RightSP08RightSP08-Right power Doppler of the five clipsSP08-Right reference maps
SP09-Left-1SP09LeftSP09-Left-1 power Doppler of the five clipsSP09-Left-1 reference maps
SP09-Left-2SP09LeftSP09-Left-2 power Doppler of the five clipsSP09-Left-2 reference maps
SP10-LeftSP10LeftSP10-Left power Doppler of the five clipsSP10-Left reference maps
SP10-RightSP10RightSP10-Right power Doppler of the five clipsSP10-Right reference maps
+ +## Subject Metadata + +- Type: Human subjects (`metadata/subject/type` = `human`) +- Subject IDs: `SP01`–`SP10` (anonymized, `metadata/subject/id`); each subject contributes 2 acquisitions (see the inventory above for side and session) +- Anatomy: Brain (transcranial), left or right temporal acoustic window +- Contrast agent: Microbubbles (bolus injection) + +## Data Validation + +`reconstruct.py` first divides `raw_data` by `scan/tgc_gain_curve` (reverse TGC), then runs a standard `zea.Pipeline` (cast → DAS beamform → envelope → normalize → log-compress) defined in `pipeline.yaml` to reconstruct a B-mode from the IQ channel data. Because the probe is a 2D matrix array insonified by a single diverging-wave transmit, `reconstruct.py` beamforms on polar (sector) grids and renders two perpendicular sector B-modes, the x-z plane (y = 0) and the y-z plane (x = 0), side by side: + +![Reference B-mode (two perpendicular sectors), SP02-Left-2](../assets/clinical_SP02-Left-2_bmode.png) + +There is little to see in a single-frame B-mode of a transcranial CEUS acquisition: through the skull the diverging-wave frame is dominated by speckle and reverberation, and the microbubbles are not distinguishable from tissue without temporal filtering. The B-mode only checks the geometry and depth scaling of the reconstruction; the content of the data appears in the power-Doppler reconstruction below. + +A display-only linear TGC (1.5 dB/cm) adds a depth-dependent dB gain to the rendered image, compensating the attenuation that `reverse_tgc` leaves uncompensated so deeper structure stays visible. It does not modify the stored data. + +### Power-Doppler reconstruction (derived product) + +`reconstruct_PD_3d.py` (config `pipeline_PD_3d.yaml`) demonstrates a flow/contrast view. For each clip it beamforms all 4000 frames, in blocks of 250, onto a real 3D polar sector volume (radius × azimuth × elevation), applies a 100 Hz slow-time high-pass (wall) filter to suppress stationary tissue, and integrates power Doppler (sum of the squared envelope over the frames), then displays x-z and y-z maximum-intensity projections (MIPs) for all five clips in dB relative to the maximum over the clips (-25 to -5 dB, gamma 1.25, `hot` colormap), with the reference `mvi` map of the same file in the last column for comparison. The wall filter and power-Doppler integration are custom `zea` pipeline ops registered in the script (`tissue_highpass`, `power_doppler`). The hardware TGC stored in `raw_data` is kept. + +![Power-Doppler 3D MIP montage with reference mvi, SP03-Left](../assets/clinical_SP03-Left_PD_montage.png) + +Run (a GPU is strongly recommended: about 14 min per acquisition with `uv sync --extra gpu`, hours with the CPU-only JAX of the plain `uv sync`): + +```bash +uv run --project /path/to/OpenH-RF python reconstruct_PD_3d.py +``` + +## Computed References + +Reference maps derived from the SYLVER processed exam of each acquisition, stored in `custom/computed_references/` (read via `zea.File(...).dataset("custom/computed_references/")`). All share one 0.6 mm Cartesian grid `(192, 171, 171)`; the `coordinates` array gives each voxel's `[x, y, z]` in metres. + +The reference maps were computed by SYLVER's proprietary processing pipeline from the complete bolus passage, not from the five 1 s clips released here. They are therefore not reproducible from the released frames, and are provided as reference targets, for instance for learning-based reconstruction, microbubble-flow or perfusion estimation from the channel data. They are a device output, not a clinically validated ground truth. + +| Field | dtype | Unit | Description | +|---|---|---|---| +| `mvi` | float32 | a.u. | Microvascular image (enhanced, time-integrated over the bolus). NaN outside the sonified cone. | +| `velocity_radial_avg` | float32 | m/s | Mean radial (along-beam) flow velocity; NaN where no flow. | +| `velocity_radial_min` | float32 | m/s | Minimum radial flow velocity; NaN where no flow. | +| `velocity_radial_max` | float32 | m/s | Maximum radial flow velocity; NaN where no flow. | +| `coordinates` | float32 | m | Per-voxel `[x, y, z]` for all maps. | + +Notes: + +- Velocities are radial (Doppler, along-beam) components. True flow speed depends on the (subject- and vessel-dependent) insonation angle, which is not known a priori for in-vivo cerebral vasculature. +- `mvi` and the velocity maps are NaN outside the sonified cone. + +`display_references.py` renders all reference maps of one acquisition to a montage. It reads `custom/computed_references/` (no beamforming) and, for each map, shows two orthogonal maximum-intensity projections (x-z on top, y-z below) on the true Cartesian geometry: `mvi` in magma and the velocities in a symmetric blue-white-red map (±0.76 m/s). It takes `SUBJECT` or `--input `: + +![Computed reference maps montage, SP02-Left-2](../assets/clinical_SP02-Left-2_references_montage.png) + +```bash +uv run --project /path/to/OpenH-RF python display_references.py +``` + +## Ethical Considerations + +Human-subject data. Channel data was acquired during the SCULPT clinical study (National registration number (ID RCB): 2025-A00023-46; NCT07324421), a prospective monocentric trial conducted at CHU Gui de Chauliac (Montpellier, France) under approval from the French ethics committee (Comité de Protection des Personnes, CPP), comparing cerebral perfusion from Resolve Stroke's SYLVER ultrasound system with routine perfusion CT in ICU/CCU patients, using SonoVue® as the contrast agent. + +The dataset contains no direct personal identifiers: subjects are referenced only by an anonymized study code, and no name, date of birth, or device/operator identifiers are stored in the released files. + +## Known Issues + +- `raw_data` has the hardware TGC baked in; `scan/tgc_gain_curve` is that (non-linear) applied curve. The reconstruction scripts divide by the curve before beamforming; a downstream user reconstructing directly must divide by the curve too. +- `sampling_frequency ≈ center_frequency` because the data is DDC baseband IQ (see Dataset Characterization), not an RF acquisition. +- Radial velocity maps are along-beam only; absolute speed requires the unknown local vessel directions. diff --git a/datasets/resolvestroke/clinical/SP02-Left-2/README.md b/datasets/resolvestroke/clinical/SP02-Left-2/README.md deleted file mode 100644 index c450e4d..0000000 --- a/datasets/resolvestroke/clinical/SP02-Left-2/README.md +++ /dev/null @@ -1,220 +0,0 @@ ---- -pretty_name: "OpenH-RF - Resolve Stroke Clinical Transcranial CEUS (SP02, Left)" -license: cc-by-4.0 -task_categories: - - other -tags: - - ultrasound - - iq - - openh-rf - - 3d - - matrix-probe - - contrast-enhanced - - ceus - - transcranial - - clinical -language: - - en -size_categories: - - 10K 0 else 0.0 + apex = float(p.distance_to_apex) # virtual source behind the array polar_limits = tuple(float(v) for v in p["polar_limits"]) z0, z1 = (float(v) for v in p["zlims"]) - # polar_pixel_grid measures radius from the apex, so offset the near bound by - # the apex -> the configured zlims are true on-axis depth. + # polar_pixel_grid takes zlims as on-axis depth from the transducer face and adds + # distance_to_apex to the radii itself, so the configured zlims go in unchanged. grid_xz = polar_pixel_grid( polar_limits, - (z0 + apex, z1), + (z0, z1), num_radial_pixels=int(p["grid_size_z"]), num_polar_pixels=int(p["grid_size_x"]), distance_to_apex=apex, @@ -122,17 +121,16 @@ def beamform_sector(pipeline, parameters, raw, grid): def main(): - out_path = OUTPUT or Path(f"{Path(INPUT).stem}_bmode.png") zea.init_device() config = Config.from_path(str(CONFIG)) - with File(str(INPUT)) as f: + with File(str(ZEA_FILE)) as f: parameters = f.load_parameters(**config.parameters) raw = f.data.raw_data[FRAME : FRAME + 1] # (1, n_tx, n_ax, n_el, n_ch) raw = reverse_tgc(raw, parameters) # undo hardware TGC before beamforming - grid_xz, grid_yz, apex = build_sector_grids(config, parameters) + grid_xz, grid_yz, apex = build_sector_grids(config) print(f"raw_data shape : {raw.shape}") print(f"sector grid : {grid_xz.shape} (polar, apex={apex * 1e3:.1f} mm)") @@ -176,8 +174,9 @@ def main(): print(f"y-z sector : {yz_slice.shape}") fig.tight_layout() - fig.savefig(str(out_path), dpi=150, bbox_inches="tight") - print(f"Saved : {out_path}") + Path(OUT).parent.mkdir(parents=True, exist_ok=True) + fig.savefig(str(OUT), dpi=150, bbox_inches="tight") + print(f"Saved : {OUT}") if __name__ == "__main__": diff --git a/datasets/resolvestroke/clinical/SP02-Left-2/reconstruct_PD_3d.py b/datasets/resolvestroke/clinical/reconstruct_PD_3d.py similarity index 53% rename from datasets/resolvestroke/clinical/SP02-Left-2/reconstruct_PD_3d.py rename to datasets/resolvestroke/clinical/reconstruct_PD_3d.py index 0e59dcd..11eeb1b 100644 --- a/datasets/resolvestroke/clinical/SP02-Left-2/reconstruct_PD_3d.py +++ b/datasets/resolvestroke/clinical/reconstruct_PD_3d.py @@ -1,11 +1,16 @@ # SPDX-License-Identifier: Apache-2.0 """Reconstruct power-Doppler 3D volumes for each clip and display as MIP montage. +Serves all 20 resolvestroke/clinical acquisitions (``SP01``-``SP10``, Left/Right), +which share probe, sequence and file layout: pick one with ``SUBJECT`` below. + For each of the 5 clips (4000 frames each), this script: -1. Beamforms N frames onto a real 3D POLAR (sector) volume -> LINEAR envelope. -2. Applies a slow-time high-pass (wall) filter to remove tissue clutter. +1. Beamforms all frames, in blocks, onto a real 3D POLAR (sector) volume -> LINEAR + envelope. The hardware TGC stored in ``raw_data`` is kept. +2. Applies a 100 Hz slow-time high-pass (wall) filter to remove tissue clutter. 3. Integrates power Doppler (sum of squared envelope over slow time). -4. Displays a 5x2 montage: rows = clips, columns = x-z MIP and y-z MIP. +4. Displays a montage: rows = x-z MIP and y-z MIP, columns = clips, plus the + reference ``mvi`` map of the file in the last column. Grid: the acquisition is a matrix-probe DIVERGING wave, so a Cartesian box wastes compute on the corners that fall outside the insonified cone. Instead we beamform @@ -19,11 +24,15 @@ Pipeline: a single pipeline in pipeline_PD_3d.yaml does everything end to end — cast -> beamform -> envelope_detect -> tissue_highpass -> power_doppler. Steps 2-3 are CUSTOM zea ops (`tissue_highpass`, `power_doppler`) registered here with -`@ops_registry`; they act on the frame (slow-time) axis, so each clip's whole stack +`@ops_registry`; they act on the frame (slow-time) axis, so each block of frames is beamformed in one pass (zea patches the grid to bound memory). Custom ops need no merged zea PR — they only need to be defined (imported) before a config referencing them is loaded, which is why the YAML can list them by name. +Runtime: about 14 min for a full acquisition on an RTX 4000 Ada with GPU JAX +(``uv sync --extra gpu`` in the OpenH-RF repo; the plain ``uv sync`` installs a CPU-only +JAX and this script then takes hours). Lower ``N_FRAMES`` for a quick look. + Usage: KERAS_BACKEND=jax uv run --project /path/to/OpenH-RF python reconstruct_PD_3d.py """ @@ -39,14 +48,13 @@ import numpy as np import zea from keras import ops as kops +from matplotlib.colors import PowerNorm from zea import Config, File, Pipeline from zea.internal.registry import ops_registry from zea.ops import Operation HERE = Path(__file__).parent -_HDF5 = "hf://nvidia/OpenH-RF/resolvestroke/clinical/SP02-Left-2/SP02-Left-2.hdf5" -DEFAULT_INPUT = "hf://nvidia/OpenH-RF/resolvestroke/clinical/SP02-Left-2/SP02-Left-2.hdf5" -CONFIG = HERE / "pipeline_PD_3d.yaml" +CONFIG = "hf://nvidia/OpenH-RF/resolvestroke/clinical/pipeline_PD_3d.yaml" # Clip definitions (matching the combined file structure) CLIPS = [ @@ -57,15 +65,20 @@ "baseline_plus20s", ] FRAMES_PER_CLIP = 4000 -N_FRAMES_BF = 100 # frames to beamform per clip -TGC_DB_PER_CM = 1.5 # display-only linear TGC (dB gain per cm of depth) +N_FRAMES_BF = 4000 # frames to beamform per clip (all of them) +BLOCK_FRAMES = 250 # frames per beamform + wall-filter pass (bounds GPU memory) +DISPLAY_DB = (-25.0, -5.0) # dB range relative to the maximum over the five clips +DISPLAY_GAMMA = 1.25 # gamma on the normalised dB image (>1 darkens the mid-tones) +COLORMAP = "hot" # --- Inputs ----------------------------------------------------------------- -# Defaults stream straight from the published corpus. Swap any of these for a -# local path to run against your own copy. -INPUT = "hf://nvidia/OpenH-RF/resolvestroke/clinical/SP02-Left-2/SP02-Left-2.hdf5" +# One script serves all 20 clinical acquisitions; pick one with SUBJECT (the +# subdirectory / file stem on the Hub, e.g. "SP07-Right"). Defaults stream straight +# from the published corpus. Swap INPUT for a local path to run against your own copy. +SUBJECT = "SP02-Left-2" +INPUT = f"hf://nvidia/OpenH-RF/resolvestroke/clinical/{SUBJECT}/{SUBJECT}.hdf5" N_FRAMES = N_FRAMES_BF -OUTPUT = None +OUT = HERE / f"{SUBJECT}_PD_montage.png" # The 3D sector-volume grid (limits, depth, resolution) and the high-pass params # all live in pipeline_PD_3d.yaml (`grid:` block and tissue_highpass params). @@ -81,9 +94,10 @@ class TissueHighpass(Operation): """Slow-time high-pass (wall) filter to suppress stationary tissue clutter. Operates along the frame (slow-time) axis with a frequency-domain filter and a - raised-cosine taper around the cutoff. Marked non-jittable because it uses the - NumPy FFT with a data-dependent mask; with the default ``jit_options="ops"`` - the surrounding pipeline still runs the other ops jitted. + raised-cosine taper around the cutoff. Implemented with Keras ops (``rfft`` / + ``irfft``), so it runs on the same device as the beamformer (the GPU when one is + available) and stays jittable: the filter mask only depends on the static frame + count, so the whole pipeline compiles as one program. """ def __init__( @@ -94,29 +108,35 @@ def __init__( time_axis: int = 0, **kwargs, ): - super().__init__(jittable=False, **kwargs) + super().__init__(**kwargs) self.cutoff_hz = cutoff_hz self.frame_rate_hz = frame_rate_hz self.transition_hz = transition_hz self.time_axis = time_axis - def call(self, **kwargs): - data = np.asarray(kops.convert_to_numpy(kwargs[self.key])) - n = data.shape[self.time_axis] - freqs = np.fft.rfftfreq(n, d=1.0 / self.frame_rate_hz) # non-negative + def frequency_mask(self, n_frames): + """Real mask over the ``n_frames // 2 + 1`` non-negative rfft bins. + + 1 above the cutoff, 0 below, with a raised-cosine taper across + [cutoff - transition, cutoff) to reduce ringing. Built with NumPy at trace + time (it is a constant for a given frame count). + """ + freqs = np.fft.rfftfreq(n_frames, d=1.0 / self.frame_rate_hz) mask = (freqs >= self.cutoff_hz).astype(np.float32) - # Raised-cosine taper across [cutoff - transition, cutoff) to reduce ringing. taper = (freqs >= self.cutoff_hz - self.transition_hz) & (freqs < self.cutoff_hz) mask[taper] = 0.5 * ( 1.0 + np.cos(np.pi * (self.cutoff_hz - freqs[taper]) / self.transition_hz) ) + return mask - spectrum = np.fft.rfft(data, axis=self.time_axis) - shape = [1] * data.ndim - shape[self.time_axis] = len(mask) - spectrum = spectrum * mask.reshape(shape) - filtered = np.fft.irfft(spectrum, n=n, axis=self.time_axis).astype(np.float32) - return {self.output_key: filtered} + def call(self, **kwargs): + # Keras FFTs work along the last axis: move slow time there and back. + data = kops.moveaxis(kwargs[self.key], self.time_axis, -1) + n = int(data.shape[-1]) + mask = kops.convert_to_tensor(self.frequency_mask(n), dtype=data.dtype) + real, imag = kops.rfft(data) + filtered = kops.irfft((real * mask, imag * mask), fft_length=n) + return {self.output_key: kops.moveaxis(filtered, -1, self.time_axis)} @ops_registry("power_doppler") @@ -132,17 +152,6 @@ def call(self, **kwargs): return {self.output_key: kops.sum(kops.square(data), axis=self.time_axis)} -def reverse_tgc(raw, parameters): - """Undo the hardware time-gain compensation baked into ``raw_data``. - - zea stores ``scan/tgc_gain_curve`` (n_ax,) as "the TGC applied to every sample; - divide by this curve to undo it". A plain per-axial-sample divide here keeps the - zea pipeline fully standard (no custom op needed). - """ - tgc = np.asarray(parameters.tgc_gain_curve, dtype=np.float32) # (n_ax,) - return np.asarray(raw, dtype=np.float32) / tgc.reshape(1, 1, -1, 1, 1) - - # --------------------------------------------------------------------------- # # Sector-volume grid. # --------------------------------------------------------------------------- # @@ -183,16 +192,24 @@ def sector_plane_coords(r, angle, apex): return lat, z +def load_reference_mips(f): + """x-z and y-z MIPs of the reference ``mvi`` map with their Cartesian extents (mm).""" + ref = "custom/computed_references" + coords = np.asarray(f.dataset(f"{ref}/coordinates")[...]) # (nz, ny, nx, 3) m + mvi = np.asarray(f.dataset(f"{ref}/mvi")[...], dtype=np.float32) + x_mm, y_mm, z_mm = coords[0, 0, :, 0] * 1e3, coords[0, :, 0, 1] * 1e3, coords[:, 0, 0, 2] * 1e3 + with np.errstate(all="ignore"): + xz, yz = np.nanmax(mvi, axis=1), np.nanmax(mvi, axis=2) + ext_xz = [x_mm.min(), x_mm.max(), z_mm.max(), z_mm.min()] + ext_yz = [y_mm.min(), y_mm.max(), z_mm.max(), z_mm.min()] + return xz, yz, ext_xz, ext_yz + + def main(): - out_path = OUTPUT or Path(f"{Path(INPUT).stem}_PD_montage.png") n_bf = N_FRAMES zea.init_device() config = Config.from_path(str(CONFIG)) - g = config["grid"] # 3D sector-volume spec - azimuth_limits = tuple(float(v) for v in g["azimuth_limits"]) - elevation_limits = tuple(float(v) for v in g["elevation_limits"]) - zlims = tuple(float(v) for v in g["zlims"]) # One pipeline, end to end: cast -> beamform -> envelope -> tissue_highpass -> # power_doppler. The custom slow-time ops resolve by name because they are @@ -200,15 +217,23 @@ def main(): pipeline = Pipeline.from_config(config) hp_cutoff = pipeline["tissue_highpass"].cutoff_hz + pd_volumes, (r, alpha, beta, apex), ref = beamform_clips(config, pipeline, hp_cutoff, n_bf) + + render(pd_volumes, r, alpha, beta, apex, ref, n_bf, hp_cutoff) + + +def beamform_clips(config, pipeline, hp_cutoff, n_bf): + """Beamform every clip of INPUT to a power-Doppler volume; also read the reference maps.""" + g = config["grid"] # 3D sector-volume spec + azimuth_limits = tuple(float(v) for v in g["azimuth_limits"]) + elevation_limits = tuple(float(v) for v in g["elevation_limits"]) + zlims = tuple(float(v) for v in g["zlims"]) + with File(str(INPUT)) as f: parameters = f.load_parameters(**config.parameters) total_frames = f.data.raw_data.shape[0] - # Apex = virtual source behind the array; from the config or |focus_distances|. - apex = g.get("distance_to_apex") - if apex is None: - focus = float(np.abs(np.ravel(parameters.focus_distances)[0])) - apex = focus if focus > 0 else 0.0 + apex = float(g["distance_to_apex"]) # virtual source behind the array grid, (r, alpha, beta) = build_sector_volume_grid( azimuth_limits, @@ -221,15 +246,18 @@ def main(): ) flatgrid = grid.reshape(-1, 3) n_vox = flatgrid.shape[0] - print(f"Total frames: {total_frames}, beamforming {n_bf} per clip") + print( + f"Total frames: {total_frames}, beamforming {n_bf} per clip, blocks of {BLOCK_FRAMES}" + ) print( f"Sector volume: {grid.shape[:-1]} = {n_vox} voxels (polar, apex={apex * 1e3:.1f} mm)" ) inputs = pipeline.prepare_parameters(parameters, grid=grid, flatgrid=flatgrid) - # Process each clip: the whole frame stack goes through the pipeline in one - # pass, so the slow-time ops see all frames. + # Process each clip block by block: every block goes through the pipeline in + # one pass (the slow-time ops see BLOCK_FRAMES frames) and the power-Doppler + # volumes of the blocks are summed. pd_volumes = [] # list of (n_r, n_az, n_el) power-Doppler volumes for clip_idx, clip_name in enumerate(CLIPS): clip_start = clip_idx * FRAMES_PER_CLIP @@ -240,66 +268,85 @@ def main(): print(f"\n Clip '{clip_name}': frames {start}-{end} (HP cutoff={hp_cutoff} Hz)") - raw = f.data.raw_data[start:end] # (n_bf, n_tx, n_ax, n_el, n_ch) - raw = reverse_tgc(raw, parameters) # undo hardware TGC before beamforming - outputs = pipeline(**{pipeline.key: raw}, **inputs, return_numpy=True) - pd = np.asarray(outputs[pipeline.output_key]) # (n_r, n_az, n_el) + pd = np.zeros(grid.shape[:-1], dtype=np.float64) + for block_start in range(start, end, BLOCK_FRAMES): + raw = f.data.raw_data[block_start : block_start + BLOCK_FRAMES] + outputs = pipeline(**{pipeline.key: raw}, **inputs, return_numpy=True) + pd += np.asarray(outputs[pipeline.output_key], dtype=np.float64) pd_volumes.append(pd) print(f" PD volume: {pd.shape}, linear max={pd.max():.4g}") - # Normalize all PD volumes to the same global max (common dB scale) + ref = load_reference_mips(f) + return pd_volumes, (r, alpha, beta, apex), ref + + +def render(pd_volumes, r, alpha, beta, apex, ref, n_bf, hp_cutoff): + """Montage of all clips (x-z / y-z MIPs) plus the reference mvi column.""" + ref_xz, ref_yz, ext_xz, ext_yz = ref + + # Normalize all PD volumes to the same global max (common dB scale). Voxels the + # beamformer leaves at exactly zero (outside its receive aperture) become NaN. global_max = max(pv.max() for pv in pd_volumes) - pd_db_volumes = [ - 10.0 * np.log10(np.maximum(pd, global_max * 1e-35) / global_max) for pd in pd_volumes - ] - - # Linear TGC (display only): a dB gain growing linearly with axial depth, - # broadcast over the sector's angular axes. On-axis depth per radial sample is - # (r - apex); the gain is zero at the shallowest sample. Reveals deep flow that - # attenuation (uncompensated after reverse_tgc) would otherwise bury. - depth_mm = (r - apex) * 1e3 # (n_r,) - gain_db = TGC_DB_PER_CM * (depth_mm - depth_mm.min()) / 10.0 - pd_db_volumes = [pd + gain_db[:, None, None] for pd in pd_db_volumes] + with np.errstate(all="ignore"): + pd_db_volumes = [ + 10.0 * np.log10(np.where(pd > 0, pd / global_max, np.nan)) for pd in pd_volumes + ] + + vmin, vmax = DISPLAY_DB + norm = PowerNorm(DISPLAY_GAMMA, vmin=vmin, vmax=vmax) + cmap = plt.get_cmap(COLORMAP).copy() + cmap.set_bad("#101010") # Scan-conversion coordinates for the two MIP planes (central plane of the fan) xz_lat, xz_z = sector_plane_coords(r, alpha, apex) # (n_r, n_az) yz_lat, yz_z = sector_plane_coords(r, beta, apex) # (n_r, n_el) - vmin, vmax = -20.0, 0.0 # tight floor so vessels stand out from diffuse perfusion zea.visualize.set_mpl_style() - fig, axes = plt.subplots(2, 5, figsize=(24, 10)) - - for col, (clip_name, pd_vol) in enumerate(zip(CLIPS, pd_db_volumes)): - # x-z MIP: project out elevation (max over the y-fan) - xz_mip = np.max(pd_vol, axis=2) # (n_r, n_az) - ax = axes[0, col] - ax.pcolormesh(xz_lat, xz_z, xz_mip, cmap="hot", vmin=vmin, vmax=vmax, shading="auto") - ax.set_aspect("equal") - ax.invert_yaxis() - ax.set_title(f"{clip_name}\nx-z MIP", fontsize=9) - ax.set_xlabel("x [mm]") - if col == 0: - ax.set_ylabel("z [mm]") - - # y-z MIP: project out azimuth (max over the x-fan) - yz_mip = np.max(pd_vol, axis=1) # (n_r, n_el) - ax = axes[1, col] - ax.pcolormesh(yz_lat, yz_z, yz_mip, cmap="hot", vmin=vmin, vmax=vmax, shading="auto") - ax.set_aspect("equal") - ax.invert_yaxis() - ax.set_title(f"{clip_name}\ny-z MIP", fontsize=9) - ax.set_xlabel("y [mm]") - if col == 0: - ax.set_ylabel("z [mm]") + fig, axes = plt.subplots(2, len(CLIPS) + 1, figsize=(4.4 * (len(CLIPS) + 1), 9.5)) + + with np.errstate(all="ignore"): + for col, (clip_name, pd_vol) in enumerate(zip(CLIPS, pd_db_volumes)): + # x-z MIP: project out elevation (max over the y-fan) + xz_mip = np.nanmax(pd_vol, axis=2) # (n_r, n_az) + ax = axes[0, col] + ax.pcolormesh(xz_lat, xz_z, xz_mip, cmap=cmap, norm=norm, shading="auto") + ax.set_aspect("equal") + ax.invert_yaxis() + ax.set_title(f"{clip_name}\nx-z MIP", fontsize=9) + ax.set_xlabel("x [mm]") + if col == 0: + ax.set_ylabel("z [mm]") + + # y-z MIP: project out azimuth (max over the x-fan) + yz_mip = np.nanmax(pd_vol, axis=1) # (n_r, n_el) + ax = axes[1, col] + ax.pcolormesh(yz_lat, yz_z, yz_mip, cmap=cmap, norm=norm, shading="auto") + ax.set_aspect("equal") + ax.invert_yaxis() + ax.set_title(f"{clip_name}\ny-z MIP", fontsize=9) + ax.set_xlabel("y [mm]") + if col == 0: + ax.set_ylabel("z [mm]") + + # Reference mvi map of the same file, for comparison (its own linear scale). + vmax_ref = float(np.nanpercentile(ref_xz, 99.9)) + for row, (img, ext, lab) in enumerate([(ref_xz, ext_xz, "x"), (ref_yz, ext_yz, "y")]): + ax = axes[row, -1] + ax.imshow(img, extent=ext, cmap=cmap, vmin=0, vmax=vmax_ref, aspect="equal") + ax.set_title(f"reference mvi\n{lab}-z MIP", fontsize=9) + ax.set_xlabel(f"{lab} [mm]") + ax.set_xlim(axes[row, 0].get_xlim()) + ax.set_ylim(axes[row, 0].get_ylim()) fig.suptitle( - f"Power Doppler 3D polar MIPs — {n_bf} frames/clip, HP cutoff {hp_cutoff} Hz", + f"Power Doppler 3D polar MIPs — {n_bf} frames/clip, HP cutoff {hp_cutoff} Hz, " + f"{vmin:.0f}..{vmax:.0f} dB, gamma {DISPLAY_GAMMA}", fontsize=14, fontweight="bold", ) fig.tight_layout() - fig.savefig(str(out_path), dpi=150, bbox_inches="tight") - print(f"\nSaved montage: {out_path}") + fig.savefig(str(OUT), dpi=150, bbox_inches="tight") + print(f"\nSaved montage: {OUT}") if __name__ == "__main__": diff --git a/datasets/resolvestroke/phantom_flow/README.md b/datasets/resolvestroke/phantom_flow/README.md index beb5e62..2862eb8 100644 --- a/datasets/resolvestroke/phantom_flow/README.md +++ b/datasets/resolvestroke/phantom_flow/README.md @@ -1,5 +1,6 @@ --- -pretty_name: "OpenH-RF - Resolve Stroke Flow Phantom (CIRS 769 + ATS523A) On/Off" +name: resolvestroke-phantom-flow +pretty_name: "Resolve Stroke Flow Phantom (CIRS 769 + ATS523A) On/Off" license: cc-by-4.0 task_categories: - other @@ -18,18 +19,17 @@ size_categories: - 10K (contact) +- Vincent Hingot +- Resolve Stroke, 29 Rue du Faubourg Saint-Jacques, 75014 Paris ## Dataset Creation Date @@ -53,50 +54,39 @@ Contact email: maxence.reberol@resolvestroke.com ## License / Terms of Use -CC BY 4.0 (see `LICENCE`). Data is released under Creative Commons Attribution -4.0 International, which permits commercial use with attribution. (The Python -scripts in this directory carry their own `SPDX-License-Identifier: Apache-2.0` -header; the dataset itself is CC BY 4.0.) +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage -Contrast-enhanced ultrasound flow quantification, microbubble detection and -tracking, power-Doppler and CEUS flow imaging, and matrix-probe diverging-wave -beamforming research (RFP task group 6.2, Blood Flow). +Contrast-enhanced ultrasound flow quantification, microbubble detection and tracking, power-Doppler and CEUS flow imaging, and matrix-probe diverging-wave beamforming research (RFP task group 6.2, Blood Flow). ## Dataset Characterization - Data collection method: Phantom (CIRS 769 + ATS523A flow phantom with microbubble contrast agent) -- Labeling method: Per-frame clip label (time offset relative to end-of-baseline), - stored in `metadata/annotations/label` (one of the five clip names above). -- Acquisition system: SYLVER (Resolve Stroke's ultrasound device). 32×32 matrix - probe, 0.50 mm pitch, 0.30 mm kerf; transmit center frequency ≈ 2.031 MHz, sound - speed 1540 m/s. Diverging-wave transmits; receive sub-apertures flattened into 256 - virtual elements. Channel data is DDC (baseband) IQ, so `sampling_frequency` - (≈ 2.031 MHz) is the post-decimation IQ rate and equals `demodulation_frequency`. - This is not an RF Nyquist rate (`n_ch = 2`, complex I/Q). +- Labeling method: Automatic, no manual annotation. The per-voxel tube mask comes from the known phantom geometry; the reference maps (`mvi`, radial velocities) are generated by SYLVER's processing pipeline from the complete bolus passage (see [Computed References](#computed-references)). Per-frame clip labels (`metadata/annotations/label`, one of the five clip names above) are assigned from the acquisition time relative to the end-of-baseline marker. +- Acquisition system: SYLVER (Resolve Stroke's ultrasound device). 32×32 matrix probe, 0.50 mm pitch, 0.30 mm kerf; transmit center frequency ≈ 2.031 MHz, sound speed 1540 m/s. Diverging-wave transmits; receive sub-apertures flattened into 256 virtual elements. Channel data is DDC (baseband) IQ, so `sampling_frequency` (≈ 2.031 MHz) is the post-decimation IQ rate and equals `demodulation_frequency`. + +## Processing the Dataset + +The acquisitions can be processed with the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/resolvestroke/phantom_flow/reconstruct.py) as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF), together with the `pipeline.yaml` definition in this folder and the [zea library](https://github.com/tue-bmd/zea). The script streams the data from the Hugging Face Hub. + +```bash +uv run --project /path/to/OpenH-RF python reconstruct.py +``` + +`reconstruct_PD_3d.py` (with `pipeline_PD_3d.yaml`) renders the power-Doppler reconstruction and `display_references.py` the computed reference maps; both are described below. + +The Python scripts carry their own `SPDX-License-Identifier: Apache-2.0` header; the dataset itself is CC BY 4.0. ## Dataset Format -Single zea HDF5 file (`phantom_flow.hdf5`) containing all 5 clips concatenated. -Gaps between clips are encoded in `scan/time_to_next_transmit`. Per-frame clip -labels are stored in `metadata/annotations/label`. - -Computed reference maps (derived from the SYLVER processed exam) are stored in the -zea `custom` group under `custom/computed_references/`: `mvi`, -`velocity_radial_avg` / `_min` / `_max`, `tube_mask`, and a shared `coordinates` -array (per-voxel `[x, y, z]` in metres), all on one 0.6 mm Cartesian grid -`(192, 171, 171)`. Read them via `zea.File(...).custom`. They live in `custom` -rather than a second track because zea uses a single global `n_frames`: a -single-frame reference map cannot share that dimension with the per-frame -`metadata/annotations/label` (20000), so a reference track fails spec validation. - -Originally written with `zea.File.create` (zea v0.1.1), validated `compliant: true` against -`validate_zea_spec.py`. `data/raw_data` is DDC IQ (last axis [I, Q]). The hardware -time-gain compensation is baked into `raw_data`; `scan/tgc_gain_curve` is the applied -(non-linear) gain per axial sample; divide by it to recover true channel amplitudes. -The reconstruction scripts divide `raw_data` by this curve before beamforming (the -`zea.Pipeline` itself stays standard; the reversal is a plain array step). +[zea v0.1.6](https://github.com/tue-bmd/zea) + +Single zea HDF5 file (`phantom_flow.hdf5`) containing all 5 clips concatenated. Gaps between clips are encoded in `scan/time_to_next_transmit`. Per-frame clip labels are stored in `metadata/annotations/label`. + +Computed reference maps (derived from the SYLVER processed exam) are stored in the zea `custom` group under `custom/computed_references/`: `mvi`, `velocity_radial_avg` / `_min` / `_max`, `tube_mask`, and a shared `coordinates` array (per-voxel `[x, y, z]` in metres), all on one 0.6 mm Cartesian grid `(192, 171, 171)`. Read them via `zea.File(...).custom`. They live in `custom` rather than a second track because zea uses a single global `n_frames`: a single-frame reference map cannot share that dimension with the per-frame `metadata/annotations/label` (20000), so a reference track fails spec validation. + +The file is in the zea HDF5 format, root `zea_version` 0.1.6, validated `compliant: true` against `validate_zea_spec.py`. `data/raw_data` is DDC IQ (last axis [I, Q]). The hardware time-gain compensation is baked into `raw_data`; `scan/tgc_gain_curve` is the applied (non-linear) gain per axial sample; divide by it to recover true channel amplitudes. The reconstruction scripts divide `raw_data` by this curve before beamforming (the `zea.Pipeline` itself stays standard; the reversal is a plain array step). ## Dataset Quantification @@ -133,38 +123,27 @@ The reconstruction scripts divide `raw_data` by this curve before beamforming (t ## Data Validation -`reconstruct.py` first divides `raw_data` by `scan/tgc_gain_curve` (reverse TGC), -then runs a standard `zea.Pipeline` (cast → DAS beamform → envelope → normalize → -log-compress) defined in `pipeline.yaml` to reconstruct a B-mode from the IQ channel -data. Because the probe is a 2D matrix array insonified by a single diverging-wave -transmit, `reconstruct.py` beamforms on polar (sector) grids and renders two -perpendicular sector B-modes, the x-z plane (y = 0) and the y-z plane (x = 0), side -by side: +`reconstruct.py` first divides `raw_data` by `scan/tgc_gain_curve` (reverse TGC), then runs a standard `zea.Pipeline` (cast → DAS beamform → envelope → normalize → log-compress) defined in `pipeline.yaml` to reconstruct a B-mode from the IQ channel data. Because the probe is a 2D matrix array insonified by a single diverging-wave transmit, `reconstruct.py` beamforms on polar (sector) grids and renders two perpendicular sector B-modes, the x-z plane (y = 0) and the y-z plane (x = 0), side by side: -![Reference B-mode (two perpendicular sectors)](phantom_flow_bmode.png) - -Run: `uv run --project /path/to/OpenH-RF python reconstruct.py` +![Reference B-mode (two perpendicular sectors)](../assets/phantom_flow_bmode.png) ### Power-Doppler reconstruction (derived product) -`reconstruct_PD_3d.py` (config `pipeline_PD_3d.yaml`) demonstrates a flow/contrast -view. For each clip it beamforms the frame stack onto a real 3D polar sector volume -(radius × azimuth × elevation), applies a slow-time high-pass (wall) filter to -suppress stationary tissue, and integrates power Doppler, then displays x-z and y-z -maximum-intensity projections (MIPs) for all five clips. The wall filter and -power-Doppler integration are custom `zea` pipeline ops registered in the script -(`tissue_highpass`, `power_doppler`). +`reconstruct_PD_3d.py` (config `pipeline_PD_3d.yaml`) demonstrates a flow/contrast view. For each clip it beamforms all 4000 frames, in blocks of 250, onto a real 3D polar sector volume (radius × azimuth × elevation), applies a 100 Hz slow-time high-pass (wall) filter to suppress stationary tissue, and integrates power Doppler (sum of the squared envelope over the frames), then displays x-z and y-z maximum-intensity projections (MIPs) for all five clips in dB relative to the maximum over the clips (-25 to -5 dB, gamma 1.25, `hot` colormap), with the reference `mvi` map of the same file in the last column for comparison. The wall filter and power-Doppler integration are custom `zea` pipeline ops registered in the script (`tissue_highpass`, `power_doppler`). The hardware TGC stored in `raw_data` is kept. + +The resulting montage is shown at the top of this card. -![Power-Doppler 3D MIP montage](phantom_flow_PD_montage.png) +Run (a GPU is strongly recommended: about 14 min per acquisition with `uv sync --extra gpu`, hours with the CPU-only JAX of the plain `uv sync`): -Run: `uv run --project /path/to/OpenH-RF python reconstruct_PD_3d.py` +```bash +uv run --project /path/to/OpenH-RF python reconstruct_PD_3d.py +``` ## Computed References -Reference maps derived from the SYLVER processed exam of this acquisition, stored -in `custom/computed_references/` (read via `zea.File(...).custom`). All share one -0.6 mm Cartesian grid `(192, 171, 171)`; the `coordinates` array gives each voxel's -`[x, y, z]` in metres. +Reference maps derived from the SYLVER processed exam of this acquisition, stored in `custom/computed_references/` (read via `zea.File(...).dataset("custom/computed_references/")`). All share one 0.6 mm Cartesian grid `(192, 171, 171)`; the `coordinates` array gives each voxel's `[x, y, z]` in metres. + +The reference maps were computed by SYLVER's proprietary processing pipeline from the complete bolus passage, not from the five 1 s clips released here. They are therefore not reproducible from the released frames, and are provided as reference targets, for instance for learning-based reconstruction, microbubble-flow or perfusion estimation from the channel data. They are a device output, not a clinically validated ground truth. `tube_mask` is the known phantom geometry and is an actual ground truth. | Field | dtype | Unit | Description | |---|---|---|---| @@ -176,28 +155,19 @@ in `custom/computed_references/` (read via `zea.File(...).custom`). All share on | `coordinates` | float32 | m | Per-voxel `[x, y, z]` for all maps. | Notes: -- Velocities are radial (Doppler, along-beam). The probe sits at ~72° to the flow, - so true speed ≈ radial ÷ cos(72°). -- Tube ground truth: two straight parallel tubes fitted to the flow, ⌀4 mm and - ⌀2 mm, ~35 mm apart, direction ≈ (−0.95, +0.04, +0.31), spanning the imaging cone. +- Velocities are radial (Doppler, along-beam). The probe sits at ~72° to the flow, so true speed ≈ radial ÷ cos(72°). +- Tube ground truth: two straight parallel tubes fitted to the flow, ⌀4 mm and ⌀2 mm, ~35 mm apart, direction ≈ (−0.95, +0.04, +0.31), spanning the imaging cone. -`display_references.py` renders all reference maps to a montage. It reads -`custom/computed_references/` (via `h5py`; no beamforming) and, for each map, shows -two orthogonal maximum-intensity projections (x-z on top, y-z below) on the true -Cartesian geometry: `mvi` in magma, the velocities in a symmetric blue-white-red -map (±0.76 m/s), and `tube_mask` as discrete labels (⌀4 mm, ⌀2 mm): +`display_references.py` renders all reference maps to a montage. It reads `custom/computed_references/` (via `h5py`; no beamforming) and, for each map, shows two orthogonal maximum-intensity projections (x-z on top, y-z below) on the true Cartesian geometry: `mvi` in magma, the velocities in a symmetric blue-white-red map (±0.76 m/s), and `tube_mask` as discrete labels (⌀4 mm, ⌀2 mm): -![Computed reference maps montage](phantom_flow_references_montage.png) +![Computed reference maps montage](../assets/phantom_flow_references_montage.png) Run: `uv run --project /path/to/OpenH-RF python display_references.py` ## Known Issues -- `raw_data` has the hardware TGC baked in; `scan/tgc_gain_curve` is that (non-linear) - applied curve. The reconstruction scripts divide by the curve before beamforming; - a downstream user reconstructing directly must divide by the curve too. -- `sampling_frequency ≈ center_frequency` because the data is DDC baseband IQ (see - Dataset Characterization), not an RF acquisition. +- `raw_data` has the hardware TGC baked in; `scan/tgc_gain_curve` is that (non-linear) applied curve. The reconstruction scripts divide by the curve before beamforming; a downstream user reconstructing directly must divide by the curve too. +- `sampling_frequency ≈ center_frequency` because the data is DDC baseband IQ (see Dataset Characterization), not an RF acquisition. ## Ethical Considerations diff --git a/datasets/resolvestroke/phantom_flow/assets/main.png b/datasets/resolvestroke/phantom_flow/assets/main.png new file mode 100644 index 0000000..ce76193 Binary files /dev/null and b/datasets/resolvestroke/phantom_flow/assets/main.png differ diff --git a/datasets/resolvestroke/phantom_flow/display_references.py b/datasets/resolvestroke/phantom_flow/display_references.py index af66bb6..ea989f3 100644 --- a/datasets/resolvestroke/phantom_flow/display_references.py +++ b/datasets/resolvestroke/phantom_flow/display_references.py @@ -17,14 +17,14 @@ matplotlib.use("Agg") -import h5py import matplotlib.pyplot as plt import numpy as np from matplotlib.colors import ListedColormap +from zea import File -HERE = Path(__file__).parent -_HDF5 = sorted(HERE.glob("*.hdf5")) -DEFAULT_INPUT = _HDF5[0] if _HDF5 else HERE / "phantom_flow.hdf5" +# --- Inputs ----------------------------------------------------------------- +# Defaults stream straight from the published corpus; override with --input. +DEFAULT_INPUT = "hf://nvidia/OpenH-RF/resolvestroke/phantom_flow/phantom_flow.hdf5" # name, colormap, contrast limits (None = auto), signed (diverging max-|.| MIP) # Columns left -> right; mask first. @@ -51,15 +51,19 @@ def mip(vol, axis, signed): def main(): ap = argparse.ArgumentParser(description=__doc__) - ap.add_argument("--input", type=Path, default=DEFAULT_INPUT) + ap.add_argument("--input", default=DEFAULT_INPUT, help="zea HDF5 (hf:// or local path)") ap.add_argument("--output", type=Path, default=None) args = ap.parse_args() - out = args.output or args.input.with_name("phantom_flow_references_montage.png") + out = args.output or Path("phantom_flow_references_montage.png") - with h5py.File(str(args.input), "r") as f: - g = f.custom.computed_references - coords = np.asarray(g["coordinates"][:]) # (nz, ny, nx, 3) = [x, y, z] m - data = {name: np.asarray(g[name][:]).astype(np.float32) for name, *_ in MAPS} + with File(args.input) as f: + # file.dataset(key) is zea's concurrent-read path (file[key] would read serially). + ref = "custom/computed_references" + coords = np.asarray(f.dataset(f"{ref}/coordinates")[...]) # (nz, ny, nx, 3) = [x, y, z] m + data = { + name: np.asarray(f.dataset(f"{ref}/{name}")[...]).astype(np.float32) + for name, *_ in MAPS + } x_mm = coords[0, 0, :, 0] * 1e3 y_mm = coords[0, :, 0, 1] * 1e3 @@ -100,7 +104,9 @@ def main(): axes[0, 0].set_ylabel("x-z MIP\nz [mm]") axes[1, 0].set_ylabel("y-z MIP\nz [mm]") - fig.suptitle(f"Computed reference maps — {args.input.name}", fontsize=13, fontweight="bold") + fig.suptitle( + f"Computed reference maps — {Path(args.input).name}", fontsize=13, fontweight="bold" + ) fig.savefig(str(out), dpi=130) print(f"Saved: {out}") diff --git a/datasets/resolvestroke/phantom_flow/phantom_flow_hdf5_structure.md b/datasets/resolvestroke/phantom_flow/phantom_flow_hdf5_structure.md deleted file mode 100644 index 91fab92..0000000 --- a/datasets/resolvestroke/phantom_flow/phantom_flow_hdf5_structure.md +++ /dev/null @@ -1,83 +0,0 @@ -# phantom_flow.hdf5 — structure & metadata - -- **File:** `/media/m2_fast/data-openh-rf/phantom_flow/phantom_flow.hdf5` -- **Size:** 6.397 GB (6,397,249,242 bytes) -- **Root attributes:** `description`='Contrast-enhanced flow phantom acquisition (flow on/off). 5 clips of 1s (4000 frames at 4kHz) extracted at: baseline-1s, +5s, +10s, +15s, +20s relative to end_of_baseline. Source: Internal lab acquisition on flow phantom (CIRS 769 + ATS523A).'; `zea_version`='0.1.1' -- **Top-level groups:** `custom`, `metadata`, `metrics`, `probe`, `tracks` - -## Tree - -``` -custom/ - computed_references/ - coordinates (192, 171, 171, 3) float32 - mvi (192, 171, 171) float32 - tube_mask (192, 171, 171) uint8 - velocity_radial_avg (192, 171, 171) float32 - velocity_radial_max (192, 171, 171) float32 - velocity_radial_min (192, 171, 171) float32 -metadata/ - annotations/ - anatomy () object - label (20000,) object - credit () object - subject/ - id () object -metrics/ -probe/ - name () object - probe_geometry (256, 3) float32 -tracks/ - track_0/ - data/ - raw_data (20000, 1, 320, 256, 2) int16 - scan/ - azimuth_angles (1,) float32 - center_frequency () float32 - demodulation_frequency () float32 - focus_distances (1,) float32 - initial_times (1,) float32 - polar_angles (1,) float32 - sampling_frequency () float32 - sound_speed () float32 - t0_delays (1, 256) float32 - tgc_gain_curve (320,) float32 - time_to_next_transmit (20000, 1) float32 - transmit_origins (1, 3) float32 - tx_apodizations (1, 256) float32 -``` - -## Group attributes - -- `/custom/computed_references` — `description`='Computed reference maps on a shared 0.6 mm Cartesian grid (see coordinates). Derived from the SYLVER processed exam.' - -## Datasets — shape, dtype, unit, description, value/stats - -| Path | Shape | dtype | Unit | Description | Value / stats | -|---|---|---|---|---|---| -| `/custom/computed_references/coordinates` | `(192, 171, 171, 3)` | `float32` | m | Per-voxel Cartesian positions (x, y, z) in metres for all maps. | min=-0.051, max=0.120497, mean=0.0210657 | -| `/custom/computed_references/mvi` | `(192, 171, 171)` | `float32` | a.u. | Microvascular image (contrast/flow intensity, time-integrated). NaN outside sonified volume, 0 inside cone far from tubes, value within ~1 cm of a tube (smooth transition). | min=0, max=1.51211, mean=0.00350104, NaN=3259292 | -| `/custom/computed_references/tube_mask` | `(192, 171, 171)` | `uint8` | – | Ground-truth tube segmentation: 1 = tube (4 mm diameter), 2 = tube (2 mm diameter); 0 = background. | min=0, max=2, mean=0.00129117 | -| `/custom/computed_references/velocity_radial_avg` | `(192, 171, 171)` | `float32` | m/s | Mean radial (along-beam) flow velocity over slow-time; NaN where no flow. Radial component — divide by cos(Doppler angle ~72 deg) for true speed. | min=-1.44523, max=1.17046, mean=-0.152576, NaN=5571770 | -| `/custom/computed_references/velocity_radial_max` | `(192, 171, 171)` | `float32` | m/s | Maximum radial flow velocity over slow-time; NaN where no flow. | min=-1.44523, max=1.43931, mean=-0.0628551, NaN=5571770 | -| `/custom/computed_references/velocity_radial_min` | `(192, 171, 171)` | `float32` | m/s | Minimum radial flow velocity over slow-time; NaN where no flow. | min=-1.44523, max=0.301096, mean=-0.229416, NaN=5571770 | -| `/metadata/annotations/anatomy` | `()` | `object` | – | Anatomy label. | `'phantom'` | -| `/metadata/annotations/label` | `(20000,)` | `object` | – | Per-frame clip label (bolus time-offset relative to end-of-baseline). | counts: baseline_minus1s×4000, baseline_plus10s×4000, baseline_plus15s×4000, baseline_plus20s×4000, baseline_plus5s×4000 | -| `/metadata/credit` | `()` | `object` | – | Credit or attribution for the dataset. | `'Aitana Waelbroeck, Carl Ferlay, Arthur Chavignon, Maxence Reberol, Vincent Hingot - Resolve Stroke'` | -| `/metadata/subject/id` | `()` | `object` | – | Subject ID. Needed for subject-wise splits. | `'phantom_flow'` | -| `/probe/name` | `()` | `object` | – | Probe model name/identifier. | `'SN2652'` | -| `/probe/probe_geometry` | `(256, 3)` | `float32` | m | Element positions (x, y, z) per element, shape (n_el, 3). | min=-0.0082, max=0.0082, mean=0 | -| `/tracks/track_0/data/raw_data` | `(20000, 1, 320, 256, 2)` | `int16` | – | Raw channel data. | min=-32765, max=32766, mean=11.019 | -| `/tracks/track_0/scan/azimuth_angles` | `(1,)` | `float32` | rad | Azimuthal angles of transmit beams. | `[0.]` | -| `/tracks/track_0/scan/center_frequency` | `()` | `float32` | Hz | Center frequency of the transmit pulse. | `2031250.0` | -| `/tracks/track_0/scan/demodulation_frequency` | `()` | `float32` | Hz | Demodulation frequency. | `2031250.0` | -| `/tracks/track_0/scan/focus_distances` | `(1,)` | `float32` | m | Transmit focus distances. | `[-0.022]` | -| `/tracks/track_0/scan/initial_times` | `(1,)` | `float32` | s | A/D converter start times per transmit. | `[1.083077e-05]` | -| `/tracks/track_0/scan/polar_angles` | `(1,)` | `float32` | rad | Polar angles of transmit beams. | `[0.]` | -| `/tracks/track_0/scan/sampling_frequency` | `()` | `float32` | Hz | Sampling frequency. | `2031250.0` | -| `/tracks/track_0/scan/sound_speed` | `()` | `float32` | m/s | Speed of sound. | `1540.0` | -| `/tracks/track_0/scan/t0_delays` | `(1, 256)` | `float32` | s | Transmit delays per element. | min=0, max=1.76589e-06, mean=6.51021e-07 | -| `/tracks/track_0/scan/tgc_gain_curve` | `(320,)` | `float32` | – | Time-gain-compensation curve. | min=15.4516, max=501.187, mean=423.616 | -| `/tracks/track_0/scan/time_to_next_transmit` | `(20000, 1)` | `float32` | s | Time between transmit events. | min=0.00025, max=4, mean=0.00104995 | -| `/tracks/track_0/scan/transmit_origins` | `(1, 3)` | `float32` | m | Transmit beam origins (x, y, z). | `[0. 0. 0.]` | -| `/tracks/track_0/scan/tx_apodizations` | `(1, 256)` | `float32` | – | Transmit apodization per element. | min=1, max=1, mean=1 | diff --git a/datasets/resolvestroke/phantom_flow/pipeline.yaml b/datasets/resolvestroke/phantom_flow/pipeline.yaml index e5a953b..37c329b 100644 --- a/datasets/resolvestroke/phantom_flow/pipeline.yaml +++ b/datasets/resolvestroke/phantom_flow/pipeline.yaml @@ -1,7 +1,8 @@ parameters: grid_type: polar polar_limits: [-0.45, 0.45] - zlims: [0.01, 0.11] + zlims: [0.01, 0.11] # on-axis depth from the transducer face [m] + distance_to_apex: 0.022 # virtual source behind the array (|focus_distances|) grid_size_z: 420 grid_size_x: 220 grid_size_y: 1 diff --git a/datasets/resolvestroke/phantom_flow/pipeline_PD_3d.yaml b/datasets/resolvestroke/phantom_flow/pipeline_PD_3d.yaml index 5d83ea9..7b75ca2 100644 --- a/datasets/resolvestroke/phantom_flow/pipeline_PD_3d.yaml +++ b/datasets/resolvestroke/phantom_flow/pipeline_PD_3d.yaml @@ -6,9 +6,10 @@ grid: azimuth_limits: [-0.45, 0.45] # x-z fan half-angle [rad] (≈ ±26°) elevation_limits: [-0.45, 0.45] # y-z fan half-angle [rad] zlims: [0.01, 0.11] # on-axis depth range [m] - n_radial: 160 # samples along each ray (~depth) - n_azimuth: 64 # rays in the x-z plane - n_elevation: 64 # rays in the y-z plane + distance_to_apex: 0.022 # virtual source behind the array + n_radial: 200 # samples along each ray (~depth, 0.5 mm) + n_azimuth: 80 # rays in the x-z plane + n_elevation: 80 # rays in the y-z plane parameters: grid_type: polar @@ -25,7 +26,7 @@ pipeline: - name: envelope_detect - name: tissue_highpass # custom op: slow-time high-pass (wall) filter params: - cutoff_hz: 50.0 + cutoff_hz: 100.0 frame_rate_hz: 4000.0 transition_hz: 10.0 - name: power_doppler # custom op: sum of squared envelope over slow time diff --git a/datasets/resolvestroke/phantom_flow/reconstruct.py b/datasets/resolvestroke/phantom_flow/reconstruct.py index 0d24a69..cc76b1a 100644 --- a/datasets/resolvestroke/phantom_flow/reconstruct.py +++ b/datasets/resolvestroke/phantom_flow/reconstruct.py @@ -37,16 +37,14 @@ from zea.beamform.pixelgrid import polar_pixel_grid HERE = Path(__file__).parent -_HDF5 = "hf://nvidia/OpenH-RF/resolvestroke/phantom_flow/phantom_flow.hdf5" -DEFAULT_INPUT = "hf://nvidia/OpenH-RF/resolvestroke/phantom_flow/phantom_flow.hdf5" -CONFIG = HERE / "pipeline.yaml" +CONFIG = "hf://nvidia/OpenH-RF/resolvestroke/phantom_flow/pipeline.yaml" # --- Inputs ----------------------------------------------------------------- # Defaults stream straight from the published corpus. Swap any of these for a # local path to run against your own copy. -INPUT = "hf://nvidia/OpenH-RF/resolvestroke/phantom_flow/phantom_flow.hdf5" +ZEA_FILE = "hf://nvidia/OpenH-RF/resolvestroke/phantom_flow/phantom_flow.hdf5" FRAME = 0 -OUTPUT = None +OUT = HERE / "assets" / f"{Path(ZEA_FILE).stem}_bmode.png" def reverse_tgc(raw, parameters): @@ -61,7 +59,7 @@ def reverse_tgc(raw, parameters): return np.asarray(raw, dtype=np.float32) / tgc.reshape(1, 1, -1, 1, 1) -def build_sector_grids(config, parameters): +def build_sector_grids(config): """Build two perpendicular 2D sector grids (x-z and y-z) for a diverging wave. zea's polar grid only supports the x-z plane (y = 0), so the y-z sector is @@ -72,19 +70,15 @@ def build_sector_grids(config, parameters): in Cartesian (x, y, z) metres; apex is the virtual-source depth in metres. """ p = config.parameters - # Diverging-wave apex = virtual source behind the array (|focus_distances|). - apex = p.get("distance_to_apex") - if apex is None: - focus = float(np.abs(np.ravel(parameters.focus_distances)[0])) - apex = focus if focus > 0 else 0.0 + apex = float(p.distance_to_apex) # virtual source behind the array polar_limits = tuple(float(v) for v in p["polar_limits"]) z0, z1 = (float(v) for v in p["zlims"]) - # polar_pixel_grid measures radius from the apex, so offset the near bound by - # the apex -> the configured zlims are true on-axis depth. + # polar_pixel_grid takes zlims as on-axis depth from the transducer face and adds + # distance_to_apex to the radii itself, so the configured zlims go in unchanged. grid_xz = polar_pixel_grid( polar_limits, - (z0 + apex, z1), + (z0, z1), num_radial_pixels=int(p["grid_size_z"]), num_polar_pixels=int(p["grid_size_x"]), distance_to_apex=apex, @@ -106,17 +100,16 @@ def beamform_sector(pipeline, parameters, raw, grid): def main(): - out_path = OUTPUT or Path(f"{Path(INPUT).stem}_bmode.png") zea.init_device() config = Config.from_path(str(CONFIG)) - with File(str(INPUT)) as f: + with File(str(ZEA_FILE)) as f: parameters = f.load_parameters(**config.parameters) raw = f.data.raw_data[FRAME : FRAME + 1] # (1, n_tx, n_ax, n_el, n_ch) raw = reverse_tgc(raw, parameters) # undo hardware TGC before beamforming - grid_xz, grid_yz, apex = build_sector_grids(config, parameters) + grid_xz, grid_yz, apex = build_sector_grids(config) print(f"raw_data shape : {raw.shape}") print(f"sector grid : {grid_xz.shape} (polar, apex={apex * 1e3:.1f} mm)") @@ -156,8 +149,9 @@ def main(): print(f"y-z sector : {yz_slice.shape}") fig.tight_layout() - fig.savefig(str(out_path), dpi=150, bbox_inches="tight") - print(f"Saved : {out_path}") + Path(OUT).parent.mkdir(parents=True, exist_ok=True) + fig.savefig(str(OUT), dpi=150, bbox_inches="tight") + print(f"Saved : {OUT}") if __name__ == "__main__": diff --git a/datasets/resolvestroke/phantom_flow/reconstruct_PD_3d.py b/datasets/resolvestroke/phantom_flow/reconstruct_PD_3d.py index cb6e8b4..5dcae95 100644 --- a/datasets/resolvestroke/phantom_flow/reconstruct_PD_3d.py +++ b/datasets/resolvestroke/phantom_flow/reconstruct_PD_3d.py @@ -1,11 +1,16 @@ # SPDX-License-Identifier: Apache-2.0 """Reconstruct power-Doppler 3D volumes for each clip and display as MIP montage. +Flow-phantom (CIRS 769 + ATS523A) CEUS acquisition, five 1 s clips of a bolus +wash-in through two tubes. + For each of the 5 clips (4000 frames each), this script: -1. Beamforms N frames onto a real 3D POLAR (sector) volume -> LINEAR envelope. -2. Applies a slow-time high-pass (wall) filter to remove tissue clutter. +1. Beamforms all frames, in blocks, onto a real 3D POLAR (sector) volume -> LINEAR + envelope. The hardware TGC stored in ``raw_data`` is kept. +2. Applies a 100 Hz slow-time high-pass (wall) filter to remove tissue clutter. 3. Integrates power Doppler (sum of squared envelope over slow time). -4. Displays a 5x2 montage: rows = clips, columns = x-z MIP and y-z MIP. +4. Displays a montage: rows = x-z MIP and y-z MIP, columns = clips, plus the + reference ``mvi`` map of the file in the last column. Grid: the acquisition is a matrix-probe DIVERGING wave, so a Cartesian box wastes compute on the corners that fall outside the insonified cone. Instead we beamform @@ -19,11 +24,15 @@ Pipeline: a single pipeline in pipeline_PD_3d.yaml does everything end to end — cast -> beamform -> envelope_detect -> tissue_highpass -> power_doppler. Steps 2-3 are CUSTOM zea ops (`tissue_highpass`, `power_doppler`) registered here with -`@ops_registry`; they act on the frame (slow-time) axis, so each clip's whole stack +`@ops_registry`; they act on the frame (slow-time) axis, so each block of frames is beamformed in one pass (zea patches the grid to bound memory). Custom ops need no merged zea PR — they only need to be defined (imported) before a config referencing them is loaded, which is why the YAML can list them by name. +Runtime: about 14 min for a full acquisition on an RTX 4000 Ada with GPU JAX +(``uv sync --extra gpu`` in the OpenH-RF repo; the plain ``uv sync`` installs a CPU-only +JAX and this script then takes hours). Lower ``N_FRAMES`` for a quick look. + Usage: KERAS_BACKEND=jax uv run --project /path/to/OpenH-RF python reconstruct_PD_3d.py """ @@ -39,14 +48,13 @@ import numpy as np import zea from keras import ops as kops +from matplotlib.colors import PowerNorm from zea import Config, File, Pipeline from zea.internal.registry import ops_registry from zea.ops import Operation HERE = Path(__file__).parent -_HDF5 = "hf://nvidia/OpenH-RF/resolvestroke/phantom_flow/phantom_flow.hdf5" -DEFAULT_INPUT = "hf://nvidia/OpenH-RF/resolvestroke/phantom_flow/phantom_flow.hdf5" -CONFIG = HERE / "pipeline_PD_3d.yaml" +CONFIG = "hf://nvidia/OpenH-RF/resolvestroke/phantom_flow/pipeline_PD_3d.yaml" # Clip definitions (matching the combined file structure) CLIPS = [ @@ -57,14 +65,18 @@ "baseline_plus20s", ] FRAMES_PER_CLIP = 4000 -N_FRAMES_BF = 100 # frames to beamform per clip +N_FRAMES_BF = 4000 # frames to beamform per clip (all of them) +BLOCK_FRAMES = 250 # frames per beamform + wall-filter pass (bounds GPU memory) +DISPLAY_DB = (-25.0, -5.0) # dB range relative to the maximum over the five clips +DISPLAY_GAMMA = 1.25 # gamma on the normalised dB image (>1 darkens the mid-tones) +COLORMAP = "hot" # --- Inputs ----------------------------------------------------------------- -# Defaults stream straight from the published corpus. Swap any of these for a -# local path to run against your own copy. +# Defaults stream straight from the published corpus. Swap INPUT for a local path to +# run against your own copy. INPUT = "hf://nvidia/OpenH-RF/resolvestroke/phantom_flow/phantom_flow.hdf5" N_FRAMES = N_FRAMES_BF -OUTPUT = None +OUT = HERE / f"{Path(INPUT).stem}_PD_montage.png" # The 3D sector-volume grid (limits, depth, resolution) and the high-pass params # all live in pipeline_PD_3d.yaml (`grid:` block and tissue_highpass params). @@ -80,9 +92,10 @@ class TissueHighpass(Operation): """Slow-time high-pass (wall) filter to suppress stationary tissue clutter. Operates along the frame (slow-time) axis with a frequency-domain filter and a - raised-cosine taper around the cutoff. Marked non-jittable because it uses the - NumPy FFT with a data-dependent mask; with the default ``jit_options="ops"`` - the surrounding pipeline still runs the other ops jitted. + raised-cosine taper around the cutoff. Implemented with Keras ops (``rfft`` / + ``irfft``), so it runs on the same device as the beamformer (the GPU when one is + available) and stays jittable: the filter mask only depends on the static frame + count, so the whole pipeline compiles as one program. """ def __init__( @@ -93,29 +106,35 @@ def __init__( time_axis: int = 0, **kwargs, ): - super().__init__(jittable=False, **kwargs) + super().__init__(**kwargs) self.cutoff_hz = cutoff_hz self.frame_rate_hz = frame_rate_hz self.transition_hz = transition_hz self.time_axis = time_axis - def call(self, **kwargs): - data = np.asarray(kops.convert_to_numpy(kwargs[self.key])) - n = data.shape[self.time_axis] - freqs = np.fft.rfftfreq(n, d=1.0 / self.frame_rate_hz) # non-negative + def frequency_mask(self, n_frames): + """Real mask over the ``n_frames // 2 + 1`` non-negative rfft bins. + + 1 above the cutoff, 0 below, with a raised-cosine taper across + [cutoff - transition, cutoff) to reduce ringing. Built with NumPy at trace + time (it is a constant for a given frame count). + """ + freqs = np.fft.rfftfreq(n_frames, d=1.0 / self.frame_rate_hz) mask = (freqs >= self.cutoff_hz).astype(np.float32) - # Raised-cosine taper across [cutoff - transition, cutoff) to reduce ringing. taper = (freqs >= self.cutoff_hz - self.transition_hz) & (freqs < self.cutoff_hz) mask[taper] = 0.5 * ( 1.0 + np.cos(np.pi * (self.cutoff_hz - freqs[taper]) / self.transition_hz) ) + return mask - spectrum = np.fft.rfft(data, axis=self.time_axis) - shape = [1] * data.ndim - shape[self.time_axis] = len(mask) - spectrum = spectrum * mask.reshape(shape) - filtered = np.fft.irfft(spectrum, n=n, axis=self.time_axis).astype(np.float32) - return {self.output_key: filtered} + def call(self, **kwargs): + # Keras FFTs work along the last axis: move slow time there and back. + data = kops.moveaxis(kwargs[self.key], self.time_axis, -1) + n = int(data.shape[-1]) + mask = kops.convert_to_tensor(self.frequency_mask(n), dtype=data.dtype) + real, imag = kops.rfft(data) + filtered = kops.irfft((real * mask, imag * mask), fft_length=n) + return {self.output_key: kops.moveaxis(filtered, -1, self.time_axis)} @ops_registry("power_doppler") @@ -131,17 +150,6 @@ def call(self, **kwargs): return {self.output_key: kops.sum(kops.square(data), axis=self.time_axis)} -def reverse_tgc(raw, parameters): - """Undo the hardware time-gain compensation baked into ``raw_data``. - - zea stores ``scan/tgc_gain_curve`` (n_ax,) as "the TGC applied to every sample; - divide by this curve to undo it". A plain per-axial-sample divide here keeps the - zea pipeline fully standard (no custom op needed). - """ - tgc = np.asarray(parameters.tgc_gain_curve, dtype=np.float32) # (n_ax,) - return np.asarray(raw, dtype=np.float32) / tgc.reshape(1, 1, -1, 1, 1) - - # --------------------------------------------------------------------------- # # Sector-volume grid. # --------------------------------------------------------------------------- # @@ -182,16 +190,24 @@ def sector_plane_coords(r, angle, apex): return lat, z +def load_reference_mips(f): + """x-z and y-z MIPs of the reference ``mvi`` map with their Cartesian extents (mm).""" + ref = "custom/computed_references" + coords = np.asarray(f.dataset(f"{ref}/coordinates")[...]) # (nz, ny, nx, 3) m + mvi = np.asarray(f.dataset(f"{ref}/mvi")[...], dtype=np.float32) + x_mm, y_mm, z_mm = coords[0, 0, :, 0] * 1e3, coords[0, :, 0, 1] * 1e3, coords[:, 0, 0, 2] * 1e3 + with np.errstate(all="ignore"): + xz, yz = np.nanmax(mvi, axis=1), np.nanmax(mvi, axis=2) + ext_xz = [x_mm.min(), x_mm.max(), z_mm.max(), z_mm.min()] + ext_yz = [y_mm.min(), y_mm.max(), z_mm.max(), z_mm.min()] + return xz, yz, ext_xz, ext_yz + + def main(): - out_path = OUTPUT or Path("phantom_flow_PD_montage.png") n_bf = N_FRAMES zea.init_device() config = Config.from_path(str(CONFIG)) - g = config["grid"] # 3D sector-volume spec - azimuth_limits = tuple(float(v) for v in g["azimuth_limits"]) - elevation_limits = tuple(float(v) for v in g["elevation_limits"]) - zlims = tuple(float(v) for v in g["zlims"]) # One pipeline, end to end: cast -> beamform -> envelope -> tissue_highpass -> # power_doppler. The custom slow-time ops resolve by name because they are @@ -199,15 +215,23 @@ def main(): pipeline = Pipeline.from_config(config) hp_cutoff = pipeline["tissue_highpass"].cutoff_hz + pd_volumes, (r, alpha, beta, apex), ref = beamform_clips(config, pipeline, hp_cutoff, n_bf) + + render(pd_volumes, r, alpha, beta, apex, ref, n_bf, hp_cutoff) + + +def beamform_clips(config, pipeline, hp_cutoff, n_bf): + """Beamform every clip of INPUT to a power-Doppler volume; also read the reference maps.""" + g = config["grid"] # 3D sector-volume spec + azimuth_limits = tuple(float(v) for v in g["azimuth_limits"]) + elevation_limits = tuple(float(v) for v in g["elevation_limits"]) + zlims = tuple(float(v) for v in g["zlims"]) + with File(str(INPUT)) as f: parameters = f.load_parameters(**config.parameters) total_frames = f.data.raw_data.shape[0] - # Apex = virtual source behind the array; from the config or |focus_distances|. - apex = g.get("distance_to_apex") - if apex is None: - focus = float(np.abs(np.ravel(parameters.focus_distances)[0])) - apex = focus if focus > 0 else 0.0 + apex = float(g["distance_to_apex"]) # virtual source behind the array grid, (r, alpha, beta) = build_sector_volume_grid( azimuth_limits, @@ -220,15 +244,18 @@ def main(): ) flatgrid = grid.reshape(-1, 3) n_vox = flatgrid.shape[0] - print(f"Total frames: {total_frames}, beamforming {n_bf} per clip") + print( + f"Total frames: {total_frames}, beamforming {n_bf} per clip, blocks of {BLOCK_FRAMES}" + ) print( f"Sector volume: {grid.shape[:-1]} = {n_vox} voxels (polar, apex={apex * 1e3:.1f} mm)" ) inputs = pipeline.prepare_parameters(parameters, grid=grid, flatgrid=flatgrid) - # Process each clip: the whole frame stack goes through the pipeline in one - # pass, so the slow-time ops see all frames. + # Process each clip block by block: every block goes through the pipeline in + # one pass (the slow-time ops see BLOCK_FRAMES frames) and the power-Doppler + # volumes of the blocks are summed. pd_volumes = [] # list of (n_r, n_az, n_el) power-Doppler volumes for clip_idx, clip_name in enumerate(CLIPS): clip_start = clip_idx * FRAMES_PER_CLIP @@ -239,58 +266,85 @@ def main(): print(f"\n Clip '{clip_name}': frames {start}-{end} (HP cutoff={hp_cutoff} Hz)") - raw = f.data.raw_data[start:end] # (n_bf, n_tx, n_ax, n_el, n_ch) - raw = reverse_tgc(raw, parameters) # undo hardware TGC before beamforming - outputs = pipeline(**{pipeline.key: raw}, **inputs, return_numpy=True) - pd = np.asarray(outputs[pipeline.output_key]) # (n_r, n_az, n_el) + pd = np.zeros(grid.shape[:-1], dtype=np.float64) + for block_start in range(start, end, BLOCK_FRAMES): + raw = f.data.raw_data[block_start : block_start + BLOCK_FRAMES] + outputs = pipeline(**{pipeline.key: raw}, **inputs, return_numpy=True) + pd += np.asarray(outputs[pipeline.output_key], dtype=np.float64) pd_volumes.append(pd) print(f" PD volume: {pd.shape}, linear max={pd.max():.4g}") - # Normalize all PD volumes to the same global max (common dB scale) + ref = load_reference_mips(f) + return pd_volumes, (r, alpha, beta, apex), ref + + +def render(pd_volumes, r, alpha, beta, apex, ref, n_bf, hp_cutoff): + """Montage of all clips (x-z / y-z MIPs) plus the reference mvi column.""" + ref_xz, ref_yz, ext_xz, ext_yz = ref + + # Normalize all PD volumes to the same global max (common dB scale). Voxels the + # beamformer leaves at exactly zero (outside its receive aperture) become NaN. global_max = max(pv.max() for pv in pd_volumes) - pd_db_volumes = [ - 10.0 * np.log10(np.maximum(pd, global_max * 1e-35) / global_max) for pd in pd_volumes - ] + with np.errstate(all="ignore"): + pd_db_volumes = [ + 10.0 * np.log10(np.where(pd > 0, pd / global_max, np.nan)) for pd in pd_volumes + ] + + vmin, vmax = DISPLAY_DB + norm = PowerNorm(DISPLAY_GAMMA, vmin=vmin, vmax=vmax) + cmap = plt.get_cmap(COLORMAP).copy() + cmap.set_bad("#101010") # Scan-conversion coordinates for the two MIP planes (central plane of the fan) xz_lat, xz_z = sector_plane_coords(r, alpha, apex) # (n_r, n_az) yz_lat, yz_z = sector_plane_coords(r, beta, apex) # (n_r, n_el) - vmin, vmax = -30.0, 0.0 zea.visualize.set_mpl_style() - fig, axes = plt.subplots(2, 5, figsize=(24, 10)) - - for col, (clip_name, pd_vol) in enumerate(zip(CLIPS, pd_db_volumes)): - # x-z MIP: project out elevation (max over the y-fan) - xz_mip = np.max(pd_vol, axis=2) # (n_r, n_az) - ax = axes[0, col] - ax.pcolormesh(xz_lat, xz_z, xz_mip, cmap="hot", vmin=vmin, vmax=vmax, shading="auto") - ax.set_aspect("equal") - ax.invert_yaxis() - ax.set_title(f"{clip_name}\nx-z MIP", fontsize=9) - ax.set_xlabel("x [mm]") - if col == 0: - ax.set_ylabel("z [mm]") - - # y-z MIP: project out azimuth (max over the x-fan) - yz_mip = np.max(pd_vol, axis=1) # (n_r, n_el) - ax = axes[1, col] - ax.pcolormesh(yz_lat, yz_z, yz_mip, cmap="hot", vmin=vmin, vmax=vmax, shading="auto") - ax.set_aspect("equal") - ax.invert_yaxis() - ax.set_title(f"{clip_name}\ny-z MIP", fontsize=9) - ax.set_xlabel("y [mm]") - if col == 0: - ax.set_ylabel("z [mm]") + fig, axes = plt.subplots(2, len(CLIPS) + 1, figsize=(4.4 * (len(CLIPS) + 1), 9.5)) + + with np.errstate(all="ignore"): + for col, (clip_name, pd_vol) in enumerate(zip(CLIPS, pd_db_volumes)): + # x-z MIP: project out elevation (max over the y-fan) + xz_mip = np.nanmax(pd_vol, axis=2) # (n_r, n_az) + ax = axes[0, col] + ax.pcolormesh(xz_lat, xz_z, xz_mip, cmap=cmap, norm=norm, shading="auto") + ax.set_aspect("equal") + ax.invert_yaxis() + ax.set_title(f"{clip_name}\nx-z MIP", fontsize=9) + ax.set_xlabel("x [mm]") + if col == 0: + ax.set_ylabel("z [mm]") + + # y-z MIP: project out azimuth (max over the x-fan) + yz_mip = np.nanmax(pd_vol, axis=1) # (n_r, n_el) + ax = axes[1, col] + ax.pcolormesh(yz_lat, yz_z, yz_mip, cmap=cmap, norm=norm, shading="auto") + ax.set_aspect("equal") + ax.invert_yaxis() + ax.set_title(f"{clip_name}\ny-z MIP", fontsize=9) + ax.set_xlabel("y [mm]") + if col == 0: + ax.set_ylabel("z [mm]") + + # Reference mvi map of the same file, for comparison (its own linear scale). + vmax_ref = float(np.nanpercentile(ref_xz, 99.9)) + for row, (img, ext, lab) in enumerate([(ref_xz, ext_xz, "x"), (ref_yz, ext_yz, "y")]): + ax = axes[row, -1] + ax.imshow(img, extent=ext, cmap=cmap, vmin=0, vmax=vmax_ref, aspect="equal") + ax.set_title(f"reference mvi\n{lab}-z MIP", fontsize=9) + ax.set_xlabel(f"{lab} [mm]") + ax.set_xlim(axes[row, 0].get_xlim()) + ax.set_ylim(axes[row, 0].get_ylim()) fig.suptitle( - f"Power Doppler 3D polar MIPs — {n_bf} frames/clip, HP cutoff {hp_cutoff} Hz", + f"Power Doppler 3D polar MIPs — {n_bf} frames/clip, HP cutoff {hp_cutoff} Hz, " + f"{vmin:.0f}..{vmax:.0f} dB, gamma {DISPLAY_GAMMA}", fontsize=14, fontweight="bold", ) fig.tight_layout() - fig.savefig(str(out_path), dpi=150, bbox_inches="tight") - print(f"\nSaved montage: {out_path}") + fig.savefig(str(OUT), dpi=150, bbox_inches="tight") + print(f"\nSaved montage: {OUT}") if __name__ == "__main__": diff --git a/datasets/resolvestroke/phantom_mp/README.md b/datasets/resolvestroke/phantom_mp/README.md index f171f65..23aa4fc 100644 --- a/datasets/resolvestroke/phantom_mp/README.md +++ b/datasets/resolvestroke/phantom_mp/README.md @@ -1,5 +1,6 @@ --- -pretty_name: "OpenH-RF - Resolve Stroke Multi-tissue Phantom (CIRS 040GSE)" +name: resolvestroke-phantom-mp +pretty_name: "Resolve Stroke Multi-tissue Phantom (CIRS 040GSE)" license: cc-by-4.0 task_categories: - other @@ -16,24 +17,24 @@ size_categories: - 1K (contact) +- Vincent Hingot +- Resolve Stroke, 29 Rue du Faubourg Saint-Jacques, 75014 Paris ## Dataset Creation Date @@ -41,39 +42,33 @@ Contact email: maxence.reberol@resolvestroke.com ## License / Terms of Use -CC BY 4.0 (see `LICENCE`). Data is released under Creative Commons Attribution -4.0 International, which permits commercial use with attribution. (The Python -scripts in this directory carry their own `SPDX-License-Identifier: Apache-2.0` -header; the dataset itself is CC BY 4.0.) +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage -Beamforming and reconstruction research (RFP task group 6.1, Generalized -Reconstruction): resolution and contrast assessment, compressed sensing, -super-resolution, and matrix-probe diverging-wave 3D beamforming on a phantom with -known target structures. +Beamforming and reconstruction research (RFP task group 6.1, Generalized Reconstruction): resolution and contrast assessment, compressed sensing, super-resolution, and matrix-probe diverging-wave 3D beamforming on a phantom with known target structures. ## Dataset Characterization - Data collection method: Phantom (CIRS 040GSE multi-purpose, multi-tissue imaging phantom) - Labeling method: None (single static acquisition; no per-frame labels) -- Acquisition system: SYLVER (Resolve Stroke's ultrasound device). 32×32 matrix - probe, 0.50 mm pitch, 0.30 mm kerf; transmit center frequency ≈ 2.031 MHz, sound - speed 1540 m/s. Diverging-wave transmits; receive sub-apertures flattened into 256 - virtual elements. Channel data is DDC (baseband) IQ, so `sampling_frequency` - (≈ 2.031 MHz) is the post-decimation IQ rate and equals `demodulation_frequency`. - This is not an RF Nyquist rate (`n_ch = 2`, complex I/Q). +- Acquisition system: SYLVER (Resolve Stroke's ultrasound device). 32×32 matrix probe, 0.50 mm pitch, 0.30 mm kerf; transmit center frequency ≈ 2.031 MHz, sound speed 1540 m/s. Diverging-wave transmits; receive sub-apertures flattened into 256 virtual elements. Channel data is DDC (baseband) IQ, so `sampling_frequency` (≈ 2.031 MHz) is the post-decimation IQ rate and equals `demodulation_frequency`. + +## Processing the Dataset + +The acquisitions can be processed with the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/resolvestroke/phantom_mp/reconstruct.py) as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF), together with the `pipeline.yaml` definition in this folder and the [zea library](https://github.com/tue-bmd/zea). The script streams the data from the Hugging Face Hub. + +```bash +uv run --project /path/to/OpenH-RF python reconstruct.py +``` + +The Python scripts carry their own `SPDX-License-Identifier: Apache-2.0` header; the dataset itself is CC BY 4.0. ## Dataset Format -Single zea HDF5 file (`phantom_mp.hdf5`), one track holding the raw channel data -and scan parameters. Originally written with `zea.File.create` (zea v0.1.1), validated -`compliant: true` against `validate_zea_spec.py`. `data/raw_data` is DDC IQ (last -axis [I, Q]). The hardware time-gain compensation is baked into `raw_data`; -`scan/tgc_gain_curve` is the applied (non-linear) gain per axial sample; divide by -it to recover true channel amplitudes. `reconstruct.py` divides `raw_data` by this -curve before beamforming (the `zea.Pipeline` itself stays standard; the reversal is -a plain array step). +[zea v0.1.6](https://github.com/tue-bmd/zea) + +Single zea HDF5 file (`phantom_mp.hdf5`), one track holding the raw channel data and scan parameters, in the zea HDF5 format, root `zea_version` 0.1.6, validated `compliant: true` against `validate_zea_spec.py`. `data/raw_data` is DDC IQ (last axis [I, Q]). The hardware time-gain compensation is baked into `raw_data`; `scan/tgc_gain_curve` is the applied (non-linear) gain per axial sample; divide by it to recover true channel amplitudes. `reconstruct.py` divides `raw_data` by this curve before beamforming (the `zea.Pipeline` itself stays standard; the reversal is a plain array step). ## Dataset Quantification @@ -108,27 +103,13 @@ a plain array step). ## Data Validation -`reconstruct.py` first divides `raw_data` by `scan/tgc_gain_curve` (reverse TGC), -then runs a standard `zea.Pipeline` (cast → DAS beamform → envelope → normalize → -log-compress) defined in `pipeline.yaml` to reconstruct a B-mode from the IQ channel -data. Because the probe is a 2D matrix array insonified by a single diverging-wave -transmit, `reconstruct.py` beamforms on polar (sector) grids and renders two -perpendicular sector B-modes, the x-z plane (y = 0) and the y-z plane (x = 0), side -by side: - -![Reference B-mode (two perpendicular sectors)](phantom_mp_bmode.png) - -Run: `uv run --project /path/to/OpenH-RF python reconstruct.py` +`reconstruct.py` first divides `raw_data` by `scan/tgc_gain_curve` (reverse TGC), then runs a standard `zea.Pipeline` (cast → DAS beamform → envelope → normalize → log-compress) defined in `pipeline.yaml` to reconstruct a B-mode from the IQ channel data. Because the probe is a 2D matrix array insonified by a single diverging-wave transmit, `reconstruct.py` beamforms on polar (sector) grids and renders two perpendicular sector B-modes, the x-z plane (y = 0) and the y-z plane (x = 0), side by side, as shown at the top of this card. ## Known Issues -- `raw_data` has the hardware TGC baked in; `scan/tgc_gain_curve` is that (non-linear) - applied curve. The reconstruction script divides by the curve before beamforming; - a downstream user reconstructing directly must divide by the curve too. -- `sampling_frequency ≈ center_frequency` because the data is DDC baseband IQ (see - Dataset Characterization), not an RF acquisition. -- The proposed 3D phantom-geometry reference (known target positions for the CIRS - 040GSE) is not yet included. +- `raw_data` has the hardware TGC baked in; `scan/tgc_gain_curve` is that (non-linear) applied curve. The reconstruction script divides by the curve before beamforming; a downstream user reconstructing directly must divide by the curve too. +- `sampling_frequency ≈ center_frequency` because the data is DDC baseband IQ (see Dataset Characterization), not an RF acquisition. +- The proposed 3D phantom-geometry reference (known target positions for the CIRS 040GSE) is not yet included. ## Ethical Considerations diff --git a/datasets/resolvestroke/phantom_mp/assets/main.png b/datasets/resolvestroke/phantom_mp/assets/main.png new file mode 100644 index 0000000..7d8be44 Binary files /dev/null and b/datasets/resolvestroke/phantom_mp/assets/main.png differ diff --git a/datasets/resolvestroke/phantom_mp/pipeline.yaml b/datasets/resolvestroke/phantom_mp/pipeline.yaml index e5a953b..37c329b 100644 --- a/datasets/resolvestroke/phantom_mp/pipeline.yaml +++ b/datasets/resolvestroke/phantom_mp/pipeline.yaml @@ -1,7 +1,8 @@ parameters: grid_type: polar polar_limits: [-0.45, 0.45] - zlims: [0.01, 0.11] + zlims: [0.01, 0.11] # on-axis depth from the transducer face [m] + distance_to_apex: 0.022 # virtual source behind the array (|focus_distances|) grid_size_z: 420 grid_size_x: 220 grid_size_y: 1 diff --git a/datasets/resolvestroke/phantom_mp/reconstruct.py b/datasets/resolvestroke/phantom_mp/reconstruct.py index 6d5b1be..4cbc589 100644 --- a/datasets/resolvestroke/phantom_mp/reconstruct.py +++ b/datasets/resolvestroke/phantom_mp/reconstruct.py @@ -35,37 +35,34 @@ from zea.beamform.pixelgrid import polar_pixel_grid HERE = Path(__file__).parent -_HDF5 = "hf://nvidia/OpenH-RF/resolvestroke/phantom_mp/phantom_mp.hdf5" -DEFAULT_INPUT = "hf://nvidia/OpenH-RF/resolvestroke/phantom_mp/phantom_mp.hdf5" -CONFIG = HERE / "pipeline.yaml" +CONFIG = "hf://nvidia/OpenH-RF/resolvestroke/phantom_mp/pipeline.yaml" # --- Inputs ----------------------------------------------------------------- # Defaults stream straight from the published corpus. Swap any of these for a # local path to run against your own copy. -INPUT = "hf://nvidia/OpenH-RF/resolvestroke/phantom_mp/phantom_mp.hdf5" +ZEA_FILE = "hf://nvidia/OpenH-RF/resolvestroke/phantom_mp/phantom_mp.hdf5" FRAME = 0 -OUTPUT = None +OUT = HERE / "assets" / f"{Path(ZEA_FILE).stem}_bmode.png" def sector_grids(p, apex): """Two perpendicular diverging-wave sector fans, each (n_radial, n_angular, 3) in Cartesian metres. The y-z fan is the x-z fan rotated 90 deg about z (swap - x and y). polar_pixel_grid measures radius from the apex, so the near bound is - offset by the apex to keep the configured zlims as true on-axis depth.""" + x and y). polar_pixel_grid takes zlims as on-axis depth and adds the apex to the + radii itself, so the configured zlims go in unchanged.""" lims = tuple(float(v) for v in p["polar_limits"]) z0, z1 = (float(v) for v in p["zlims"]) - xz = polar_pixel_grid(lims, (z0 + apex, z1), int(p["grid_size_z"]), int(p["grid_size_x"]), apex) + xz = polar_pixel_grid(lims, (z0, z1), int(p["grid_size_z"]), int(p["grid_size_x"]), apex) yz = xz.copy() yz[..., 0], yz[..., 1] = 0.0, xz[..., 0] return np.stack([xz, yz]) # (2, n_r, n_theta, 3) def main(): - out_path = OUTPUT or Path(f"{Path(INPUT).stem}_bmode.png") zea.init_device() config = Config.from_path(str(CONFIG)) - with File(str(INPUT)) as f: + with File(str(ZEA_FILE)) as f: parameters = f.load_parameters(**config.parameters) raw = f.data.raw_data[FRAME : FRAME + 1] # (1, n_tx, n_ax, n_el, n_ch) @@ -73,7 +70,7 @@ def main(): tgc = np.asarray(parameters.tgc_gain_curve, np.float32) raw = np.asarray(raw, np.float32) / tgc.reshape(1, 1, -1, 1, 1) - apex = float(np.abs(np.ravel(parameters.focus_distances)[0])) # virtual-source depth + apex = float(config.parameters.distance_to_apex) # virtual source behind the array grid = sector_grids(config.parameters, apex) # (2, n_r, n_theta, 3): [x-z, y-z] # Beamform both fans in one pass; reshape_grid restores the (2, n_r, n_theta) shape. @@ -110,8 +107,9 @@ def main(): fig.colorbar(pm, cax=cax, label="dB") fig.tight_layout() - fig.savefig(str(out_path), dpi=150, bbox_inches="tight") - print(f"Saved: {out_path}") + Path(OUT).parent.mkdir(parents=True, exist_ok=True) + fig.savefig(str(OUT), dpi=150, bbox_inches="tight") + print(f"Saved: {OUT}") if __name__ == "__main__": diff --git a/datasets/resolvestroke/saddle/README.md b/datasets/resolvestroke/saddle/README.md index 8d4795d..2b8b9b4 100644 --- a/datasets/resolvestroke/saddle/README.md +++ b/datasets/resolvestroke/saddle/README.md @@ -1,5 +1,6 @@ --- -pretty_name: "OpenH-RF - Resolve Stroke Saddle-Array Reference B-modes (Transcranial CEUS + Phantom)" +name: resolvestroke-saddle +pretty_name: "Resolve Stroke Saddle-Array Reference B-modes (Transcranial CEUS + Phantom)" license: cc-by-4.0 task_categories: - other @@ -19,32 +20,26 @@ size_categories: - n<1K --- -# OpenH-RF - Resolve Stroke Saddle-Array Reference B-modes +# Resolve Stroke Saddle-Array Reference B-modes -## Dataset Description +![Saddle-array B-modes for all 21 datasets](../assets/saddle_bmode_montage.png) -Single-frame anatomical B-mode acquisitions from the "saddle" imaging sequence of -Resolve Stroke's SYLVER ultrasound device, using a 32×32 matrix probe. Each raw -acquisition contains, alongside the multi-thousand-frame contrast-enhanced -ultrasound (CEUS) sequence, one -wide-angle diverging-wave frame (the *saddle* sequence: `x_ang` −24°…+24° in nine -steps, no elevation steering, cylindrical elevation focus) received on the full -1024-element aperture. Beamformed, this single frame gives a sector B-mode of the -imaging plane: the structural reference view acquired at the same probe placement -as the contrast (CEUS) recording. - -This directory contains one such reference B-mode per dataset: 20 clinical -transcranial acquisitions (SCULPT study) and 1 static matrix-probe imaging phantom -(21 files total). It is the structural companion to the OpenH-RF Resolve Stroke -clinical CEUS clip submission, and the two use the same anonymized subject codes. +*Saddle-array B-modes of all 21 files in [`saddle/data/`](https://huggingface.co/datasets/nvidia/OpenH-RF/tree/main/resolvestroke/saddle/data), one panel per dataset; the phantom (`PMP01`) shows a regular column of point targets.* -## Dataset Contributor(s) +## Dataset Description -Aitana Waelbroeck\*, Carl Ferlay\*, Arthur Chavignon\*, Maxence Reberol\*, Vincent Hingot\* +Single-frame anatomical B-mode acquisitions from the "saddle" imaging sequence of Resolve Stroke's SYLVER ultrasound device, using a 32×32 matrix probe. Each raw acquisition contains, alongside the multi-thousand-frame contrast-enhanced ultrasound (CEUS) sequence, one wide-angle diverging-wave frame (the *saddle* sequence: `x_ang` −24°…+24° in nine steps, no elevation steering, cylindrical elevation focus) received on the full 1024-element aperture through four consecutive 256-element receive events per transmit. Beamformed, this single frame gives a sector B-mode of the imaging plane: the structural reference view acquired at the same probe placement as the contrast (CEUS) recording. -\* Resolve Stroke (29 Rue du Faubourg Saint-Jacques, 75014 Paris) +This directory contains one such reference B-mode per dataset: 20 clinical transcranial acquisitions (SCULPT study) and 1 static matrix-probe imaging phantom (21 files total). It is the structural companion to the OpenH-RF Resolve Stroke clinical CEUS clip submission, and the two use the same anonymized subject codes. + +## Dataset Contributor(s) -Contact email: maxence.reberol@resolvestroke.com +- Aitana Waelbroeck +- Carl Ferlay +- Arthur Chavignon +- Maxence Reberol (contact) +- Vincent Hingot +- Resolve Stroke, 29 Rue du Faubourg Saint-Jacques, 75014 Paris ## Dataset Creation Date @@ -52,48 +47,39 @@ Contact email: maxence.reberol@resolvestroke.com ## License / Terms of Use -CC BY 4.0 (see `LICENCE`). Data is released under Creative Commons Attribution -4.0 International, which permits commercial use with attribution. (The Python -scripts in this directory carry their own `SPDX-License-Identifier: Apache-2.0` -header; the dataset itself is CC BY 4.0.) +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage -Matrix-probe diverging-wave beamforming research, anatomical B-mode -reconstruction, and structural reference for the companion transcranial CEUS -flow/perfusion datasets (OpenH-RF request-for-proposals task group 6.2, Blood Flow). +Matrix-probe diverging-wave beamforming research, anatomical B-mode reconstruction, and structural reference for the companion transcranial CEUS flow/perfusion datasets (OpenH-RF request-for-proposals task group 6.2, Blood Flow). ## Dataset Characterization -- Data collection method: 10 human subjects, 20 acquisitions (2 per subject: - different side and/or session), transcranial through the temporal acoustic - window, plus 1 static imaging phantom (wire/point targets). +- Data collection method: 10 human subjects, 20 acquisitions (2 per subject: different side and/or session), transcranial through the temporal acoustic window, plus 1 static imaging phantom (wire/point targets). - Labeling method: none (a single unlabeled anatomical frame per file). -- Acquisition system: SYLVER (Resolve Stroke's ultrasound device). SN2672 32×32 - matrix probe, 0.50 mm pitch, 0.30 mm kerf; transmit center frequency ≈ 2.031 MHz, - sound speed 1540 m/s. The *saddle* sequence transmits 9 diverging waves steered - `x_ang` −24°…+24° (6° steps), `y_ang = 0`, virtual source at −50 mm, with an - elevation (saddle) focus at 120 mm. Receive is the full probe, packed as 4 - sub-apertures of 256 elements each and flattened into 1024 virtual elements - (element index = `aperture·256 + element`, matching PyCompute's `apElemPos` - ordering). Channel data is digital down-converted (DDC) baseband IQ, so `sampling_frequency` - (≈ 2.031 MHz) is the post-decimation IQ rate and equals `demodulation_frequency`; - it is not an RF Nyquist rate (`n_ch = 2`, complex I/Q). +- Acquisition system: SYLVER (Resolve Stroke's ultrasound device). SN2672 32×32 matrix probe, 0.50 mm pitch, 0.30 mm kerf; transmit center frequency ≈ 2.031 MHz, sound speed 1540 m/s. The *saddle* sequence transmits 9 diverging waves steered `x_ang` −24°…+24° (6° steps), `y_ang = 0`, virtual source at −50 mm, with an elevation (saddle) focus at 120 mm. The system receives 256 channels at a time, so each transmit is fired four times in a row, once per 256-element receive sub-aperture; the four receptions are stacked into 1024 virtual elements (element index = `aperture·256 + element`, matching PyCompute's `apElemPos` ordering), giving the full probe on receive. Channel data is digital down-converted (DDC) baseband IQ, so `sampling_frequency` (≈ 2.031 MHz) is the post-decimation IQ rate and equals `demodulation_frequency`. + +## Processing the Dataset + +The acquisitions can be processed with the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/resolvestroke/saddle/reconstruct.py) as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF), together with the `pipeline.yaml` definition in this folder and the [zea library](https://github.com/tue-bmd/zea). The script streams the data from the Hugging Face Hub. + +```bash +uv run --project /path/to/OpenH-RF python reconstruct.py +``` + +`reconstruct.py` streams `PMP01.hdf5` from the Hub by default; set `ZEA_FILE` at the top of the script to another of the 21 files (or a local path). The B-mode PNG is written to `assets/_bmode.png`. + +The Python scripts carry their own `SPDX-License-Identifier: Apache-2.0` header; the dataset itself is CC BY 4.0. ## Dataset Format -One zea HDF5 file per dataset under `data/`, each holding a single frame of DDC IQ -channel data (`data/raw_data`, last axis `[I, Q]`). Originally written with `zea.File.create` -(zea v0.1.1), validated `compliant: true` against `validate_zea_spec.py`. +[zea v0.1.6](https://github.com/tue-bmd/zea) + +One zea HDF5 file per dataset under `data/`, each holding a single frame of DDC IQ channel data (`data/raw_data`, last axis `[I, Q]`), in the zea HDF5 format, root `zea_version` 0.1.6, validated `compliant: true` against `validate_zea_spec.py`. -Files are named `[-][-].hdf5` (anonymized subject code, imaging -side, and a sequential index when a subject/side has more than one acquisition); -the phantom is `PMP01.hdf5`. +Files are named `[-][-].hdf5` (anonymized subject code, imaging side, and a sequential index when a subject/side has more than one acquisition); the phantom is `PMP01.hdf5`. -The hardware time-gain compensation is baked into `raw_data`; `scan/tgc_gain_curve` -is the applied (non-linear) gain per axial sample. `reconstruct.py` beamforms the -stored IQ directly (it does **not** undo the TGC, so deeper structure stays bright); -divide `raw_data` by `scan/tgc_gain_curve` first to recover true channel amplitudes. +The hardware time-gain compensation is baked into `raw_data`; `scan/tgc_gain_curve` is the applied (non-linear) gain per axial sample. `reconstruct.py` beamforms the stored IQ directly (it does **not** undo the TGC, so deeper structure stays bright); divide `raw_data` by `scan/tgc_gain_curve` first to recover true channel amplitudes. ## Dataset Quantification @@ -124,54 +110,22 @@ divide `raw_data` by `scan/tgc_gain_curve` first to recover true channel amplitu ## Subject Metadata - Type: 10 human subjects (20 acquisitions) + 1 phantom -- Subject IDs: `SP01`–`SP10` (anonymized), `PMP01` (phantom). Each subject - contributes 2 files (different side and/or session). +- Subject IDs: `SP01`–`SP10` (anonymized), `PMP01` (phantom). Each subject contributes 2 files (different side and/or session). - Anatomy: Brain (transcranial), via the temporal acoustic window. ## Data Validation -`reconstruct.py` runs a standard `zea.Pipeline` (cast → DAS beamform → envelope → -normalize → log-compress), configured in `pipeline.yaml`, to reconstruct a B-mode -from the IQ channel data. The probe is a 2D matrix array insonified by -diverging-wave transmits, so it beamforms on a polar (sector) grid (a fan spanning -the divergence angle in the x-z plane at y = 0, apex at the virtual source) and -scan-converts the result. - -The montage below shows the reconstruction of all 21 files, one panel per dataset. -The phantom (PMP01) shows a regular column of point targets, which checks the depth -scaling and geometry. - -![Saddle-array B-modes for all 21 datasets](saddle_bmode_montage.png) - -Set up the OpenH-RF environment once (clone -and run `uv sync` in it), then reconstruct any file: - -``` -uv run --project /path/to/OpenH-RF python reconstruct.py --input data/.hdf5 -``` +`reconstruct.py` runs a standard `zea.Pipeline` (cast → DAS beamform → envelope → normalize → log-compress), configured in `pipeline.yaml`, to reconstruct a B-mode from the IQ channel data. The probe is a 2D matrix array insonified by diverging-wave transmits, so it beamforms on a polar (sector) grid (a fan spanning the divergence angle in the x-z plane at y = 0, apex at the virtual source) and scan-converts the result. -The B-mode PNG is written to `outputs/_bmode.png` (override with `--output`). -With no `--input`, the first file under `data/` is used. +The montage at the top of this card shows the reconstruction of all 21 files, one panel per dataset. The phantom (PMP01) shows a regular column of point targets, which checks the depth scaling and geometry. ## Ethical Considerations -Human-subject data. Channel data was acquired during the SCULPT clinical study -(National registration number (ID RCB): 2025-A00023-46; NCT07324421), a prospective -monocentric trial conducted at CHU Gui de Chauliac (Montpellier, France) under approval -from the French ethics committee (Comité de Protection des Personnes, CPP), comparing -cerebral perfusion from Resolve Stroke's SYLVER ultrasound system with routine perfusion -CT in ICU/CCU patients, using SonoVue® as the contrast agent. +Human-subject data. Channel data was acquired during the SCULPT clinical study (National registration number (ID RCB): 2025-A00023-46; NCT07324421), a prospective monocentric trial conducted at CHU Gui de Chauliac (Montpellier, France) under approval from the French ethics committee (Comité de Protection des Personnes, CPP), comparing cerebral perfusion from Resolve Stroke's SYLVER ultrasound system with routine perfusion CT in ICU/CCU patients, using SonoVue® as the contrast agent. -The dataset contains no direct personal identifiers: subjects are referenced only by -an anonymized study code, and no name, date of birth, or operator identifiers are -stored in the released files. The only hardware field is the probe model (`SN2672`), -which is identical across all files and identifies the study device, not any subject. +The dataset contains no direct personal identifiers: subjects are referenced only by an anonymized study code, and no name, date of birth, or operator identifiers are stored in the released files. The only hardware field is the probe model (`SN2672`), which is identical across all files and identifies the study device, not any subject. ## Known Issues -- `raw_data` has the hardware TGC baked in; `scan/tgc_gain_curve` is that (non-linear) - applied curve. `reconstruct.py` beamforms the stored IQ as-is (leaving the TGC in, - which keeps deep structure bright); a user wanting true channel amplitudes must - divide `raw_data` by the curve. -- `sampling_frequency ≈ center_frequency` because the data is DDC baseband IQ (see - Dataset Characterization), not an RF acquisition. +- `raw_data` has the hardware TGC baked in; `scan/tgc_gain_curve` is that (non-linear) applied curve. `reconstruct.py` beamforms the stored IQ as-is (leaving the TGC in, which keeps deep structure bright); a user wanting true channel amplitudes must divide `raw_data` by the curve. +- `sampling_frequency ≈ center_frequency` because the data is DDC baseband IQ (see Dataset Characterization), not an RF acquisition. diff --git a/datasets/resolvestroke/saddle/assets/main.png b/datasets/resolvestroke/saddle/assets/main.png new file mode 100644 index 0000000..ba9498e Binary files /dev/null and b/datasets/resolvestroke/saddle/assets/main.png differ diff --git a/datasets/resolvestroke/saddle/pipeline.yaml b/datasets/resolvestroke/saddle/pipeline.yaml index 39a16e3..2fda330 100644 --- a/datasets/resolvestroke/saddle/pipeline.yaml +++ b/datasets/resolvestroke/saddle/pipeline.yaml @@ -1,7 +1,8 @@ parameters: grid_type: polar polar_limits: [-0.45, 0.45] # sector half-angle in rad (≈ ±26°) - zlims: [0.01, 0.12] # on-axis depth range in m (reconstruct.py offsets the apex) + zlims: [0.01, 0.12] # on-axis depth range in m (zea adds distance_to_apex) + distance_to_apex: 0.05 # virtual source behind the array (|focus_distances|) grid_size_z: 420 # radial samples grid_size_x: 220 # angular samples grid_size_y: 1 # 1 => 2D sector (unlocks polar grid) diff --git a/datasets/resolvestroke/saddle/reconstruct.py b/datasets/resolvestroke/saddle/reconstruct.py index eadf4a5..e9f16f4 100644 --- a/datasets/resolvestroke/saddle/reconstruct.py +++ b/datasets/resolvestroke/saddle/reconstruct.py @@ -33,63 +33,54 @@ from zea import Config, File, Pipeline HERE = Path(__file__).parent -# Datasets live under data/; fall back to a file sitting next to the script. -_HDF5 = "hf://nvidia/OpenH-RF/resolvestroke/saddle/data/PMP01.hdf5" -DEFAULT_INPUT = _HDF5[0] if _HDF5 else None -CONFIG = HERE / "pipeline.yaml" +CONFIG = "hf://nvidia/OpenH-RF/resolvestroke/saddle/pipeline.yaml" +HF_DIR = "hf://nvidia/OpenH-RF/resolvestroke/saddle/data" # --- Inputs ----------------------------------------------------------------- -# Defaults stream straight from the published corpus. Swap any of these for a -# local path to run against your own copy. -INPUT = "hf://nvidia/OpenH-RF/resolvestroke/saddle/data/PMP01.hdf5" -OUTPUT = None # Output PNG path (default: outputs/_bmode.png) - - -def main(): - # Default output goes to outputs/ next to the script; create it if needed. - out_path = OUTPUT or (HERE / "outputs" / f"{Path(INPUT).stem}_bmode.png") - out_path.parent.mkdir(parents=True, exist_ok=True) - - zea.init_device() - config = Config.from_path(str(CONFIG)) - - with File(str(INPUT)) as f: +# Defaults stream straight from the published corpus. Any of the 21 files works: +# the 20 clinical acquisitions (SP01-Left-1 ... SP10-Right) or the phantom PMP01. +# Swap ZEA_FILE for a local path to run against your own copy. +ZEA_FILE = f"{HF_DIR}/PMP01.hdf5" +OUT = HERE / "assets" / f"{Path(ZEA_FILE).stem}_bmode.png" + + +def reconstruct(input_path, config, pipeline=None): + """Beamform the single saddle frame of ``input_path`` with ``config``. + + Returns: + (image, grid, grid_type): ``image`` is the log-compressed B-mode + (n_radial, n_angular) for a polar grid; ``grid`` the matching Cartesian + (x, y, z) sample positions in metres. + """ + with File(str(input_path)) as f: + # pipeline.yaml pins distance_to_apex (the virtual source behind the array); + # zea takes zlims as on-axis depth and adds the apex to the radii itself. parameters = f.load_parameters(**config.parameters) - if parameters.grid_type == "polar": - # For a diverging wave the polar-grid apex is the virtual source - # (|focus_distances|); derive it unless pipeline.yaml pins it. - apex = config.parameters.get("distance_to_apex") - if apex is None: - focus = float(np.abs(np.ravel(parameters.focus_distances)[0])) - apex = focus if focus > 0 else 0.0 - # zea measures the polar near-bound as radius-from-apex, so shift the - # near zlim by the apex → the configured zlims are true on-axis depth. - z0, z1 = (float(v) for v in config.parameters["zlims"]) - overrides = { - **config.parameters, - "distance_to_apex": apex, - "zlims": (z0 + apex, z1), - } - parameters = f.load_parameters(**overrides) raw = f.data.raw_data[0:1] # single frame → (1, n_tx, n_ax, n_el, n_ch) - print(f"raw_data shape : {raw.shape}") - print(f"grid : {parameters.grid.shape} ({parameters.grid_type})") - - pipeline = Pipeline.from_config(config) + pipeline = pipeline or Pipeline.from_config(config) inputs = pipeline.prepare_parameters(parameters) outputs = pipeline(**{pipeline.key: raw}, **inputs, return_numpy=True) image = np.asarray(outputs[pipeline.output_key])[0] + grid = np.asarray(parameters.grid) # (..., 3), last axis (x, y, z) in metres + return image, grid, parameters.grid_type + + +def main(): + + zea.init_device() + config = Config.from_path(str(CONFIG)) + image, grid, grid_type = reconstruct(ZEA_FILE, config) + print(f"grid : {grid.shape} ({grid_type})") # Display dynamic range from pipeline.yaml (default 40 dB). dr = config.parameters.get("dynamic_range", [-40, 0]) vmin, vmax = float(dr[0]), float(dr[1]) - grid = np.asarray(parameters.grid) # (..., 3), last axis (x, y, z) in metres zea.visualize.set_mpl_style() fig, ax = plt.subplots(figsize=(7, 7)) - if parameters.grid_type == "polar": + if grid_type == "polar": # Scan-convert: place each (radial, angular) sample at its Cartesian (x, z). x_mm, z_mm = grid[..., 0] * 1e3, grid[..., 2] * 1e3 pm = ax.pcolormesh(x_mm, z_mm, image, cmap="gray", vmin=vmin, vmax=vmax, shading="auto") @@ -116,10 +107,11 @@ def main(): cax = make_axes_locatable(ax).append_axes("right", size="5%", pad=0.05) fig.colorbar(pm, cax=cax, label="dB") fig.tight_layout() - fig.savefig(str(out_path), dpi=120, bbox_inches="tight") + Path(OUT).parent.mkdir(parents=True, exist_ok=True) + fig.savefig(str(OUT), dpi=120, bbox_inches="tight") print(f"Reconstructed : {image.shape}") - print(f"Saved : {out_path}") + print(f"Saved : {OUT}") if __name__ == "__main__": diff --git a/datasets/siemens-healthineers/README.md b/datasets/siemens-healthineers/README.md index f325d96..ab47e6f 100644 --- a/datasets/siemens-healthineers/README.md +++ b/datasets/siemens-healthineers/README.md @@ -1,5 +1,6 @@ --- -pretty_name: "OpenH-RF — Siemens In-Vivo Raw Ultrasound Channel Data" +name: siemens-healthineers +pretty_name: "Siemens In-Vivo Raw Ultrasound Channel Data" license: cc-by-4.0 task_categories: - other @@ -16,32 +17,23 @@ size_categories: - 1K (Siemens Healthineers, Ultrasound; contact) +- Jane Bucholz, BHS (Medical Imaging); PgDip HS (Ultrasound); DMU (General); PgDip Public Health (Dist) (Siemens Healthineers, Ultrasound) +- Craig Williams, PG Dip Medical Ultrasound, BSc (Hons) Diagnostic Radiography (Siemens Healthineers, Ultrasound) +- Walter Simson, PhD (additional contributor) ## Dataset Creation Date @@ -49,38 +41,41 @@ Contact: Ismayil Guracar ([ismayil.guracar@siemens-healthineers.com](mailto:isma ## License / Terms of Use -[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage -Developing and benchmarking methods that operate on raw ultrasound channel -data: learned/adaptive beamforming, and self-supervised representation -learning on RF/IQ data. Subject IDs enable subject-wise splits. the -reference split holds out Subject_13 and Subject_14 for validation. +Developing and benchmarking methods that operate on raw ultrasound channel data: learned/adaptive beamforming, and self-supervised representation learning on RF/IQ data. Subject IDs enable subject-wise splits. the reference split holds out Subject_13 and Subject_14 for validation. ## Dataset Characterization -- **Data Collection Method:** healthy adult volunteers in a -non-clinical research setting. -- **Labeling Method:** human-annotated subject/anatomy metadata and -per-frame quality tags (`contact`, `challenging`). -- **Acquisition system:** Siemens ACUSON Sequoia development scanner, 10L4 -180-element linear array (0.201 mm pitch, 36.2 mm aperture; elements -0.2 × 9 mm); transmit center frequency 6.5 MHz; baseband IQ sampled at -13.33 MHz. Full synthetic aperture multi-static acquisition: 180 -single-element transmit events per frame, all 180 elements received. +- **Data Collection Method:** healthy adult volunteers in a non-clinical research setting. +- **Labeling Method:** human-annotated subject/anatomy metadata and per-frame quality tags (`contact`, `challenging`). +- **Acquisition system:** Siemens ACUSON Sequoia development scanner, 10L4 180-element linear array (0.201 mm pitch, 36.2 mm aperture; elements 0.2 × 9 mm); transmit center frequency 6.5 MHz; baseband IQ sampled at 13.33 MHz. Full synthetic aperture multi-static acquisition: 180 single-element transmit events per frame, all 180 elements received. + +## Processing the Dataset + +The acquisitions can be processed with the `pipeline.yaml` definition in this folder and the [zea library](https://github.com/tue-bmd/zea). + +`zea` streams the data from the Hugging Face Hub and processes it according to the pipeline. You can try it out with the following command: + +```bash +zea process \ + --dataset hf://nvidia/OpenH-RF/siemens-healthineers/data/Subject_03_acq_008.hdf5 \ + --config hf://nvidia/OpenH-RF/siemens-healthineers/pipeline.yaml +``` + +Alternatively, you can use the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/siemens-healthineers/reconstruct.py) as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF). ## Dataset Format -Packaged in the `zea` [file format](https://zea.readthedocs.io/en/openh-rf-latest/data-acquisition.html) -(current release `zea_version` 0.1.6), **one HDF5 -file per acquisition**: +[zea v0.1.6](https://github.com/tue-bmd/zea) + +Packaged in the `zea` [file format](https://zea.readthedocs.io/en/openh-rf-latest/data-acquisition.html) (current release `zea_version` 0.1.6), **one HDF5 file per acquisition**: -- On-scanner quadrature demodulation to baseband IQ, demodulation frequency stored per -file. +- On-scanner quadrature demodulation to baseband IQ, demodulation frequency stored per file. - On-scanner analog TGC and digital filtering with scaling are applied. -- FSA transmit scheme encoded explicitly (one-hot `tx_apodizations`, -zero `t0_delays`, transmit origins at element positions). +- FSA transmit scheme encoded explicitly (one-hot `tx_apodizations`, zero `t0_delays`, transmit origins at element positions). Per-file contents: @@ -112,54 +107,33 @@ All spatial maps carry per-pixel Cartesian `coordinates` in meters, last axis `[ **Current OpenH-RF release:** 590 HDF5 files; 228.54 GB (228,538,449,920 bytes) stored; root `zea_version` **0.1.6**. Sizes include all HDF5 contents and use decimal units (MB = 10^6 bytes, GB = 10^9 bytes, TB = 10^12 bytes), not decoded-array memory or original-source download sizes. -- **590 acquisitions (4,541 frames) from 15 subjects**, one continuous -dataset: 51 acquisitions / 411 frames (Subjects 01–03) from the first -acquisition round and 539 acquisitions / 4,130 frames (Subjects 04–15) from -the second. -- **File naming:** one HDF5 per acquisition, `Subject_XX_acq_NNN.hdf5` -(acquisitions numbered per subject in acquisition order). +- **590 acquisitions (4,539 frames) from 15 subjects**, one continuous dataset: 51 acquisitions / 411 frames (Subjects 01–03) from the first acquisition round and 539 acquisitions / 4,128 frames (Subjects 04–15) from the second. +- **File naming:** one HDF5 per acquisition, `Subject_XX_acq_NNN.hdf5` (acquisitions numbered per subject in acquisition order). - **Stored HDF5 size:** 228.54 GB (228,538,449,920 bytes); raw IQ stored as float32 I/Q, one HDF5 per acquisition. -- Per-frame boolean quality labels support curation: `contact` and -`challenging` (see feature table). Acquisitions not yet through -manual review default to `contact=True, challenging=False`. +- Per-frame boolean quality labels support curation: `contact` and `challenging` (see feature table). Acquisitions not yet through manual review default to `contact=True, challenging=False`. ## Subject Metadata Aggregate statistics only; files carry anonymized subject IDs, no PHI. - **15 adult volunteers in total** (Subject_01–Subject_15). -- Acquisitions per subject (590 total): Subject_01: 12, Subject_02: 6, -Subject_03: 33, Subject_04: 44, Subject_05: 41, Subject_06: 47, -Subject_07: 35, Subject_08: 51, Subject_09: 44, Subject_10: 32, -Subject_11: 49, Subject_12: 36, Subject_13: 36, Subject_14: 52, -Subject_15: 72. -- Anatomies scanned (by acquisition): breast (284), thyroid (117), -axilla (59), neck (29), abdomen (20), carotid (18), other/unlabeled (63). -- All scans are B-mode-style views (predominantly superficial anatomy) on a -single probe and system; no pathology labels are asserted. +- Acquisitions per subject (590 total): Subject_01: 12, Subject_02: 6, Subject_03: 33, Subject_04: 44, Subject_05: 41, Subject_06: 47, Subject_07: 35, Subject_08: 51, Subject_09: 44, Subject_10: 32, Subject_11: 49, Subject_12: 36, Subject_13: 36, Subject_14: 52, Subject_15: 72. +- Anatomies scanned (by acquisition): breast (284), thyroid (117), axilla (59), neck (29), abdomen (20), carotid (18), other/unlabeled (63). +- All scans are B-mode-style views (predominantly superficial anatomy) on a single probe and system; no pathology labels are asserted. ## Data Validation -[`pipeline.yaml`](pipeline.yaml). `data/image` is produced by this same -pipeline at conversion time, so `reconstruct.py` reproduces it exactly — a -round-trip check that the probe geometry, transmit description, and timing are -recorded correctly. Reference B-modes rendered by this pipeline — one per -anatomy (breast, thyroid, axilla, carotid, neck, abdomen) — are in +[`pipeline.yaml`](pipeline.yaml). `data/image` is produced by this same pipeline at conversion time, so `reconstruct.py` reproduces it — a round-trip check that the probe geometry, transmit description, and timing are recorded correctly. Reference B-modes rendered by this pipeline are in [`assets/`](assets), and the strip at the top of this card is four of them. + +![Carotid cineloop, ten frames](assets/Subject_03_acq_008.gif) +Ten frames of `data/Subject_03_acq_008.hdf5`. `custom/contact` marks the first frame as off-subject — use those per-frame flags when picking frames, rather than assuming frame 0 is usable. ## Known Issues -- Time-axis length (`n_ax` = `raw_data.shape[2]`) varies per acquisition -(native lengths range ~700–2270 samples); each file is stored at its own -native length. +- Time-axis length (`n_ax` = `raw_data.shape[2]`) varies per acquisition (native lengths range ~700–2270 samples); each file is stored at its own native length. - `view_difficulty` annotations exist for the first acquisition round only. ## Ethical Considerations -The released data is anonymized and contains no protected health information -(PHI). Acquisitions were performed under Siemens Healthineers internal -ultrasound research safety, human-safety, and consent procedures. Metadata -labels, file names, and source identifiers are screened against the HIPAA Safe -Harbor standard, with GDPR-equivalent handling for any EU-origin data; this -release contains no EU-origin data. The dataset is released under the CC BY -4.0 license. +The released data is anonymized and contains no protected health information (PHI). Acquisitions were performed under Siemens Healthineers internal ultrasound research safety, human-safety, and consent procedures. Metadata labels, file names, and source identifiers are screened against the HIPAA Safe Harbor standard, with GDPR-equivalent handling for any EU-origin data; this release contains no EU-origin data. The dataset is released under the CC BY 4.0 license. diff --git a/datasets/siemens-healthineers/assets/Subject_01_acq_006_recon.png b/datasets/siemens-healthineers/assets/Subject_01_acq_006_recon.png new file mode 100644 index 0000000..27ac49e Binary files /dev/null and b/datasets/siemens-healthineers/assets/Subject_01_acq_006_recon.png differ diff --git a/datasets/siemens-healthineers/assets/Subject_03_acq_008.gif b/datasets/siemens-healthineers/assets/Subject_03_acq_008.gif new file mode 100644 index 0000000..bc781be Binary files /dev/null and b/datasets/siemens-healthineers/assets/Subject_03_acq_008.gif differ diff --git a/datasets/siemens-healthineers/assets/anatomies.png b/datasets/siemens-healthineers/assets/anatomies.png new file mode 100644 index 0000000..58e0aaa Binary files /dev/null and b/datasets/siemens-healthineers/assets/anatomies.png differ diff --git a/datasets/siemens-healthineers/assets/main.png b/datasets/siemens-healthineers/assets/main.png new file mode 100644 index 0000000..2251773 Binary files /dev/null and b/datasets/siemens-healthineers/assets/main.png differ diff --git a/datasets/siemens-healthineers/pipeline.yaml b/datasets/siemens-healthineers/pipeline.yaml index d477f6b..091cc13 100644 --- a/datasets/siemens-healthineers/pipeline.yaml +++ b/datasets/siemens-healthineers/pipeline.yaml @@ -1,3 +1,14 @@ +parameters: + xlims: [-0.018, 0.018] + zlims: [0.0, 0.035] # RF runs out at 40.7 mm + # Isotropic pixels (~35.5 um both ways). The stored data/image is exactly + # isotropic (0.0791 mm over 36 x 40 mm); 1014 x 1100 over 36 x 35 mm is not, + # and stretched every reconstruction ~12% vertically against the reference. + grid_size_x: 1014 + grid_size_z: 986 + n_ch: 2 # baseband IQ + selected_transmits: all + dynamic_range: [-60, 0] pipeline: operations: - beamform diff --git a/datasets/siemens-healthineers/reconstruct.py b/datasets/siemens-healthineers/reconstruct.py index c61e9d3..ffbbccb 100644 --- a/datasets/siemens-healthineers/reconstruct.py +++ b/datasets/siemens-healthineers/reconstruct.py @@ -5,10 +5,9 @@ B-mode reconstruction of in-vivo IQ channel data from a clinical scanner. -The script renders a three-panel figure: IQ magnitude, the stored B-mode, and -the zea reconstruction. ``data/image`` was produced by this same processing at -conversion time, so the reconstruction reproduces it exactly -- a round-trip -check on the converted data. +``data/image`` was produced by this same processing at conversion time, so the +reconstruction reproduces it exactly -- a round-trip check on the converted +data. The grid and display range live in ``pipeline.yaml``. Requires zea>=0.1.6 (https://github.com/tue-bmd/zea), the library that does the ultrasound processing here, together with one of its Keras backends (JAX, @@ -29,72 +28,53 @@ import keras import matplotlib.pyplot as plt import zea -from zea.ops import Beamform, EnvelopeDetect, LogCompress, Normalize +from zea import Config, File, Pipeline HERE = Path(__file__).parent # --- Inputs ----------------------------------------------------------------- # Defaults stream straight from the published corpus. Swap any of these for a # local path to run against your own copy. -INPUT = "hf://nvidia/OpenH-RF/siemens-healthineers/data/Subject_01_acq_006.hdf5" -OUTPUT = None # PNG path (default: _recon.png) +ZEA_FILE = "hf://nvidia/OpenH-RF/siemens-healthineers/data/Subject_01_acq_006.hdf5" +CONFIG = HERE / "pipeline.yaml" +OUT = None # PNG path (default: _recon.png) FRAME = 0 -DEPTH_M = 0.035 # reconstruct down to 35 mm -SAVE_PIPELINE = None # Optionally write the pipeline to a reusable pipeline.yaml - - -def coords_to_imshow_mm(coords): - """Per-pixel coordinates (z, x, 3), last axis [x, y, z] in metres -> - mpl imshow extent [left, right, bottom, top] in mm.""" - x, z = coords[..., 0], coords[..., 2] - return [x.min() * 1e3, x.max() * 1e3, z.max() * 1e3, z.min() * 1e3] +HF_CONFIG = "hf://nvidia/OpenH-RF/siemens-healthineers/pipeline.yaml" # where CONFIG is published def main(): - output = OUTPUT or Path(Path(INPUT).stem + "_recon.png") + output = OUT or HERE / "assets" / f"{Path(ZEA_FILE).stem}_recon.png" zea.init_device() + config = Config.from_path(str(CONFIG)) - with zea.File(str(INPUT)) as f: - raw = f.data.raw_data[:] - img_coords = f.data.image.coordinates[:] - # Acquisition parameters straight from the file; reconstruction grid - # derived from the stored B-mode's coordinates so the results align. - nz, nx = img_coords.shape[0], img_coords.shape[1] - params = f.load_parameters( - grid_size_x=nx, - grid_size_z=nz, - xlims=[float(img_coords[..., 0].min()), float(img_coords[..., 0].max())], - zlims=[float(img_coords[..., 2].min()), DEPTH_M], - n_ch=raw.shape[-1], # 2 = baseband IQ - selected_transmits="all", - ) + with File(str(ZEA_FILE)) as f: + params = f.load_parameters(**config.parameters) + raw = f.data.raw_data[FRAME : FRAME + 1] print(f"raw_data: {raw.shape}") - pipeline = zea.Pipeline( - operations=[ - Beamform(beamformer="delay_and_sum"), - EnvelopeDetect(), - Normalize(), - LogCompress(), - ] - ) - if SAVE_PIPELINE is not None: - pipeline.to_yaml(str(SAVE_PIPELINE)) - print(f"Saved pipeline recipe to {SAVE_PIPELINE}") + pipeline = Pipeline.from_config(config) inputs = pipeline.prepare_parameters(params) - recon = keras.ops.convert_to_numpy(pipeline(data=raw[FRAME : FRAME + 1], **inputs)["data"])[0] + recon = keras.ops.convert_to_numpy(pipeline(data=raw, **inputs)["data"])[0] recon_ext = [v * 1e3 for v in params.extent_imshow] print(f"Reconstructed: {recon.shape}") zea.visualize.set_mpl_style() fig, ax = plt.subplots(figsize=(6, 6)) - ax.imshow(recon, aspect="equal", cmap="gray", vmin=-60, vmax=0, extent=recon_ext) + ax.imshow( + recon, + aspect="equal", + cmap="gray", + vmin=params.dynamic_range[0], + vmax=params.dynamic_range[1], + extent=recon_ext, + ) ax.set_xlabel("Lateral [mm]") ax.set_ylabel("Depth [mm]") plt.tight_layout() + Path(output).parent.mkdir(parents=True, exist_ok=True) plt.savefig(output, dpi=150, bbox_inches="tight") print(f"Saved {output}") diff --git a/datasets/stanford-murine/README.md b/datasets/stanford-murine/README.md index 91000d3..2da4292 100644 --- a/datasets/stanford-murine/README.md +++ b/datasets/stanford-murine/README.md @@ -1,4 +1,5 @@ --- +name: stanford-murine license: cc-by-4.0 pretty_name: Stanford Murine Liver and Sound-Speed Phantom Ultrasound task_categories: @@ -22,30 +23,39 @@ size_categories: # Stanford Murine Liver and Sound-Speed Phantom Ultrasound +![B-mode reconstructions of a rat liver and the ATS 549 phantom](assets/hero.png) + +*Frame 0 reconstructed with `reconstruct.py` (L12-3v, 60 dB). Top: the exposed liver of [Rat3](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/stanford-murine/data/RatExperiments/VerasonicsAcq/Rat3/ExposedLiver/DATA_Tracks_20190319_115804.hdf5), `multifocal` track. Bottom: the ATS 549 phantom's [lesions](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/stanford-murine/data/SoSExperiments/august13_2020/L12_3v/ATS_phantom/lesion/DATA_Tracks_20200813_164505.hdf5) and [point targets](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/stanford-murine/data/SoSExperiments/august13_2020/L12_3v/ATS_phantom/point_target/DATA_Tracks_20200813_164745.hdf5), `hadamard` track. The point targets curve into arcs because the phantom is beamformed at 1540 m/s while its own sound speed is lower — the mismatch this dataset is built to estimate and correct.* + ## Dataset Description -This dataset contains pre-beamformed pulse-echo ultrasound channel data from -murine livers and sound-speed phantoms. The data were acquired on a Verasonics -Vantage 256 using multifocal, Hadamard-encoded, and full synthetic aperture -(FSA) transmit sequences. The dataset supports research on beamforming, -sound-speed estimation, and aberration correction; it is not intended for -clinical diagnosis. +This dataset contains pre-beamformed pulse-echo ultrasound channel data from murine livers and sound-speed phantoms. The data were acquired on a Verasonics Vantage 256 using multifocal, Hadamard-encoded, and full synthetic aperture (FSA) transmit sequences. The dataset supports research on beamforming, sound-speed estimation, and aberration correction; it is not intended for clinical diagnosis. The source data are available from Figshare: - [Murine liver acquisitions](https://doi.org/10.25452/figshare.plus.28291985) - [Sound-speed phantom and meat-layer acquisitions](https://doi.org/10.25452/figshare.plus.28291988) -The converted demo dataset is hosted at -[openh-rf/stanford](https://huggingface.co/datasets/openh-rf/stanford). +The original Figshare releases were published under Apache 2.0; this converted release is CC BY 4.0. + +The converted dataset is hosted at [nvidia/OpenH-RF/stanford-murine](https://huggingface.co/datasets/nvidia/OpenH-RF/tree/main/stanford-murine). -## Dataset Contributors +## Dataset Contributor(s) -The source datasets were created at Stanford University by Arsenii V. -Telichko, Rehman Ali, Andrew Andrzejek, Thurston Brevett, Benjamin N. Frey, -Brian Boitnott, Jihye Baek, Louise Zhuang, Hoda Hashemi, Jun Hong Park, Caelia -Thomas, and Jeremy Dahl. The contributing organization is the Dahl Lab, -Stanford University. Contact: `jjdahl@stanford.edu`. +The source datasets were created at Stanford University (Dahl Lab): + +- Arsenii V. Telichko +- Rehman Ali +- Andrew Andrzejek +- Thurston Brevett +- Benjamin N. Frey +- Brian Boitnott +- Jihye Baek +- Louise Zhuang +- Hoda Hashemi +- Jun Hong Park +- Caelia Thomas +- Jeremy Dahl (contact) ## Dataset Creation Date @@ -53,7 +63,7 @@ Stanford University. Contact: `jjdahl@stanford.edu`. ## License / Terms of Use -This dataset is provided in OpenH-RF under the Creative Commons Attribution 4.0 International License (CC BY 4.0) (https://creativecommons.org/licenses/by/4.0/). The original Figshare releases were published under Apache 2.0. +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage @@ -64,33 +74,24 @@ The data are intended for development and validation of: - aberration correction through tissue or meat layers; and - comparisons among multifocal, Hadamard-encoded, and FSA acquisition schemes. -The provided pipeline is a reference delay-and-sum B-mode reconstruction, not -a prescribed preprocessing pipeline for model training. +The provided pipeline is a reference delay-and-sum B-mode reconstruction, not a prescribed preprocessing pipeline for model training. ## Dataset Characterization ### Data Collection Method -The collection combines in-vivo and post-mortem animal acquisitions with -table-top phantom acquisitions: +The collection combines in-vivo and post-mortem animal acquisitions with table-top phantom acquisitions: -- The murine study contains liver scans from 20 Zucker rats: 4 lean controls - and 16 obese animals used as a model of hepatic steatosis. -- The phantom study contains an ATS 549 phantom and six agarose/graphite - phantoms with varying n-propanol concentration. Many acquisitions include a - 10–15 mm porcine or galline meat layer as a controlled aberrator. +- The murine study contains liver scans from 20 Zucker rats: 4 lean controls and 16 obese animals used as a model of hepatic steatosis. +- The phantom study contains an ATS 549 phantom and six agarose/graphite phantoms with varying n-propanol concentration. Many acquisitions include a 10–15 mm porcine or galline meat layer as a controlled aberrator. ### Labeling Method -No image, pixel-wise segmentation, or manual annotation labels are supplied. -Rat-level sound-speed, fat-percentage, and histopathology measurements are -derived from the source quantification workbooks when available. Phantom -sound speeds were measured independently with a through-transmission setup. +No image, pixel-wise segmentation, or manual annotation labels are supplied. Rat-level sound-speed, fat-percentage, and histopathology measurements are derived from the source quantification workbooks when available. Phantom sound speeds were measured independently with a through-transmission setup. ### Acquisition System -All acquisitions use a Verasonics Vantage 256 research scanner. The converted -demo covers: +All acquisitions use a Verasonics Vantage 256 research scanner. The converted demo covers: | Probe | Geometry | Elements | Center frequency | Sampling frequency | |---|---|---:|---:|---:| @@ -98,18 +99,29 @@ demo covers: | L12-3v | Linear | 192 | 6.0 or 7.813 MHz | 25.0 or 31.25 MHz | | L12-5 | Linear | 256 | 7.5 MHz | 31.25 MHz | -Each output file bundles three tracks named `multifocal`, `hadamard`, and -`synthetic_aperture`. Transmit delays, transmit apodization, focal distances, -origins, receive initial times, probe geometry, acquisition sound speed, and -sampling frequencies are read from the corresponding MAT file. -For the rat acquisitions, the Verasonics sound-speed setting is 1540 m/s; this -is retained in `scan/sound_speed` for reconstruction. It is distinct from the -post-acquisition liver sound-speed measurements described below. +Each output file bundles three tracks named `multifocal`, `hadamard`, and `synthetic_aperture`. Transmit delays, transmit apodization, focal distances, origins, receive initial times, probe geometry, acquisition sound speed, and sampling frequencies are read from the corresponding MAT file. For the rat acquisitions, the Verasonics sound-speed setting is 1540 m/s; this is retained in `scan/sound_speed` for reconstruction. It is distinct from the post-acquisition liver sound-speed measurements described below. + +## Processing the Dataset + +The acquisitions can be processed with the `pipeline_multifocal.yaml`, `pipeline_hadamard.yaml` and `pipeline_synthetic_aperture.yaml` definitions in this folder and the [zea library](https://github.com/tue-bmd/zea). Each file bundles three tracks, one per transmit sequence (`multifocal`, `hadamard`, `synthetic_aperture`), and each track has its own pipeline. + +`zea` streams the data from the Hugging Face Hub and processes it according to the pipeline; pass the matching track with `--track`. You can try it out with the following command: + +```bash +zea process \ + --dataset hf://nvidia/OpenH-RF/stanford-murine/data/RatExperiments/VerasonicsAcq/Rat3/ExposedLiver/DATA_Tracks_20190319_115804.hdf5 \ + --config hf://nvidia/OpenH-RF/stanford-murine/pipeline_synthetic_aperture.yaml \ + --track synthetic_aperture \ + --n-frames 1 +``` + +Alternatively, you can use the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/stanford-murine/reconstruct.py) as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF). It reconstructs the first frame of every track in `ZEA_FILE` with the same pipelines and writes one image per track to `assets/`. On top of the YAML, the script inserts a baseband FIR low-pass after demodulation, with its cutoff at half the probe bandwidth (4.0 MHz for L12-3v, 3.0 MHz for L12-5, 1.3 MHz for C5-2v). The cutoff depends on the probe, so it is not part of the pipeline YAMLs and `zea process` runs without it; the difference is small on the rat acquisitions and clearly visible at depth on the phantoms. ## Dataset Format -Converted data use the zea HDF5 representation. Channel samples have dimension -order: +[zea v0.1.4](https://github.com/tue-bmd/zea) + +Converted data use the zea HDF5 representation. Channel samples have dimension order: ```text (n_frames, n_tx, n_ax, n_el, n_ch) @@ -117,34 +129,23 @@ order: The converter: -1. reads every acquired Verasonics receive-buffer frame, limited to the sample - interval referenced by each sequence's Receive metadata; -2. coherently averages duplicate physical elements from overlapping receive - apertures while retaining full amplitude for elements recorded once; +1. reads every acquired Verasonics receive-buffer frame, limited to the sample interval referenced by each sequence's Receive metadata; +2. coherently averages duplicate physical elements from overlapping receive apertures while retaining full amplitude for elements recorded once; 3. decodes the positive/negative Hadamard acquisitions; -4. trims axial samples that are zero across every frame, transmit, element, and - channel; and +4. trims axial samples that are zero across every frame, transmit, element, and channel; and 5. stores the resulting RF channel data as `float32`. -The `float32` dtype is intentional. Coherent aperture averaging can create -half-integer samples, and Hadamard decoding can exceed the `int16` range. -Saving decoded RF as `int16` would round or overflow valid samples. +The `float32` dtype is intentional. Coherent aperture averaging can create half-integer samples, and Hadamard decoding can exceed the `int16` range. Saving decoded RF as `int16` would round or overflow valid samples. -Reconstructed B-mode images and segmentation masks are not stored in the -converted files. They are generated separately by the validation pipeline. +Reconstructed B-mode images and segmentation masks are not stored in the converted files. They are generated separately by the validation pipeline. ## Dataset Quantification **Current OpenH-RF release:** 245 HDF5 files; 328.99 GB (328,985,804,800 bytes) stored; root `zea_version` **0.1.4**. Sizes include all HDF5 contents and use decimal units (MB = 10^6 bytes, GB = 10^9 bytes, TB = 10^12 bytes), not decoded-array memory or original-source download sizes. -The original four-file demo (not the complete HF release summarized above) contains four HDF5 acquisition bundles: one Rat9 bundle -and one phantom bundle for each of C5-2v, L12-3v, and L12-5. Each bundle has -three sequence tracks, and each track retains every frame in its source MAT -file. That demo has 12 tracks and 64 frames and occupies approximately -5.5 GiB; HDF5 writer and compression versions may change the exact byte count. +The original four-file demo (not the complete HF release summarized above) contains four HDF5 acquisition bundles: one Rat9 bundle and one phantom bundle for each of C5-2v, L12-3v, and L12-5. Each bundle has three sequence tracks, and each track retains every frame in its source MAT file. That demo has 12 tracks and 64 frames and occupies approximately 5.5 GiB; HDF5 writer and compression versions may change the exact byte count. -The two complete source Figshare records contain approximately 491.9 GB in -total. No train, validation, or test split is defined. The current converted-release size and file count are reported above; the scripts do not assign learning splits. +The two complete source Figshare records contain approximately 491.9 GB in total. No train, validation, or test split is defined. The current converted-release size and file count are reported above; the scripts do not assign learning splits. Representative `raw_data` shapes in the demo are: @@ -155,13 +156,7 @@ Representative `raw_data` shapes in the demo are: | Phantom, L12-3v | `(2, 576, 1613, 192, 1)` | `(2, 192, 1613, 192, 1)` | `(10, 192, 1613, 192, 1)` | | Phantom, L12-5 | `(2, 512, 2018, 256, 1)` | `(2, 256, 2018, 256, 1)` | `(2, 256, 2018, 256, 1)` | -The Rat9 multifocal sequence uses the three focal depths programmed in the -source MAT file: 17.74, 29.57, and 41.39 mm. Its programmed imaging range -extends to 50.46 mm, so all three foci are intentional. The stored RF tensor -ends near 21.24 mm because samples beyond that depth are zero across every -transmit and receive channel and the converter removes this all-zero tail. -The focal metadata are retained even when a focus lies beyond the remaining -nonzero RF support. +The Rat9 multifocal sequence uses the three focal depths programmed in the source MAT file: 17.74, 29.57, and 41.39 mm. Its programmed imaging range extends to 50.46 mm, so all three foci are intentional. The stored RF tensor ends near 21.24 mm because samples beyond that depth are zero across every transmit and receive channel and the converter removes this all-zero tail. The focal metadata are retained even when a focus lies beyond the remaining nonzero RF support. Core per-track features are: @@ -181,12 +176,7 @@ Core per-track features are: ## Subject Metadata -The source murine cohort contains 4 lean Zucker rats (2 male and 2 female) and -16 obese Zucker rats (8 male and 8 female), beginning at 13 weeks of age. The -obese animals were fed a high-fat diet for up to eight weeks. Converted rat -files store the rat identifier and, when present in the source workbooks, sex, -fat percentage, measured liver sound speed, weight, diet duration, and -histopathology-derived values. +The source murine cohort contains 4 lean Zucker rats (2 male and 2 female) and 16 obese Zucker rats (8 male and 8 female), beginning at 13 weeks of age. The obese animals were fed a high-fat diet for up to eight weeks. Converted rat files store the rat identifier and, when present in the source workbooks, sex, fat percentage, measured liver sound speed, weight, diet duration, and histopathology-derived values. Values with standard DataSpec fields are stored under `metadata/subject`: @@ -196,149 +186,36 @@ Values with standard DataSpec fields are stored under `metadata/subject`: | `weight` | `float32` | kg | Animal weight, converted from workbook grams | | `sex` | string | unitless | Sex reported by the source workbook | -Other workbook measurements are scalar `float32` datasets under -`custom/rat_quantification/`, each with `unit` and `description` attributes. -These include the ground-truth sound-speed mean and standard deviation, -sequence-specific local and global sound-speed estimates, liver-lobe -sound-speed measurements when available, diet duration, fat-percentage -uncertainty, water temperature, and quantitative histopathology scores. The -workbook's non-fat percentage is omitted because it is exactly -`100 - metadata/subject/fat_percentage` for every rat. These measurements are -scalars rather than homogeneous `sos_map` images because the source provides -no spatial coordinates for these rat-level measurements. - -The phantom data contain no human subjects. Phantom identifiers describe the -probe, material configuration, meat layer, and acquisition session. The source -records report independently measured phantom and meat-layer sound speeds and -temperature-dependent uncertainties. - -## Data Validation - -The reconstruction path uses the first stored frame and a zea `Pipeline`: RF -demodulation and baseband FIR filtering, delay-and-sum beamforming, envelope -detection, maximum normalization, and log compression. The pipeline YAML files -use three pixels per acoustic wavelength and disable pressure-field weighting. -Beamforming is split into bounded patches so the deepest C5-2v grid fits on a -24 GB GPU. +Other workbook measurements are scalar `float32` datasets under `custom/rat_quantification/`, each with `unit` and `description` attributes. These include the ground-truth sound-speed mean and standard deviation, sequence-specific local and global sound-speed estimates, liver-lobe sound-speed measurements when available, diet duration, fat-percentage uncertainty, water temperature, and quantitative histopathology scores. The workbook's non-fat percentage is omitted because it is exactly `100 - metadata/subject/fat_percentage` for every rat. These measurements are scalars rather than homogeneous `sos_map` images because the source provides no spatial coordinates for these rat-level measurements. -From the repository root, generate the demo files and reference images with: +The phantom data contain no human subjects. Phantom identifiers describe the probe, material configuration, meat layer, and acquisition session. The source records report independently measured phantom and meat-layer sound speeds and temperature-dependent uncertainties. -```bash -python examples/stanford/download.py --demo -python examples/stanford/convert.py --dataset rat --demo -python examples/stanford/convert.py --dataset phantom --demo - -CUDA_VISIBLE_DEVICES=0 JAX_PLATFORMS=cuda KERAS_BACKEND=jax \ - python examples/stanford/reconstruct.py --dataset rat --demo --rat-id 9 -CUDA_VISIBLE_DEVICES=0 JAX_PLATFORMS=cuda KERAS_BACKEND=jax \ - python examples/stanford/reconstruct.py --dataset phantom --demo - -python examples/stanford/stitch.py --dataset rat --demo --rat-id 9 -python examples/stanford/stitch.py --dataset phantom --demo -``` - -The reconstructed PNGs and demodulated spectra are written under -`examples/stanford/outputs`. The stitched review documents are: - -- `examples/stanford/outputs/RatExperiments/all_bmode_reconstructions.pdf` -- `examples/stanford/outputs/SoSExperiments/all_sos_bmode_reconstructions.pdf` +## Data Validation -The reconstruction scripts are validated with zea 0.1.4. +The reference pipelines reconstruct the first stored frame of each track: RF demodulation, delay-and-sum beamforming without pressure-field weighting, envelope detection, maximum normalization and log compression, shown over a 60 dB dynamic range. The grid is derived at three pixels per acoustic wavelength over the acquired aperture and depth. `reconstruct.py` adds the probe-dependent baseband FIR described in [Processing the Dataset](#processing-the-dataset); without it, `zea process` and `reconstruct.py` produce the same image. The figure at the top of this card shows the `multifocal` track of Rat3 and the `hadamard` track of two ATS 549 acquisitions, reconstructed with `reconstruct.py`. `assets/main.png` is the same Rat3 `multifocal` reconstruction on its native grid, without axes. ## Known Issues -- The demo MAT files store `Receive/TGC` profile index 1 but do not store the - selected profile's `TGC/Waveform`. A profile index is not a gain curve, so - the converter omits the optional `scan/tgc_gain_curve` field rather than - inventing a unity curve. The RF samples already reflect acquisition-time - TGC, but that gain cannot be calibrated or undone from these files alone. -- Respiratory motion affected early in-vivo rat acquisitions. Later - acquisitions were made shortly after euthanasia, as described by the source - record and associated publication. -- The source record notes settling and slight inhomogeneity in phantom 4 and - phantom 5. -- Strong meat/phantom interfaces and curved-probe reverberation can dominate - individual B-mode images. Reconstruction limits preserve the acquired depth - rather than cropping those regions automatically. +- The demo MAT files store `Receive/TGC` profile index 1 but do not store the selected profile's `TGC/Waveform`. A profile index is not a gain curve, so the converter omits the optional `scan/tgc_gain_curve` field rather than inventing a unity curve. The RF samples already reflect acquisition-time TGC, but that gain cannot be calibrated or undone from these files alone. +- Respiratory motion affected early in-vivo rat acquisitions. Later acquisitions were made shortly after euthanasia, as described by the source record and associated publication. +- The source record notes settling and slight inhomogeneity in phantom 4 and phantom 5. +- Strong meat/phantom interfaces and curved-probe reverberation can dominate individual B-mode images. Reconstruction limits preserve the acquired depth rather than cropping those regions automatically. - The data contain no image or segmentation ground truth. ## Ethical Considerations -No human data are included. The murine study was approved by Stanford -University's Institutional Administrative Panel on Laboratory Animal Care. -Animals were scanned under 2% isoflurane anesthesia on a heated platform with -continuous temperature monitoring; the associated publication describes the -euthanasia and tissue-measurement procedures. The public sources do not state -an approval number or explicit ARRIVE 2.0 compliance. - -The phantom acquisitions require no human- or animal-subject approval. Both -Figshare records confirm that no human personally identifiable information is -present. - -## Scripts and Paths - -- `download.py`: download the raw Figshare demo subset or full records. -- `download_figshare_full.sh`: resumably mirror both full Figshare records to - `/ultra20/figshare` with progress and checksum verification. -- `convert.py`: convert source MAT files to zea HDF5. -- `reconstruct.py`: reconstruct HDF5 tracks to B-mode PNGs and spectra. -- `stitch.py`: combine PNGs into review PDFs. -- `upload.py`: upload selected HDF5 files, this README, and pipeline YAMLs. -- `delete_hf.py`: delete existing remote `zea/` files before replacement. - -Default paths: - -- Raw rat input: `examples/stanford/data/RatExperiments` -- Raw phantom input: `examples/stanford/data/SoSExperiments` -- Converted rat output: `examples/stanford/data/RatExperiments_zea` -- Converted phantom output: `examples/stanford/data/SoSExperiments_zea` -- Reconstruction output: `examples/stanford/outputs` - -For full conversion after downloading both Figshare records: - -```bash -python examples/stanford/download.py --full -python examples/stanford/convert.py --dataset rat --full -python examples/stanford/convert.py --dataset phantom --full -python examples/stanford/reconstruct.py --dataset rat --full -python examples/stanford/reconstruct.py --dataset phantom --full -python examples/stanford/stitch.py --dataset rat --full -python examples/stanford/stitch.py --dataset phantom --full -``` - -To resume an interrupted conversion without rewriting completed bundles, add -`--skip-existing`. A bundle is skipped only when both its atomic HDF5 output -and text summary are present. - -To keep a separate, resumable mirror of both complete Figshare records under -`/ultra20/figshare`, with per-file and overall progress bars plus size and MD5 -verification, run: - -```bash -examples/stanford/download_figshare_full.sh -``` - -This writes `RatExperiments_download` and `SoSExperiments_download`. Use -`examples/stanford/download_figshare_full.sh --dry-run` to validate and -summarize the public records without downloading them. +No human data are included. The murine study was approved by Stanford University's Institutional Administrative Panel on Laboratory Animal Care. Animals were scanned under 2% isoflurane anesthesia on a heated platform with continuous temperature monitoring; the associated publication describes the euthanasia and tissue-measurement procedures. The public sources do not state an approval number or explicit ARRIVE 2.0 compliance. -Use `--min-rat N` or `--rat-id N` with `convert.py` to limit rat conversion. -Raw MAT files remain local; `upload.py` selects converted HDF5 files and -documentation explicitly. +The phantom acquisitions require no human- or animal-subject approval. Both Figshare records confirm that no human personally identifiable information is present. ## Citation Please cite the applicable source dataset and its associated publication: -- Telichko, A. V. et al. (2025). *Pulse-Echo Ultrasound Murine In Vivo - Acquisitions*. Figshare+. -- Ali, R. et al. (2025). *Pulse-Echo Ultrasound Sound Speed Phantom and Meat - Aberrating Layer Acquisitions*. Figshare+. +- Telichko, A. V. et al. (2025). *Pulse-Echo Ultrasound Murine In Vivo Acquisitions*. Figshare+. +- Ali, R. et al. (2025). *Pulse-Echo Ultrasound Sound Speed Phantom and Meat Aberrating Layer Acquisitions*. Figshare+. -- Telichko, A. V. et al. “Noninvasive Estimation of Local Speed of Sound by - Pulse-Echo Ultrasound in a Rat Model of Nonalcoholic Fatty Liver.” *Physics - in Medicine & Biology* 67(1), 015007 (2022). +- Telichko, A. V. et al. “Noninvasive Estimation of Local Speed of Sound by Pulse-Echo Ultrasound in a Rat Model of Nonalcoholic Fatty Liver.” *Physics in Medicine & Biology* 67(1), 015007 (2022). -- Ali, R. et al. “Local Sound Speed Estimation for Pulse-Echo Ultrasound in - Layered Media.” *IEEE TUFFC* 69(2), 500–511 (2022). +- Ali, R. et al. “Local Sound Speed Estimation for Pulse-Echo Ultrasound in Layered Media.” *IEEE TUFFC* 69(2), 500–511 (2022). diff --git a/datasets/stanford-murine/assets/hero.png b/datasets/stanford-murine/assets/hero.png new file mode 100644 index 0000000..bf3e682 Binary files /dev/null and b/datasets/stanford-murine/assets/hero.png differ diff --git a/datasets/stanford-murine/assets/main.png b/datasets/stanford-murine/assets/main.png new file mode 100644 index 0000000..187c924 Binary files /dev/null and b/datasets/stanford-murine/assets/main.png differ diff --git a/datasets/stanford-murine/pipeline_hadamard.yaml b/datasets/stanford-murine/pipeline_hadamard.yaml index 29a227a..048d670 100644 --- a/datasets/stanford-murine/pipeline_hadamard.yaml +++ b/datasets/stanford-murine/pipeline_hadamard.yaml @@ -3,14 +3,9 @@ pipeline: operations: # Overlap averaging creates fractional RF, and Hadamard decoding can exceed int16. - demodulate - - name: fir_filter - params: - axis: -3 - complex_channels: true - name: beamform params: enable_pfield: false - # Bound work units so the deep C5-2v grid fits on a 24 GB GPU. - envelope_detect # Use the true maximum; percentile normalization would clip strong reflectors. - name: normalize @@ -23,3 +18,6 @@ parameters: sound_speed: 1540 # Derive both grid dimensions at three samples per acoustic wavelength. pixels_per_wavelength: 3 + dynamic_range: + - -60 + - 0 diff --git a/datasets/stanford-murine/pipeline_multifocal.yaml b/datasets/stanford-murine/pipeline_multifocal.yaml index 5e69e4d..03eb178 100644 --- a/datasets/stanford-murine/pipeline_multifocal.yaml +++ b/datasets/stanford-murine/pipeline_multifocal.yaml @@ -3,14 +3,9 @@ pipeline: operations: # Coherent overlap averaging can create fractional RF samples; no cast is needed. - demodulate - - name: fir_filter - params: - axis: -3 - complex_channels: true - name: beamform params: enable_pfield: false - # Bound work units so the deep C5-2v grid fits on a 24 GB GPU. - envelope_detect # Use the true maximum; percentile normalization would clip strong reflectors. - name: normalize @@ -23,3 +18,6 @@ parameters: sound_speed: 1540 # Derive both grid dimensions at three samples per acoustic wavelength. pixels_per_wavelength: 3 + dynamic_range: + - -60 + - 0 diff --git a/datasets/stanford-murine/pipeline_synthetic_aperture.yaml b/datasets/stanford-murine/pipeline_synthetic_aperture.yaml index fa89ea5..261b777 100644 --- a/datasets/stanford-murine/pipeline_synthetic_aperture.yaml +++ b/datasets/stanford-murine/pipeline_synthetic_aperture.yaml @@ -4,14 +4,9 @@ pipeline: # Coherent overlap averaging can create fractional RF samples; no cast is needed. - apply_window - demodulate - - name: fir_filter - params: - axis: -3 - complex_channels: true - name: beamform params: enable_pfield: false - # Bound work units so the deep C5-2v grid fits on a 24 GB GPU. - envelope_detect # Use the true maximum; percentile normalization would clip strong reflectors. - name: normalize @@ -24,3 +19,6 @@ parameters: sound_speed: 1540 # Derive both grid dimensions at three samples per acoustic wavelength. pixels_per_wavelength: 3 + dynamic_range: + - -60 + - 0 diff --git a/datasets/stanford-murine/reconstruct.py b/datasets/stanford-murine/reconstruct.py index a2b4a74..b7723b0 100644 --- a/datasets/stanford-murine/reconstruct.py +++ b/datasets/stanford-murine/reconstruct.py @@ -6,8 +6,12 @@ B-mode reconstruction of murine full synthetic aperture, multifocal and Hadamard-encoded channel data. -Acquisition parameters are built from each track's own metadata, and one -displayed image is saved per track. +Each file bundles three tracks. Every track is reconstructed with its own +pipeline_.yaml: the acquisition parameters come from the track's own +metadata and the YAML adds the reconstruction grid and dynamic range. On top of +the YAML, a baseband FIR low-pass at half the probe bandwidth is inserted after +demodulation; its taps depend on the probe, so `zea process --track ` +runs the same pipeline without it. One displayed image is saved per track. Requires zea>=0.1.6 (https://github.com/tue-bmd/zea), the library that does the ultrasound processing here, together with one of its Keras backends (JAX, @@ -40,53 +44,7 @@ "Rat3/ExposedLiver/DATA_Tracks_20190319_115804.hdf5" ) CONFIG_DIR = "hf://nvidia/OpenH-RF/stanford-murine" # holds the pipeline_*.yaml configs -OUT_DIR = HERE # every PNG is written here - - -def metadata_scalar(group, name: str) -> float: - """Read one scan scalar as a plain float.""" - return float(getattr(group, name)) - - -def reconstruction_limits( - file, track, raw_data, np -) -> tuple[tuple[float, float], tuple[float, float]]: - """Derive the acquired field of view without storing dummy image maps.""" - sampling_frequency = metadata_scalar(track.scan, "sampling_frequency") - sound_speed = metadata_scalar(track.scan, "sound_speed") or 1540.0 - if sampling_frequency is None or sampling_frequency <= 0: - raise ValueError( - "A positive sampling_frequency is required to determine reconstruction depth." - ) - - initial_times = np.asarray(track.scan.initial_times, dtype=np.float64) - zmin = max(0.0, float(np.min(initial_times) * sound_speed / 2.0)) - zmax = float( - (np.max(initial_times) + (raw_data.shape[2] - 1) / sampling_frequency) * sound_speed / 2.0 - ) - - geometry = np.asarray(file.probe.probe_geometry) - if file.probe.type != "curved": - return ( - (float(np.min(geometry[:, 0])), float(np.max(geometry[:, 0]))), - (zmin, zmax), - ) - - # Curved-array elements lie on a circle whose center is below the array - # origin. Recover its radius and opening angle from the saved geometry. - x_positions = geometry[:, 0] - z_positions = geometry[:, 2] - curved_elements = np.abs(z_positions) > np.finfo(geometry.dtype).eps - radii = -(x_positions[curved_elements] ** 2 + z_positions[curved_elements] ** 2) / ( - 2.0 * z_positions[curved_elements] - ) - radius = float(np.median(radii)) - angles = np.arctan2(x_positions, z_positions + radius) - x_limit = max( - float(np.max(np.abs(x_positions))), - float((zmax + radius) * np.sin(np.max(np.abs(angles)))), - ) - return (-x_limit, x_limit), (zmin, zmax) +OUT_DIR = HERE / "assets" # every PNG is written here def folded_frequency(frequency: float, sampling_frequency: float) -> float: @@ -117,7 +75,27 @@ def demodulation_filter( return firwin(num_taps, cutoff, fs=sampling_frequency).astype(np.float32), cutoff +def with_fir_filter(config): + """Insert the baseband FIR after demodulation. + + Its taps depend on each file's probe bandwidth, so they are computed per track + below and the step is not part of the pipeline YAMLs; ``zea process`` runs the + same pipeline without it. + """ + import zea + + config = zea.Config(config.as_dict()) + operations = list(config.pipeline.operations) + operations.insert( + operations.index("demodulate") + 1, + {"name": "fir_filter", "params": {"axis": -3, "complex_channels": True}}, + ) + config.pipeline.operations = operations + return config + + def reconstruct_track(path: str, output_dir: Path, track_index: int, config, pipeline) -> Path: + """Reconstruct the first frame of one track with its YAML pipeline plus the FIR.""" import matplotlib.pyplot as plt import numpy as np import zea @@ -127,49 +105,25 @@ def reconstruct_track(path: str, output_dir: Path, track_index: int, config, pip with zea.File(str(path)) as file: track = file.tracks[track_index] track_label = track.label - extent_data = track.data.raw_data[:1] - xlims, zlims = reconstruction_limits(file, track, extent_data, np) - config_params = config.parameters.as_dict() - - # Older Stanford files sometimes marked real RF as already demodulated. - demodulation_frequency = metadata_scalar(track.scan, "demodulation_frequency") - center_frequency = metadata_scalar(track.scan, "center_frequency") - demodulation_override = {} - if center_frequency is not None and ( - demodulation_frequency is None or abs(demodulation_frequency) < 1.0 - ): - demodulation_override["demodulation_frequency"] = center_frequency - parameter_overrides = { - **config_params, - **demodulation_override, - "grid_type": "cartesian", - } - parameter_overrides.update({"xlims": xlims, "zlims": zlims}) - - dynamic_range = (-50, 0) if file.probe_name == "C5-2v" else (-60, 0) - parameter_overrides["dynamic_range"] = np.array(dynamic_range, dtype=np.float32) - parameters = track.load_parameters(**parameter_overrides) + # Acquisition parameters come from the file; the pipeline YAML adds the + # reconstruction grid and display settings on top. + parameters = track.load_parameters() + parameters.update(config.parameters.as_dict()) data = track.data.raw_data[:1, parameters.selected_transmits] - filter_taps, _ = demodulation_filter( - float(parameters.sampling_frequency), - float(parameters.demodulation_frequency), - float(parameters.probe_bandwidth_percent), - np, - ) - pipeline_parameters = pipeline.prepare_parameters( - parameters, - fir_filter_taps=filter_taps, - ) - - outputs = pipeline(return_numpy=True, **{pipeline.key: data}, **pipeline_parameters) - image = np.squeeze(outputs[pipeline.output_key]) - image = np.clip(image, dynamic_range[0], dynamic_range[1]) - demodulation_frequency = float( - parameter_overrides.get("demodulation_frequency") - or metadata_scalar(track.scan, "demodulation_frequency") - or 0.0 - ) + filter_taps, _ = demodulation_filter( + float(parameters.sampling_frequency), + float(parameters.demodulation_frequency), + float(parameters.probe_bandwidth_percent), + np, + ) + outputs = pipeline( + return_numpy=True, + **{pipeline.key: data}, + **pipeline.prepare_parameters(parameters, fir_filter_taps=filter_taps), + ) + image = np.squeeze(outputs[pipeline.output_key]) + dynamic_range = tuple(float(v) for v in parameters.dynamic_range) stem = Path(path).stem out_path = output_dir / f"{stem}_{track_label}.png" @@ -223,7 +177,7 @@ def main() -> None: # Build each track pipeline once, then reuse it for every track in the file. pipeline_configs = {} for track_label, filename in CONFIG_BY_TRACK.items(): - config = zea.Config.from_path(f"{CONFIG_DIR}/{filename}") + config = with_fir_filter(zea.Config.from_path(f"{CONFIG_DIR}/{filename}")) pipeline_configs[track_label] = (config, zea.Pipeline.from_config(config)) OUT_DIR.mkdir(parents=True, exist_ok=True) diff --git a/datasets/strasbourg-basel/README.md b/datasets/strasbourg-basel/README.md index 2273366..705f839 100644 --- a/datasets/strasbourg-basel/README.md +++ b/datasets/strasbourg-basel/README.md @@ -1,5 +1,6 @@ --- -pretty_name: "OpenH-RF — BoneSRF Robot-Tracked Fractured-Femur Phantom Dataset" +name: strasbourg-basel +pretty_name: "BoneSRF Robot-Tracked Fractured-Femur Phantom Dataset" license: cc-by-4.0 task_categories: - other @@ -22,257 +23,116 @@ size_categories: - n<1K --- -# BoneSRF — Robot-Tracked Fractured-Femur Phantom Channel Data +# BoneSRF -**BoneSRF** (Bone Surface Reflection) is an ultrasound channel-data dataset built -around a simple question: can pre-beamformed RF data recovered from a handheld, -point-of-care scanner support bone-surface / fracture-reflection research? It -contributes three 3D-printed, fractured femur phantoms — scanned, beamformed-RF- -inverted, and packaged in the OpenH-RF `zea` format — to the OpenH-RF initiative. +

+ BoneSRF B-mode of phantom 2, whole-bone sweep +

-Nine scans in total: each of the three phantoms was swept three times (`distal`, -`proximal`, `wholebone`), giving nine `zea` HDF5 files in [`data/`](data/). Every -scan is **robot-tracked** — the probe was mounted on a robotic arm and its pose -independently recorded — and every file also carries that phantom's **CT scan and -multi-label segmentation** inside it. +*B-mode of a fractured-femur phantom, frame 45 of [`data/phantom2_wholebone.hdf5`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/strasbourg-basel/BoneSRF/data/phantom2_wholebone.hdf5), beamformed from the recovered channel data.* ## Dataset Description -The channel data in this dataset is **not a direct per-element sensor recording**. -A Clarius handheld probe does not expose its raw per-element channel data, only its -own internally beamformed RF output. Every `raw_data` array here is therefore a -numerical estimate: the per-element channel data consistent with the probe's known -per-scanline focused acquisition geometry (transmit delays, apodization, walking -sub-aperture) that, if beamformed the same way, would reproduce the real Clarius -output. This estimate is recovered by solving a conjugate-gradient least-squares -(CGLS) inversion of a zea `DASOperator` built from that acquisition geometry, -against the real beamformed phantom scans as the inversion target. - -This is phantom data — not simulated, clinical, or in-vivo — intended for -full-matrix-capture-style beamforming and image-reconstruction research at a -bone-tissue interface, and as a worked example of recovering pre-beamformed data -from beamformed-only ultrasound exports. +

+ BoneSRF +

-### How the data was generated - -1. **Real acquisition.** A Clarius handheld-probe-class linear array (L20HD3) was - used to scan each phantom, producing the probe's own beamformed RF output — this - is the *real*, physically acquired data, not simulated. -2. **Inversion.** The beamformed RF is inverted back into pre-beamformed, - per-element channel data using - [`das-inverse` (`clarius` branch)](https://github.com/sankethvedula/das-inverse/tree/clarius) - — a CGLS solver over a `zea.inverse.DASOperator` forward model of the probe's - focused, walking-sub-aperture transmit sequence (`invert_clarius_beamformed.py`). - The result is what this dataset calls "simulated" channel data: not - sensor-captured, but numerically consistent with the real beamformed acquisition - it was inverted from. -3. **Packaging.** The inverted channel data, transmit-sequence metadata, probe - geometry, per-frame probe pose, and the phantom's CT + segmentation are written - out as one `zea` HDF5 file per scan, matching the OpenH-RF format spec. - -### Probe tracking - -Every scan is **tracked**: the probe was mounted on a robotic arm, and its pose was -independently recorded via a trakSTAR electromagnetic tracking system with a fixed -fCal image-to-probe calibration. The tracking stream is packaged **per-frame -indexed** inside each file's metadata (`metadata/probe_pose`: translation, rotation, -timestamps) — `metadata/probe_pose[i]` corresponds directly to `raw_data[i]`, index -for index. It does not need a separate sidecar file, and the raw tracking capture is -not shipped — only the recovered, aligned pose stream. - -### The three phantoms +**BoneSRF** (Bone Surface Reflection) is an ultrasound channel-data dataset for bone-surface and fracture-reflection research. It contains three 3D-printed, fractured femur phantoms, scanned with a handheld point-of-care probe, inverted back to pre-beamformed RF and packaged in the OpenH-RF `zea` format. -Each phantom is a 3D-printed femur, modeled from a CC BY 4.0–licensed femur bone -dataset, with a fracture pattern simulated differently for each of the three. Each -printed femur is immersed in ultrasound-coupling gel and scanned by a robotic arm, -which gives repeatable, controlled probe trajectories instead of a freehand scan. -Each phantom is scanned three times, at three positions along the bone: +Each phantom was swept three times (`distal`, `proximal`, `wholebone`), giving nine `zea` HDF5 files. Every scan is robot-tracked: the probe was mounted on a robotic arm and its pose recorded separately. Each file also carries that phantom's CT scan and multi-label segmentation. -- **`distal`** — a sweep over the distal region of the femur -- **`proximal`** — a sweep over the proximal region of the femur -- **`wholebone`** — a sweep covering the full length of the femur +The channel data here is not a direct per-element sensor recording. A Clarius handheld probe does not expose its raw per-element channel data, only its own internally beamformed RF output. Every `raw_data` array is therefore a numerical estimate: the per-element channel data consistent with the probe's known per-scanline focused acquisition geometry (transmit delays, apodization, walking sub-aperture) that would reproduce the real Clarius output if beamformed the same way. It is recovered by a conjugate-gradient least-squares (CGLS) inversion of a zea `DASOperator` built from that geometry, using the real beamformed phantom scans as the inversion target. -**Fracture design:** `REQUIRES_CONTRIBUTOR` — the specific fracture pattern -simulated in each phantom (location; type: transverse / oblique / comminuted / -hairline; displacement) has not yet been documented. The CT segmentation carried in -each file is ground truth for the physical phantom geometry in the meantime. +This is phantom data, not simulated, clinical or in-vivo. It is intended for full-matrix-capture-style beamforming and image-reconstruction research at a bone-tissue interface, and as an example of recovering pre-beamformed data from beamformed-only ultrasound exports. -## Folder structure - -``` -BoneSRF/ -├── README.md ← this file (dataset overview + data card for all nine scans) -├── LICENCE (CC BY 4.0) -├── pipeline.yaml (saved zea.Pipeline — one pipeline, shared by all nine scans) -├── reconstruct.py (runs pipeline.yaml on any scan → reference_bmodes/.png) -├── data/ -│ ├── phantom1_distal.hdf5 (zea channel data + per-frame probe pose + CT) -│ ├── phantom1_proximal.hdf5 -│ ├── phantom1_wholebone.hdf5 -│ ├── phantom2_*.hdf5 (same three sweeps) -│ └── phantom3_*.hdf5 (same three sweeps) -└── reference_bmodes/ - └── .png (one reference reconstruction per scan) -``` +### How the data was generated -Every file in `data/` stands alone: it holds the (CGLS-recovered) pre-beamformed -channel data, the full transmit-sequence and probe metadata needed to beamform it, -the per-frame tracked probe pose, and the CT + segmentation of the phantom it -depicts. +1. **Acquisition.** Each phantom was scanned with a Clarius L20HD3 linear array, producing the probe's own beamformed RF output. This is the physically acquired data, not simulated. +2. **Inversion.** The beamformed RF is inverted back into pre-beamformed, per-element channel data with [`das-inverse` (`clarius` branch)](https://github.com/sankethvedula/das-inverse/tree/clarius), a CGLS solver over a `zea.inverse.DASOperator` forward model of the probe's focused, walking-sub-aperture transmit sequence (`invert_clarius_beamformed.py`). The result is not sensor-captured, but numerically consistent with the real beamformed acquisition it was inverted from. +3. **Packaging.** The inverted channel data, transmit-sequence metadata, probe geometry, per-frame probe pose, and the phantom's CT + segmentation are written out as one `zea` HDF5 file per scan, matching the OpenH-RF format spec. -## Reconstructing a B-mode +### Probe tracking -`reconstruct.py` loads the saved `zea.Pipeline` from `pipeline.yaml` and runs it on -one frame of one scan: +The probe was mounted on a robotic arm and its pose recorded separately by a trakSTAR electromagnetic tracking system with a fixed fCal image-to-probe calibration. The pose stream is stored per frame in each file's metadata (`metadata/probe_pose`: translation, rotation, timestamps), so `metadata/probe_pose[i]` corresponds to `raw_data[i]`. The raw tracking capture is not shipped, only the recovered, aligned pose stream. -```bash -python reconstruct.py # all nine, at their reference frames -python reconstruct.py phantom1_distal # one scan, at its reference frame -python reconstruct.py phantom1_distal --frame 40 --device cpu -``` +### The three phantoms -The pipeline is `cast` → `apply_window` → `beamform` (delay-and-sum with a -physically-motivated per-transmit `pfield` weighting, since this is a per-scanline -focused walking-sub-aperture acquisition rather than full synthetic aperture) → -`keras.ops.abs` → axial-only Gaussian blur → `normalize` → `log_compress`, with -display parameters (dynamic range, p-field settings) also read from `pipeline.yaml`. -Acquisition geometry comes from each file's own `scan`/`probe` groups. +Each phantom is a 3D-printed femur, modeled from a CC BY 4.0–licensed femur bone dataset, with a different simulated fracture pattern. Each printed femur is immersed in ultrasound-coupling gel and scanned by a robotic arm, which gives repeatable probe trajectories rather than a freehand scan. Each phantom is scanned three times, at three positions along the bone: -This intentionally avoids `zea.inverse`: that module is for the CGLS inversion that -*produced* these files, not for reconstructing from them. Runtime is ~30 s per frame -on CPU. Each file is a full multi-frame sweep of 20.24 GB to 24.49 GB, but only the requested -frame is read. +- **`distal`**: a sweep over the distal region of the femur +- **`proximal`**: a sweep over the proximal region of the femur +- **`wholebone`**: a sweep covering the full length of the femur ## Dataset Contributor(s) -Sidaty El Hadramy, Philippe C. Cattin, Juan Verde — IHU Strasbourg and the -Department of Biomedical Engineering, University of Basel. +- Sidaty El Hadramy (IHU Strasbourg; Department of Biomedical Engineering, University of Basel) +- Philippe C. Cattin (IHU Strasbourg; Department of Biomedical Engineering, University of Basel) +- Juan Verde (IHU Strasbourg; Department of Biomedical Engineering, University of Basel) ## Dataset Creation Date -Original Clarius acquisitions: 19/06/2026 (`phantom1_distal`) and 25/06/2026 -(`phantom1_proximal`, acquisition ID `20260625-ihu-04_BoneSRF-01_proximal_robot_r3`). -The acquisition date of the other seven sweeps was not separately recorded — see -[Known Issues](#known-issues). Converted to `zea` format 21/07/2026–05/08/2026 and -re-converted 13/08/2026–14/08/2026 to align probe tracking to `raw_data` -frame-by-frame. CT and segmentation embedded into the files 09/09/2026. +Clarius acquisitions: 19/06/2026 (`phantom1_distal`) and 25/06/2026 (`phantom1_proximal`). The other seven sweeps carry no recorded acquisition date, see [Known Issues](#known-issues). Converted to the `zea` format in 2026. ## License / Terms of Use -CC BY 4.0 — see [`LICENCE`](LICENCE). Confirmed by the contributor as cleared for -CC BY 4.0 release (phantom data; no patient consent or third-party IP encumbrance -applies). The CT and segmentation data carried inside the files is released under -the same terms. The femur geometry underlying the 3D-printed phantoms is itself -sourced from a CC BY 4.0–licensed bone model dataset. +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage -Full-matrix-capture-style beamforming research on recovered (not directly sensed) -channel data at a bone-tissue interface: delay-and-sum reconstruction, adaptive or -aberration-correction beamforming benchmarking, robot/EM-tracked probe-pose fusion -research, and as a reference example for recovering pre-beamformed data from -beamformed-only ultrasound exports (e.g. other handheld/point-of-care scanners with -the same limitation). +Full-matrix-capture-style beamforming research on recovered (not directly sensed) channel data at a bone-tissue interface: delay-and-sum reconstruction, adaptive and aberration-correction beamforming benchmarks, and robot/EM-tracked probe-pose fusion. It also serves as a reference for recovering pre-beamformed data from other beamformed-only scanners. ## Dataset Characterization -- **Data Collection Method:** phantom (3D-printed, bone-mimicking femur, immersed in - ultrasound-coupling gel); scanned with a Clarius handheld-probe-class linear array - mounted on and moved by a robotic arm; raw channel data recovered via CGLS - inversion of the probe's beamformed RF output (not a direct per-element - recording); probe pose independently tracked via a trakSTAR EM tracking system, - fCal-calibrated. -- **Labeling Method:** a CT scan of each 3D-printed phantom and a multi-label - segmentation of it (authored in 3D Slicer) are carried **inside each of that - phantom's three files**, under `custom/ct/` and `custom/ct_segmentation/` — see - [CT reference imaging](#ct-reference-imaging). There are no annotations on the RF - data itself. -- **Acquisition system:** Clarius L20HD3, 192-element linear array, 0.130 mm pitch - (24.8 mm aperture), 10 MHz center frequency, 30 MHz sampling frequency, 1540 m/s - sound speed, ~5.1 cm imaging depth (1984–2016 axial samples depending on scan), - single fixed transmit focus at 25.3–25.8 mm (verified: `focus_distances` is - constant across all 192 transmits within each scan), 192 focused transmits per - frame (one per lateral scanline, no steering), walking sub-aperture per scanline — - Hanning-windowed, 47–97 of 192 elements active per transmit (mean ~84, i.e. - roughly a quarter to a half of the array, narrowest at the array edges). +- **Data Collection Method:** phantom (3D-printed, bone-mimicking femur, immersed in ultrasound-coupling gel); scanned with a Clarius handheld-probe-class linear array mounted on and moved by a robotic arm; raw channel data recovered via CGLS inversion of the probe's beamformed RF output (not a direct per-element recording); probe pose independently tracked via a trakSTAR EM tracking system, fCal-calibrated. +- **Labeling Method:** a CT scan of each 3D-printed phantom and a multi-label segmentation of it (authored in 3D Slicer) are stored inside each of that phantom's three files, under `custom/ct/` and `custom/ct_segmentation/`. See [CT reference imaging](#ct-reference-imaging). There are no annotations on the RF data itself. +- **Acquisition system:** Clarius L20HD3, 192-element linear array, 0.130 mm pitch (24.8 mm aperture), 10 MHz center frequency, 30 MHz sampling frequency, 1540 m/s sound speed, ~5.1 cm imaging depth (1984–2016 axial samples depending on scan), single fixed transmit focus at 25.3–25.8 mm (`focus_distances` is constant across all 192 transmits within a scan), 192 focused transmits per frame (one per lateral scanline, no steering), Hanning-windowed walking sub-aperture per scanline with 47 to 97 of 192 elements active per transmit (mean 84, narrowest at the array edges). -## CT reference imaging +The CT and segmentation data inside the files are released under the same CC BY 4.0 terms as the channel data. The femur geometry behind the 3D-printed phantoms comes from a CC BY 4.0–licensed bone model dataset. -Each phantom's CT scan and its multi-label 3D Slicer segmentation are carried inside -**every one** of that phantom's three `zea` files, under `custom/ct/` and -`custom/ct_segmentation/`. They are not shipped as separate `.nrrd` sidecars, so no -file depends on another. +## Processing the Dataset -| Dataset | Contents | -|---|---| -| `custom/ct/volume` | CT volume, `int16`, stored `(k, j, i)` (slice, row, column) | -| `custom/ct/spacing`, `origin`, `direction`, `affine` | Grid geometry in SI metres; `affine` maps voxel index `(i, j, k, 1)` to an LPS position | -| `custom/ct/nrrd_header` | Verbatim header of the source NRRD (distances in millimetres) | -| `custom/ct_segmentation/labelmap` | Layered binary labelmap on the same grid, `uint8` | -| `custom/ct_segmentation/segment_*` | Per-segment name, label value, layer, colour, bounding box and 3D Slicer ID | +The acquisitions can be processed with the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/strasbourg-basel/reconstruct.py) as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF), together with the `pipeline.yaml` definition in this folder and the [zea library](https://github.com/tue-bmd/zea). The script streams the data from the Hugging Face Hub. -Grid geometry differs per phantom: +The script loads the saved `zea.Pipeline` from `pipeline.yaml`, runs it on one frame of one scan, and writes `assets/.png`. -| Phantom | CT grid | Stored array | Source spacing (mm) | -|---|---|---|---| -| phantom1 | `512 × 512 × 594` | `(594, 512, 512)` | `0.546875 × 0.546875 × 0.6` | -| phantom2 | `512 × 512 × 574` | `(574, 512, 512)` | `0.50390625 × 0.50390625 × 0.6` | -| phantom3 | `512 × 512 × 594` | `(594, 512, 512)` | `0.5625 × 0.5625 × 0.6` | +The constants at the top of the script select what is reconstructed: -Each segmentation has three segments, corresponding directly to the three RF sweeps -of that phantom. **Label values repeat across layers** — 3D Slicer keeps segments on -separate internal labelmap layers — so read a segment's mask as -`labelmap[..., segment_layers[s]] == segment_label_values[s]` rather than treating -the array as one flat labelmap: +- `SCAN`: the scan to reconstruct, as a local path or an `hf://` URI. +- `FRAME`: the frame to beamform. `None` uses that scan's reference frame, the one its reference image was rendered from. +- `DEVICE`: where to run, e.g. `"cpu"`, `"cuda:0"` or `"auto:1"`. +- `CT`: also plot the CT carried in the file, to `assets/ct_.png`. -| Segment | `segment_label_values` | `segment_layers` | Corresponds to | -|---|---|---|---| -| `BoneSRF-1_Proximal` | 1 | 0 | `phantom1_proximal` | -| `BoneSRF-1_Distal` | 2 | 0 | `phantom1_distal` | -| `BoneSRF-1_Complete` | 1 | 1 | `phantom1_wholebone` | -| `BoneSRF-2_Complete` | 1 | 0 | `phantom2_wholebone` | -| `BoneSRF-2_Distal` | 1 | 1 | `phantom2_distal` | -| `BoneSRF-2_Proximal` | 2 | 1 | `phantom2_proximal` | -| `BoneSRF-3_Proximal` | 2 | 0 | `phantom3_proximal` | -| `BoneSRF-3_Distal` | 3 | 0 | `phantom3_distal` | -| `BoneSRF-3_Complete` | 1 | 1 | `phantom3_wholebone` | +These values reconstruct `phantom2_wholebone` at its reference frame (45) on the CPU: + +```python +SCAN = "hf://nvidia/OpenH-RF/strasbourg-basel/BoneSRF/data/phantom2_wholebone.hdf5" +FRAME = None +DEVICE = "cpu" +``` + +and produce the B-mode image shown at the top of this card. + +The pipeline is `cast` → `apply_window` → `beamform` (delay-and-sum with a per-transmit `pfield` weighting, since this is a per-scanline focused walking-sub-aperture acquisition rather than full synthetic aperture) → `keras.ops.abs` → axial-only Gaussian blur → `normalize` → `log_compress`. Display parameters (dynamic range, p-field settings) also come from `pipeline.yaml`; acquisition geometry comes from each file's own `scan` and `probe` groups. -The CT is reference imaging of the physical phantom in scanner (LPS) space. It is -**not spatially registered** to the RF frames or to the tracked probe poses; no -CT↔ultrasound registration is provided with this submission. +The reconstruction does not use `zea.inverse`. That module is for the CGLS inversion that produced these files, not for reading them back. Runtime is about 30 s per frame on CPU. Each file is a full sweep of 20 to 25 GB, but only the requested frame is read. ## Dataset Format -Submitted in the [`zea` file format](https://zea.readthedocs.io/en/openh-rf-latest/) -as nine HDF5 files in [`data/`](data/), blosc-compressed. +[zea v0.1.6](https://github.com/tue-bmd/zea) -The channel data was recovered from the probe's real, beamformed RF output by -CGLS-inverting a `zea.inverse.DASOperator` built from the known acquisition -geometry. `t0_delays`, `tx_apodizations`, `focus_distances`, `transmit_origins`, -`polar_angles`, and `waveforms_two_way` are copied directly from the values that -inversion's DAS operator was built with — not re-derived or guessed. The source -`.npz` had no explicit `demodulation_frequency`; it was substituted with -`center_frequency` per the standard convention for RF (non-IQ) sources (verified: -`demodulation_frequency` = `center_frequency` = 10 MHz in every file). +Submitted in the [`zea` file format](https://zea.readthedocs.io/en/openh-rf-latest/) as nine HDF5 files. -Probe pose (`metadata/probe_pose`: translation, rotation, timestamps) was recovered -from the trakSTAR tracking capture via a fixed fCal image-to-probe calibration, -resampled onto each `raw_data` frame's own acquisition time before conversion, and -packaged as a **per-frame indexed signal** (`metadata/probe_pose[i]` ↔ -`raw_data[i]`). +The channel data was recovered from the probe's real, beamformed RF output by CGLS-inverting a `zea.inverse.DASOperator` built from the known acquisition geometry. `t0_delays`, `tx_apodizations`, `focus_distances`, `transmit_origins`, `polar_angles`, and `waveforms_two_way` are copied directly from the values that inversion's DAS operator was built with, not re-derived. The source `.npz` had no explicit `demodulation_frequency`; it was substituted with `center_frequency` per the standard convention for RF (non-IQ) sources. -CT and segmentation (an addition beyond the original proposal) were copied verbatim -out of the `.nrrd` files that previously shipped alongside the RF data, so that -every file is self-contained; the verbatim source NRRD headers are preserved with -them. Grid geometry is converted from the NRRD's millimetres to zea's SI metres. +Probe pose (`metadata/probe_pose`: translation, rotation, timestamps) was recovered from the trakSTAR tracking capture via a fixed fCal image-to-probe calibration, resampled onto each `raw_data` frame's own acquisition time before conversion, and stored per frame, so `metadata/probe_pose[i]` corresponds to `raw_data[i]`. -**Reading these files requires `h5py` built against HDF5 ≥ 2.0** (e.g. `h5py` ≥ -3.16) — see [Known Issues](#known-issues). +CT and segmentation were copied verbatim out of the `.nrrd` files that previously shipped alongside the RF data, so that every file is self-contained; the source NRRD headers are preserved with them. Grid geometry is converted from the NRRD's millimetres to zea's SI metres. + +Reading these files requires `h5py` built against HDF5 ≥ 2.0 (e.g. `h5py` ≥ 3.16), see [Known Issues](#known-issues). ### Fields -Every file has the same field structure; `n_frames` and `n_ax` vary per scan (see -[the nine scans](#the-nine-scans)). +Every file has the same field structure; `n_frames` and `n_ax` vary per scan (see [Dataset Quantification](#dataset-quantification)). | Field | Shape | dtype | Units | Description | |---|---|---|---|---| @@ -299,48 +159,102 @@ Every file has the same field structure; `n_frames` and `n_ax` vary per scan (se | `custom.ct_segmentation.segment_label_values` | `(3,)` | uint8 | — | Label value of each segment within its own layer | | `custom.ct_segmentation.segment_layers` | `(3,)` | uint8 | — | Index into the last axis of `labelmap` holding each segment | +## Folder structure + +``` +BoneSRF/ +├── README.md ← this file (dataset overview + data card for all nine scans) +├── LICENCE (CC BY 4.0) +├── pipeline.yaml (saved zea.Pipeline, shared by all nine scans) +├── reconstruct.py (runs pipeline.yaml on any scan, and plots its CT) +├── assets/ +│ ├── reference_bmode.png (the B-mode shown below) +│ └── ct_.png (CT slices of the scan's phantom) +├── data/ +│ ├── phantom1_distal.hdf5 (zea channel data + per-frame probe pose + CT) +│ ├── phantom1_proximal.hdf5 +│ ├── phantom1_wholebone.hdf5 +│ ├── phantom2_*.hdf5 (same three sweeps) +│ └── phantom3_*.hdf5 (same three sweeps) +└── reference_bmodes/ + └── .png (one reference reconstruction per scan) +``` + +Every file in `data/` is self-contained: it holds the CGLS-recovered pre-beamformed channel data, the transmit-sequence and probe metadata needed to beamform it, the per-frame probe pose, and the CT and segmentation of the phantom it shows. + +## CT reference imaging + +Each phantom's CT scan and its multi-label 3D Slicer segmentation are stored inside all three of that phantom's `zea` files, under `custom/ct/` and `custom/ct_segmentation/`. They are not shipped as separate `.nrrd` sidecars, so no file depends on another. + +

+ CT slices of phantom2 +

+ +Three slices of phantom2's CT, written by `reconstruct.py` with `CT = True`, with the `BoneSRF-2_Complete` segment outlined in red. The printed femur is hollow, so it reads dark against the bright coupling gel, and the coronal view shows the fracture: the bone is in separate, displaced pieces. + +| Dataset | Contents | +|---|---| +| `custom/ct/volume` | CT volume, `int16`, stored `(k, j, i)` (slice, row, column) | +| `custom/ct/spacing`, `origin`, `direction`, `affine` | Grid geometry in SI metres; `affine` maps voxel index `(i, j, k, 1)` to an LPS position | +| `custom/ct/nrrd_header` | Verbatim header of the source NRRD (distances in millimetres) | +| `custom/ct_segmentation/labelmap` | Layered binary labelmap on the same grid, `uint8` | +| `custom/ct_segmentation/segment_*` | Per-segment name, label value, layer, colour, bounding box and 3D Slicer ID | + +Grid geometry differs per phantom: + +| Phantom | CT grid | Stored array | Source spacing (mm) | +|---|---|---|---| +| phantom1 | `512 × 512 × 594` | `(594, 512, 512)` | `0.546875 × 0.546875 × 0.6` | +| phantom2 | `512 × 512 × 574` | `(574, 512, 512)` | `0.50390625 × 0.50390625 × 0.6` | +| phantom3 | `512 × 512 × 594` | `(594, 512, 512)` | `0.5625 × 0.5625 × 0.6` | + +Each segmentation has three segments, one per RF sweep of that phantom. Label values repeat across layers, because 3D Slicer keeps segments on separate internal labelmap layers, so read a segment's mask as `labelmap[..., segment_layers[s]] == segment_label_values[s]` rather than treating the array as one flat labelmap: + +| Segment | `segment_label_values` | `segment_layers` | Corresponds to | +|---|---|---|---| +| `BoneSRF-1_Proximal` | 1 | 0 | `phantom1_proximal` | +| `BoneSRF-1_Distal` | 2 | 0 | `phantom1_distal` | +| `BoneSRF-1_Complete` | 1 | 1 | `phantom1_wholebone` | +| `BoneSRF-2_Complete` | 1 | 0 | `phantom2_wholebone` | +| `BoneSRF-2_Distal` | 1 | 1 | `phantom2_distal` | +| `BoneSRF-2_Proximal` | 2 | 1 | `phantom2_proximal` | +| `BoneSRF-3_Proximal` | 2 | 0 | `phantom3_proximal` | +| `BoneSRF-3_Distal` | 3 | 0 | `phantom3_distal` | +| `BoneSRF-3_Complete` | 1 | 1 | `phantom3_wholebone` | + +The CT is reference imaging of the physical phantom in scanner (LPS) space. It is not spatially registered to the RF frames or to the tracked probe poses; no CT-to-ultrasound registration is provided. + ## Dataset Quantification **Current OpenH-RF release:** 9 HDF5 files; 200.75 GB (200,745,025,536 bytes) stored; root `zea_version` **0.1.6**. Sizes include all HDF5 contents and use decimal units (MB = 10^6 bytes, GB = 10^9 bytes, TB = 10^12 bytes), not decoded-array memory or original-source download sizes. -Nine acquisitions, one continuous sweep each; 1428 frames in total, 200.75 GB of stored HDF5 data. No train / val / test split (each file is a single reference acquisition). -Every value below was read back from the files themselves. - -| Scan | Frames | Poses | `n_ax` | Focus | Reference frame | Size on disk | -|---|---|---|---|---|---|---| -| `phantom1_distal` | 144 | 144 | 2016 | 25.70 mm | 130 | 20,534,067,200 B (20.53 GB) | -| `phantom1_proximal` | 174 | 174 | 2000 | 25.55 mm | 100 | 24,492,900,352 B (24.49 GB) | -| `phantom1_wholebone` | 158 | 158 | 1984 | 25.30 mm | 150 | 21,930,508,288 B (21.93 GB) | -| `phantom2_distal` | 162 | 162 | 2000 | 25.65 mm | 40 | 22,799,908,864 B (22.80 GB) | -| `phantom2_proximal` | 142 | 142 | 2016 | 25.75 mm | 40 | 20,236,337,152 B (20.24 GB) | -| `phantom2_wholebone` | 155 | 155 | 2016 | 25.80 mm | 45 | 21,953,183,744 B (21.95 GB) | -| `phantom3_distal` | 166 | 166 | 1984 | 25.30 mm | 100 | 23,042,916,352 B (23.04 GB) | -| `phantom3_proximal` | 167 | 167 | 2016 | 25.80 mm | 0 | 23,672,127,488 B (23.67 GB) | -| `phantom3_wholebone` | 159 | 159 | 1984 | 25.35 mm | 100 | 22,083,076,096 B (22.08 GB) | +Nine acquisitions, one continuous sweep each; 1,427 frames in total. No train / val / test split (each file is a single reference acquisition). Every scan has one tracked probe pose per frame. + +| Scan | Frames | `n_ax` | Focus | Reference frame | Size on disk | +|---|---|---|---|---|---| +| `phantom1_distal` | 144 | 2016 | 25.70 mm | 130 | 20.53 GB | +| `phantom1_proximal` | 174 | 2000 | 25.55 mm | 100 | 24.49 GB | +| `phantom1_wholebone` | 158 | 1984 | 25.30 mm | 150 | 21.93 GB | +| `phantom2_distal` | 162 | 2000 | 25.65 mm | 40 | 22.80 GB | +| `phantom2_proximal` | 142 | 2016 | 25.75 mm | 40 | 20.24 GB | +| `phantom2_wholebone` | 155 | 2016 | 25.80 mm | 45 | 21.95 GB | +| `phantom3_distal` | 166 | 1984 | 25.30 mm | 100 | 23.04 GB | +| `phantom3_proximal` | 167 | 2016 | 25.80 mm | 0 | 23.67 GB | +| `phantom3_wholebone` | 159 | 1984 | 25.35 mm | 100 | 22.08 GB | ## Subject Metadata -3D-printed, bone-mimicking musculoskeletal phantoms ("BoneSRF" — bone surface -reflection targets); no human or animal subject. Scanned with a robot-mounted -Clarius L20HD3 linear array at 10 MHz / ~5.1 cm depth / single transmit focus. +3D-printed, bone-mimicking musculoskeletal phantoms (bone surface reflection targets); no human or animal subject. Scanned with a robot-mounted Clarius L20HD3 linear array at 10 MHz / ~5.1 cm depth / single transmit focus. ## Data Validation -Every file was validated against the installed `zea` data spec — `File.validate()` -(structural) and `File.validate_spec()` (full dtype / shape / dimension consistency) -— and all nine report compliant, with `/data/raw_data` present and non-empty. -`reconstruct.py` runs end-to-end on every scan and is deterministic across runs; the -images in [`reference_bmodes/`](reference_bmodes/) are its output. +All nine files pass the `zea` data spec, both `File.validate()` (structural) and `File.validate_spec()` (dtype, shape and dimension consistency). `reconstruct.py` runs end-to-end on every scan; the images in `reference_bmodes/` are its output. ## Known Issues -- **Acquisition dates are incomplete.** Only `phantom1_distal` (19/06/2026) and - `phantom1_proximal` (25/06/2026) have a recorded original Clarius acquisition date; - the other seven sweeps do not carry one in the file or in the source capture. -- **No CT↔ultrasound registration.** The CT lives in scanner LPS space and the probe - poses in trakSTAR tracker space. Nothing in this submission relates the two. -- **CT intensity units are unverified.** The source NRRD headers record no unit. The - value range (−1024 … ~500) is consistent with Hounsfield units, but this has not - been confirmed by the contributor. +- **Fracture patterns are not documented per phantom.** The location, type (transverse / oblique / comminuted / hairline) and displacement of each phantom's fracture are not recorded. The CT segmentation in each file is ground truth for the physical phantom geometry. +- **Acquisition dates are incomplete.** Only `phantom1_distal` (19/06/2026) and `phantom1_proximal` (25/06/2026) have a recorded original Clarius acquisition date; the other seven sweeps do not carry one in the file or in the source capture. +- **No CT-to-ultrasound registration.** The CT is in scanner LPS space and the probe poses in trakSTAR tracker space. Nothing here relates the two. +- **CT intensity units are unverified.** The source NRRD headers record no unit. The value range (−1024 to about 500) is consistent with Hounsfield units, but this has not been confirmed. ## Ethical Considerations diff --git a/datasets/strasbourg-basel/assets/bonesrf_logo.png b/datasets/strasbourg-basel/assets/bonesrf_logo.png new file mode 100644 index 0000000..98f080c Binary files /dev/null and b/datasets/strasbourg-basel/assets/bonesrf_logo.png differ diff --git a/datasets/strasbourg-basel/assets/ct_phantom2_proximal.png b/datasets/strasbourg-basel/assets/ct_phantom2_proximal.png new file mode 100644 index 0000000..c2d25a4 Binary files /dev/null and b/datasets/strasbourg-basel/assets/ct_phantom2_proximal.png differ diff --git a/datasets/strasbourg-basel/assets/ct_phantom2_wholebone.png b/datasets/strasbourg-basel/assets/ct_phantom2_wholebone.png new file mode 100644 index 0000000..abfe84d Binary files /dev/null and b/datasets/strasbourg-basel/assets/ct_phantom2_wholebone.png differ diff --git a/datasets/strasbourg-basel/assets/phantom2_proximal.png b/datasets/strasbourg-basel/assets/phantom2_proximal.png new file mode 100644 index 0000000..0d1098f Binary files /dev/null and b/datasets/strasbourg-basel/assets/phantom2_proximal.png differ diff --git a/datasets/strasbourg-basel/assets/reference_bmode.png b/datasets/strasbourg-basel/assets/reference_bmode.png new file mode 100644 index 0000000..bb53f41 Binary files /dev/null and b/datasets/strasbourg-basel/assets/reference_bmode.png differ diff --git a/datasets/strasbourg-basel/reconstruct.py b/datasets/strasbourg-basel/reconstruct.py index 80fa0af..064aabf 100644 --- a/datasets/strasbourg-basel/reconstruct.py +++ b/datasets/strasbourg-basel/reconstruct.py @@ -24,15 +24,16 @@ from pathlib import Path +import keras import matplotlib.pyplot as plt import numpy as np import zea from zea import Config, File, Pipeline HERE = Path(__file__).parent -DATA = HERE / "data" CONFIG = HERE / "pipeline.yaml" -OUTPUT_DIR = HERE / "reference_bmodes" +OUTPUT_DIR = HERE / "assets" +ASSETS = HERE / "assets" # The frame each scan's committed reference_bmodes/.png was rendered from. REFERENCE_FRAME = { @@ -47,12 +48,16 @@ "phantom3_wholebone": 100, } +# The CT segment outlining the phantom, per sweep (see the README's segment table). +CT_SEGMENT = {"distal": "Distal", "proximal": "Proximal", "wholebone": "Complete"} + # --- Inputs ----------------------------------------------------------------- # Defaults stream straight from the published corpus. Swap any of these for a # local path to run against your own copy. SCAN = "hf://nvidia/OpenH-RF/strasbourg-basel/BoneSRF/data/phantom2_proximal.hdf5" FRAME = None # frame to beamform (default: that scan's reference frame) DEVICE = None # CUDA device ID (e.g. 'cuda:0', 'auto:1', or 'cpu') +CT = True # also write assets/ct_.png from the CT embedded in the file def reconstruct(source: str, scan: str, frame: int, config: Config) -> None: @@ -71,7 +76,7 @@ def reconstruct(source: str, scan: str, frame: int, config: Config) -> None: pipeline = Pipeline.from_config(config) outputs = pipeline(data=raw, **pipeline.prepare_parameters(parameters)) - recon = np.array(outputs["data"]) # (n_frames, grid_z, grid_x, n_ch) + recon = keras.ops.convert_to_numpy(outputs["data"]) # (n_frames, grid_z, grid_x, n_ch) # No envelope_detect in pipeline.yaml (see its comments), so the trailing # n_ch=1 axis survives to the output; squeeze it for a 2D image. image = zea.display.to_8bit(np.squeeze(recon[0]), dynamic_range=parameters.dynamic_range) @@ -88,24 +93,78 @@ def reconstruct(source: str, scan: str, frame: int, config: Config) -> None: print(f" saved {out.relative_to(HERE)}") +def plot_ct(source: str, scan: str) -> None: + """Plot three slices of the file's CT and write assets/ct_.png. + + The segment outlined is the one covering this scan's sweep of the phantom. + """ + phantom, sweep = scan.split("_") + segment = f"BoneSRF-{phantom[-1]}_{CT_SEGMENT[sweep]}" + + ct, seg = "custom/ct", "custom/ct_segmentation" + with File(str(source)) as f: + volume = f.dataset(f"{ct}/volume")[:] # (k, j, i), a few hundred MB + names = list(f.dataset(f"{seg}/segment_names")[:]) + s = names.index(segment) + layer = int(f.dataset(f"{seg}/segment_layers")[s]) + label = int(f.dataset(f"{seg}/segment_label_values")[s]) + mask = f.dataset(f"{seg}/labelmap")[..., layer] == label + # spacing is index-ordered (i, j, k) in meters; the arrays are stored (k, j, i). + si, sj, sk = f.dataset(f"{ct}/spacing")[:] * 1e3 + + # Slice through the middle of the bone, trimming the air around the phantom. + kc, jc, ic = (int(np.median(axis)) for axis in np.nonzero(mask)) + solid = volume > -500 # the phantom and its container, i.e. everything but air + (k0, k1), (j0, j1), (i0, i1) = ( + (int(used[0]), int(used[-1]) + 1) + for used in (np.nonzero(solid.any(axis=tuple({0, 1, 2} - {axis})))[0] for axis in range(3)) + ) + kk, jj, ii = slice(k0, k1), slice(j0, j1), slice(i0, i1) + panels = [ # view, what to slice out, then the (offset, spacing, name) of each axis + ("Coronal", (kk, jc, ii), (i0, si, "i"), (k0, sk, "k")), + ("Sagittal", (kk, jj, ic), (j0, sj, "j"), (k0, sk, "k")), + ("Axial", (kc, jj, ii), (i0, si, "i"), (j0, sj, "j")), + ] + + zea.visualize.set_mpl_style() + fig, axes = plt.subplots(1, 3, figsize=(12, 5), constrained_layout=True) + for ax, (view, index, (x0, sx, x), (y0, sy, y)) in zip(axes, panels): + image, outline = volume[index], mask[index] + xs = (x0 + np.arange(image.shape[1])) * sx + ys = (y0 + np.arange(image.shape[0])) * sy + # The print is hollow, so the bone reads dark against the bright coupling gel. + ax.imshow( + image, + cmap="gray", + vmin=-1024, + vmax=250, + aspect="equal", + extent=(xs[0], xs[-1], ys[-1], ys[0]), + ) + ax.contour(xs, ys, outline, levels=[0.5], colors="red", linewidths=0.9) + ax.set_title(view) + ax.set_xlabel(f"{x} (mm)") + ax.set_ylabel(f"{y} (mm)") + fig.suptitle(f"{scan}: CT with {segment} outlined") + + out = ASSETS / f"ct_{scan}.png" + fig.savefig(str(out), bbox_inches="tight", dpi=140) + plt.close(fig) + print(f" saved {out.relative_to(HERE)}") + + def main(): - if SCAN: - sources = {Path(SCAN).stem: str(SCAN)} - else: - sources = {scan: str(DATA / f"{scan}.hdf5") for scan in REFERENCE_FRAME} - scans = list(sources) - unknown = [s for s in scans if s not in REFERENCE_FRAME] - if unknown: - raise SystemExit(f"unknown scan(s) {unknown}; expected one of {list(REFERENCE_FRAME)}") - if FRAME is not None and len(scans) > 1: - raise SystemExit("FRAME applies to a single scan; name one") + scan = Path(SCAN).stem + frame = FRAME if FRAME is not None else REFERENCE_FRAME[scan] zea.init_device(device=DEVICE, verbose=False) OUTPUT_DIR.mkdir(exist_ok=True) + ASSETS.mkdir(exist_ok=True) config = Config.from_path(str(CONFIG)) - for scan, source in sources.items(): - reconstruct(source, scan, FRAME if FRAME is not None else REFERENCE_FRAME[scan], config) + reconstruct(SCAN, scan, frame, config) + if CT: + plot_ct(SCAN, scan) if __name__ == "__main__": diff --git a/datasets/technion/bladder/README.md b/datasets/technion/bladder/README.md index 3122810..d36f9ec 100644 --- a/datasets/technion/bladder/README.md +++ b/datasets/technion/bladder/README.md @@ -1,5 +1,6 @@ --- -pretty_name: "OpenH-RF — Technion Bladder Pre-Beamformed Channel Data" +name: technion-bladder +pretty_name: "Technion Bladder Pre-Beamformed Channel Data" license: cc-by-4.0 task_categories: - image-to-image @@ -16,23 +17,23 @@ size_categories: - 1K (primary contact) +- Ortal Senouf +- Dean Zadok +- Alex M. Bronstein (PI) +- Technion – Israel Institute of Technology ## Dataset Creation Date @@ -40,40 +41,38 @@ Source data 2018; converted to the OpenH-RF (zea) format 07/16/2026. ## License / Terms of Use -CC BY 4.0. The contributors confirm intent to release under CC BY 4.0 with no -third-party IP encumbrances (proposal §8). +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage -Primary: **generalized reconstruction** (§6.1) — learned receive beamforming and -image reconstruction from raw channel data. The quasi-static bladder is also -suited to multi-line-transmission (MLT) emulation and high-frame-rate research, -and to anatomy/cohort interpretation (§6.5). +Primary: **generalized reconstruction** (§6.1) — learned receive beamforming and image reconstruction from raw channel data. The quasi-static bladder is also suited to multi-line-transmission (MLT) emulation and high-frame-rate research, and to anatomy/cohort interpretation (§6.5). ## Dataset Characterization -- **Data Collection Method:** in-vivo human (research platform) — GE Vivid S70 - scanner with raw per-element channel access, tissue-harmonic mode. -- **Labeling Method:** N/A — no per-frame image label; the `zea.Pipeline` in - `pipeline.yaml` reconstructs a B-mode from the channel data for validation. -- **Acquisition system:** GE Vivid S70 scanner; GE 3Sc-RS 64-element phased-array - probe, 0.30 mm pitch; sector scan, 180 transmit beams steered over ±45.13° - (≈90.25° FOV), one image line per transmit. - Per proposal: 2.56-cycle 1.6 MHz transmit, no transmit apodization, - tissue-harmonic mode, harmonic echo demodulated to I/Q at 3.44 MHz and filtered, - ~18 fps; transversal plane with slow longitudinal probe sweep to decorrelate - frames. +- **Data Collection Method:** in-vivo human (research platform) — GE Vivid S70 scanner with raw per-element channel access, tissue-harmonic mode. +- **Labeling Method:** N/A — no per-frame image label; the `zea.Pipeline` in `pipeline.yaml` reconstructs a B-mode from the channel data for validation. +- **Acquisition system:** GE Vivid S70 scanner; GE 3Sc-RS 64-element phased-array probe, 0.30 mm pitch; sector scan, 180 transmit beams steered over ±45.13° (≈90.25° FOV), one image line per transmit. Per proposal: 2.56-cycle 1.6 MHz transmit, no transmit apodization, tissue-harmonic mode, harmonic echo demodulated to I/Q at 3.44 MHz and filtered, ~18 fps; transversal plane with slow longitudinal probe sweep to decorrelate frames. + +## Processing the Dataset + +The acquisitions can be processed with the `pipeline.yaml` definition in this folder and the [zea library](https://github.com/tue-bmd/zea). + +`zea` streams the data from the Hugging Face Hub and processes it according to the pipeline. You can try it out with the following command: + +```bash +zea process \ + --dataset hf://nvidia/OpenH-RF/technion/bladder/data/a1.hdf5 \ + --config hf://nvidia/OpenH-RF/technion/bladder/pipeline.yaml \ + --n-frames 10 +``` + +Alternatively, you can use the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/technion/bladder/reconstruct.py) as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF). ## Dataset Format -zea file format, one HDF5 file per sweep (`data/.hdf5`, e.g. `a1.hdf5`, -`ak.hdf5`, `s2.hdf5`). The source complex `double` samples were repackaged to -`float32` I/Q with I and Q on the final channel axis (`n_ch = 2`); values are -otherwise verbatim (band-pass filtered baseband IQ, as archived). Each file -carries `metadata/subject/{id,type=human}`, `metadata/credit`, and -`metadata/annotations/{anatomy=bladder, label=in vivo, view=transverse suprapubic -pelvic ultrasound}`. Probe model (`probe.name = GE 3Sc-RS`) and scanner -(`us_machine = GE Vivid S70`) are stored too. +[zea v0.1.4](https://github.com/tue-bmd/zea) + +zea file format, one HDF5 file per sweep (`data/.hdf5`, e.g. `a1.hdf5`, `ak.hdf5`, `s2.hdf5`). The source complex `double` samples were repackaged to `float32` I/Q with I and Q on the final channel axis (`n_ch = 2`); values are otherwise verbatim (band-pass filtered baseband IQ, as archived). Each file carries `metadata/subject/{id,type=human}`, `metadata/credit`, and `metadata/annotations/{anatomy=bladder, label=in vivo, view=transverse suprapubic pelvic ultrasound}`. Probe model (`probe.name = GE 3Sc-RS`) and scanner (`us_machine = GE Vivid S70`) are stored too. ## Dataset Quantification @@ -94,12 +93,7 @@ pelvic ultrasound}`. Probe model (`probe.name = GE 3Sc-RS`) and scanner ## Subject Metadata -**Seven in-vivo human volunteers**, 14 sweeps, 1,508 frames. (The proposal's -"six" was an undercount; verified from the acquisitions to be seven distinct -volunteers.) No phantom is included in this collection — the calibration phantom -is a separate submission (`../phantom/`). No PHI stored: only anonymized -`subject.id`, `subject.type = human`, and `annotations.anatomy = bladder`. -Age and sex were not recorded for these acquisitions. +**Seven in-vivo human volunteers**, 14 sweeps, 1,508 frames. (The proposal's "six" was an undercount; verified from the acquisitions to be seven distinct volunteers.) No phantom is included in this collection — the calibration phantom is a separate submission (`../phantom/`). No PHI stored: only anonymized `subject.id`, `subject.type = human`, and `annotations.anatomy = bladder`. Age and sex were not recorded for these acquisitions. | Subject | Sweeps (files) | Frames | |---|---|---| @@ -113,37 +107,19 @@ Age and sex were not recorded for these acquisitions. ## Data Validation -`reconstruct.py` reconstructs a B-mode from `raw_data` using the `zea.Pipeline` -defined in `pipeline.yaml`: delay-and-sum beamforming on a polar scanline grid -(one image line per transmit, receive dynamic focusing at f-number 1) → envelope -detection → normalization → log compression → sector scan conversion. Run it on -any file to reproduce a reference frame: - -``` -python reconstruct.py data/s2.hdf5 --frame 54 --out bmode_s2.png -``` +`reconstruct.py` reconstructs a B-mode from `raw_data` using the `zea.Pipeline` defined in `pipeline.yaml`: delay-and-sum beamforming on a polar scanline grid (one image line per transmit, receive dynamic focusing) → envelope detection → normalization → log compression → sector scan conversion. -Reference output: `bmode_s2.png`. The pipeline matches the acquisition's own -receive-beamforming geometry (`code/processing/`), so the reconstruction -reproduces the expected sector B-mode. +Reference output: `bmode.png` — frame 30 of `data/a1.hdf5` (in `assets/`). The pipeline matches the acquisition's own receive-beamforming geometry (`code/processing/`), so the reconstruction reproduces the expected sector B-mode. ## Known Issues -- **No paired image target** (unlike the cardiac set); the B-mode is derived from - the channel data, not supplied. -- **Transmit fundamental (1.6 MHz) not stored** — only the 3.44 MHz demodulation - frequency is in the files, so `center_frequency` equals the demodulation - frequency. +- **No paired image target** (unlike the cardiac set); the B-mode is derived from the channel data, not supplied. +- **Transmit fundamental (1.6 MHz) not stored** — only the 3.44 MHz demodulation frequency is in the files, so `center_frequency` equals the demodulation frequency. ## Ethical Considerations -**Privacy safeguards (HIPAA and GDPR).** Pre-beamformed RF channel data contains -no facial or otherwise identifying imagery. All records are de-identified to the -HIPAA Safe Harbor standard, with direct identifiers removed and any dates -generalized to bands. As an EU institution we additionally comply with GDPR, -holding any pseudonymized subject identifiers separately on access-controlled -storage and never sharing them. The released data are de-identified and contain -only the channel signals and acquisition metadata. +**Privacy safeguards (HIPAA and GDPR).** Pre-beamformed RF channel data contains no facial or otherwise identifying imagery. All records are de-identified to the HIPAA Safe Harbor standard, with direct identifiers removed and any dates generalized to bands. As an EU institution we additionally comply with GDPR, holding any pseudonymized subject identifiers separately on access-controlled storage and never sharing them. The released data are de-identified and contain only the channel signals and acquisition metadata. + +**Ethics.** The data were collected under ethical best practices on healthy volunteers. -**Ethics.** The data were collected under ethical best practices on healthy -volunteers. +The contributors confirm intent to release under CC BY 4.0 with no third-party IP encumbrances (proposal §8). diff --git a/datasets/technion/bladder/assets/bmode.png b/datasets/technion/bladder/assets/bmode.png new file mode 100644 index 0000000..016cbda Binary files /dev/null and b/datasets/technion/bladder/assets/bmode.png differ diff --git a/datasets/technion/bladder/assets/cine.gif b/datasets/technion/bladder/assets/cine.gif new file mode 100644 index 0000000..705aa3e Binary files /dev/null and b/datasets/technion/bladder/assets/cine.gif differ diff --git a/datasets/technion/bladder/reconstruct.py b/datasets/technion/bladder/reconstruct.py index 5c242a9..772fa36 100644 --- a/datasets/technion/bladder/reconstruct.py +++ b/datasets/technion/bladder/reconstruct.py @@ -32,10 +32,10 @@ # --- Inputs ----------------------------------------------------------------- # Defaults stream straight from the published corpus. Swap any of these for a # local path to run against your own copy. -ZEA_FILE = "hf://nvidia/OpenH-RF/technion/bladder/data/ak.hdf5" +ZEA_FILE = "hf://nvidia/OpenH-RF/technion/bladder/data/a1.hdf5" CONFIG = "hf://nvidia/OpenH-RF/technion/bladder/pipeline.yaml" -FRAME = 0 -OUT = HERE / "bmode.png" +FRAME = 10 +OUT = HERE / "assets" / "bmode.png" def main(): @@ -65,6 +65,7 @@ def main(): cax = make_axes_locatable(ax).append_axes("right", size="5%", pad=0.05) fig.colorbar(im, cax=cax, label="a.u. (8-bit)") fig.tight_layout() + Path(OUT).parent.mkdir(parents=True, exist_ok=True) fig.savefig(str(OUT), dpi=130, bbox_inches="tight") print(f"raw {raw.shape} -> B-mode {recon.shape}; saved {OUT}") diff --git a/datasets/technion/cardiac/README.md b/datasets/technion/cardiac/README.md index ed97569..1af40ef 100644 --- a/datasets/technion/cardiac/README.md +++ b/datasets/technion/cardiac/README.md @@ -1,5 +1,6 @@ --- -pretty_name: "OpenH-RF — Technion Cardiac Pre-Beamformed Channel Data" +name: technion-cardiac +pretty_name: "Technion Cardiac Pre-Beamformed Channel Data" license: cc-by-4.0 task_categories: - image-to-image @@ -16,22 +17,23 @@ size_categories: - n<1K --- -# OpenH-RF — Cardiac pre-beamformed RF channel data (paired with DAS targets) +# Technion Cardiac Pre-beamformed RF Channel Data (paired with DAS targets) + +![Apical four-chamber view, one 32-frame cardiac cine loop](assets/cine.gif) + +*Apical four-chamber view: one cine loop (32 frames) from [`data/c1.hdf5`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/technion/cardiac/data/c1.hdf5).* ## Dataset Description -Real, **in-vivo human** pre-beamformed ultrasound **channel data** for cardiac -imaging: per-element I/Q recorded before receive beamforming on a 64-element -phased array — a sector scan of 140 transmit beams steered over ±37.5°, one image -line per transmit (steering angles in `scan.polar_angles`). Each frame is **paired with -its conventional delay-and-sum reconstruction** (stored as `beamformed_data`), -making this a ready-made input→target set for learned reconstruction / -beamforming. 777 frames across 25 cine loops from six subjects (a–f). +Real, **in-vivo human** pre-beamformed ultrasound **channel data** for cardiac imaging: per-element I/Q recorded before receive beamforming on a 64-element phased array — a sector scan of 140 transmit beams steered over ±37.5°, one image line per transmit (steering angles in `scan.polar_angles`). Each frame is **paired with its conventional delay-and-sum reconstruction** (stored as `beamformed_data`), making this a ready-made input→target set for learned reconstruction / beamforming. 777 frames across 25 cine loops from six subjects (a–f). ## Dataset Contributor(s) -Sanketh Vedula, Ortal Senouf, Dean Zadok, Alex M. Bronstein (PI) — -Technion – Israel Institute of Technology. Primary contact: sanketh@campus.technion.ac.il. +- Sanketh Vedula (primary contact) +- Ortal Senouf +- Dean Zadok +- Alex M. Bronstein (PI) +- Technion – Israel Institute of Technology ## Dataset Creation Date @@ -39,45 +41,45 @@ Acquired 2018; converted to the OpenH-RF (zea) format 07/16/2026. ## License / Terms of Use -CC BY 4.0. The data is the contributors' own research acquisition, cleared for -CC BY 4.0 with no third-party IP encumbrances. +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage -Primary: **generalized reconstruction** (§6.1) — learning to map raw per-element -channel data to a focused image (learned receive/transmit beamforming, -super-resolution, clutter suppression), trained and evaluated against the paired -delay-and-sum target. Secondary: motion estimation across the cardiac cine loops -(§6.4) and anatomy/cohort interpretation (§6.5). +Primary: **generalized reconstruction** (§6.1) — learning to map raw per-element channel data to a focused image (learned receive/transmit beamforming, super-resolution, clutter suppression), trained and evaluated against the paired delay-and-sum target. Secondary: motion estimation across the cardiac cine loops (§6.4) and anatomy/cohort interpretation (§6.5). ## Dataset Characterization -- **Data Collection Method:** in-vivo human (research platform) — GE Vivid S70 - scanner with raw per-element channel access. -- **Labeling Method:** derived ground truth — the paired `beamformed_data` is the - conventional delay-and-sum reconstruction of each frame. -- **Acquisition system:** GE Vivid S70 scanner; GE 3Sc-RS 64-element phased-array - probe, 0.30 mm pitch; sector scan, 140 acquisition lines over a ~75° sector - (±37.5°); 2.5 MHz transmit; apical four-chamber view (A4C). +- **Data Collection Method:** in-vivo human (research platform) — GE Vivid S70 scanner with raw per-element channel access. +- **Labeling Method:** derived ground truth — the paired `beamformed_data` is the conventional delay-and-sum reconstruction of each frame. +- **Acquisition system:** GE Vivid S70 scanner; GE 3Sc-RS 64-element phased-array probe, 0.30 mm pitch; sector scan, 140 acquisition lines over a ~75° sector (±37.5°); 2.5 MHz transmit; apical four-chamber view (A4C). + +## Processing the Dataset + +The acquisitions can be processed with the `pipeline.yaml` definition in this folder and the [zea library](https://github.com/tue-bmd/zea). + +`zea` streams the data from the Hugging Face Hub and processes it according to the pipeline. You can try it out with the following command: + +```bash +zea process \ + --dataset hf://nvidia/OpenH-RF/technion/cardiac/data/c1.hdf5 \ + --config hf://nvidia/OpenH-RF/technion/cardiac/pipeline.yaml \ + --n-frames 32 +``` + +Alternatively, you can use the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/technion/cardiac/reconstruct.py) as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF). ## Dataset Format -zea file format, one HDF5 file per cine loop (`data/.hdf5`, e.g. -`a1.hdf5` = subject a, clip 1; `f2.hdf5` = patient-set subject f). The source -complex `int16` samples were repackaged to `float32` I/Q with I and Q on the -final channel axis (`n_ch = 2`); values are otherwise verbatim. Each file carries -`metadata/subject/{id,type=human}`, `metadata/credit`, and -`metadata/annotations/{anatomy=cardiac, label=in vivo, view=apical four-chamber (A4C)}`. Probe -model (`probe.name = GE 3Sc-RS`) and scanner (`us_machine = GE Vivid S70`) are -stored too. +[zea v0.1.4](https://github.com/tue-bmd/zea) + +zea file format, one HDF5 file per cine loop (`data/.hdf5`, e.g. `a1.hdf5` = subject a, clip 1; `f2.hdf5` = patient-set subject f). The source complex `int16` samples were repackaged to `float32` I/Q with I and Q on the final channel axis (`n_ch = 2`); values are otherwise verbatim. Each file carries `metadata/subject/{id,type=human}`, `metadata/credit`, and `metadata/annotations/{anatomy=cardiac, label=in vivo, view=apical four-chamber (A4C)}`. Probe model (`probe.name = GE 3Sc-RS`) and scanner (`us_machine = GE Vivid S70`) are stored too. ## Dataset Quantification **Current OpenH-RF release:** 25 HDF5 files; 14.99 GB (14,992,998,400 bytes) stored; root `zea_version` **0.1.4**. Sizes include all HDF5 contents and use decimal units (MB = 10^6 bytes, GB = 10^9 bytes, TB = 10^12 bytes), not decoded-array memory or original-source download sizes. - **Frames / cines / subjects:** 777 frames · 25 cine loops · 6 subjects (a–f). -- **Train / val / test split:** N/A (contributor to define; the `f2` patient set - is a natural held-out cine). +- **Train / val / test split:** N/A (contributor to define; the `f2` patient set is a natural held-out cine). - **Stored HDF5 size:** 14.99 GB (14,992,998,400 bytes). | Field | Shape | dtype | Units | Description | @@ -93,46 +95,23 @@ stored too. ## Subject Metadata -Six subjects (a–e main set, f patient set), 777 frames across 25 cine loops. -In-vivo human; no PHI stored (only `subject.id` a1…f2, `subject.type = human`, -`anatomy = cardiac`, `view = apical four-chamber (A4C)`). Age and sex were not recorded for these -acquisitions. +Six subjects (a–e main set, f patient set), 777 frames across 25 cine loops. In-vivo human; no PHI stored (only `subject.id` a1…f2, `subject.type = human`, `anatomy = cardiac`, `view = apical four-chamber (A4C)`). Age and sex were not recorded for these acquisitions. ## Data Validation -`reconstruct.py` reconstructs a B-mode from `raw_data` using the `zea.Pipeline` -defined in `pipeline.yaml`: delay-and-sum on a polar scanline grid (one image line -per acquisition line, receive dynamic focusing) → envelope detection → -normalization → log compression → sector scan conversion. Run: - -``` -python reconstruct.py data/a1.hdf5 --frame 15 --out bmode_a1.png -``` +`reconstruct.py` reconstructs a B-mode from `raw_data` using the `zea.Pipeline` defined in `pipeline.yaml`: delay-and-sum on a polar scanline grid (one image line per acquisition line, receive dynamic focusing) → envelope detection → normalization → log compression → sector scan conversion. -Reference output: `bmode_a1.png`. Each frame is also paired with its conventional -delay-and-sum reconstruction in `beamformed_data` (the target for the raw→image -learning task) — note its depth scale is approximate because the acquisition axial -rate is not stored (see Known Issues). +Reference output: `bmode.png` — frame 8 of `data/c1.hdf5` (in `assets/`). Each frame is also paired with its conventional delay-and-sum reconstruction in `beamformed_data` (the target for the raw→image learning task) — note its depth scale is approximate because the acquisition axial rate is not stored (see Known Issues). ## Known Issues -- **Axial sample rate not stored.** The consolidated source `.mat` files do not - carry the acquisition header, so `sampling_frequency` (6.0 MHz) is a best - estimate and the reconstructed depth scale is approximate. This applies both to - the raw→image reconstruction and to the paired `beamformed_data` target (exact - in value, approximate in depth axis). -- **Sector-angle convention.** Lines are stored as ±37.5° centred about - boresight, the physically correct convention for a phased array. +- **Axial sample rate not stored.** The consolidated source `.mat` files do not carry the acquisition header, so `sampling_frequency` (6.0 MHz) is a best estimate and the reconstructed depth scale is approximate. This applies both to the raw→image reconstruction and to the paired `beamformed_data` target (exact in value, approximate in depth axis). +- **Sector-angle convention.** Lines are stored as ±37.5° centred about boresight, the physically correct convention for a phased array. ## Ethical Considerations -**Privacy safeguards (HIPAA and GDPR).** Pre-beamformed RF channel data contains -no facial or otherwise identifying imagery. All records are de-identified to the -HIPAA Safe Harbor standard, with direct identifiers removed and any dates -generalized to bands. As an EU institution we additionally comply with GDPR, -holding any pseudonymized subject identifiers separately on access-controlled -storage and never sharing them. The released data are de-identified and contain -only the channel signals and acquisition metadata. +**Privacy safeguards (HIPAA and GDPR).** Pre-beamformed RF channel data contains no facial or otherwise identifying imagery. All records are de-identified to the HIPAA Safe Harbor standard, with direct identifiers removed and any dates generalized to bands. As an EU institution we additionally comply with GDPR, holding any pseudonymized subject identifiers separately on access-controlled storage and never sharing them. The released data are de-identified and contain only the channel signals and acquisition metadata. + +**Ethics.** The data were collected under ethical best practices on healthy volunteers. -**Ethics.** The data were collected under ethical best practices on healthy -volunteers. +The data is the contributors' own research acquisition, cleared for CC BY 4.0 with no third-party IP encumbrances. diff --git a/datasets/technion/cardiac/assets/bmode.png b/datasets/technion/cardiac/assets/bmode.png new file mode 100644 index 0000000..c146b9d Binary files /dev/null and b/datasets/technion/cardiac/assets/bmode.png differ diff --git a/datasets/technion/cardiac/assets/cine.gif b/datasets/technion/cardiac/assets/cine.gif new file mode 100644 index 0000000..8e5a3bc Binary files /dev/null and b/datasets/technion/cardiac/assets/cine.gif differ diff --git a/datasets/technion/cardiac/reconstruct.py b/datasets/technion/cardiac/reconstruct.py index 1a9f97e..df8d5b7 100644 --- a/datasets/technion/cardiac/reconstruct.py +++ b/datasets/technion/cardiac/reconstruct.py @@ -32,10 +32,10 @@ # --- Inputs ----------------------------------------------------------------- # Defaults stream straight from the published corpus. Swap any of these for a # local path to run against your own copy. -ZEA_FILE = "hf://nvidia/OpenH-RF/technion/cardiac/data/e3.hdf5" +ZEA_FILE = "hf://nvidia/OpenH-RF/technion/cardiac/data/c1.hdf5" CONFIG = "hf://nvidia/OpenH-RF/technion/cardiac/pipeline.yaml" -FRAME = 0 -OUT = HERE / "bmode.png" +FRAME = 8 +OUT = HERE / "assets" / "bmode.png" def main(): @@ -65,6 +65,7 @@ def main(): cax = make_axes_locatable(ax).append_axes("right", size="5%", pad=0.05) fig.colorbar(im, cax=cax, label="a.u. (8-bit)") fig.tight_layout() + Path(OUT).parent.mkdir(parents=True, exist_ok=True) fig.savefig(str(OUT), dpi=130, bbox_inches="tight") print(f"raw {raw.shape} -> B-mode {recon.shape}; saved {OUT}") diff --git a/datasets/technion/phantom/README.md b/datasets/technion/phantom/README.md index 6d6d3eb..af779c2 100644 --- a/datasets/technion/phantom/README.md +++ b/datasets/technion/phantom/README.md @@ -1,5 +1,6 @@ --- -pretty_name: "OpenH-RF — Technion Phantom Pre-Beamformed Channel Data" +name: technion-phantom +pretty_name: "Technion Phantom Pre-Beamformed Channel Data" license: cc-by-4.0 task_categories: - image-to-image @@ -16,21 +17,23 @@ size_categories: - n<1K --- -# OpenH-RF — Tissue-mimicking phantom pre-beamformed RF channel data +# Technion Tissue-mimicking Phantom Pre-beamformed RF Channel Data + +![Tissue-mimicking phantom with point targets and an anechoic cyst](assets/bmode.png) + +*Frame 6 of [`data/ph.hdf5`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/technion/phantom/data/ph.hdf5), reconstructed by `reconstruct.py`.* ## Dataset Description -Pre-beamformed ultrasound **channel data** from a tissue-mimicking phantom, -acquired on the same 64-element phased-array sector scheme as the in-vivo -collection (180 transmit beams steered over ±45.13°, one image line per -transmit), for **calibration and verification**. Contains -resolvable point targets and an anechoic cyst — a clean reference for validating -beamforming and reconstruction. 12 frames, one acquisition. +Pre-beamformed ultrasound **channel data** from a tissue-mimicking phantom, acquired on the same 64-element phased-array sector scheme as the in-vivo collection (180 transmit beams steered over ±45.13°, one image line per transmit), for **calibration and verification**. Contains resolvable point targets and an anechoic cyst — a clean reference for validating beamforming and reconstruction. 12 frames, one acquisition. ## Dataset Contributor(s) -Sanketh Vedula, Ortal Senouf, Dean Zadok, Alex M. Bronstein (PI) — -Technion – Israel Institute of Technology. Primary contact: sanketh@campus.technion.ac.il. +- Sanketh Vedula (primary contact) +- Ortal Senouf +- Dean Zadok +- Alex M. Bronstein (PI) +- Technion – Israel Institute of Technology ## Dataset Creation Date @@ -38,30 +41,38 @@ Source data 2018; converted to the OpenH-RF (zea) format 07/16/2026. ## License / Terms of Use -CC BY 4.0 (proposal §8). +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage -Calibration and end-to-end verification of the beamforming/reconstruction -pipeline (point-target resolution, cyst contrast). Phantom tier (×1). +Calibration and end-to-end verification of the beamforming/reconstruction pipeline (point-target resolution, cyst contrast). Phantom tier (×1). ## Dataset Characterization -- **Data Collection Method:** phantom — tissue-mimicking phantom (Gammex 403GS LE, - Gammex Inc., Middleton, WI, USA), acquired on the same scanner/probe as the - in-vivo collection for calibration. +- **Data Collection Method:** phantom — tissue-mimicking phantom (Gammex 403GS LE, Gammex Inc., Middleton, WI, USA), acquired on the same scanner/probe as the in-vivo collection for calibration. - **Labeling Method:** N/A (calibration target; known phantom geometry). -- **Acquisition system:** GE Vivid S70 scanner; GE 3Sc-RS 64-element phased-array - probe, 0.30 mm pitch; sector scan, 180 transmit beams steered over ±45.13° - (≈90.25° FOV), one image line per transmit; IQ demodulated at 3.44 MHz. +- **Acquisition system:** GE Vivid S70 scanner; GE 3Sc-RS 64-element phased-array probe, 0.30 mm pitch; sector scan, 180 transmit beams steered over ±45.13° (≈90.25° FOV), one image line per transmit; IQ demodulated at 3.44 MHz. + +## Processing the Dataset + +The acquisitions can be processed with the `pipeline.yaml` definition in this folder and the [zea library](https://github.com/tue-bmd/zea). + +`zea` streams the data from the Hugging Face Hub and processes it according to the pipeline. You can try it out with the following command: + +```bash +zea process \ + --dataset hf://nvidia/OpenH-RF/technion/phantom/data/ph.hdf5 \ + --config hf://nvidia/OpenH-RF/technion/phantom/pipeline.yaml \ + --n-frames 1 +``` + +Alternatively, you can use the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/technion/phantom/reconstruct.py) as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF). ## Dataset Format -zea file format, a single HDF5 file `data/ph.hdf5`. Source complex samples -repackaged to `float32` I/Q (`n_ch = 2`), values verbatim. Carries -`metadata/subject/{id=ph, type=phantom}`, `metadata/credit`, probe model -(`probe.name = GE 3Sc-RS`) and scanner (`us_machine = GE Vivid S70`). ("phantom" -is recorded only as `subject.type`, not as an anatomy or label.) +[zea v0.1.4](https://github.com/tue-bmd/zea) + +zea file format, a single HDF5 file `data/ph.hdf5`. Source complex samples repackaged to `float32` I/Q (`n_ch = 2`), values verbatim. Carries `metadata/subject/{id=ph, type=phantom}`, `metadata/credit`, probe model (`probe.name = GE 3Sc-RS`) and scanner (`us_machine = GE Vivid S70`). ("phantom" is recorded only as `subject.type`, not as an anatomy or label.) ## Dataset Quantification @@ -85,21 +96,13 @@ N/A — inanimate phantom (GAMMEX 403GS LE); `subject.type = phantom`. ## Data Validation -`reconstruct.py` reconstructs a B-mode from `raw_data` using the `zea.Pipeline` -in `pipeline.yaml` (delay-and-sum on a polar scanline grid → envelope → -normalization → log compression → sector scan conversion). Run: - -``` -python reconstruct.py data/ph.hdf5 --frame 6 --out bmode_ph.png -``` +`reconstruct.py` reconstructs a B-mode from `raw_data` using the `zea.Pipeline` in `pipeline.yaml` (delay-and-sum on a polar scanline grid → envelope → normalization → log compression → sector scan conversion). -Reference output: `bmode_ph.png` — resolvable point targets and a well-defined -anechoic cyst at ~65 mm. +Reference output: `bmode.png` — frame 6 of `data/ph.hdf5`, shown above: resolvable point targets and a well-defined anechoic cyst at ~65 mm. ## Known Issues -- Same scan scheme and probe as the in-vivo bladder collection (GE - tissue-harmonic); acquired as its calibration reference. GAMMEX 403GS LE. +- Same scan scheme and probe as the in-vivo bladder collection (GE tissue-harmonic); acquired as its calibration reference. GAMMEX 403GS LE. ## Ethical Considerations diff --git a/datasets/technion/phantom/assets/bmode.png b/datasets/technion/phantom/assets/bmode.png new file mode 100644 index 0000000..12295c8 Binary files /dev/null and b/datasets/technion/phantom/assets/bmode.png differ diff --git a/datasets/technion/phantom/reconstruct.py b/datasets/technion/phantom/reconstruct.py index 05bee55..8d7f243 100644 --- a/datasets/technion/phantom/reconstruct.py +++ b/datasets/technion/phantom/reconstruct.py @@ -34,8 +34,8 @@ # local path to run against your own copy. ZEA_FILE = "hf://nvidia/OpenH-RF/technion/phantom/data/ph.hdf5" CONFIG = "hf://nvidia/OpenH-RF/technion/phantom/pipeline.yaml" -FRAME = 0 -OUT = HERE / "bmode.png" +FRAME = 6 +OUT = HERE / "assets" / "bmode.png" def main(): @@ -65,6 +65,7 @@ def main(): cax = make_axes_locatable(ax).append_axes("right", size="5%", pad=0.05) fig.colorbar(im, cax=cax, label="a.u. (8-bit)") fig.tight_layout() + Path(OUT).parent.mkdir(parents=True, exist_ok=True) fig.savefig(str(OUT), dpi=130, bbox_inches="tight") print(f"raw {raw.shape} -> B-mode {recon.shape}; saved {OUT}") diff --git a/datasets/tel-aviv/README.md b/datasets/tel-aviv/README.md index baa0fa1..af6f550 100644 --- a/datasets/tel-aviv/README.md +++ b/datasets/tel-aviv/README.md @@ -1,4 +1,5 @@ --- +name: tel-aviv license: cc-by-4.0 pretty_name: Ilovitsh Lab Mice Tumors task_categories: [image-segmentation] @@ -7,23 +8,22 @@ tags: [ultrasound, rf, openh-rf] # Ilovitsh Lab Mice Tumors & Water-Bead Phantoms +![Reconstructed cineloop from 01-scan-3.hdf5](assets/01-scan-3.gif) + +*Cine loop of the rotational sweep through a mouse tumor, [`mouse_tumor/seg/01-scan-3.hdf5`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/tel-aviv/mouse_tumor/seg/01-scan-3.hdf5), reconstructed from the raw channel data with `mouse_tumor/pipeline.yaml`.* + ## Dataset Description -This dataset provides ultrasound data captured via a motorized 1D transducer array. It captures both in-vivo tumors in mice and in-silico water-bead phantoms, and was originally acquired as part of our work on implicit neural representations (INR) [1]. -The primary task for this released dataset is the segmentation of tumors (in mice) and water beads (in phantoms) from multi-angle ultrasound acquisitions. +This dataset provides ultrasound data captured via a motorized 1D transducer array. It captures both in-vivo tumors in mice and in-silico water-bead phantoms, and was originally acquired as part of our work on implicit neural representations (INR) [1]. The primary task for this released dataset is the segmentation of tumors (in mice) and water beads (in phantoms) from multi-angle ultrasound acquisitions. -Data was acquired using a motorized 1D array transducer with 128 elements (IP104, Sonic Concepts) operated by a Vantage 256 system (Verasonics Inc.). -For the in-vivo data, 5 breast cancer tumor-bearing mice were scanned under anesthesia. -Each volume was sampled across a 180° rotation at 1.25° intervals, yielding 144 angular frames per acquisition. -At each angle, five plane waves were steered linearly between -5° and 5°. +Data was acquired using a motorized 1D array transducer with 128 elements (IP104, Sonic Concepts) operated by a Vantage 256 system (Verasonics Inc.). For the in-vivo data, 5 breast cancer tumor-bearing mice were scanned under anesthesia. Each volume was sampled across a 180° rotation at 1.25° intervals, yielding 144 angular frames per acquisition. At each angle, five plane waves were steered linearly between -5° and 5°. -Each beamformed B-mode image was semi-manually annotated by a non-professional using MedSAM [2]. -Volumetric comparison of these segmentation masks against manual measurements produced a mean volumetric error of 6.8% ± 1.5%. +Each beamformed B-mode image was semi-manually annotated by a non-professional using MedSAM [2]. Volumetric comparison of these segmentation masks against manual measurements produced a mean volumetric error of 6.8% ± 1.5%. ## Dataset Contributor(s) -Ilovitsh Lab, Tel Aviv University. -Contributors: Tal Grutman and Tali Ilovitsh. +- Tal Grutman (Ilovitsh Lab, Tel Aviv University) +- Tali Ilovitsh (Ilovitsh Lab, Tel Aviv University) ## Dataset Creation Date @@ -31,7 +31,7 @@ Contributors: Tal Grutman and Tali Ilovitsh. ## License / Terms of Use -CC BY 4.0. Data has been cleared for this license (institutional review approval TAU-MD-IL-2407-154–5). +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage @@ -43,9 +43,13 @@ Segmentation of tumors in mice and water-bead phantoms. - **Labeling method:** Semi-automatic, labelled with MedSAM [2]. - **Acquisition system:** 128-element phased array (IP104, Sonic Concepts) with a 0.22 mm pitch, 13.5 mm elevation aperture, and center frequency of 3.5 MHz. Controlled by Vantage 256 (Verasonics Inc.) and a motorized rotary (RTY-IP100). Sampling rate 14 MHz. +## Processing the Dataset + +The acquisitions can be processed with the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/tel-aviv/reconstruct.py) as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF), together with the `pipeline.yaml` definitions in `mouse_tumor/` and `phantom/` and the [zea library](https://github.com/tue-bmd/zea). The script streams the data from the Hugging Face Hub and overlays the stored segmentation on the reconstructed B-mode. + ## Dataset Format -zea file format. +[zea v0.1.6](https://github.com/tue-bmd/zea) ## Dataset Quantification @@ -62,8 +66,7 @@ zea file format. ## Subject Metadata -5 tumor-bearing female FVB/NHanHsd mice (injected with Met-1 mouse breast carcinoma cells). -2 phantoms containing 3 water gel beads in an agarose mixture. +5 tumor-bearing female FVB/NHanHsd mice (injected with Met-1 mouse breast carcinoma cells). 2 phantoms containing 3 water gel beads in an agarose mixture. | Subject / Phantom | Imaging Depth (cm) | |---|---| @@ -83,7 +86,7 @@ zea file format. Animal-related procedures were conducted in accordance with the guidelines provided by the Institutional Animal Research Ethical Committee. +The data has been cleared for release under CC BY 4.0 (institutional review approval TAU-MD-IL-2407-154–5). ## References -[1] Grutman et al., “Implicit neural representation for scalable 3D reconstruction from sparse ultrasound images,” npj. Acoust., 2025. https://doi.org/10.1038/s44384-025-00018-5 -[2] Ma et al., “Segment anything in medical images,” Nat. Commun., 2024. https://www.nature.com/articles/s41467-024-44824-z +[1] Grutman et al., “Implicit neural representation for scalable 3D reconstruction from sparse ultrasound images,” npj. Acoust., 2025. https://doi.org/10.1038/s44384-025-00018-5 [2] Ma et al., “Segment anything in medical images,” Nat. Commun., 2024. https://www.nature.com/articles/s41467-024-44824-z diff --git a/datasets/tel-aviv/assets/01-scan-3.gif b/datasets/tel-aviv/assets/01-scan-3.gif new file mode 100644 index 0000000..1a2e5b2 Binary files /dev/null and b/datasets/tel-aviv/assets/01-scan-3.gif differ diff --git a/datasets/tel-aviv/assets/main.png b/datasets/tel-aviv/assets/main.png new file mode 100644 index 0000000..179ee86 Binary files /dev/null and b/datasets/tel-aviv/assets/main.png differ diff --git a/datasets/tel-aviv/mouse_tumor/pipeline.yaml b/datasets/tel-aviv/mouse_tumor/pipeline.yaml index e6f5938..0bd9270 100644 --- a/datasets/tel-aviv/mouse_tumor/pipeline.yaml +++ b/datasets/tel-aviv/mouse_tumor/pipeline.yaml @@ -2,7 +2,7 @@ parameters: xlims: [-0.01902, 0.01863] zlims: [0.0, 0.072] grid_size_x: 256 - grid_size_z: 256 + grid_size_z: 352 pipeline: operations: diff --git a/datasets/tel-aviv/reconstruct.py b/datasets/tel-aviv/reconstruct.py index 4c0f62c..24c105f 100644 --- a/datasets/tel-aviv/reconstruct.py +++ b/datasets/tel-aviv/reconstruct.py @@ -37,14 +37,15 @@ # local path to run against your own copy. The phantom acquisitions live under # tel-aviv/phantom and carry no segmentation. ZEA_FILE = "hf://nvidia/OpenH-RF/tel-aviv/mouse_tumor/seg/01-scan-3.hdf5" -PIPELINE = "hf://nvidia/OpenH-RF/tel-aviv/mouse_tumor/pipeline.yaml" +CONFIG = HERE / "mouse_tumor" / "pipeline.yaml" # this is what the run loads +HF_CONFIG = "hf://nvidia/OpenH-RF/tel-aviv/mouse_tumor/pipeline.yaml" # where CONFIG is published FRAME = 46 -OUT = HERE / "01-scan-3_frame046.png" +OUT = HERE / "assets" / "01-scan-3_frame046.png" def main(): zea.init_device() - config = zea.Config.from_path(PIPELINE) + config = zea.Config.from_path(str(CONFIG)) with zea.File(ZEA_FILE) as f: parameters = f.load_parameters(**config.parameters) @@ -74,6 +75,7 @@ def main(): ax.set_xlabel("Lateral [mm]") ax.set_ylabel("Axial depth [mm]") fig.tight_layout() + Path(OUT).parent.mkdir(parents=True, exist_ok=True) fig.savefig(OUT, dpi=300, bbox_inches="tight") print(f"Saved {OUT}") diff --git a/datasets/tue-aaa/README.md b/datasets/tue-aaa/README.md index 24d64c1..2212611 100644 --- a/datasets/tue-aaa/README.md +++ b/datasets/tue-aaa/README.md @@ -1,5 +1,6 @@ --- -pretty_name: "OpenH-RF — TU/e PULS/e — Abdominal Aortic Aneurysm (C5-2v) channel data" +name: tue-aaa +pretty_name: "TU/e PULS/e — Abdominal Aortic Aneurysm (C5-2v) channel data" license: cc-by-4.0 task_categories: - image-to-image @@ -15,7 +16,11 @@ language: - en --- -# Abdominal Aortic Aneurysm (AAA) curved-array channel data +# TU/e Abdominal Aortic Aneurysm (AAA) Curved-Array Channel Data + +![Reconstructed cineloop from AAA_subject11.hdf5](assets/AAA_subject11.gif) + +*Cine loop of [`data/AAA_subject11.hdf5`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/tue-aaa/data/AAA_subject11.hdf5), reconstructed from the raw channel data.* ## Dataset Description @@ -23,10 +28,8 @@ This dataset contains ultrasound channel data acquired in vivo from patients wit ## Dataset Contributor(s) -PULS/e group -Department of Biomedical Engineering -Eindhoven University of Technology -contact: Hans-Martin Schwab (h.schwab@tue.nl) +- Hans-Martin Schwab (contact) +- PULS/e group, Department of Biomedical Engineering, Eindhoven University of Technology ## Dataset Creation Date @@ -34,12 +37,12 @@ July 2026 ## License / Terms of Use -CC BY 4.0. +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage -Advanced beamforming -Motion estimation +- Advanced beamforming +- Motion estimation ## Dataset Characterization @@ -47,15 +50,31 @@ Motion estimation - **Acquisition system:** Verasonics Vantage, C5-2v curved array, 128 elements, center frequency 3.6 MHz - **Transmit sequence:** 15 steered diverging waves (polar angles −12° … +12°) +## Processing the Dataset + +The acquisitions can be processed with the `pipeline.yaml` definition in this folder and the [zea library](https://github.com/tue-bmd/zea). + +`zea` streams the data from the Hugging Face Hub and processes it according to the pipeline. You can try it out with the following command: + +```bash +zea process \ + --dataset hf://nvidia/OpenH-RF/tue-aaa/data/AAA_subject11.hdf5 \ + --config hf://nvidia/OpenH-RF/tue-aaa/pipeline.yaml +``` + +Alternatively, you can use the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/tue-aaa/reconstruct.py) as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF). + ## Dataset Format +[zea v0.1.4](https://github.com/tue-bmd/zea) + zea file format. Subject metadata is stored under `metadata/subject` (`age`, `sex`, `bmi`); attribution under `metadata/credit`. ## Dataset Quantification **Current OpenH-RF release:** 15 HDF5 files; 53.08 GB (53,077,606,400 bytes) stored; root `zea_version` **0.1.4**. Sizes include all HDF5 contents and use decimal units (MB = 10^6 bytes, GB = 10^9 bytes, TB = 10^12 bytes), not decoded-array memory or original-source download sizes. -- **Samples / frames:** 500 acquisitions +- **Samples / frames:** 15 acquisitions (one HDF5 file each), 500 frames per acquisition — 7,500 frames total - **Stored HDF5 size:** 53.08 GB (53,077,606,400 bytes). | Field | Shape | dtype | Units | Description | @@ -64,7 +83,7 @@ zea file format. Subject metadata is stored under `metadata/subject` (`age`, `se ## Subject Metadata -Patients are dominantly male, aged 63–90, and scanned in the Netherlands. +Patients are dominantly male (13 M / 2 F), aged 66–87, and scanned in the Netherlands. ## Data Validation diff --git a/datasets/tue-aaa/assets/AAA_subject11.gif b/datasets/tue-aaa/assets/AAA_subject11.gif new file mode 100644 index 0000000..d8b2375 Binary files /dev/null and b/datasets/tue-aaa/assets/AAA_subject11.gif differ diff --git a/datasets/tue-aaa/assets/main.png b/datasets/tue-aaa/assets/main.png new file mode 100644 index 0000000..7d46589 Binary files /dev/null and b/datasets/tue-aaa/assets/main.png differ diff --git a/datasets/tue-aaa/pipeline.yaml b/datasets/tue-aaa/pipeline.yaml index 1134a2d..37b76af 100644 --- a/datasets/tue-aaa/pipeline.yaml +++ b/datasets/tue-aaa/pipeline.yaml @@ -17,8 +17,8 @@ pipeline: - 1.0 - log_compress parameters: - grid_size_x: 580 - grid_size_z: 600 + grid_size_x: 960 + grid_size_z: 832 xlims: - -0.1 - 0.1 diff --git a/datasets/tue-aaa/reconstruct.py b/datasets/tue-aaa/reconstruct.py index 9697c6a..e362fa9 100644 --- a/datasets/tue-aaa/reconstruct.py +++ b/datasets/tue-aaa/reconstruct.py @@ -26,8 +26,8 @@ from pathlib import Path +import keras import matplotlib.pyplot as plt -import numpy as np import zea from zea import Config, File, Pipeline from zea.ops import ( @@ -41,8 +41,6 @@ ) HERE = Path(__file__).parent -DEFAULT_INPUT = "hf://nvidia/OpenH-RF/tue-aaa/data/AAA_subject11.hdf5" -DEFAULT_OUTPUT = HERE / "AAApatient01_bmode.png" CONFIG = HERE / "pipeline.yaml" # Custom reconstruction parameters. These are passed to load_parameters and @@ -60,8 +58,9 @@ # --- Inputs ----------------------------------------------------------------- # Defaults stream straight from the published corpus. Swap any of these for a # local path to run against your own copy. -INPUT = "hf://nvidia/OpenH-RF/tue-aaa/data/AAA_subject11.hdf5" -OUTPUT = DEFAULT_OUTPUT +ZEA_FILE = "hf://nvidia/OpenH-RF/tue-aaa/data/AAA_subject11.hdf5" +OUT = HERE / "assets" / "AAApatient01_bmode.png" +HF_CONFIG = "hf://nvidia/OpenH-RF/tue-aaa/pipeline.yaml" DEVICE = None # CUDA device ID (e.g. 'cuda:0', 'auto:1', or 'cpu') @@ -97,7 +96,7 @@ def main(): config = Config.from_path(str(CONFIG)) # Load file: read acquisition parameters (with config overrides) and raw RF data - with File(str(INPUT)) as f: + with File(str(ZEA_FILE)) as f: parameters = f.load_parameters(**config.parameters) raw = f.data.raw_data[0:1] # (n_frames, n_tx, n_ax, n_el, 1) — RF @@ -107,7 +106,7 @@ def main(): outputs = pipeline(data=raw, **inputs) # Convert the output tensor to a NumPy array and save as PNG - recon = np.array(outputs["data"]) # (n_frames, grid_z, grid_x) + recon = keras.ops.convert_to_numpy(outputs["data"]) # (n_frames, grid_z, grid_x) image = zea.display.to_8bit(recon[0], dynamic_range=parameters.dynamic_range) zea.visualize.set_mpl_style() @@ -119,10 +118,11 @@ def main(): ) plt.xlabel("X (mm)") plt.ylabel("Z (mm)") - plt.savefig(str(OUTPUT), bbox_inches="tight", dpi=100) + Path(OUT).parent.mkdir(parents=True, exist_ok=True) + plt.savefig(str(OUT), bbox_inches="tight", dpi=100) print(f"Reconstructed : {recon.shape}") - print(f"Saved : {OUTPUT}") + print(f"Saved : {OUT}") if __name__ == "__main__": diff --git a/datasets/tue-cardiac/README.md b/datasets/tue-cardiac/README.md index 24aa165..a720882 100644 --- a/datasets/tue-cardiac/README.md +++ b/datasets/tue-cardiac/README.md @@ -1,5 +1,6 @@ --- -pretty_name: "OpenH-RF — TU/e Cardiac RF Multi-Transmit" +name: tue-cardiac +pretty_name: "TU/e Cardiac RF Multi-Transmit" license: cc-by-4.0 task_categories: - other @@ -23,40 +24,36 @@ size_categories: # TU/e Cardiac PLAX Multi-Transmit RF + + + + + + + +
+*Parasternal long-axis cine loops of four volunteers, pulse-inversion harmonic tracks: focused ([`subject-002`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/tue-cardiac/data/subject-002.hdf5)), wide ([`subject-004`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/tue-cardiac/data/subject-004.hdf5)), focused ([`subject-006`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/tue-cardiac/data/subject-006.hdf5)) and wide ([`subject-009`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/tue-cardiac/data/subject-009.hdf5)).* ## Dataset Description -This dataset contains pre-beamformed radio-frequency (RF) channel data from -in-vivo human cardiac ultrasound acquisitions of 12 distinct adult healthy -volunteers, aged 26–33 years. The operator attempted a parasternal long-axis (PLAX) -view for every participant. Each participant file contains eight separately acquired 100-frame -tracks (3s,33Hz) that vary the transmit encoding: focused fundamental, focused -pulse-inversion harmonic, wide fundamental, wide pulse-inversion harmonic, -plane wave, diverging wave, Hadamard-coded aperture, and random binary-coded -aperture. +This dataset contains pre-beamformed radio-frequency (RF) channel data from in-vivo human cardiac ultrasound acquisitions of 12 distinct adult healthy volunteers, aged 26–33 years. The operator attempted a parasternal long-axis (PLAX) view for every participant. Each participant file contains eight separately acquired 100-frame tracks (3s,33Hz) that vary the transmit encoding: focused fundamental, focused pulse-inversion harmonic, wide fundamental, wide pulse-inversion harmonic, plane wave, diverging wave, Hadamard-coded aperture, and random binary-coded aperture. -The data were acquired on a Verasonics Vantage 256 research platform with a -Philips S5-1 phased-array probe. The contribution is intended to support generalized -reconstruction, transmit-encoding research, REFoCUS recovery, compressed -sensing, and comparisons of fundamental and second-harmonic imaging. +The data were acquired on a Verasonics Vantage 256 research platform with a Philips S5-1 phased-array probe. The contribution is intended to support generalized reconstruction, transmit-encoding research, REFoCUS recovery, compressed sensing, and comparisons of fundamental and second-harmonic imaging. ## Dataset Contributor(s) -- **Authors:** Simon Penninga and Ruud van Sloun -- **Contributor and contact:** Simon Penninga (`s.w.penninga@tue.nl`) -- **Organization:** Biomedical Diagnostics Lab, Eindhoven University of - Technology (TU/e), the Netherlands +- Simon Penninga (author; contact) +- Ruud van Sloun (author) +- Biomedical Diagnostics Lab, Eindhoven University of Technology (TU/e), the Netherlands ## Dataset Creation Date -The recordings were acquired in 2026. The dataset was packaged on 2026-09-02. +The recordings were acquired in 2026. ## License / Terms of Use -The dataset license is [Creative Commons Attribution 4.0 International -(CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/). The full license -text is provided in [`LICENSE`](LICENSE). +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage @@ -71,48 +68,16 @@ Intended research uses include: The dataset must not be used as a clinically validated diagnostic product. -## Citation - -Suggested citation for the dataset: - -> Penninga, S., & van Sloun, R. (2026). *TU/e Cardiac RF Multi-Transmit* [Data set]. -> Biomedical Diagnostics Lab, Eindhoven University of Technology. OpenH-RF. - -```bibtex -@misc{penninga_tue_cardiac_plax_2026, - title = {TU/e Cardiac PLAX Multi-Transmit RF}, - author = {Penninga, Simon and van Sloun, Ruud}, - year = {2026}, - publisher = {Biomedical Diagnostics Lab, Eindhoven University of Technology}, - howpublished = {OpenH-RF dataset}, -} -``` - - ## Dataset Characterization -- **Data collection method:** in-vivo human cardiac ultrasound on a research - platform. +- **Data collection method:** in-vivo human cardiac ultrasound on a research platform. - **Anatomy and view:** heart; attempted parasternal long-axis (PLAX) view. -- **Cohort:** 12 adult healthy volunteers, aged 26–33 years, recruited from - among researcher colleagues. No known pathologies were reported. -- **Labeling method:** acquisition-derived track labels plus the shared - contributor-supplied cohort label `healthy_volunteer`. This is not a clinical - diagnosis. No segmentation, diagnostic, or image-quality labels are supplied. -- **Acquisition protocol:** the eight modes were acquired sequentially in the - fixed order shown below. The probe was kept as steady as possible, but no - probe tracking was available. Before each recording, the volunteer was - instructed to breathe out. +- **Cohort:** 12 adult healthy volunteers, aged 26–33 years, recruited from among researcher colleagues. No known pathologies were reported. +- **Acquisition protocol:** the eight modes were acquired sequentially in the fixed order shown below. The probe was kept as steady as possible. Before each recording, the volunteer was instructed to breathe out. - **Operator:** No ultrasound training or prior experience. -- **System settings:** receive gain and TGC settings were fixed across all - participants and tracks. -- **Acquisition system:** Verasonics Vantage 256 with a Philips S5-1 phased-array - probe. -- **Probe:** phased-array, 80 active elements; recorded geometry has shape `(80, 3)` in - metres, nominal probe center frequency 3.125 MHz, and 128% fractional - bandwidth. -- **Receive sampling:** 15.625 MHz; 2,304 axial samples; raw `int16` RF with - one real channel. +- **Acquisition system:** Verasonics Vantage 256 with a Philips S5-1 phased-array probe. +- **Probe:** phased-array, 80 active elements, nominal probe center frequency 3.125 MHz, and 128% fractional bandwidth. +- **Receive sampling:** 15.625 MHz; 2,304 axial samples; raw `int16` RF with one real channel. For pulse inversion imaging, the two acquisitions are summed in buffer. - **Nominal sound speed:** 1,540 m/s. - **Frame rate:** approximately 33 frames/s for each track. - **Coordinate convention:** x = lateral, y = elevation, z = axial/depth. @@ -126,12 +91,19 @@ Suggested citation for the dataset: | 05 | `planewave` | Steered plane wave, ±45° | 80 | 3.90625 MHz | Infinite transmit focus | | 06 | `diverging` | Diverging wave, ±45° | 80 | 3.90625 MHz | Negative virtual-focus distance varies with steering angle | | 07 | `hadamard` | Full-aperture Hadamard code | 80 | 3.90625 MHz | Zero transmit delays and ±1 apodization; REFoCUS decoding required | -| 08 | `random` | Full-aperture fixed random binary code | 80 | 3.90625 MHz | Zero transmit delays and ±1 apodization; regularized REFoCUS decoding required | +| 08 | `random` | Full-aperture fixed random binary code | 80 | 3.90625 MHz | Zero transmit delays and ±1 apodization; REFoCUS decoding required | + +## Processing the Dataset + +The acquisitions can be processed with the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/tue-cardiac/reconstruct.py) as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF), together with the `pipelines/*.yaml` definitions in this folder and the [zea library](https://github.com/tue-bmd/zea). The script streams the data from the Hugging Face Hub. + +Each file holds the eight transmit-encoding tracks. Set `ZEA_FILE`, `TRACK` and `FRAME` at the top of the script, together with the matching pipeline under `pipelines/` as `CONFIG` (`PIPELINE_FOR_TRACK` lists which track uses which; see also [Data Validation](#data-validation)). The image is written to `assets/`. ## Dataset Format -The final dataset contains 12 zea HDF5 files under `data/`, one per -pseudonymized participant: +[zea v0.1.4](https://github.com/tue-bmd/zea) + +The final dataset contains 12 zea HDF5 files under `data/`, one per pseudonymized participant: ```text data/ @@ -140,22 +112,9 @@ data/ subject-012.hdf5 ``` -Each file has one shared `/probe` group, one de-identified `/metadata` group, -and eight entries under `/tracks`. Track labels identify the transmit encoding. -Raw channel data for each track are stored at: - -```text -/tracks/track_N/data/raw_data -``` +Each file has one shared `/probe` group, one de-identified `/metadata` group, and eight entries under `/tracks`. Track labels identify the transmit encoding. Raw channel data for each track are stored at: -The source tensor order is preserved as -`(n_frames, n_tx, n_ax, n_el, n_ch) = (100, 80, 2304, 80, 1)` with dtype -`int16`. Values are uncalibrated Verasonics receive samples. -The source Verasonics workspaces are converted with zea. Probe geometry, -transmit delays, transmit apodizations, waveforms, receive sampling, time-gain -compensation, sound speed, and relative transmit timing are retained. Exact -participant acquisition dates/times, source filenames, local paths, and the -private source-directory-to-pseudonym mapping are not retained. +The source tensor order is preserved as `(n_frames, n_tx, n_ax, n_el, n_ch) = (100, 80, 2304, 80, 1)` with dtype `int16`. Values are uncalibrated Verasonics receive samples. ### Core per-file feature table @@ -183,25 +142,62 @@ private source-directory-to-pseudonym mapping are not retained. | `/tracks/track_N/scan/waveforms_one_way` | track dependent | float32 | V | One-way transmit waveform model | | `/tracks/track_N/scan/waveforms_two_way` | track dependent | float32 | V | Two-way transmit waveform model | -## Dataset Quantification +## Transmit Types + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Focused fundamentalFocused pulse-inversion harmonicWide fundamentalWide pulse-inversion harmonic
Focused fundamental cardiac imageFocused pulse-inversion harmonic cardiac imageWide fundamental cardiac imageWide pulse-inversion harmonic cardiac image
Plane waveDiverging waveHadamard-coded apertureRandom binary-coded aperture
Plane-wave cardiac imageDiverging-wave cardiac imageHadamard-coded aperture cardiac imageRandom binary-coded aperture cardiac image
Output images of reconstruct.py for every transmit type of subject 2.
+ +## Citation -**Current OpenH-RF release:** 12 HDF5 files; 199.18 GB (199,180,025,844 bytes) stored; root `zea_version` **0.1.4**. Sizes include all HDF5 contents and use decimal units (MB = 10^6 bytes, GB = 10^9 bytes, TB = 10^12 bytes), not decoded-array memory or original-source download sizes. +Suggested citation for the dataset: + +> Penninga, S. W., & van Sloun, R. J. G. (2026). *TU/e Cardiac RF Multi-Transmit* [Data set]. Eindhoven University of Technology, OpenH-RF. -- **Participants/files:** 12 distinct people represented by 12 pseudonymous HDF5 files. +```bibtex +@misc{penninga_tue_cardiac_plax_2026, + title = {TU/e Cardiac PLAX Multi-Transmit RF}, + author = {Penninga, Simon W. and van Sloun, Ruud J. G.}, + year = {2026}, + publisher = {Biomedical Diagnostics Lab, Eindhoven University of Technology}, + howpublished = {OpenH-RF dataset}, +} +``` + +## Dataset Quantification + +**Current OpenH-RF release:** 12 HDF5 files; 199.18 GB (199,180,025,844 bytes) stored; root `zea_version` **0.1.4**. +- **Participants/files:** 12 distinct people represented by 12 HDF5 files. - **Tracks/acquisitions:** 8 tracks per file; 96 acquisitions total. - **Frames:** 100 per track; 9,600 frames total. - **Raw-data tensor:** `(100, 80, 2304, 80, 1)` per track. -## Subject Metadata - -The dataset contains 12 distinct adult healthy volunteers aged 26–33 years. -They were recruited from among the contributor's researcher colleagues, and no -known pathologies were reported. Only sequential pseudonyms and the shared -`healthy_volunteer` cohort classification are stored in the HDF5 files. The age -range is reported only in aggregate; individual ages are not stored. Sex was -intentionally not reported or encoded. The shared anatomy/view label is cardiac -attempted PLAX, and individual diagnostic labels are not included. - ## Data Validation Four saved zea pipeline configurations are provided under `pipelines/`: @@ -213,35 +209,17 @@ Four saved zea pipeline configurations are provided under `pipelines/`: | `pipeline_hadamard.yaml` | `hadamard` | Adjoint REFoCUS decoding before beamforming | | `pipeline_random.yaml` | `random` | Tikhonov-regularized REFoCUS decoding before beamforming | -All configurations perform RF filtering, demodulation, delay-and-sum -beamforming, envelope detection, normalization, log compression, and scan -conversion. `reconstruct.py` selects the appropriate configuration from the -requested track label; `--pipeline` can still override that selection. - -Example reconstruction: - -```bash -python reconstruct.py data/subject-001.hdf5 \ - --track focused_fund --frame 0 -``` +All configurations perform RF filtering, demodulation, delay-and-sum beamforming, envelope detection, normalization, log compression, and scan conversion. ## Known Issues - A good PLAX view is not always available for all recordings. -- Some transmit types, like random apodization recordings, do not give a - good quality B-mode. They are not intended to be used for imaging, but - for comparison. -- The two harmonic tracks are pulse-inversion-accumulated nonlinear - measurements and should not be treated as linear equivalents of the - fundamental tracks. -- Accumulated harmonic data may lack per-transmit timing arrays; the measured - frame rate is documented instead. -- Hadamard and random tracks require REFoCUS decoding before conventional - beamforming. -- A Verasonics scalar lens-delay correction is retained under `/custom` for - provenance, but it is not equivalent to zea's refractive lens model. +- Some transmit types, like random apodization recordings, do not give a good quality B-mode. They are not intended to have the best quality, but for comparison. +- The two harmonic tracks are pulse-inversion-accumulated nonlinear measurements and should not be treated as linear equivalents of the fundamental tracks. +- Accumulated harmonic data may lack per-transmit timing arrays; the measured frame rate is documented instead. +- Hadamard and random tracks require REFoCUS decoding before conventional beamforming. +- A Verasonics scalar lens-delay correction is retained under `/custom` for provenance, but it is not equivalent to zea's refractive lens model. ## Ethical Considerations -Approval was obtained from the Ethical Review Board TU/e (Eindhoven University of Technology). -Reference: ERB2023EE7 Contact details for the Ethical Review Board TU/e: T +31 (0)40 247 6259 +Approval was obtained from the Ethical Review Board TU/e (Eindhoven University of Technology). Reference: ERB2023EE7 Contact details for the Ethical Review Board TU/e: T +31 (0)40 247 6259 diff --git a/datasets/tue-cardiac/assets/subject-002_diverging_frame-000.png b/datasets/tue-cardiac/assets/subject-002_diverging_frame-000.png new file mode 100644 index 0000000..c27551d Binary files /dev/null and b/datasets/tue-cardiac/assets/subject-002_diverging_frame-000.png differ diff --git a/datasets/tue-cardiac/assets/subject-002_focused_fund_frame-000.png b/datasets/tue-cardiac/assets/subject-002_focused_fund_frame-000.png new file mode 100644 index 0000000..d18fac7 Binary files /dev/null and b/datasets/tue-cardiac/assets/subject-002_focused_fund_frame-000.png differ diff --git a/datasets/tue-cardiac/assets/subject-002_focused_harm_frame-000.png b/datasets/tue-cardiac/assets/subject-002_focused_harm_frame-000.png new file mode 100644 index 0000000..86ecb04 Binary files /dev/null and b/datasets/tue-cardiac/assets/subject-002_focused_harm_frame-000.png differ diff --git a/datasets/tue-cardiac/assets/subject-002_hadamard_frame-000.png b/datasets/tue-cardiac/assets/subject-002_hadamard_frame-000.png new file mode 100644 index 0000000..a4a795d Binary files /dev/null and b/datasets/tue-cardiac/assets/subject-002_hadamard_frame-000.png differ diff --git a/datasets/tue-cardiac/assets/subject-002_planewave_frame-000.png b/datasets/tue-cardiac/assets/subject-002_planewave_frame-000.png new file mode 100644 index 0000000..5a16dd3 Binary files /dev/null and b/datasets/tue-cardiac/assets/subject-002_planewave_frame-000.png differ diff --git a/datasets/tue-cardiac/assets/subject-002_random_frame-000.png b/datasets/tue-cardiac/assets/subject-002_random_frame-000.png new file mode 100644 index 0000000..59a9f60 Binary files /dev/null and b/datasets/tue-cardiac/assets/subject-002_random_frame-000.png differ diff --git a/datasets/tue-cardiac/assets/subject-002_wide_fund_frame-000.png b/datasets/tue-cardiac/assets/subject-002_wide_fund_frame-000.png new file mode 100644 index 0000000..d3d1f32 Binary files /dev/null and b/datasets/tue-cardiac/assets/subject-002_wide_fund_frame-000.png differ diff --git a/datasets/tue-cardiac/assets/subject-002_wide_harm_frame-000.png b/datasets/tue-cardiac/assets/subject-002_wide_harm_frame-000.png new file mode 100644 index 0000000..7787e33 Binary files /dev/null and b/datasets/tue-cardiac/assets/subject-002_wide_harm_frame-000.png differ diff --git a/datasets/tue-cardiac/assets/subject-012_focused_fund_frame-000.png b/datasets/tue-cardiac/assets/subject-012_focused_fund_frame-000.png new file mode 100644 index 0000000..2aaa337 Binary files /dev/null and b/datasets/tue-cardiac/assets/subject-012_focused_fund_frame-000.png differ diff --git a/datasets/tue-cardiac/assets/subject2_focused_harm_nb80.gif b/datasets/tue-cardiac/assets/subject2_focused_harm_nb80.gif new file mode 100644 index 0000000..9fd55f4 Binary files /dev/null and b/datasets/tue-cardiac/assets/subject2_focused_harm_nb80.gif differ diff --git a/datasets/tue-cardiac/assets/subject4_wide_harm_nb80.gif b/datasets/tue-cardiac/assets/subject4_wide_harm_nb80.gif new file mode 100644 index 0000000..4e630ef Binary files /dev/null and b/datasets/tue-cardiac/assets/subject4_wide_harm_nb80.gif differ diff --git a/datasets/tue-cardiac/assets/subject6_focused_harm_nb80.gif b/datasets/tue-cardiac/assets/subject6_focused_harm_nb80.gif new file mode 100644 index 0000000..5e8feac Binary files /dev/null and b/datasets/tue-cardiac/assets/subject6_focused_harm_nb80.gif differ diff --git a/datasets/tue-cardiac/assets/subject9_wide_harm_nb80.gif b/datasets/tue-cardiac/assets/subject9_wide_harm_nb80.gif new file mode 100644 index 0000000..5053040 Binary files /dev/null and b/datasets/tue-cardiac/assets/subject9_wide_harm_nb80.gif differ diff --git a/datasets/tue-cardiac/reconstruct.py b/datasets/tue-cardiac/reconstruct.py index 53547d0..44dd32f 100644 --- a/datasets/tue-cardiac/reconstruct.py +++ b/datasets/tue-cardiac/reconstruct.py @@ -20,8 +20,6 @@ python reconstruct.py """ -from __future__ import annotations - import os os.environ.setdefault("KERAS_BACKEND", "jax") @@ -53,19 +51,16 @@ # Defaults stream straight from the published corpus. Swap any of these for a # local path to run against your own copy. ZEA_FILE = "hf://nvidia/OpenH-RF/tue-cardiac/data/subject-012.hdf5" -TRACK = "focused_fund" # one of TRACKS; must match PIPELINE below +TRACK = "focused_fund" # one of TRACKS; must match CONFIG below FRAME = 0 -PIPELINE = "hf://nvidia/OpenH-RF/tue-cardiac/pipelines/pipeline.yaml" -OUTPUT = None +CONFIG = "hf://nvidia/OpenH-RF/tue-cardiac/pipelines/pipeline.yaml" +OUT = HERE / "assets" / f"{Path(ZEA_FILE).stem}_{TRACK}_frame-{FRAME:03d}.png" DEVICE = None # e.g. cpu, cuda:0, auto:1 -def main() -> int: - pipeline_path = PIPELINE or HERE / "pipelines" / PIPELINE_FOR_TRACK[TRACK] - output_path = OUTPUT or HERE / "reference" / ( - f"{Path(ZEA_FILE).stem}_{TRACK}_frame-{FRAME:03d}.png" - ) - output_path.parent.mkdir(parents=True, exist_ok=True) +def main(): + pipeline_path = CONFIG or HERE / "pipelines" / PIPELINE_FOR_TRACK[TRACK] + OUT.parent.mkdir(parents=True, exist_ok=True) zea.init_device(device=DEVICE, verbose=False) config = Config.from_path(str(pipeline_path)) @@ -113,11 +108,10 @@ def main() -> int: cax = make_axes_locatable(axis).append_axes("right", size="5%", pad=0.05) figure.colorbar(rendered, cax=cax, label="dB") figure.tight_layout() - figure.savefig(output_path, dpi=150, bbox_inches="tight", metadata={}) + figure.savefig(OUT, dpi=150, bbox_inches="tight", metadata={}) plt.close(figure) - print(f"saved {output_path}") - return 0 + print(f"saved {OUT}") if __name__ == "__main__": - raise SystemExit(main()) + main() diff --git a/datasets/tue-carotid/README.md b/datasets/tue-carotid/README.md index 7c84d0e..4149ca1 100644 --- a/datasets/tue-carotid/README.md +++ b/datasets/tue-carotid/README.md @@ -1,4 +1,5 @@ --- +name: tue-carotid pretty_name: "TU/e carotid 2023" license: cc-by-4.0 zea_repo_id: zeahub/zea-carotid-2023 @@ -19,23 +20,12 @@ language: ![Longitudinal view of a carotid bifurcation](assets/5_long_bifur_R_0000.gif) -One cardiac cycle of a longitudinal bifurcation scan, -[`data/5_long_bifur_R_0000.hdf5`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/tue-carotid/data/5_long_bifur_R_0000.hdf5). +*One cardiac cycle of a longitudinal bifurcation scan, [`data/5_long_bifur_R_0000.hdf5`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/tue-carotid/data/5_long_bifur_R_0000.hdf5).* -`zea` renders it straight from the Hub with the -`pipeline.yaml` in this folder. Try it out with the following command: - -```bash -zea process \ - --dataset hf://nvidia/OpenH-RF/tue-carotid/data/5_long_bifur_R_0000.hdf5 \ - --config hf://nvidia/OpenH-RF/tue-carotid/pipeline.yaml -``` ## Dataset Description -The dataset includes carotid artery scans from 10 subjects. -Each file includes 100 frames, at least one cardiac cycle. -The acquisition scheme it consists of 128 line scanning interleaved with 21 plane waves. +The dataset includes carotid artery scans from 10 subjects. Each file includes 150 frames, at least one cardiac cycle. The acquisition scheme it consists of 128 line scanning interleaved with 21 plane waves. Included views: @@ -47,20 +37,18 @@ Included views: - Longitudinal section 1cm from bifurcation - Longitudinal section -The acquisitions were performed with a Verasonics 256. -The acquisitions were performed with a linear probe (Verasonics L11-5v). -The same operator performed all acquisitions. +The acquisitions were performed with a Verasonics 256. The acquisitions were performed with a linear probe (Verasonics L11-5v). The same operator performed all acquisitions. -## Dataset Contributors(s) +## Dataset Contributor(s) -Wessel van Nierop -Tristan Stevens -Oisín Nolan -Simon Penninga -Beatrice Federici -Vincent van der Schaft -Ben Luijten -Ruud van Sloun +- Wessel van Nierop +- Tristan Stevens +- Oisín Nolan +- Simon Penninga +- Beatrice Federici +- Vincent van der Schaft +- Ben Luijten +- Ruud van Sloun ## Dataset Creation Date @@ -68,7 +56,7 @@ Nov 2023 ## License / Terms of Use -CC BY 4.0 +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage @@ -80,27 +68,55 @@ Image-quality / beamforming has both plane-wave (21) and focused acquisitions (1 - Labeling Method: acquisitions per view - Acquisition system: see file +## Processing the Dataset + +The acquisitions can be processed with the `pipeline.yaml` definition in this folder and the [zea library](https://github.com/tue-bmd/zea). + +`zea` streams the data from the Hugging Face Hub and processes it according to the pipeline. You can try it out with the following command: + +```bash +zea process \ + --dataset hf://nvidia/OpenH-RF/tue-carotid/data/5_long_bifur_R_0000.hdf5 \ + --config hf://nvidia/OpenH-RF/tue-carotid/pipeline.yaml +``` + +Alternatively, you can use the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/tue-carotid/reconstruct.py) as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF). + ## Dataset Format -zea v1.3.0 +[zea v0.1.6](https://github.com/tue-bmd/zea) ## Dataset Quantification -**Current OpenH-RF release:** 80 HDF5 files; 435.40 GB (435,403,030,528 bytes) stored; root `zea_version` **0.1.6**. Sizes include all HDF5 contents and use decimal units (MB = 10^6 bytes, GB = 10^9 bytes, TB = 10^12 bytes), not decoded-array memory or original-source download sizes. +**OpenH-RF release:** 80 HDF5 files; 435.40 GB (435,403,030,528 bytes) stored; root `zea_version` **0.1.6**. Sizes include all HDF5 contents and use decimal units (MB = 10^6 bytes, GB = 10^9 bytes, TB = 10^12 bytes), not decoded-array memory or original-source download sizes. -- Number of samples / frames / acquisitions -- Train / validation / test split (if applicable) -- Total size on disk -- Per-sample feature table (name, shape, dtype, units, description) — see NV-Raw2Insights-US for reference format +- **Samples / frames / acquisitions:** 80 acquisitions (one HDF5 file each) from 10 subjects, 150 frames per acquisition — 12,000 frames total. +- **Transmit events per frame:** 149 — 128 focused lines interleaved with 21 plane waves. +- **Train / validation / test split:** none; each file is a single acquisition. +- **Total size on disk:** 435.40 GB (435,403,030,528 bytes). -## Subject Metadata +### Per-File Feature Summary -None +Every file has one track (`tracks/track_0`) with the same field structure; `n_frames = 150`, `n_tx = 149`, `n_ax = 2176`, `n_el = 128`. + +| Field | Shape | Dtype | Units | Description | +|---|---|---|---|---| +| `tracks/track_0/data/raw_data` | `(150, 149, 2176, 128, 1)` | int16 | ADC counts | Raw per-element RF channel data: (frames, transmits, axial samples, elements, 1) | +| `tracks/track_0/scan/t0_delays` | `(149, 128)` | float32 | s | Per-transmit, per-element transmit delay | +| `tracks/track_0/scan/tx_apodizations` | `(149, 128)` | float32 | unitless [-1, 1] | Per-transmit, per-element transmit apodization weight | +| `tracks/track_0/scan/polar_angles` | `(149,)` | float32 | rad | Per-transmit polar steering angle | +| `tracks/track_0/scan/azimuth_angles` | `(149,)` | float32 | rad | Per-transmit azimuth steering angle | +| `tracks/track_0/scan/focus_distances` | `(149,)` | float32 | m | Per-transmit focal distance (`inf` for the 21 plane waves) | +| `tracks/track_0/scan/transmit_origins` | `(149, 3)` | float32 | m | Per-transmit beam origin (x, y, z) | +| `tracks/track_0/scan/initial_times` | `(149,)` | float32 | s | Per-transmit A/D start time | +| `tracks/track_0/scan/time_to_next_transmit` | `(150, 149)` | float32 | s | Per-frame, per-transmit inter-transmit interval | +| `probe/probe_geometry` | `(128, 3)` | float32 | m | Element positions (x, y, z) | -## Data Validation +Scalars: `probe/name` = `verasonics_l11_5v` (linear, 128 elements), `probe/probe_center_frequency` = 6.25 MHz, `scan/center_frequency` = `scan/demodulation_frequency` = 7.8125 MHz, `scan/sampling_frequency` = 31.25 MHz, `scan/sound_speed` = 1540 m/s. `us_machine` = Verasonics Vantage 256. No derived data products are stored; `raw_data` is the only `data/` field. -[reconstruct.py](./reconstruct.py) -[pipeline.yaml](./pipeline.yaml) +## Subject Metadata + +None ## Known Issues diff --git a/datasets/tue-carotid/assets/main.png b/datasets/tue-carotid/assets/main.png new file mode 100644 index 0000000..8beaf8a Binary files /dev/null and b/datasets/tue-carotid/assets/main.png differ diff --git a/datasets/tue-carotid/reconstruct.py b/datasets/tue-carotid/reconstruct.py index c321a7b..662bd57 100644 --- a/datasets/tue-carotid/reconstruct.py +++ b/datasets/tue-carotid/reconstruct.py @@ -34,14 +34,14 @@ # Defaults stream straight from the published corpus. Swap any of these for a # local path to run against your own copy. ZEA_FILE = "hf://nvidia/OpenH-RF/tue-carotid/data/5_long_bifur_R_0000.hdf5" -PIPELINE = "hf://nvidia/OpenH-RF/tue-carotid/pipeline.yaml" +CONFIG = "hf://nvidia/OpenH-RF/tue-carotid/pipeline.yaml" FRAME = 0 -OUT = HERE / "zea_carotid_2023_sample.png" +OUT = HERE / "assets" / "zea_carotid_2023_sample.png" def main(): zea.init_device() - config = zea.Config.from_path(PIPELINE) + config = zea.Config.from_path(CONFIG) # Load data and parameters with zea.File(ZEA_FILE) as f: @@ -49,7 +49,7 @@ def main(): raw_data = f.data.raw_data[FRAME, parameters.selected_transmits] # Process data through the pipeline - pipeline = zea.Pipeline.from_path(PIPELINE, with_batch_dim=False) + pipeline = zea.Pipeline.from_path(CONFIG, with_batch_dim=False) bmode = pipeline(data=raw_data, **pipeline.prepare_parameters(parameters))["data"] # Save PNG @@ -65,6 +65,7 @@ def main(): plt.xlabel("x [mm]") plt.ylabel("z [mm]") plt.title("TU/e carotid 2023 sample") + Path(OUT).parent.mkdir(parents=True, exist_ok=True) plt.savefig(str(OUT), dpi=300, bbox_inches="tight") print(f"Saved to {OUT}") diff --git a/datasets/tumunich/README.md b/datasets/tumunich/README.md index e28e9ea..08632aa 100644 --- a/datasets/tumunich/README.md +++ b/datasets/tumunich/README.md @@ -1,5 +1,6 @@ --- -pretty_name: "OpenH-RF — Robotic Tracked Ultrasound (Verasonics L11-5gH, arm, CIRS 054GS and CIRS 074 thyroid phantoms)" +name: tumunich +pretty_name: "Robotic Tracked Ultrasound (Verasonics L11-5gH, arm, CIRS 054GS and CIRS 074 thyroid phantoms)" license: cc-by-4.0 task_categories: - image-to-3d @@ -18,40 +19,38 @@ size_categories: # Robotic Tracked Ultrasound — Verasonics L11-5gH, vascular arm, CIRS 054GS and CIRS 074 thyroid phantoms + + + + + + + + + + +
Acquisition setupReconstructionPanoramic reconstruction (based on robotic tracking)
+ +*Acquisition setup (left), a single-frame reconstruction (middle) and a panoramic reconstruction compounded along the robot-tracked sweep (right) of the CIRS 054GS phantom, [`data/cirs_phantom/synth_apert_sweep_1.hdf5`](https://huggingface.co/datasets/nvidia/OpenH-RF/blob/main/tumunich/data/cirs_phantom/synth_apert_sweep_1.hdf5).* + ## Dataset Description -Robotically tracked freehand ultrasound of three tissue-mimicking phantoms, acquired with a Verasonics -Vantage NXT 64 and an L11-5gH linear probe held by a KUKA LBR robot: +Robotically tracked freehand ultrasound of three tissue-mimicking phantoms, acquired with a Verasonics Vantage NXT 64 and an L11-5gH linear probe held by a KUKA LBR robot: -- a **Blue Phantom Gen II PICC, PIV and Arterial Line Vascular Access training model (BPA304-HP)** — - an upper-extremity arm phantom with a nine-vessel system (cephalic, basilic, medial cubital, radial - and ulnar veins plus brachial, radial and ulnar arteries) in self-healing tissue-mimicking material; +- a **Blue Phantom Gen II PICC, PIV and Arterial Line Vascular Access training model (BPA304-HP)** — an upper-extremity arm phantom with a nine-vessel system (cephalic, basilic, medial cubital, radial and ulnar veins plus brachial, radial and ulnar arteries) in self-healing tissue-mimicking material; - a **CIRS Model 054GS** General Purpose Ultrasound Phantom; -- a **CIRS Model 074** Thyroid Ultrasound Training Phantom — a slightly enlarged thyroid gland in an - anthropomorphic neck of Zerdine® hydrogel, with the trachea, common carotid artery and internal - jugular vein as internal landmarks and one cyst plus one isoechoic stiff lesion per lobe. - -Each frame is a multi-angle plane-wave acquisition of raw pre-beamformed channel data, and every -frame carries the measured robot end-effector pose, so the sweeps can be compounded into 3D. -**Multiple tracked sweeps were recorded from each phantom**: the sweeps belonging to one scan area are -concatenated into a single track within that file (multi-angle acquisition, except the synth aperture dataset). -The phantom was not moved between the scans: they are overlapping and tracking information is calibrated with respect to each other. -For the multi-angle data, individual sweeps are always recorded between two defined poses with varying out-of-plane rotation (around the x-axis). -The first sweep refers to -15 degree, with every following sweep raising that angle by 5 degree until it reaches 15 degree (in total 7 scans). -For the two transverse scans in the CIRS phantom, we only recorded -15 to +10 degree, due to the limited scan area of the robot. -For the two transverse scans in the thyroid phantom, we only recorded -10 to +10 degree, due to the limited scan area of the robot. -`custom/sweep_index` identifies the originating sweep per frame. This is **phantom** data (no human -or animal subjects). The intended contribution is a raw-channel-data benchmark for tracked freehand -reconstruction and beamforming. +- a **CIRS Model 074** Thyroid Ultrasound Training Phantom — a slightly enlarged thyroid gland in an anthropomorphic neck of Zerdine® hydrogel, with the trachea, common carotid artery and internal jugular vein as internal landmarks and one cyst plus one isoechoic stiff lesion per lobe. + +Each frame is a multi-angle plane-wave acquisition of raw pre-beamformed channel data, and every frame carries the measured robot end-effector pose, so the sweeps can be compounded into 3D. **Multiple tracked sweeps were recorded from each phantom**: the sweeps belonging to one scan area are concatenated into a single track within that file (multi-angle acquisition, except the synth aperture dataset). The phantom was not moved between the scans: they are overlapping and tracking information is calibrated with respect to each other. For the multi-angle data, individual sweeps are always recorded between two defined poses with varying out-of-plane rotation (around the x-axis). The first sweep refers to -15 degree, with every following sweep raising that angle by 5 degree until it reaches 15 degree (in total 7 scans). For the two transverse scans in the CIRS phantom, we only recorded -15 to +10 degree, due to the limited scan area of the robot. For the two transverse scans in the thyroid phantom, we only recorded -10 to +10 degree, due to the limited scan area of the robot. `custom/sweep_index` identifies the originating sweep per frame. This is **phantom** data (no human or animal subjects). The intended contribution is a raw-channel-data benchmark for tracked freehand reconstruction and beamforming. ## Dataset Contributor(s) -CAMP (Computer Aided Medical Procedures), Technical University of Munich (TUM). -Primary contact: Felix Dülmer . -Mohammad Farid Azampour -Rüdiger Göbl -Oliver Zettinig -Nassir Navab +- Felix Dülmer (primary contact) +- Mohammad Farid Azampour +- Rüdiger Göbl +- Oliver Zettinig +- Nassir Navab +- CAMP (Computer Aided Medical Procedures), Technical University of Munich (TUM) ## Dataset Creation Date @@ -59,50 +58,33 @@ Nassir Navab ## License / Terms of Use -CC BY 4.0. The data is phantom-derived and carries no patient consent or IP encumbrances, so it is -cleared for CC BY 4.0. +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage -Primary task: **robotic tracked ultrasound acquisition** — compounding the per-frame robot poses with -the tracked sweeps for freehand 3D reconstruction. The raw channel data also supports advanced -beamforming research. +Primary task: **robotic tracked ultrasound acquisition** — compounding the per-frame robot poses with the tracked sweeps for freehand 3D reconstruction. The raw channel data also supports advanced beamforming research. ## Dataset Characterization - **Data Collection Method:** phantom -- **Labeling Method:** per-file annotations of anatomy and imaging plane in - `metadata/annotations`, constant within a file and broadcast over its frames; no per-frame - labels. A co-registered robot pose stream is provided per frame. -- **Acquisition system:** Verasonics Vantage NXT 64; L11-5gH linear array, 128 elements, 0.30 mm pitch, - 7.6 MHz center frequency, 76.8% fractional bandwidth; 30.3 MHz receive sampling; 1540 m/s assumed - sound speed. Transmit: 7 plane-wave angles (−18° … +18° in 6° steps), each acquired with three - walking 64-element mux sub-apertures (21 acquisitions per frame). Imaging depth: 50 mm for the arm phantom - and 60 mm for the CIRS phantom. +- **Labeling Method:** per-file annotations of anatomy and imaging plane in `metadata/annotations`, constant within a file and broadcast over its frames; no per-frame labels. A co-registered robot pose stream is provided per frame. +- **Acquisition system:** Verasonics Vantage NXT 64; L11-5gH linear array, 128 elements, 0.30 mm pitch, 7.6 MHz center frequency, 76.8% fractional bandwidth; 30.3 MHz receive sampling; 1540 m/s assumed sound speed. Transmit: 7 plane-wave angles (−18° … +18° in 6° steps), each acquired with three walking 64-element mux sub-apertures (21 acquisitions per frame). Imaging depth: 50 mm for the arm phantom and 60 mm for the CIRS phantom. + +## Processing the Dataset + +The acquisitions can be processed with the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/tumunich/reconstruct.py) as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF), together with the `pipeline.yaml` definition in this folder and the [zea library](https://github.com/tue-bmd/zea). The script streams the data from the Hugging Face Hub. + +`reconstruct.py` beamforms a single frame and compares it to the stored Verasonics B-mode; [`reconstruct_panorama.py`](https://github.com/open-h/OpenH-RF/blob/main/datasets/tumunich/reconstruct_panorama.py), with `pipeline_panorama.yaml`, compounds a tracked sweep into the panoramic reconstruction shown above, using the per-frame robot poses. ## Dataset Format -*zea* HDF5. The recorded Verasonics `.vrs` files are read natively in Python (no MATLAB): the -multiplexed 64-channel receive apertures are expanded to the full 128-element probe dimension, and -the per-transmit hardware time tags embedded in the channel data are decoded to build -`time_to_next_transmit`. The transmit-time-gain-compensation (TGC) applied on the Verasonics hardware -is left in the raw data; no additional filtering, decimation, or demodulation is applied before -packaging. - -The `image` accompanying each frame is **not** our reconstruction: it is the B-mode produced by the -**Verasonics vendor pipeline** and displayed live by VSX during acquisition, saved from the display. -That pipeline coherently compounds the IQ of all 21 receive events (7 plane-wave angles × 3 walking -64-element mux sub-apertures) onto the display grid, then applies envelope detection, power -compression, a reject floor and simple temporal persistence before mapping to 8-bit grayscale. Two -consequences matter for use: the persistence means **consecutive frames are not fully independent**, -and the compression is not calibrated, so the image is a **qualitative reference only** — reconstruct -from `raw_data` for anything quantitative. The same note is stored in the files themselves, as the -`description` of `tracks/track_0/data/image`. - -Zero-valued rows pad the top of each stored frame so the image starts at the transducer face rather -than at the imaging start depth, which matters when compounding the tracked sweeps into 3D. -`tracks/track_0/data/image/coordinates` already accounts for the padding, so the exact offset can be -read back per file rather than assumed. +[zea v0.1.4](https://github.com/tue-bmd/zea) + +*zea* HDF5. The recorded Verasonics `.vrs` files are read natively in Python (no MATLAB): the multiplexed 64-channel receive apertures are expanded to the full 128-element probe dimension, and the per-transmit hardware time tags embedded in the channel data are decoded to build `time_to_next_transmit`. The transmit-time-gain-compensation (TGC) applied on the Verasonics hardware is left in the raw data; no additional filtering, decimation, or demodulation is applied before packaging. + +The `image` accompanying each frame is **not** our reconstruction: it is the B-mode produced by the **Verasonics vendor pipeline** and displayed live by VSX during acquisition, saved from the display. That pipeline coherently compounds the IQ of all 21 receive events (7 plane-wave angles × 3 walking 64-element mux sub-apertures) onto the display grid, then applies envelope detection, power compression, a reject floor and simple temporal persistence before mapping to 8-bit grayscale. Two consequences matter for use: the persistence means **consecutive frames are not fully independent**, and the compression is not calibrated, so the image is a **qualitative reference only** — reconstruct from `raw_data` for anything quantitative. The same note is stored in the files themselves, as the `description` of `tracks/track_0/data/image`. + +Zero-valued rows pad the top of each stored frame so the image starts at the transducer face rather than at the imaging start depth, which matters when compounding the tracked sweeps into 3D. `tracks/track_0/data/image/coordinates` already accounts for the padding, so the exact offset can be read back per file rather than assumed. ## Dataset Quantification @@ -111,8 +93,7 @@ read back per file rather than assumed. - **Phantoms:** 3 (vascular access arm phantom, CIRS 054GS, CIRS 074 thyroid) - **Files:** 11 zea HDF5 files across 3 sub-dataset folders - **Sweeps:** multiple tracked sweeps per phantom, concatenated per file -- **Frames / acquisitions:** 17,852 frames in total; ~200–425 frames per sweep - (i.e. for one file with 7 sweeps, ~2100 frames) +- **Frames / acquisitions:** 17,852 frames in total; ~200–425 frames per sweep (i.e. for one file with 7 sweeps, ~2100 frames) - **Stored HDF5 size:** 62.25 GB (62,245,175,296 bytes). - **Train / val / test split:** N/A @@ -122,11 +103,7 @@ read back per file rather than assumed. | `cirs_phantom/` | 5 | 6,204 | 7 (longitudinal), 6 (transverse), 1 (synth. aperture) | | `thyroid_phantom/` | 4 | 6,630 | 7 (longitudinal), 5 (transverse) | -The shapes below are for the CIRS 054GS file. The thyroid phantom files have identical per-frame -shapes and imaging geometry (60 mm depth, 2816 axial samples). **The arm phantom files were acquired -at 50 mm depth and so have 2560 axial samples** — their `raw_data` is `(#frames, 21, 2560, 128, 1)`. -Otherwise the layout is the same across all three, differing only in the leading frame count and the -number of sweeps. +The shapes below are for the CIRS 054GS file. The thyroid phantom files have identical per-frame shapes and imaging geometry (60 mm depth, 2816 axial samples). **The arm phantom files were acquired at 50 mm depth and so have 2560 axial samples** — their `raw_data` is `(#frames, 21, 2560, 128, 1)`. Otherwise the layout is the same across all three, differing only in the leading frame count and the number of sweeps. | Field | Shape | dtype | Units | Description | |---|---|---|---|---| @@ -140,52 +117,31 @@ number of sweeps. | `metadata/annotations/view` | `()` | str | — | Imaging plane: `longitudinal` or `transverse`. Absent for the arm phantom, swept along the limb | | `custom/sweep_index` | `(#frames,)` | int32 | — | Which of the sweeps each frame belongs to | -The probe pose is an independently sampled stream; `probe_pose.start_time_offset` and -`time_to_next_transmit` place it on the same clock as the frames so the pose can be interpolated at -each frame's acquisition time. +The probe pose is an independently sampled stream; `probe_pose.start_time_offset` and `time_to_next_transmit` place it on the same clock as the frames so the pose can be interpolated at each frame's acquisition time. -`sweep_index` is the one quantity that has no home in the zea schema, so it is stored as a zea -custom element (written via `File.create(custom=[...])`, read back as `f.custom.sweep_index`). +`sweep_index` is the one quantity that has no home in the zea schema, so it is stored as a zea custom element (written via `File.create(custom=[...])`, read back as `f.custom.sweep_index`). ### Acoustic lens -`probe/lens_thickness` is the Verasonics `Trans.lensCorrection` one-way lens path (0.6 mm; this probe -is defined with `Trans.units = 'mm'`). `probe/lens_sound_speed` is **not** reported by Verasonics and -is set to a nominal 1000 m/s for the silicone lens — override it with -`convert.py --lens-sound-speed`. Storing the lens physically means the reconstruction only sets -`apply_lens_correction: true` and zea applies its refraction model itself, rather than the -reconstruction patching `initial_times` by hand. +`probe/lens_thickness` is the Verasonics `Trans.lensCorrection` one-way lens path (0.6 mm; this probe is defined with `Trans.units = 'mm'`). `probe/lens_sound_speed` is **not** reported by Verasonics and is set to a nominal 1000 m/s for the silicone lens — override it with `convert.py --lens-sound-speed`. Storing the lens physically means the reconstruction only sets `apply_lens_correction: true` and zea applies its refraction model itself, rather than the reconstruction patching `initial_times` by hand. ## Subject Metadata - **Subjects:** 3 tissue-mimicking phantoms (no human/animal subjects). - **Phantom models:** - - Blue Phantom Gen II PICC, PIV and Arterial Line Vascular Access training model (BPA304-HP) — - upper-extremity arm phantom, nine-vessel system, self-healing tissue-mimicking material. + - Blue Phantom Gen II PICC, PIV and Arterial Line Vascular Access training model (BPA304-HP) — upper-extremity arm phantom, nine-vessel system, self-healing tissue-mimicking material. - CIRS Model 054GS (General Purpose Ultrasound Phantom). - - CIRS Model 074 (Thyroid Ultrasound Training Phantom) — thyroid gland in an anthropomorphic neck, - Zerdine® hydrogel, trachea/carotid/jugular landmarks, one cyst and one isoechoic stiff lesion per - lobe. + - CIRS Model 074 (Thyroid Ultrasound Training Phantom) — thyroid gland in an anthropomorphic neck, Zerdine® hydrogel, trachea/carotid/jugular landmarks, one cyst and one isoechoic stiff lesion per lobe. - **Sweeps:** multiple tracked freehand sweeps per phantom. - **Probe model:** Verasonics L11-5gH. ## Data Validation -`reconstruct.py` runs a `zea.Pipeline` that beamforms a frame from the raw channel data and compares -it to the stored Verasonics B-mode. The chain is: cast → band-pass (transducer band) → demodulate → -delay-and-sum compounding over all 21 transmits → envelope detect → normalize → power compression. +`reconstruct.py` runs a `zea.Pipeline` that beamforms a frame from the raw channel data and compares it to the stored Verasonics B-mode. The chain is: cast → band-pass (transducer band) → demodulate → delay-and-sum compounding over all 21 transmits → envelope detect → normalize → power compression. -Both the operation chain and the reconstruction parameters are written to `pipeline.yaml`: the -`parameters:` key holds `f_number` (the Verasonics senscutoff aperture, 1.155), the grid limits, and -`apply_lens_correction: true`. Everything else — probe geometry, sound speed, the lens — is read from -the file by `File.load_parameters()`, so the whole recipe is reproducible from the HDF5 plus that one -YAML. Each run rewrites `pipeline.yaml` from the sample it was given and then loads it back to do the -reconstruction, so the shipped recipe is always the one that produced the image. The operation chain -is the same for all three phantoms; only the data-derived grid limits differ, which is why one -`pipeline.yaml` covers the whole submission. +Both the operation chain and the reconstruction parameters are written to `pipeline.yaml`: the `parameters:` key holds `f_number` (the Verasonics senscutoff aperture, 1.155), the grid limits, and `apply_lens_correction: true`. Everything else — probe geometry, sound speed, the lens — is read from the file by `File.load_parameters()`, so the whole recipe is reproducible from the HDF5 plus that one YAML. Each run rewrites `pipeline.yaml` from the sample it was given and then loads it back to do the reconstruction, so the shipped recipe is always the one that produced the image. The operation chain is the same for all three phantoms; only the data-derived grid limits differ, which is why one `pipeline.yaml` covers the whole submission. -Reference reconstructions are stored next to the sample they came from as -`data//_reconstructed.png`. +Reference reconstructions are stored next to the sample they came from as `data//_reconstructed.png`. ## Known Issues @@ -193,5 +149,6 @@ Reference reconstructions are stored next to the sample they came from as ## Ethical Considerations -Phantom data only — no human or animal subjects, no PHI, and no IRB/consent required. No -de-identification is applicable. +Phantom data only — no human or animal subjects, no PHI, and no IRB/consent required. No de-identification is applicable. + +The data is phantom-derived and carries no patient consent or IP encumbrances, so it is cleared for CC BY 4.0. diff --git a/datasets/tumunich/assets/acquisition_description.jpg b/datasets/tumunich/assets/acquisition_description.jpg new file mode 100644 index 0000000..3bc9ba3 Binary files /dev/null and b/datasets/tumunich/assets/acquisition_description.jpg differ diff --git a/datasets/tumunich/assets/hero.png b/datasets/tumunich/assets/hero.png new file mode 100644 index 0000000..e008867 Binary files /dev/null and b/datasets/tumunich/assets/hero.png differ diff --git a/datasets/tumunich/assets/panorama_reconstructed.png b/datasets/tumunich/assets/panorama_reconstructed.png new file mode 100644 index 0000000..8552deb Binary files /dev/null and b/datasets/tumunich/assets/panorama_reconstructed.png differ diff --git a/datasets/tumunich/assets/reconstructed.png b/datasets/tumunich/assets/reconstructed.png new file mode 100644 index 0000000..5e79889 Binary files /dev/null and b/datasets/tumunich/assets/reconstructed.png differ diff --git a/datasets/tumunich/pipeline_panorama.yaml b/datasets/tumunich/pipeline_panorama.yaml new file mode 100644 index 0000000..4e01556 --- /dev/null +++ b/datasets/tumunich/pipeline_panorama.yaml @@ -0,0 +1,57 @@ +pipeline: + operations: + - name: keras.ops.cast + params: + dtype: float32 + - name: band_pass_filter + params: + passband: + - 4680000.122070313 + - 10519999.877929688 + - demodulate + - name: pipeline + operations: + - name: patched_grid + operations: + - name: tof_correction + params: + jit_compile: false + with_batch_dim: false + - name: delay_and_sum + params: + jit_compile: false + with_batch_dim: false + params: {} + - reshape_grid + - envelope_detect +parameters: + f_number: 1.155 + apply_lens_correction: true + xlims: + - -0.019200000911951065 + - 0.019200000911951065 + zlims: + - 0.0010131578892469406 + - 0.06008026376366615 + selected_transmits: + - 0 + - 1 + - 2 + - 3 + - 4 + - 5 + - 6 + - 7 + - 8 + - 9 + - 10 + - 11 + - 12 + - 13 + - 14 + - 15 + - 16 + - 17 + - 18 + - 19 + - 20 diff --git a/datasets/tumunich/reconstruct.py b/datasets/tumunich/reconstruct.py index fbb2ccb..2a7d300 100644 --- a/datasets/tumunich/reconstruct.py +++ b/datasets/tumunich/reconstruct.py @@ -5,21 +5,20 @@ B-mode reconstruction of walking-aperture robotic ultrasound sweep channel data. -Each steering angle is acquired three times with the 64-element transmit and -receive aperture walked across the array, so a reconstruction has to compound -all 21 acquisitions to cover the full probe. The grey levels reproduce the +Each steering angle is acquired three times with the aperture walked across the +array, so a reconstruction has to compound all 21 acquisitions to cover the full +probe. Transmit uses a 64-element aperture; receive is multiplexed into three +42/44-element blocks that tile the 128 elements. The grey levels reproduce the Verasonics display mapping, making them directly comparable to the VSX B-mode -stored in the file as ``data/image``, which is rendered alongside. The -comparison figure follows the sample as ``_reconstructed.png``, unless -OUTPUT says otherwise. +stored in the file as ``data/image``. -Requires zea>=0.1.6 (https://github.com/tue-bmd/zea), the library that does the +Requires zea>=0.1.7 (https://github.com/tue-bmd/zea), the library that does the ultrasound processing here, together with one of its Keras backends (JAX, PyTorch or TensorFlow). Installation instructions are at https://zea.readthedocs.io/en/latest/installation.html. Usage: - python reconstruct.py + uv run python reconstruct.py """ import os @@ -50,8 +49,8 @@ from zea.ops.base import Operation HERE = Path(__file__).parent -DEFAULT_INPUT = "hf://nvidia/OpenH-RF/tumunich/data/cirs_phantom/synth_apert_sweep_1.hdf5" -CONFIG = HERE / "pipeline.yaml" +CONFIG = HERE / "pipeline.yaml" # written by build_pipeline(); this is what the run loads +HF_CONFIG = "hf://nvidia/OpenH-RF/tumunich/pipeline.yaml" # where CONFIG is published PARAMETERS = { @@ -62,11 +61,16 @@ # --- Inputs ----------------------------------------------------------------- # Defaults stream straight from the published corpus. Swap any of these for a # local path to run against your own copy. -INPUT = "hf://nvidia/OpenH-RF/tumunich/data/cirs_phantom/synth_apert_sweep_1.hdf5" -OUTPUT = None # PNG to write (default: _reconstructed.png next to the sample) +ZEA_FILE = "hf://nvidia/OpenH-RF/tumunich/data/cirs_phantom/synth_apert_sweep_1.hdf5" +OUT = HERE / "assets" / f"{Path(ZEA_FILE).stem}_reconstructed.png" FRAME = 0 # Zero-based frame index to reconstruct DYNAMIC_RANGE = [-40, 0] # dB range shown +# Leading fast-time samples to blank before beamforming. Samples 0-1 are transmit +# pulse feedthrough clipped at the ADC rail: the same value on every driven element +# (~32000 against a typical echo of ~284), on exactly the elements that transmit. +FEEDTHROUGH_SAMPLES = 2 + def coords_to_imshow_mm(coords): """openh-rf per-pixel coordinates (z, x, 3), last axis [x, y, z] in metres @@ -125,6 +129,7 @@ def reconstruct_frame(f, frame): n_tx = f.scan.polar_angles.shape[0] selected = list(range(n_tx)) raw = np.asarray(f.data.raw_data[frame : frame + 1, selected]).copy() + raw[:, :, :FEEDTHROUGH_SAMPLES] = 0 sound_speed = float(np.asarray(f.scan.sound_speed)) initial_times = np.asarray(f.scan.initial_times, dtype=np.float64) @@ -167,18 +172,19 @@ def reconstruct_frame(f, frame): # The grid starts at the imaging start depth, so pad the near field back on # to line the result up with the stored Verasonics B-mode. axial_spacing = (end_depth - start_depth) / (generated.shape[0] - 1) - generated = np.pad(generated, ((round(start_depth / axial_spacing), 0), (0, 0))) + generated = np.pad( + generated, + ((round(start_depth / axial_spacing), 0), (0, 0)), + constant_values=DYNAMIC_RANGE[0], + ) return generated def main(): - global OUTPUT - if OUTPUT is None: - OUTPUT = Path(f"{Path(INPUT).stem}_reconstructed.png") zea.init_device() - with zea.File(str(INPUT)) as f: + with zea.File(str(ZEA_FILE)) as f: display_coords = f.data.image.coordinates[:] generated = reconstruct_frame(f, FRAME) print(f"Reconstructed: {generated.shape}") @@ -186,6 +192,7 @@ def main(): extent = coords_to_imshow_mm(display_coords) zea.visualize.set_mpl_style() fig, ax = plt.subplots(figsize=(6, 8)) + ax.imshow( generated, aspect="equal", @@ -197,8 +204,9 @@ def main(): ax.set_xlabel("Lateral [mm]") ax.set_ylabel("Depth [mm]") plt.tight_layout() - plt.savefig(OUTPUT, dpi=150, bbox_inches="tight") - print(f"Saved {OUTPUT}") + Path(OUT).parent.mkdir(parents=True, exist_ok=True) + plt.savefig(OUT, dpi=150, bbox_inches="tight") + print(f"Saved {OUT}") if __name__ == "__main__": diff --git a/datasets/tumunich/reconstruct_panorama.py b/datasets/tumunich/reconstruct_panorama.py new file mode 100644 index 0000000..f80f6a8 --- /dev/null +++ b/datasets/tumunich/reconstruct_panorama.py @@ -0,0 +1,234 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Extended-field-of-view reconstruction for the tumunich dataset of OpenH-RF. + +The robot translates the probe 70 mm along its own lateral axis during the +acquisition, so frames taken at different points in the sweep see overlapping +but offset slices of the phantom. This script beamforms ``N_FRAMES`` of them +and compounds them onto one canvas, giving a panorama about 108 mm wide: the +38.4 mm aperture plus the 70 mm of travel. + +It uses the operations of that reference pipeline but stops at the envelope, and +normalises once over the finished panorama instead of once per frame. The +pipeline is written out beside this script under its own name, so the reference +config is left alone. + +Frames are placed from ``metadata/probe_pose``, which records the robot +trajectory as an absolute measurement. The pose is a pure translation +here: the quaternions are constant, and all the motion is on one axis. + +Usage: + uv run python reconstruct_panorama.py +""" + +import os + +os.environ.setdefault("KERAS_BACKEND", "jax") +os.environ.setdefault("MPLBACKEND", "Agg") + +from pathlib import Path + +import matplotlib.pyplot as plt +import numpy as np +import zea +from reconstruct import ( + DYNAMIC_RANGE, + FEEDTHROUGH_SAMPLES, + PARAMETERS, + build_pipeline, +) + +HERE = Path(__file__).parent +CONFIG = HERE / "pipeline_panorama.yaml" + +# --- Inputs ----------------------------------------------------------------- +# Defaults stream straight from the published corpus. Swap any of these for a +# local path to run against your own copy. +INPUT = "hf://nvidia/OpenH-RF/tumunich/data/cirs_phantom/synth_apert_sweep_1.hdf5" +OUTPUT = None # PNG to write (default: _panorama.png next to the sample) +N_FRAMES = 20 # Frames to compound, spread evenly over the travel +BLEND_TAPER = 0.25 # Fraction of each frame's width ramped down at either edge +DISPLAY_PERCENTILE = 99.95 # Panorama percentile mapped to 0 dB (see blend_panorama) + + +def frame_times(f): + """Acquisition time of each frame in seconds, measured from the first transmit.""" + intervals = np.asarray(f.scan.time_to_next_transmit, dtype=np.float64).sum(axis=1) + return np.concatenate([[0.0], np.cumsum(intervals)[:-1]]) + + +def displacement_by_pose(f): + """Per-frame lateral probe displacement in metres, relative to the first frame. + + ``start_time_offset`` is defined by the zea spec as the time between the + first transmit event and sample 0 of the pose signal, so it belongs on the + pose clock and not on the frame clock: pose sample ``t`` was taken at + acquisition time ``t + start_time_offset``. + """ + pose = f.metadata.probe_pose + translation = np.asarray(pose.translation, dtype=np.float64) + timestamps = np.asarray(pose.timestamps, dtype=np.float64) + start_time_offset = float(np.asarray(pose.start_time_offset)) + + # The sweep is a pure translation, so the axis that moves is the sweep axis. + sweep_axis = int(np.argmax(translation.max(axis=0) - translation.min(axis=0))) + position = np.interp(frame_times(f), timestamps + start_time_offset, translation[:, sweep_axis]) + return position - position[0] + + +def select_frames(displacement, n_frames): + """Frame indices whose positions are spread as evenly as possible over the travel. + + Evenly in space, not in frame number: the robot is stationary for the first + ~30 and last ~45 frames, so an even split by index would stack several + frames on top of each other at both ends and leave gaps in the middle. + """ + targets = np.linspace(displacement.min(), displacement.max(), n_frames) + indices = {int(np.argmin(np.abs(displacement - target))) for target in targets} + return sorted(indices) + + +def beamform_frame(f, frame): + """Beamform one frame onto the axial extent of the stored B-mode.""" + n_tx = f.scan.polar_angles.shape[0] + selected = list(range(n_tx)) + raw = np.asarray(f.data.raw_data[frame : frame + 1, selected]).copy() + raw[:, :, :FEEDTHROUGH_SAMPLES] = 0 + + sound_speed = float(np.asarray(f.scan.sound_speed)) + initial_times = np.asarray(f.scan.initial_times, dtype=np.float64) + start_depth = float(initial_times[0]) * sound_speed / 2 + + display_coords = f.data.image.coordinates[:] + end_depth = float(display_coords[..., 2].max()) + display_axial_spacing = display_coords[1, 0, 2] - display_coords[0, 0, 2] + img_coords = display_coords[round(start_depth / display_axial_spacing) :].copy() + img_coords[..., 2] = np.linspace(start_depth, end_depth, img_coords.shape[0])[:, None] + + parameters = { + **PARAMETERS, + "xlims": [float(img_coords[..., 0].min()), float(img_coords[..., 0].max())], + "zlims": [float(img_coords[..., 2].min()), float(img_coords[..., 2].max())], + "selected_transmits": selected, + } + + center_frequency = float(np.asarray(f.scan.center_frequency).reshape(-1)[0]) + bandwidth_fraction = float(np.asarray(f.probe.probe_bandwidth_percent).reshape(-1)[0]) / 100.0 + passband = ( + center_frequency * (1 - bandwidth_fraction / 2), + center_frequency * (1 + bandwidth_fraction / 2), + ) + + # The reference pipeline without its last two stages, the per-frame Normalize + # and LogCompress: this script normalises once over the finished panorama. + operations = list(build_pipeline(passband).operations)[:-2] + config = zea.Pipeline(operations=operations).to_config() + config["parameters"] = parameters + config.to_yaml(str(CONFIG)) + + config = zea.Config.from_path(str(CONFIG)) + pipeline = zea.Pipeline.from_config(config) + params = f.load_parameters(**config.parameters) + inputs = pipeline.prepare_parameters(params) + generated = pipeline(data=raw, **inputs, return_numpy=True)["data"][0] + + # The grid starts at the imaging start depth, so pad the near field back on + # to line the result up with the stored Verasonics B-mode. These are linear + # envelope values, so the fill is plain silence. + axial_spacing = (end_depth - start_depth) / (generated.shape[0] - 1) + generated = np.pad(generated, ((round(start_depth / axial_spacing), 0), (0, 0))) + return generated + + +def blend_panorama(frames, offsets, taper=BLEND_TAPER, percentile=DISPLAY_PERCENTILE): + """Compound linear-envelope frames onto one canvas at the given column offsets. + + The panorama is normalised once here, against a high percentile rather than + the maximum: the point targets are specular and sit ~95x above the speckle, + so normalising by the peak would drop the tissue to -40 dB and render the + result nearly black. The default percentile sits on the knee between speckle + and specular, putting the tissue median near the -24.7 dB of the single-frame + reference and letting the point targets saturate, which is what a scanner + does anyway. + + Each frame is tapered at its lateral edges so the ends of the panorama, + where only one frame contributes, fade out instead of ending on a hard seam. + """ + n_depth, n_columns = frames[0].shape + offsets = np.asarray(offsets, dtype=np.float64) + offsets = offsets - offsets.min() + width = int(np.ceil(offsets.max())) + n_columns + + weight_profile = np.ones(n_columns) + n_ramp = max(1, int(taper * n_columns)) + ramp = np.hanning(2 * n_ramp)[:n_ramp] + weight_profile[:n_ramp] = ramp + weight_profile[-n_ramp:] = ramp[::-1] + + total = np.zeros((n_depth, width)) + weight = np.zeros((n_depth, width)) + for frame, offset in zip(frames, offsets): + column = int(round(offset)) + total[:, column : column + n_columns] += frame * weight_profile[None, :] + weight[:, column : column + n_columns] += weight_profile[None, :] + + covered = weight > 0 + compounded = np.divide(total, weight, out=np.zeros_like(total), where=covered) + + reference = np.percentile(compounded[covered], percentile) + panorama = 20.0 * np.log10(np.maximum(compounded / reference, 1e-12)) + panorama[~covered] = DYNAMIC_RANGE[0] + return np.clip(panorama, DYNAMIC_RANGE[0], DYNAMIC_RANGE[1]) + + +def main(): + global OUTPUT + if OUTPUT is None: + OUTPUT = Path(f"{Path(INPUT).stem}_panorama.png") + + zea.init_device() + + with zea.File(str(INPUT)) as f: + displacement = displacement_by_pose(f) + selected = select_frames(displacement, N_FRAMES) + travel = displacement.max() - displacement.min() + print(f"Sweep travel {travel * 1e3:.1f} mm; compounding {len(selected)} frames: {selected}") + + coordinates = f.data.image.coordinates[:] + frames = [beamform_frame(f, frame) for frame in selected] + + lateral_extent = float(coordinates[..., 0].max() - coordinates[..., 0].min()) + column_spacing = lateral_extent / (frames[0].shape[1] - 1) + offsets = displacement[selected] / column_spacing + + panorama = blend_panorama(frames, offsets) + print(f"Panorama: {panorama.shape} ({panorama.shape[1] * column_spacing * 1e3:.1f} mm wide)") + + # Lateral axis is the probe's own, extended by the sweep: canvas column 0 is + # the left edge of the frame taken furthest along the travel. + lateral_start = (float(coordinates[..., 0].min()) + float(displacement[selected].min())) * 1e3 + extent = [ + lateral_start, + lateral_start + panorama.shape[1] * column_spacing * 1e3, + float(coordinates[..., 2].max()) * 1e3, + 0.0, + ] + + zea.visualize.set_mpl_style() + fig, ax = plt.subplots(figsize=(16, 9)) + ax.imshow( + panorama, + aspect="equal", + cmap="gray", + extent=extent, + vmin=DYNAMIC_RANGE[0], + vmax=DYNAMIC_RANGE[1], + ) + ax.set_xlabel("Lateral [mm]") + ax.set_ylabel("Depth [mm]") + plt.tight_layout() + plt.savefig(OUTPUT, dpi=150, bbox_inches="tight") + print(f"Saved {OUTPUT}") + + +if __name__ == "__main__": + main() diff --git a/datasets/twente-cavitation/README.md b/datasets/twente-cavitation/README.md index 287a6ed..925faf2 100644 --- a/datasets/twente-cavitation/README.md +++ b/datasets/twente-cavitation/README.md @@ -1,46 +1,65 @@ --- -pretty_name: "OpenH-RF — Hermen de Roo / Passive cavitation detection" +name: twente-cavitation +pretty_name: "Twente Passive Cavitation Detection of Flowing Microbubbles" license: cc-by-4.0 task_categories: - image-classification tags: - ultrasound - - rf + - rf - openh-rf - cavitation language: - en size_categories: - - 1K (contact) ## Dataset Creation Date Data recorded on 01/19/2026. Dataset created on 07/09/2026. ## License / Terms of Use -I confirm that the data is cleared for use under CC BY 4.0. + +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage The dataset contains data over a large pressure range, from very low pressures up to the very high pressures used in therapeutic ultrasound. With this data one can quantify the treatment threshold and treatment effects over this wide range. The dataset also includes data for different levels of perfusion by varying the flowrate, from which the effect of perfusion on treatment efficacy can be studied. The data is intended to be processed with passive cavitation detection algorithms. ## Dataset Characterization - **Data Collection Method:** phantom -- **Labeling Method:** N/A +- **Labeling Method:** N/A - **Acquisition system:** Verasonics Vantage 256, L11-4v transducer. 128 elements, 7.24MHz center frequency, 27.778 MHz sampling rate +## Processing the Dataset + +The acquisitions can be processed with the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/twente-cavitation/reconstruct.py) as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF), together with the `pipeline.yaml` definition in this folder and the [zea library](https://github.com/tue-bmd/zea). The script streams the data from the Hugging Face Hub. + +Set `ZEA_FILE` at the top of the script to pick an acquisition and `N_FRAMES` to set how many frames are averaged; the map is written to `assets/.png`. + ## Dataset Format -.zea file format. No preprocessing is applied. + +[zea v0.1.6](https://github.com/tue-bmd/zea) + +[zea file format](https://zea.readthedocs.io/en/latest/data-acquisition.html). No preprocessing is applied. ## Dataset Quantification @@ -51,8 +70,7 @@ The dataset contains data over a large pressure range, from very low pressures u - **Stored HDF5 size:** 10.85 GB (10,850,533,376 bytes). - All recordings were taken under identical conditions, except for the driving pressure and flowrate of the microbubble solution through the channel. -Each acquisition is one zea HDF5 file with a single track (`tracks/track_0`). The -per-frame channel data plus the scan/probe fields needed to reconstruct it are: +Each acquisition is one zea HDF5 file with a single track (`tracks/track_0`). The per-frame channel data plus the scan/probe fields needed to reconstruct it are: | Field | Shape | dtype | Units | Description | |---|---|---|---|---| @@ -71,11 +89,9 @@ per-frame channel data plus the scan/probe fields needed to reconstruct it are: | `scan/tgc_gain_curve` | (16384,) | float32 | a.u. | Time-gain-compensation curve applied along the axial dimension. | | `tracks/track_0/transmit_only` | scalar | bool | — | False (the array receives). | -> **Note.** The table below is the **acquisition matrix** — it lists which files exist -> and under what driving pressure / flowrate, not the internal layout of a sample. +> **Note.** The table below is the **acquisition matrix** — it lists which files exist and under what driving pressure / flowrate, not the internal layout of a sample. -Files are named `cavitation_bubbles_kPa_mL.hdf5`, where -`` is the microbubble flowrate in mL/min (`01` = 0.1, `05` = 0.5, `2` = 2). +Files are named `cavitation_bubbles_kPa_mL.hdf5`, where `` is the microbubble flowrate in mL/min (`01` = 0.1, `05` = 0.5, `2` = 2). | Name | Acoustic driving pressure [kPa]| Microbubble flowrate [mL/min] | |--- |--- |--- | @@ -99,19 +115,21 @@ Files are named `cavitation_bubbles_kPa_mL.hdf5`, where | cavitation_bubbles_500kPa_2mL.hdf5 | 500 | 2 | | cavitation_bubbles_1000kPa_2mL.hdf5 | 1000 | 2 | - ## Subject Metadata Only one phantom was used. This is a phantom made of PVCp with a single flow channel ~200 micrometer diameter. The used scanner is a Verasonics Vantage 256 with a L11-4v transducer. ## Data Validation -An reconstruction pipeline can be found in pipeline.yaml. The script reconstruct.py is an example of the reconstruction of the data, using the minimum variance / Capon beamformer. An example reconstruction is saved with this dataset, and named reference_image_1000kPa_2mL_per_min.png, which was generated using the Capon beamforming algorithm using epsilon = 2, on the datafile named cavitation_bubbles_1000kPa_2mL_per_min.hdf5. By default the script saves the map next to the input file with the same name and a `.png` extension (e.g. `my_file.hdf5` → `my_file.png`); pass `--output` to override. Usage: - python reconstruct.py - python reconstruct.py --input my_file.hdf5 --device cpu - python reconstruct.py --input my_file.hdf5 --output my_map.png --frames 20 --device cuda:0 +The reconstruction pipeline is in `pipeline.yaml`. `reconstruct.py` is an example reconstruction of the data using the minimum variance (Capon) beamformer integrated in zea, with diagonal loading epsilon = 1e-2: the array only receives, so the transmit model is overridden and the chain aligns purely on receive curvature (one-way passive beamforming), averaging the envelope energy over sampling instants and frames. An example output from `cavitation_bubbles_1000kPa_01mL_per_min.hdf5` is [`assets/cavitation_bubbles_1000kPa_01mL_per_min.png`](assets/cavitation_bubbles_1000kPa_01mL_per_min.png). The script writes the map to `assets/.png`. Usage: +```bash +python reconstruct.py +``` ## Known Issues No known issues. ## Ethical Considerations + This is phantom acquisition data, hence no human-subject IRB/HIPAA approval is required. + +The contributors confirm that the data is cleared for use under CC BY 4.0. diff --git a/datasets/twente-cavitation/assets/Schematic_setup.png b/datasets/twente-cavitation/assets/Schematic_setup.png new file mode 100644 index 0000000..f494d93 Binary files /dev/null and b/datasets/twente-cavitation/assets/Schematic_setup.png differ diff --git a/datasets/twente-cavitation/assets/cavitation_bubbles_1000kPa_01mL_per_min.png b/datasets/twente-cavitation/assets/cavitation_bubbles_1000kPa_01mL_per_min.png new file mode 100644 index 0000000..ad42348 Binary files /dev/null and b/datasets/twente-cavitation/assets/cavitation_bubbles_1000kPa_01mL_per_min.png differ diff --git a/datasets/twente-cavitation/assets/hero.png b/datasets/twente-cavitation/assets/hero.png new file mode 100644 index 0000000..1a1db01 Binary files /dev/null and b/datasets/twente-cavitation/assets/hero.png differ diff --git a/datasets/twente-cavitation/assets/main.png b/datasets/twente-cavitation/assets/main.png new file mode 100644 index 0000000..aec3111 Binary files /dev/null and b/datasets/twente-cavitation/assets/main.png differ diff --git a/datasets/twente-cavitation/pipeline.yaml b/datasets/twente-cavitation/pipeline.yaml index 3cab04f..03f9175 100644 --- a/datasets/twente-cavitation/pipeline.yaml +++ b/datasets/twente-cavitation/pipeline.yaml @@ -11,12 +11,15 @@ pipeline: diagonal_loading: 0.01 - envelope_detect parameters: - grid_size_x: 387 - grid_size_z: 577 + grid_size_x: 200 + grid_size_z: 300 xlims: - - -0.019 - - 0.019 + - -0.01 + - 0.01 zlims: - - 0.002 - - 0.06 + - 0.01 + - 0.04 apply_lens_correction: true + dynamic_range: + - -20 + - 0 diff --git a/datasets/twente-cavitation/reconstruct.py b/datasets/twente-cavitation/reconstruct.py index aa41a08..34d6b58 100644 --- a/datasets/twente-cavitation/reconstruct.py +++ b/datasets/twente-cavitation/reconstruct.py @@ -7,10 +7,11 @@ This is a *passive* acquisition: the L11-4v never transmits (the file records ``tx_apodizations`` as all zeros); a separate 2.25 MHz single-element transducer -insonifies the tube with 444 us pulses and the array only receives. A standard -pulse-echo reconstruction fails twice over -- the all-zero ``tx_apodizations`` -make the transmit-delay model return inf, and the pulse-echo delays sample each -pixel before the cavitation signal has reached the array. +insonifies with long pulses and the array only receives. A standard +pulse-echo reconstruction fails twice over because there is no transmit signal +-- the all-zero ``tx_apodizations`` make the transmit-delay model return inf, +and the pulse-echo delays sample each pixel before the cavitation signal has +reached the array. Both are fixed with two parameter overrides, with no custom operations needed: ``tx_apodizations`` -> ones restores a valid delay computation, and @@ -52,19 +53,16 @@ ) HERE = Path(__file__).parent -DEFAULT_INPUT = ( - "hf://nvidia/OpenH-RF/twente-cavitation/data/cavitation_bubbles_10kPa_01mL_per_min.hdf5" -) -CONFIG = HERE / "pipeline.yaml" - -# Grid at half-wavelength sampling over the full aperture. +# 0.1 mm isotropic pixels over a 20 x 30 mm field of view around the tube. On a +# CPU, a 50 x 75 grid gives a reasonable map much faster. PARAMETERS = { - "grid_size_x": 387, - "grid_size_z": 577, - "xlims": [-0.019, 0.019], - "zlims": [0.002, 0.060], + "grid_size_x": 200, + "grid_size_z": 300, + "xlims": [-0.010, 0.010], + "zlims": [0.010, 0.040], "apply_lens_correction": True, + "dynamic_range": [-20, 0], } # Sampling instants t_c within the 444 us insonification window. Each run of @@ -79,10 +77,13 @@ # --- Inputs ----------------------------------------------------------------- # Defaults stream straight from the published corpus. Swap any of these for a # local path to run against your own copy. -INPUT = "hf://nvidia/OpenH-RF/twente-cavitation/data/cavitation_bubbles_10kPa_01mL_per_min.hdf5" -OUTPUT = None # Output PNG path (default: input file name with a .png extension) -FRAMES = 20 # Number of frames to average -DEVICE = "auto:1" # Device to use (e.g. 'cpu', 'cuda:0', or 'auto:1') +ZEA_FILE = ( + "hf://nvidia/OpenH-RF/twente-cavitation/data/cavitation_bubbles_1000kPa_01mL_per_min.hdf5" +) +CONFIG = HERE / "pipeline.yaml" # written by write_config(); this is what the run loads +N_FRAMES = 10 # number of frames to average +OUT = HERE / "assets" / f"{Path(ZEA_FILE).stem}.png" +HF_CONFIG = "hf://nvidia/OpenH-RF/twente-cavitation/pipeline.yaml" # where CONFIG is published def build_pipeline() -> Pipeline: @@ -100,25 +101,25 @@ def build_pipeline() -> Pipeline: ) -def main(): - global OUTPUT - if OUTPUT is None: - OUTPUT = Path(Path(INPUT).stem + ".png") +def write_config(pipeline: Pipeline, path: Path) -> None: + """Serialize the pipeline and acquisition parameters to a YAML config file.""" + config = pipeline.to_config() + config["parameters"] = PARAMETERS + config.to_yaml(str(path)) - zea.init_device(device=DEVICE, verbose=False) - # Build the pipeline in code and save it (with parameters) to pipeline.yaml, +def main(): + zea.init_device() + + # Build the pipeline in code, save it (with parameters) to pipeline.yaml, # then load that YAML back in so the shipped YAML is exactly what runs. - pipeline = build_pipeline() - config = pipeline.to_config() - config["parameters"] = PARAMETERS - config.to_yaml(str(CONFIG)) + write_config(build_pipeline(), CONFIG) config = Config.from_path(str(CONFIG)) pipeline = Pipeline.from_config(config) - with File(str(INPUT)) as f: + with File(ZEA_FILE) as f: parameters = f.load_parameters(**config.parameters) - raw = f.data.raw_data[:FRAMES] # (n_frames, n_tx, n_ax, n_el, 1) + raw = f.data.raw_data[:N_FRAMES] # (n_frames, n_tx, n_ax, n_el, 1) # Passive-acquisition overrides (see module docstring). parameters["tx_apodizations"] = np.ones_like(np.asarray(parameters["tx_apodizations"])) @@ -138,17 +139,22 @@ def main(): pam_db = keras.ops.convert_to_numpy(post(data=amplitude)["data"]) zea.visualize.set_mpl_style() - extent_mm = [v * 1e3 for v in parameters.extent_imshow] + vmin, vmax = parameters.dynamic_range fig, ax = plt.subplots(figsize=(6, 8)) - im = ax.imshow(np.clip(pam_db, -25, 0), extent=extent_mm, cmap="inferno") + im = ax.imshow( + np.clip(pam_db, vmin, vmax), + extent=parameters.extent_imshow * 1e3, + cmap="inferno", + ) ax.set_xlabel("X (mm)") ax.set_ylabel("Z (mm)") cax = make_axes_locatable(ax).append_axes("right", size="5%", pad=0.05) fig.colorbar(im, cax=cax, label="dB") - plt.savefig(str(OUTPUT), bbox_inches="tight", dpi=100) + OUT.parent.mkdir(parents=True, exist_ok=True) + plt.savefig(str(OUT), bbox_inches="tight", dpi=100) plt.close() - print(f"Saved : {OUTPUT}") + print(f"raw {raw.shape} -> PAM {pam_db.shape}; saved {OUT}") if __name__ == "__main__": diff --git a/datasets/twente-microbubblesim/README.md b/datasets/twente-microbubblesim/README.md index 2df7fe0..899fa4e 100644 --- a/datasets/twente-microbubblesim/README.md +++ b/datasets/twente-microbubblesim/README.md @@ -1,5 +1,6 @@ --- -pretty_name: "OpenH-RF — Waveform-Specific Synthetic Microbubble RF Dataset" +name: twente-microbubblesim +pretty_name: "Waveform-Specific Synthetic Microbubble RF Dataset" license: cc-by-4.0 task_categories: - image-to-image @@ -15,26 +16,22 @@ size_categories: - "n<1K" --- -# OpenH-RF synthetic ultrasound data +# Waveform-Specific Synthetic Microbubble RF Dataset ## Dataset Description -This synthetic 3-D ultrasound dataset contains nonlinear radiofrequency (RF) -responses from microbubble contrast agents for cardiovascular-flow imaging. It -was generated to study the effect of ultrasound transmit-waveform shape on RF -signals and deep-learning methods for microbubble super-resolution. It contains -simulated data, not clinical, phantom, or in-vivo animal data. +This synthetic 3-D ultrasound dataset contains nonlinear radiofrequency (RF) responses from microbubble contrast agents for cardiovascular-flow imaging. It was generated to study the effect of ultrasound transmit-waveform shape on RF signals and deep-learning methods for microbubble super-resolution. It contains exclusively simulated data. -## Dataset Contributors +![alt text](./assets/pulses_waveform.png) +The 12 different driving-pulse waveforms used in this simulated dataset. -| Contributor | Affiliation / role | Email | -|---|---|---| -| Rienk Zorgdrager | PhD candidate, University of Twente | `r.c.zorgdrager@utwente.nl` | -| Anass Hameddine | PhD candidate, University of Twente | `a.hameddine@utwente.nl` | -| Michel Versluis | Professor, physical and medical acoustics, University of Twente | `m.versluis@utwente.nl` | -| Guillaume Lajoinie | Associate Professor, Physics of Fluids Group, University of Twente | `g.p.r.lajoinie@utwente.nl` | -**Primary contacts:** Anass Hameddine and Rienk Zorgdrager. +## Dataset Contributor(s) + +- Rienk Zorgdrager (PhD candidate, University of Twente; primary contact) +- Anass Hameddine (PhD candidate, University of Twente; primary contact) +- Michel Versluis (Professor, physical and medical acoustics, University of Twente) +- Guillaume Lajoinie (Associate Professor, Physics of Fluids Group, University of Twente) ## Dataset Creation Date @@ -42,82 +39,59 @@ simulated data, not clinical, phantom, or in-vivo animal data. ## License / Terms of Use -This dataset is released under the Creative Commons Attribution 4.0 -International license (CC BY 4.0). See [LICENCE](LICENCE). The contributors -confirm that the simulated RF outputs, bubble ground truth, pulse waveforms, -calibrated P4-1 inputs, and incorporated assets used to produce this dataset -are cleared for public redistribution under CC BY 4.0, consistent with the -accepted proposal and the steering-group IP policy. +[Creative Commons Attribution 4.0 International (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/legalcode.en). Retain attribution and identify modifications when reusing the data. ## Intended Usage -The dataset is intended for studying the effect of transmit waveforms on -deep-learning methods for microbubble super-resolution imaging. +The dataset is intended for studying the effect of transmit waveforms on deep-learning methods for microbubble super-resolution imaging. ## Dataset Characterization - **Data collection method:** Synthetic. -- **Ground truth:** Simulated 3-D microbubble positions are stored as zea custom - elements (`bubble_x`, `bubble_y`, and `bubble_z`). -- **Bubble populations:** Monodisperse bubbles with a radius of 2.4 micrometres - and 5% standard deviation, and a polydisperse SonoVue population. -- **Acquisition system:** An experimentally calibrated virtual ATL P4-1 - transducer with 96 elements, realistic 3-D plane-wave pressure fields, a - 62.5 MHz RF sampling rate, and 12 transmit waveforms. The overall probe - dimensions are 0.02854 m × 0.016 m; each element is 0.000245 m wide and - 0.016 m high. Its official nominal operating band is 1.0e6–4.0e6 Hz. -- **Simulation scale:** Up to 27.5 microbubbles/cm³, with 250 random bubble - distributions per population. -- **Bubble-response model:** The implementation is a modified private derivative - of the simulator described in Zorgdrager et al. (2025). It uses a custom, - parallelized radial microbubble-response solver based on the Rayleigh–Plesset - equation and adds 3-D realistic pressure-field calculation and coupling for - the calibrated virtual ATL P4-1. The exact source-code version was not - separately recorded; the dataset was generated with the internal label - **private dataset-generation release 2026-07-12**. The solver source is - private and is not distributed with this dataset. +- **Ground truth:** Simulated 3-D microbubble positions are stored as zea custom elements (`bubble_x`, `bubble_y`, and `bubble_z`). +- **Bubble populations:** Monodisperse bubbles with a radius of 2.4 micrometres and 5% standard deviation, and a polydisperse SonoVue population. +- **Acquisition system:** An experimentally calibrated virtual ATL P4-1 transducer with 96 elements, realistic 3-D plane-wave pressure fields, a 62.5 MHz RF sampling rate, and 12 transmit waveforms. The overall probe dimensions are 0.02854 m × 0.016 m; each element is 0.000245 m wide and 0.016 m high. Its official nominal operating band is 1.0e6–4.0e6 Hz. +- **Simulation scale:** Up to 27.5 microbubbles/cm³, with 250 random bubble distributions per population. +- **Bubble-response model:** The implementation is a modified private derivative of the simulator described in Zorgdrager et al. (2025). It uses a custom, parallelized radial microbubble-response solver based on the Rayleigh–Plesset equation and adds 3-D realistic pressure-field calculation and coupling for the calibrated virtual ATL P4-1. The exact source-code version was not separately recorded; the dataset was generated with the internal label **private dataset-generation release 2026-07-12**. The solver source is private and is not distributed with this dataset. + +## Processing the Dataset + +The acquisitions can be processed with the `reconstruct.py` [script](https://github.com/open-h/OpenH-RF/blob/main/datasets/twente-microbubblesim/reconstruct.py) as provided in the [OpenH-RF GitHub repository](https://github.com/open-h/OpenH-RF), together with the pipeline definitions in `pipeline/` and the [zea library](https://github.com/tue-bmd/zea). The script streams the data from the Hugging Face Hub. + +Every pulse track has its own verified pipeline file, `pipeline/pipeline_track__