-
Notifications
You must be signed in to change notification settings - Fork 2
add imaris metadata strategy #71
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -52,6 +52,17 @@ def clean_keys(obj): | |||||||||||||||||||
| return obj | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| def h5_attr_to_str(attr): | ||||||||||||||||||||
| """Convert an HDF5 byte-array attribute to a Python string. | ||||||||||||||||||||
|
|
||||||||||||||||||||
| Imaris stores attributes as arrays of single-byte values, e.g. | ||||||||||||||||||||
| [b'4', b'.', b'0'] → '4.0' | ||||||||||||||||||||
| """ | ||||||||||||||||||||
| if attr is None: | ||||||||||||||||||||
| return None | ||||||||||||||||||||
| return "".join(b.decode("utf-8") for b in attr) | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| def build_bids_metadata(custom_attrs): | ||||||||||||||||||||
| """Convert custom attribute dict into BIDS microscopy-compliant metadata.""" | ||||||||||||||||||||
| # --- PixelSize and Units --- | ||||||||||||||||||||
|
|
@@ -109,6 +120,89 @@ def build_bids_metadata(custom_attrs): | |||||||||||||||||||
| return bids_json | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| def build_bids_metadata_from_native_imaris(hdf5_file): | ||||||||||||||||||||
| """Build BIDS metadata directly from native Imaris HDF5 attributes. | ||||||||||||||||||||
|
|
||||||||||||||||||||
| Fallback for .ims files that lack embedded OME XML tags. | ||||||||||||||||||||
| Computes voxel sizes from image extents: pixel_size = (ExtMax - ExtMin) / N_voxels | ||||||||||||||||||||
| """ | ||||||||||||||||||||
| img = hdf5_file["DataSetInfo/Image"] | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # --- Image dimensions --- | ||||||||||||||||||||
| nx = int(h5_attr_to_str(img.attrs["X"])) | ||||||||||||||||||||
| ny = int(h5_attr_to_str(img.attrs["Y"])) | ||||||||||||||||||||
| nz = int(h5_attr_to_str(img.attrs["Z"])) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # --- Physical extents (in file units, typically µm) --- | ||||||||||||||||||||
| ext_min_x = float(h5_attr_to_str(img.attrs["ExtMin0"])) | ||||||||||||||||||||
| ext_max_x = float(h5_attr_to_str(img.attrs["ExtMax0"])) | ||||||||||||||||||||
| ext_min_y = float(h5_attr_to_str(img.attrs["ExtMin1"])) | ||||||||||||||||||||
| ext_max_y = float(h5_attr_to_str(img.attrs["ExtMax1"])) | ||||||||||||||||||||
| ext_min_z = float(h5_attr_to_str(img.attrs["ExtMin2"])) | ||||||||||||||||||||
| ext_max_z = float(h5_attr_to_str(img.attrs["ExtMax2"])) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # --- Compute voxel sizes --- | ||||||||||||||||||||
| px_x = abs(ext_max_x - ext_min_x) / nx | ||||||||||||||||||||
| px_y = abs(ext_max_y - ext_min_y) / ny | ||||||||||||||||||||
| px_z = abs(ext_max_z - ext_min_z) / nz | ||||||||||||||||||||
| pixel_size = [px_x, px_y, px_z] | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # --- Units --- | ||||||||||||||||||||
| unit_raw = h5_attr_to_str(img.attrs.get("Unit", None)) | ||||||||||||||||||||
| if unit_raw is None: | ||||||||||||||||||||
| unit_label = "um" | ||||||||||||||||||||
| else: | ||||||||||||||||||||
| unit_label = unit_raw.replace("µ", "u") if "µ" in unit_raw else unit_raw | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # --- Channel metadata --- | ||||||||||||||||||||
| extra_channels = {} | ||||||||||||||||||||
| ch_idx = 0 | ||||||||||||||||||||
| while f"DataSetInfo/Channel {ch_idx}" in hdf5_file: | ||||||||||||||||||||
| ch_grp = hdf5_file[f"DataSetInfo/Channel {ch_idx}"] | ||||||||||||||||||||
| ch_info = {} | ||||||||||||||||||||
| for attr_name in ["Name", "LSMExcitationWavelength", "LSMEmissionWavelength", | ||||||||||||||||||||
| "Color", "Description"]: | ||||||||||||||||||||
|
||||||||||||||||||||
| for attr_name in ["Name", "LSMExcitationWavelength", "LSMEmissionWavelength", | |
| "Color", "Description"]: | |
| for attr_name in [ | |
| "Name", | |
| "LSMExcitationWavelength", | |
| "LSMEmissionWavelength", | |
| "Color", | |
| "Description", | |
| ]: |
Copilot
AI
Mar 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strategy 1 only catches KeyError, TypeError, ValueError, but reading the embedded OME dataset (hdf5_file[...][:]) can also raise OSError (e.g., dataset exists but can’t be read). If you want to continue on to strategies 2/3 in that case, consider adding OSError to this except list.
Copilot
AI
Mar 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strategy 2’s tif fallback only catches IndexError, KeyError, TypeError, ValueError, but opening/reading a found .ome.tif can also raise OSError/FileNotFoundError (e.g., corrupt file, permissions). In that case the script will crash instead of continuing to the native Imaris HDF5 fallback. Consider including OSError (and/or FileNotFoundError) in this except clause so Strategy 3 can still run when the tif path exists but can’t be read.
| except (IndexError, KeyError, TypeError, ValueError) as e: | |
| except (IndexError, KeyError, TypeError, ValueError, OSError) as e: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
h5_attr_to_strassumes the attribute is an iterable of 1-byte values (each with.decode()), but h5py attributes are often returned as a scalarbytes/str, a NumPy scalar (e.g.np.bytes_), or a NumPy array ofuint8. In those cases this will raise (e.g. iterating abytesyields ints). Consider making this helper handle:str(return as-is),bytes/np.bytes_(single decode), NumPyndarray(decode bytes dtype or convertuint8via.tobytes()), and strip any trailing\x00padding if present.