11"""Tests for reading whole-slide images."""
22
3+ import copy
34import json
5+ import logging
46import os
57import pathlib
68import random
79import re
810import shutil
911from copy import deepcopy
12+ from pathlib import Path
1013from time import time
1114
1215# When no longer supporting Python <3.9 this should be collections.abc.Iterable
1720import pytest
1821import zarr
1922from click .testing import CliRunner
23+ from packaging .version import Version
2024from skimage .filters import threshold_otsu
2125from skimage .metrics import peak_signal_noise_ratio , structural_similarity
2226from skimage .morphology import binary_dilation , disk , remove_small_objects
@@ -2034,11 +2038,11 @@ def test_ngff_empty_datasets_mpp(tmp_path):
20342038 assert wsi .info .mpp is None
20352039
20362040
2037- def test_nff_no_scale_transforms_mpp (tmp_path ):
2041+ def test_ngff_no_scale_transforms_mpp (tmp_path ):
20382042 """Test that mpp is None if no scale transforms are present."""
20392043 sample = _fetch_remote_sample ("ngff-1" )
20402044 # Create a copy of the sample with no axes
2041- sample_copy = tmp_path / "ngff-1"
2045+ sample_copy = tmp_path / "ngff-1.zarr "
20422046 shutil .copytree (sample , sample_copy )
20432047 with open (sample_copy / ".zattrs" , "r" ) as fh :
20442048 zattrs = json .load (fh )
@@ -2051,6 +2055,172 @@ def test_nff_no_scale_transforms_mpp(tmp_path):
20512055 assert wsi .info .mpp is None
20522056
20532057
2058+ def test_ngff_missing_omero_version (tmp_path ):
2059+ """Test that the reader can handle missing omero version."""
2060+ sample = _fetch_remote_sample ("ngff-1" )
2061+ # Create a copy of the sample
2062+ sample_copy = tmp_path / "ngff-1.zarr"
2063+ shutil .copytree (sample , sample_copy )
2064+ with open (sample_copy / ".zattrs" , "r" ) as fh :
2065+ zattrs = json .load (fh )
2066+ # Remove the omero version
2067+ del zattrs ["omero" ]["version" ]
2068+ with open (sample_copy / ".zattrs" , "w" ) as fh :
2069+ json .dump (zattrs , fh , indent = 2 )
2070+ wsireader .WSIReader .open (sample_copy )
2071+
2072+
2073+ def test_ngff_missing_multiscales_returns_false (tmp_path ):
2074+ """Test that missing multiscales key returns False for is_ngff."""
2075+ sample = _fetch_remote_sample ("ngff-1" )
2076+ # Create a copy of the sample
2077+ sample_copy = tmp_path / "ngff-1.zarr"
2078+ shutil .copytree (sample , sample_copy )
2079+ with open (sample_copy / ".zattrs" , "r" ) as fh :
2080+ zattrs = json .load (fh )
2081+ # Remove the multiscales key
2082+ del zattrs ["multiscales" ]
2083+ with open (sample_copy / ".zattrs" , "w" ) as fh :
2084+ json .dump (zattrs , fh , indent = 2 )
2085+ assert not wsireader .is_ngff (sample_copy )
2086+
2087+
2088+ def test_ngff_wrong_format_metadata (tmp_path , caplog ):
2089+ """Test that is_ngff is False and logs a warning if metadata is wrong."""
2090+ sample = _fetch_remote_sample ("ngff-1" )
2091+ # Create a copy of the sample
2092+ sample_copy = tmp_path / "ngff-1.zarr"
2093+ shutil .copytree (sample , sample_copy )
2094+ with open (sample_copy / ".zattrs" , "r" ) as fh :
2095+ zattrs = json .load (fh )
2096+ # Change the format to something else
2097+ zattrs ["multiscales" ] = "foo"
2098+ with open (sample_copy / ".zattrs" , "w" ) as fh :
2099+ json .dump (zattrs , fh , indent = 2 )
2100+ with caplog .at_level (logging .WARNING ):
2101+ assert not wsireader .is_ngff (sample_copy )
2102+ assert "must be present and of the correct type" in caplog .text
2103+
2104+
2105+ def test_ngff_omero_below_min_version (tmp_path ):
2106+ """Test for FileNotSupported when omero version is below minimum."""
2107+ sample = _fetch_remote_sample ("ngff-1" )
2108+ # Create a copy of the sample
2109+ sample_copy = tmp_path / "ngff-1.zarr"
2110+ shutil .copytree (sample , sample_copy )
2111+ with open (sample_copy / ".zattrs" , "r" ) as fh :
2112+ zattrs = json .load (fh )
2113+ # Change the format to something else
2114+ zattrs ["omero" ]["version" ] = "0.0"
2115+ with open (sample_copy / ".zattrs" , "w" ) as fh :
2116+ json .dump (zattrs , fh , indent = 2 )
2117+ with pytest .raises (FileNotSupported ):
2118+ wsireader .WSIReader .open (sample_copy )
2119+
2120+
2121+ def test_ngff_omero_above_max_version (tmp_path ):
2122+ """Test for FileNotSupported when omero version is above maximum."""
2123+ sample = _fetch_remote_sample ("ngff-1" )
2124+ # Create a copy of the sample
2125+ sample_copy = tmp_path / "ngff-1.zarr"
2126+ shutil .copytree (sample , sample_copy )
2127+ with open (sample_copy / ".zattrs" , "r" ) as fh :
2128+ zattrs = json .load (fh )
2129+ # Change the format to something else
2130+ zattrs ["omero" ]["version" ] = "10.0"
2131+ with open (sample_copy / ".zattrs" , "w" ) as fh :
2132+ json .dump (zattrs , fh , indent = 2 )
2133+ with pytest .raises (FileNotSupported ):
2134+ wsireader .WSIReader .open (sample_copy )
2135+
2136+
2137+ def test_ngff_multiscales_below_min_version (tmp_path ):
2138+ """Test for FileNotSupported when multiscales version is below minimum."""
2139+ sample = _fetch_remote_sample ("ngff-1" )
2140+ # Create a copy of the sample
2141+ sample_copy = tmp_path / "ngff-1.zarr"
2142+ shutil .copytree (sample , sample_copy )
2143+ with open (sample_copy / ".zattrs" , "r" ) as fh :
2144+ zattrs = json .load (fh )
2145+ # Change the format to something else
2146+ zattrs ["multiscales" ][0 ]["version" ] = "0.0"
2147+ with open (sample_copy / ".zattrs" , "w" ) as fh :
2148+ json .dump (zattrs , fh , indent = 2 )
2149+ with pytest .raises (FileNotSupported ):
2150+ wsireader .WSIReader .open (sample_copy )
2151+
2152+
2153+ def test_ngff_multiscales_above_max_version (tmp_path ):
2154+ """Test for FileNotSupported when multiscales version is above maximum."""
2155+ sample = _fetch_remote_sample ("ngff-1" )
2156+ # Create a copy of the sample
2157+ sample_copy = tmp_path / "ngff-1.zarr"
2158+ shutil .copytree (sample , sample_copy )
2159+ with open (sample_copy / ".zattrs" , "r" ) as fh :
2160+ zattrs = json .load (fh )
2161+ # Change the format to something else
2162+ zattrs ["multiscales" ][0 ]["version" ] = "10.0"
2163+ with open (sample_copy / ".zattrs" , "w" ) as fh :
2164+ json .dump (zattrs , fh , indent = 2 )
2165+ with pytest .raises (FileNotSupported ):
2166+ wsireader .WSIReader .open (sample_copy )
2167+
2168+
2169+ def test_ngff_non_numeric_version (tmp_path , monkeypatch ):
2170+ """Test that the reader can handle non-numeric omero versions."""
2171+
2172+ # Patch the is_ngff function to change the min/max version
2173+ if_ngff = wsireader .is_ngff # noqa: F841
2174+ min_version = Version ("0.4" )
2175+ max_version = Version ("0.5" )
2176+
2177+ def patched_is_ngff (
2178+ path : Path ,
2179+ min_version : Version = min_version ,
2180+ max_version : Version = max_version ,
2181+ ) -> bool :
2182+ """Patched is_ngff function with new min/max version."""
2183+ return is_ngff (path , min_version , max_version )
2184+
2185+ monkeypatch .setattr (wsireader , "is_ngff" , patched_is_ngff )
2186+
2187+ sample = _fetch_remote_sample ("ngff-1" )
2188+ # Create a copy of the sample
2189+ sample_copy = tmp_path / "ngff-1.zarr"
2190+ shutil .copytree (sample , sample_copy )
2191+ with open (sample_copy / ".zattrs" , "r" ) as fh :
2192+ zattrs = json .load (fh )
2193+ # Set the omero version to a non-numeric string
2194+ zattrs ["omero" ]["version" ] = "0.5-dev"
2195+ with open (sample_copy / ".zattrs" , "w" ) as fh :
2196+ json .dump (zattrs , fh , indent = 2 )
2197+ wsireader .WSIReader .open (sample_copy )
2198+
2199+
2200+ def test_ngff_inconsistent_multiscales_versions (tmp_path , caplog ):
2201+ """Test that the reader logs a warning inconsistent multiscales versions."""
2202+ sample = _fetch_remote_sample ("ngff-1" )
2203+ # Create a copy of the sample
2204+ sample_copy = tmp_path / "ngff-1.zarr"
2205+ shutil .copytree (sample , sample_copy )
2206+ with open (sample_copy / ".zattrs" , "r" ) as fh :
2207+ zattrs = json .load (fh )
2208+ # Set the versions to be inconsistent
2209+ multiscales = zattrs ["multiscales" ]
2210+ # Needs at least 2 multiscales to be inconsistent
2211+ if len (multiscales ) < 2 :
2212+ multiscales .append (copy .deepcopy (multiscales [0 ]))
2213+ for i , _ in enumerate (multiscales ):
2214+ multiscales [i ]["version" ] = f"0.{ i } -dev"
2215+ zattrs ["omero" ]["multiscales" ] = multiscales
2216+ with open (sample_copy / ".zattrs" , "w" ) as fh :
2217+ json .dump (zattrs , fh , indent = 2 )
2218+ # Capture logger output to check for warning
2219+ with caplog .at_level (logging .WARNING ), pytest .raises (FileNotSupported ):
2220+ wsireader .WSIReader .open (sample_copy )
2221+ assert "multiple versions" in caplog .text
2222+
2223+
20542224class TestReader :
20552225 scenarios = [
20562226 (
0 commit comments