From fe4e11e9a7059be68c94cbf555fba4ec3e74188a Mon Sep 17 00:00:00 2001 From: Zeeshan Modi <92383127+Zeesejo@users.noreply.github.com> Date: Fri, 10 Apr 2026 09:27:38 +0200 Subject: [PATCH] fix(ZarrAvgMerger): use separate tmpdir attrs to prevent value_store tmpdir from being garbage collected MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, when both `value_store` and `count_store` were None, `self.tmpdir` was assigned twice sequentially — causing the first TemporaryDirectory (for value_store) to be immediately garbage collected when the second one (for count_store) overwrote the reference. This silently deleted the value store's backing directory. Fix: introduce `self.value_tmpdir` and `self.count_tmpdir` as separate attributes so both temporary directories remain alive for the lifetime of the ZarrAvgMerger instance. Fixes #8476 --- monai/inferers/merger.py | 53 ++++++++++++---------------------------- 1 file changed, 16 insertions(+), 37 deletions(-) diff --git a/monai/inferers/merger.py b/monai/inferers/merger.py index 31e9b5d632..f511f8b03e 100644 --- a/monai/inferers/merger.py +++ b/monai/inferers/merger.py @@ -286,32 +286,33 @@ def __init__( self.value_dtype = value_dtype self.count_dtype = count_dtype self.store = store - self.tmpdir: TemporaryDirectory | None + # Use separate tmpdir attributes so both TemporaryDirectory objects remain alive + # for the full lifetime of this instance. Previously a single `self.tmpdir` was + # overwritten when both value_store and count_store were None, causing the first + # TemporaryDirectory to be garbage-collected immediately. + self.value_tmpdir: TemporaryDirectory | None = None + self.count_tmpdir: TemporaryDirectory | None = None # Handle zarr v3 vs older versions is_zarr_v3 = version_geq(get_package_version("zarr"), "3.0.0") if is_zarr_v3: if value_store is None: - self.tmpdir = TemporaryDirectory() - self.value_store = zarr.storage.LocalStore(self.tmpdir.name) # type: ignore + self.value_tmpdir = TemporaryDirectory() + self.value_store = zarr.storage.LocalStore(self.value_tmpdir.name) # type: ignore else: self.value_store = value_store # type: ignore if count_store is None: - self.tmpdir = TemporaryDirectory() - self.count_store = zarr.storage.LocalStore(self.tmpdir.name) # type: ignore + self.count_tmpdir = TemporaryDirectory() + self.count_store = zarr.storage.LocalStore(self.count_tmpdir.name) # type: ignore else: self.count_store = count_store # type: ignore else: - self.tmpdir = None self.value_store = zarr.storage.TempStore() if value_store is None else value_store # type: ignore self.count_store = zarr.storage.TempStore() if count_store is None else count_store # type: ignore self.chunks = chunks - # Handle compressor/codecs based on zarr version - is_zarr_v3 = version_geq(get_package_version("zarr"), "3.0.0") - # Initialize codecs/compressor attributes with proper types self.codecs: list | None = None self.value_codecs: list | None = None @@ -322,50 +323,28 @@ def __init__( if codecs is not None: self.codecs = codecs elif compressor is not None: - # Convert compressor to codec format - if isinstance(compressor, (list, tuple)): - self.codecs = compressor - else: - self.codecs = [compressor] + self.codecs = compressor if isinstance(compressor, (list, tuple)) else [compressor] else: self.codecs = None if value_codecs is not None: self.value_codecs = value_codecs elif value_compressor is not None: - if isinstance(value_compressor, (list, tuple)): - self.value_codecs = value_compressor - else: - self.value_codecs = [value_compressor] + self.value_codecs = value_compressor if isinstance(value_compressor, (list, tuple)) else [value_compressor] else: self.value_codecs = None if count_codecs is not None: self.count_codecs = count_codecs elif count_compressor is not None: - if isinstance(count_compressor, (list, tuple)): - self.count_codecs = count_compressor - else: - self.count_codecs = [count_compressor] + self.count_codecs = count_compressor if isinstance(count_compressor, (list, tuple)) else [count_compressor] else: self.count_codecs = None else: # For zarr v2, use compressors - if codecs is not None: - # If codecs are specified in v2, use the first codec as compressor - self.codecs = codecs[0] if isinstance(codecs, (list, tuple)) else codecs - else: - self.codecs = compressor # type: ignore[assignment] - - if value_codecs is not None: - self.value_codecs = value_codecs[0] if isinstance(value_codecs, (list, tuple)) else value_codecs - else: - self.value_codecs = value_compressor # type: ignore[assignment] - - if count_codecs is not None: - self.count_codecs = count_codecs[0] if isinstance(count_codecs, (list, tuple)) else count_codecs - else: - self.count_codecs = count_compressor # type: ignore[assignment] + self.codecs = codecs[0] if isinstance(codecs, (list, tuple)) else codecs if codecs is not None else compressor # type: ignore[assignment] + self.value_codecs = value_codecs[0] if isinstance(value_codecs, (list, tuple)) else value_codecs if value_codecs is not None else value_compressor # type: ignore[assignment] + self.count_codecs = count_codecs[0] if isinstance(count_codecs, (list, tuple)) else count_codecs if count_codecs is not None else count_compressor # type: ignore[assignment] # Create zarr arrays with appropriate parameters based on version if is_zarr_v3: