Coverage for openhcs/processing/backends/lib_registry/pyclesperanto_registry.py: 49.3%

55 statements  

« prev     ^ index     » next       coverage.py v7.10.3, created at 2025-08-14 05:57 +0000

1""" 

2Clean Pyclesperanto Registry Implementation 

3 

4Implements clean abstraction with internal library-specific logic. 

5All pyclesperanto-specific details (dtype conversions, Z-parameters, etc.) 

6are handled internally without leaking into the ABC. 

7""" 

8from __future__ import annotations 

9 

10import inspect 

11import numpy as np 

12from functools import wraps 

13from typing import Tuple, Callable, List, Any, Dict 

14from openhcs.constants import MemoryType 

15from openhcs.core.utils import optional_import 

16from .unified_registry import LibraryRegistryBase, ProcessingContract, FunctionMetadata 

17 

18cle = optional_import("pyclesperanto") 

19 

20 

21class PyclesperantoRegistry(LibraryRegistryBase): 

22 """Clean pyclesperanto registry with internal library-specific logic.""" 

23 

24 # Library-specific exclusions extending common ones 

25 EXCLUSIONS = LibraryRegistryBase.COMMON_EXCLUSIONS | { 

26 'push_zyx', 'pull_zyx', 'create_zyx', 'set_wait_for_kernel_finish', 

27 'get_device', 'select_device', 'list_available_devices' 

28 } 

29 

30 # Modules to scan for functions 

31 MODULES_TO_SCAN = [""] # Pyclesperanto functions are in main namespace 

32 

33 # Memory type for this registry 

34 MEMORY_TYPE = MemoryType.PYCLESPERANTO.value 

35 

36 # Float dtype for this registry 

37 FLOAT_DTYPE = np.float32 

38 

39 def __init__(self): 

40 super().__init__("pyclesperanto") 

41 # Internal constants for dtype handling 

42 self._BINARY_FUNCTIONS = {'binary_infsup', 'binary_supinf'} 

43 self._UINT8_FUNCTIONS = {'mode', 'mode_box', 'mode_sphere'} 

44 self._IMAGE_PARAM_NAMES = {"src", "source", "image", "input", "src1", "input_image", "input_image0"} 

45 

46 # ===== ESSENTIAL ABC METHODS ===== 

47 def get_library_version(self) -> str: 

48 return cle.__version__ 

49 

50 def is_library_available(self) -> bool: 

51 return cle is not None 

52 

53 def get_library_object(self): 

54 return cle 

55 

56 # ===== HOOK IMPLEMENTATIONS ===== 

57 def _create_array(self, shape: Tuple[int, ...], dtype): 

58 return np.random.rand(*shape).astype(dtype) 

59 

60 def _check_first_parameter(self, first_param, func_name: str) -> bool: 

61 return (first_param.name.lower() in self._IMAGE_PARAM_NAMES and 

62 first_param.kind in (inspect.Parameter.POSITIONAL_ONLY, 

63 inspect.Parameter.POSITIONAL_OR_KEYWORD)) 

64 

65 def _preprocess_input(self, image, func_name: str): 

66 return self._convert_input_dtype(image, func_name) 

67 

68 def _postprocess_output(self, result, original_image, func_name: str): 

69 return self._convert_output_dtype(result, original_image.dtype, func_name) 

70 

71 # ===== LIBRARY-SPECIFIC HELPER METHODS ===== 

72 def _convert_input_dtype(self, image, func_name): 

73 """Internal dtype conversion logic.""" 

74 if func_name in self._BINARY_FUNCTIONS: 

75 return ((image > 0.5) * 255).astype(np.uint8) 

76 elif func_name in self._UINT8_FUNCTIONS: 

77 return (np.clip(image, 0, 1) * 255).astype(np.uint8) 

78 return image 

79 

80 def _convert_output_dtype(self, result, original_dtype, func_name): 

81 """Internal output dtype conversion.""" 

82 if func_name in self._BINARY_FUNCTIONS or func_name in self._UINT8_FUNCTIONS: 

83 if result.dtype != original_dtype: 

84 if result.dtype == np.uint8 and original_dtype == np.float32: 

85 return result.astype(np.float32) / 255.0 

86 elif result.dtype == np.bool_ and original_dtype == np.float32: 

87 return result.astype(np.float32) 

88 return result 

89 

90 # ===== LIBRARY-SPECIFIC IMPLEMENTATIONS ===== 

91 def _stack_2d_results(self, func, test_3d): 

92 """Stack 2D results using CLE.""" 

93 results = [func(test_3d[z]) for z in range(test_3d.shape[0])] 

94 return cle.concatenate_along_z(*results) 

95 

96 def _arrays_close(self, arr1, arr2): 

97 """Compare arrays using CLE.""" 

98 return np.allclose(arr1.get(), arr2.get(), rtol=1e-5, atol=1e-8) 

99 

100 def _expand_2d_to_3d(self, array_2d): 

101 """Expand 2D array to 3D using CLE concatenation.""" 

102 temp = cle.concatenate_along_z(array_2d, array_2d) 

103 return temp[0:1, :, :]