Skip to content

TIFFWriter should write resolution when given ImageData #2364

Description

@JeppeKlitgaard

Description

I think it would be very helpful if TIFFWriter.write() added the correct metadata to the TIFF Files for at least the X and Y resolutions, which I observe to be present in other datasets (https://www.morphosource.org/concern/media/000905028?locale=en as an example).

This data is available in ImageData.geometry (an ImageGeometry instance), so could be used without cost.

Additionally it would be nice to optionally add a JSON or similar sidecar file to the output with the full ImageGeometry, i.e. writer.write(sidecar="json") would dump a JSON representation of the ImageGeometry next to the TIFF stack.

Aside

As an aside: from what I can tell the NikonDataReader doesn't read the distance units (or at least my AcquisitionGeometry ends up reporting distance_units="distance", though I think maybe distance_units is not suppose to be a unit, but rather a quantity (length, pixel, angle))

In any case, it would be neat if the unit data from NikonDataReader could be propagated throughout a reconstruction pipeline without manually keeping track of it.

Current workaround

I currently create a dataclass:

@dataclass(frozen=True, slots=True)
class ReconstructionGeometry:
    voxel_num_x: int
    voxel_num_y: int
    voxel_num_z: int
    voxel_size_x_m: float
    voxel_size_y_m: float
    voxel_size_z_m: float
    center_x_m: float
    center_y_m: float
    center_z_m: float

    @classmethod
    def from_cil_image_geometry(
        cls, ig: ImageGeometry, physical_unit_scale_to_m: float = 1.0
    ) -> Self:
        return cls(
            voxel_num_x=ig.voxel_num_x,
            voxel_num_y=ig.voxel_num_y,
            voxel_num_z=ig.voxel_num_z,
            voxel_size_x_m=ig.voxel_size_x * physical_unit_scale_to_m,
            voxel_size_y_m=ig.voxel_size_y * physical_unit_scale_to_m,
            voxel_size_z_m=ig.voxel_size_z * physical_unit_scale_to_m,
            center_x_m=ig.center_x * physical_unit_scale_to_m,
            center_y_m=ig.center_y * physical_unit_scale_to_m,
            center_z_m=ig.center_z * physical_unit_scale_to_m,
        )

    def save_as_json(self, path: str | Path) -> None:
        path = Path(path)

        with path.open("w") as f:
            json.dump(asdict(self), f, indent=4)

Which I then construct with physical_unit_scale_to_m=1e-3 since my Nikon data is in millimeters. I then save the sidecar after doing writer.write().

Environment

import cil, sys
print(cil.version.version, cil.version.commit_hash, sys.version, sys.platform)
> 26.0.0  3.13.15 | packaged by conda-forge | (main, Aug 10 2026, 13:05:01) [GCC 14.4.0] linux

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions