Source code for ewokstomo.tasks.convert_volume
import logging
import shutil
from pathlib import Path
import numpy as np
from ewokscore import Task
from ewokscore.model import BaseInputModel, BaseOutputModel
from nabu.io.cast_volume import cast_volume
from nabu.io.utils import get_first_hdf5_entry
from pydantic import Field
from tomoscan.esrf.volume import EDFVolume, HDF5Volume, JP2KVolume, MultiTIFFVolume
from typing import Literal
volume_formats = {
"hdf5": HDF5Volume,
"h5": HDF5Volume,
"tiff": MultiTIFFVolume,
"edf": EDFVolume,
"jp2": JP2KVolume,
}
logger = logging.getLogger(__name__)
[docs]
class ConvertVolumeTo16BitOutputsModel(BaseOutputModel):
converted_volume_path: str = Field(
..., description="The file path of the converted 16-bit volume."
)
[docs]
class ConvertVolumeTo16Bit( # type: ignore[call-arg]
Task,
input_model=ConvertVolumeTo16BitInputsModel,
output_model=ConvertVolumeTo16BitOutputsModel,
):
"""
Task to convert volume to 16-bit format and save it to disk.
The output file format is determined by the Nabu configuration dictionary provided as input.
The task also removes the original 32-bit volume after conversion to save disk space.
"""
[docs]
def run(self):
logger.info("Starting volume conversion to 16-bit.")
input_volume = self.get_input_value("reconstructed_volume_path")
logger.info(f"Input volume path: {input_volume}")
nabu_dict = self.get_input_value("nabu_dict")
# Determine input and output file formats
input_format_str = nabu_dict.get("output", {}).get("file_format", "hdf5")
output_format_str = self.get_input_value("output_format")
# Construct output file path
input_path = Path(input_volume)
output_filename = input_path.stem
if output_format_str == "hdf5":
output_filename = output_filename + ".hdf5"
elif output_format_str == "tiff":
output_filename = output_filename + ".tiff"
input_parent_str = str(input_path.parent)
if "32Bit" in input_parent_str:
output_volume = input_parent_str.replace("32Bit", "16Bit").replace(
input_format_str, output_format_str
)
else:
logger.warning(
f"'32Bit' not found in input path '{input_parent_str}'. "
"Falling back to a '16Bit' subdirectory next to the input."
)
output_volume = (
input_parent_str.replace(input_format_str, output_format_str) + "_16Bit"
)
output_volume = Path(output_volume) / output_filename
output_volume.parent.mkdir(parents=True, exist_ok=True)
self.outputs.converted_volume_path = str(output_volume)
# Create volume objects for input and output formats
input_volume_class = volume_formats.get(input_format_str)
input_vol_obj = self._make_input_volume(
file_format=input_volume_class, file_path=input_path
)
output_volume_class = volume_formats.get(output_format_str)
output_vol_obj = self._make_output_volume(
file_format=output_volume_class, file_path=output_volume
)
cast_volume(
input_volume=input_vol_obj,
output_volume=output_vol_obj,
output_data_type=np.uint16,
remove_input_volume=True,
)
if input_format_str == "hdf5" and Path(output_vol_obj.url.file_path()).exists():
if "32Bit" in str(input_path.parent) and not input_path.exists():
shutil.rmtree(input_path.parent)
else:
logger.warning(
f"Skipping removal of '{input_path.parent}' because it does not contain '32Bit' in its path."
)
logger.info("Volume conversion completed.")
def _make_input_volume(self, file_format, file_path):
if file_format == HDF5Volume:
entry = get_first_hdf5_entry(str(file_path))
entry_path = f"{entry}/reconstruction"
return HDF5Volume(file_path=file_path, data_path=entry_path)
elif file_format == MultiTIFFVolume:
return MultiTIFFVolume(file_path=file_path)
else:
constructor = file_format
volume = constructor(
folder=str(file_path.parent), volume_basename=file_path.name
)
return volume
def _make_output_volume(self, file_format, file_path):
if file_format == HDF5Volume:
return HDF5Volume(file_path=file_path, data_path="/volume")
elif file_format == MultiTIFFVolume:
return MultiTIFFVolume(file_path=file_path)
else:
constructor = file_format
volume = constructor(
folder=str(file_path.parent), volume_basename=file_path.name
)
return volume