Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -299,3 +299,4 @@ test.xml
/docs/source/05_reference/_autosummary
/docs/source/05_reference/_autosummary
codex.md
AGENTS.MD
11 changes: 7 additions & 4 deletions src/navigate/model/data_sources/bdv_data_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,11 @@ def __init__(self, file_name: str = None, mode: str = "w") -> None:
Parameters
----------
file_name : str
The name of the file to write to.
mode : str
The mode to open the file in. Must be "w" for write or "r" for read.
Path to the output. For BDV/HDF5 use a ".h5" file; for BDV/N5 use a
".n5" file; for TIFF-based filelist export use a directory containing
".tif" or ".tiff" files.
mode : {'w', 'r'}
Mode to open the file in. Must be 'w' for write (export) or 'r' for read.
"""
#: np.array: The image.
self.image = None
Expand All @@ -86,7 +88,8 @@ def __init__(self, file_name: str = None, mode: str = "w") -> None:
#: str: The file type.
self.__file_type = os.path.splitext(os.path.basename(file_name))[-1][1:].lower()

if self.__file_type not in ["h5", "n5"]:
# Allow HDF5, N5, and TIFF (filelist) outputs for BigDataViewer metadata
if self.__file_type not in ["h5", "n5", "tif", "tiff"]:
error_statement = f"Unknown file type {self.__file_type}."
logger.error(error_statement)
raise ValueError(error_statement)
Expand Down
76 changes: 66 additions & 10 deletions src/navigate/model/data_sources/tiff_data_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import uuid
from pathlib import Path
import logging
from typing import Dict, Any

# Third Party Imports
import tifffile
Expand All @@ -45,6 +46,7 @@
from .data_source import DataSource, DataReader
from ..metadata_sources.metadata import Metadata
from ..metadata_sources.ome_tiff_metadata import OMETIFFMetadata
from ..metadata_sources.bdv_metadata import BigDataViewerMetadata


# Logger Setup
Expand Down Expand Up @@ -74,26 +76,36 @@ def __init__(
"""
#: np.ndarray: Image data
self.image = None

#: list: List of positions on a per-slice basis.
self._views = []

super().__init__(file_name=file_name, mode=mode)

#: str: Directory to save the data to.
self.save_directory = Path(self.file_name).parent

# Is this an OME-TIFF?
# TODO: check the header, rather than use the file extension
# Check if the file is OME-TIFF and create the appropriate metadata object
if self.file_name.endswith(".ome.tiff") or self.file_name.endswith(".ome.tif"):
#: bool: Is this an OME-TIFF file?
self._is_ome = True

#: Metadata: Metadata object
self.metadata = OMETIFFMetadata()
else:
#: bool: Is this an OME-TIFF file?
self._is_ome = False

# Metadata: Metadata object
self.metadata = Metadata()

#: BigDataViewerMetadata: Metadata for BigDataViewer
self.bdv_metadata = BigDataViewerMetadata()

#: bool: Is this a bigtiff file?
self._is_bigtiff = is_bigtiff

# For file writing, do we assume all files end with tiff or tif?
#: bool: For file writing, do we assume all files end with tiff or tif?
self.__double_f = self.file_name.endswith("tiff")

# Keep track of z, time, channel indices
Expand Down Expand Up @@ -170,7 +182,7 @@ def get_data(
channel: int = 0,
z: int = -1,
resolution: int = 1,
) -> npt.ArrayLike:
) -> npt.ArrayLike or None:
"""Get data according to timepoint, position, channel and z-axis id

Parameters
Expand Down Expand Up @@ -226,13 +238,18 @@ def write(self, data: npt.ArrayLike, **kw) -> None:
data : npt.ArrayLike
Data to write to file.
kw : dict
Keyword arguments to pass to tifffile.imsave.
Keyword arguments to pass to tifffile.imsave. Includes stage coordinates
in format {'x': 11259.4, 'y': 11759.4, 'z': 68.0, 'theta': 0.0, 'f': 100.0}
"""
self.mode = "w"

# Get the current frame and position
c, z, self._current_time, self._current_position = self._cztp_indices(
self._current_frame, self.metadata.per_stack
) # find current channel
)

# If it is the first frame of the stack, create a new image file.
ome_xml = None
if z == 0:
if c == 0:
# Make sure we're set up for writing
Expand All @@ -241,10 +258,10 @@ def write(self, data: npt.ArrayLike, **kw) -> None:
ome_xml = self.metadata.to_xml(
c=c, t=self._current_time, file_name=self.file_name, uid=self.uid
).encode()
else:
ome_xml = None

if len(kw) > 0:
# On a per-stack basis, we store the stage coordinates.
# Resets for each stack.
self._views.append(kw)

if self.is_ome:
Expand All @@ -269,8 +286,7 @@ def write(self, data: npt.ArrayLike, **kw) -> None:
self._current_frame += 1

# Check if this was the last frame to write
# print("Switch")
c, z, _, _ = self._cztp_indices(self._current_frame, self.metadata.per_stack)
c, z, t, p = self._cztp_indices(self._current_frame, self.metadata.per_stack)
if (z == 0) and (c == 0):
self.close(True)

Expand Down Expand Up @@ -369,9 +385,49 @@ def close(self, internal=False) -> None:
)
else:
self.image.close()

if not internal:
self._closed = True

# Write the metadata to XML
if len(self._views) > 0:
self.bdv_metadata.write_xml(
os.path.join(self.save_directory, "dataset.xml"), self._views
)

def set_metadata_from_configuration_experiment(
self, configuration: Dict[str, Any], microscope_name: str = None
) -> None:
"""Sets the metadata from according to the microscope configuration.

Child method also provides information to the BigDataViewerMetadata.

Parameters
----------
configuration : Dict[str, Any]
Configuration experiment.
microscope_name : str
The microscope name
"""
self.metadata.active_microscope = microscope_name
self.metadata.configuration = configuration
self.get_shape_from_metadata()

self.bdv_metadata.active_microscope = microscope_name
self.bdv_metadata.configuration = configuration

def set_metadata(self, metadata_config: dict) -> None:
"""Sets the metadata

Parameters
----------
metadata_config : dict
shape configuration: "c", "z", "t", "p", "is_dynamic", "per_stack"
"""
self.metadata.set_from_dict(metadata_config)
self.bdv_metadata.set_from_dict(metadata_config)
self.get_shape_from_metadata()


class TiffReader(DataReader):
def __init__(self, tiff_file: tifffile.TiffFile):
Expand Down
18 changes: 10 additions & 8 deletions src/navigate/model/features/image_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ def __init__(
# camera flip flags
if self.microscope_name is None:
self.microscope_name = self.model.active_microscope_name

camera_config = self.model.configuration["configuration"]["microscopes"][
self.microscope_name
]["camera"]
Expand All @@ -151,13 +152,14 @@ def __init__(
self.disk_space_check_interval = 60

#: int: Minimum disk space required in bytes.
self.min_disk_space = 10 * 1024 * 1024 * 1024 # 10 GB
self.min_disk_space = 10 * 1024 * 1024 * 1024 # 10 GB

#: float: Time of last disk space check
self.last_disk_space_check = 0

#: bool: Flag to indicate if initialized before
self.initialized = False

# initialize saving
self.initialize_saving(sub_dir, image_name)

Expand All @@ -177,7 +179,10 @@ def save_image(self, frame_ids):
continue

# Check disk space at regular intervals to prevent running out of space
if time.time() - self.last_disk_space_check > self.disk_space_check_interval:
if (
time.time() - self.last_disk_space_check
> self.disk_space_check_interval
):
_, _, free = shutil.disk_usage(self.save_directory)
logger.info(f"Free Disk Space: {free / 1024 / 1024 / 1024} GB")
if free < self.min_disk_space:
Expand Down Expand Up @@ -349,9 +354,7 @@ def get_saving_file_name(self, sub_dir="", image_name=None):
os.makedirs(self.save_directory)
logger.debug(f"Save Directory Created - {self.save_directory}")
except (PermissionError, OSError, FileNotFoundError):
logger.debug(
f"Unable to Create Save Directory - {self.save_directory}"
)
logger.debug(f"Unable to Create Save Directory - {self.save_directory}")
self.model.stop_acquisition = True
self.model.event_queue.put(
"warning",
Expand All @@ -378,6 +381,7 @@ def get_saving_file_name(self, sub_dir="", image_name=None):

def initialize_saving(self, sub_dir="", image_name=None):

# Check if previously initialized data source exists and close it
if self.data_source is not None:
self.data_source.close()
self.data_source = None
Expand All @@ -393,9 +397,7 @@ def initialize_saving(self, sub_dir="", image_name=None):
os.makedirs(self.mip_directory)
logger.debug(f"MIP Directory Created - {self.mip_directory}")
except (PermissionError, OSError, FileNotFoundError):
logger.debug(
f"Unable to Create MIP Directory - {self.mip_directory}"
)
logger.debug(f"Unable to Create MIP Directory - {self.mip_directory}")
self.model.stop_acquisition = True
self.model.event_queue.put(
"warning",
Expand Down
Loading