import os import tempfile from pathlib import Path from unittest.mock import patch import numpy as np from PIL import Image as PILImage from backend.data_types import DataField def test_load_file(): from backend.nodes.image import Image as ImageNode node = ImageNode() with tempfile.TemporaryDirectory() as tmpdir: arr = np.random.default_rng(1).integers(0, 256, (48, 64), dtype=np.uint8) img = PILImage.fromarray(arr, mode="L") path = os.path.join(tmpdir, "test_gray.png") img.save(path) result = node.load(filename=path) assert len(result) == 2 assert isinstance(result[0], str) field = result[1] assert field.data.shape == (48, 64) assert field.data.dtype == np.float64 arr_rgb = np.random.default_rng(2).integers(0, 256, (32, 32, 3), dtype=np.uint8) img_rgb = PILImage.fromarray(arr_rgb, mode="RGB") path_rgb = os.path.join(tmpdir, "test_rgb.png") img_rgb.save(path_rgb) result_rgb = node.load(filename=path_rgb) assert len(result_rgb) == 2 assert result_rgb[1].data.shape == (32, 32) data_npy = np.random.default_rng(3).standard_normal((50, 60)) path_npy = os.path.join(tmpdir, "test.npy") np.save(path_npy, data_npy) result_npy = node.load(filename=path_npy) assert np.allclose(result_npy[1].data, data_npy) custom_colormap = { "mode": "custom", "stops": [ {"position": 0.0, "color": "#000000"}, {"position": 0.5, "color": "#ff0000"}, {"position": 1.0, "color": "#ffffff"}, ], } result_custom = node.load(filename=path, colormap_map=custom_colormap) assert isinstance(result_custom[1].colormap, dict) assert result_custom[1].colormap["mode"] == "custom" assert len(result_custom[1].colormap["stops"]) == 3 result_from_path = node.load(filename="", path=path) assert len(result_from_path) == 2 assert result_from_path[1].data.shape == (48, 64) def test_load_file_npz(): from backend.nodes.image import Image node = Image() with tempfile.TemporaryDirectory() as tmpdir: data = np.random.default_rng(99).standard_normal((30, 40)) path = os.path.join(tmpdir, "test.npz") np.savez(path, my_array=data) result = node.load(filename=path) assert len(result) == 2 assert np.allclose(result[1].data, data) def test_load_file_cache(): from backend.nodes.image import Image node = Image() Image._load_fields_cached.cache_clear() with tempfile.TemporaryDirectory() as tmpdir: data = np.arange(16, dtype=np.float64).reshape(4, 4) path = os.path.join(tmpdir, "cached.npy") np.save(path, data) import backend.importers.array_image as _ai with patch.object(_ai, "load", wraps=_ai.load) as loader: _, first = node.load(filename=path) _, second = node.load(filename=path) assert loader.call_count == 1 assert np.allclose(first.data, data) assert np.allclose(second.data, data) assert first is not second first.data[0, 0] = -999.0 _, third = node.load(filename=path) assert third.data[0, 0] == data[0, 0] Image._load_fields_cached.cache_clear() def test_load_file_not_found(): from backend.nodes.image import Image node = Image() try: node.load(filename="/nonexistent/path/file.png") assert False, "Should have raised FileNotFoundError" except FileNotFoundError: pass def test_load_file_unsupported(): from backend.nodes.image import Image node = Image() with tempfile.TemporaryDirectory() as tmpdir: path = os.path.join(tmpdir, "test.xyz") with open(path, "w") as f: f.write("hello") try: node.load(filename=path) assert False, "Should have raised an error for .xyz" except Exception: pass def test_load_file_warning(): from backend.nodes.image import Image as ImageNode node = ImageNode() warnings = [] ImageNode._broadcast_warning_fn = lambda nid, msg: warnings.append(msg) ImageNode._current_node_id = "test" with tempfile.TemporaryDirectory() as tmpdir: arr = np.random.default_rng(10).integers(0, 256, (16, 16), dtype=np.uint8) img = PILImage.fromarray(arr) path = os.path.join(tmpdir, "test.png") img.save(path) result = node.load(filename=path) assert len(result) == 2 assert len(warnings) == 1 assert "Uncalibrated" in warnings[0] ImageNode._broadcast_warning_fn = None def test_load_file_ibw(): from backend.nodes.image import Image node = Image() demo_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "demo")) # Use first available .ibw file in demo ibw_path = None for candidate in ["Calcite.ibw", "DNA.ibw", "nanoparticles.npy"]: p = os.path.join(demo_dir, candidate) if os.path.exists(p): ibw_path = p break if ibw_path is None: return result = node.load(filename=ibw_path) assert len(result) >= 2 for item in result[1:]: assert isinstance(item, DataField) assert item.data.dtype == np.float64 assert item.xreal > 0 def test_load_empty_filename_raises(): from backend.nodes.image import Image node = Image() try: node.load(filename="") assert False, "Expected ValueError" except ValueError: pass def test_load_directory_raises(): from backend.nodes.image import Image node = Image() with tempfile.TemporaryDirectory() as tmpdir: try: node.load(filename=tmpdir) assert False, "Expected IsADirectoryError" except IsADirectoryError: pass def test_load_float_tiff(): """float32 TIFF images should be converted to float64 (covers non-uint8 branch).""" import tifffile from backend.nodes.image import Image node = Image() Image._load_fields_cached.cache_clear() with tempfile.TemporaryDirectory() as tmpdir: arr = np.random.default_rng(42).random((16, 16)).astype(np.float32) path = os.path.join(tmpdir, "float.tiff") tifffile.imwrite(path, arr) result = node.load(filename=path) assert len(result) == 2 assert result[1].data.dtype == np.float64 Image._load_fields_cached.cache_clear()