Coverage for python / lsst / scarlet / lite / io / hierarchical_blend.py: 47%

52 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-14 23:28 +0000

1from __future__ import annotations 

2 

3from dataclasses import dataclass 

4from typing import Any 

5 

6import numpy as np 

7from numpy.typing import DTypeLike 

8 

9from ..bbox import Box 

10from .blend_base import ScarletBlendBaseData 

11from .migration import PRE_SCHEMA, MigrationRegistry, migration 

12from .utils import PersistenceError, decode_metadata, encode_metadata 

13 

14__all__ = ["HierarchicalBlendData"] 

15 

16CURRENT_SCHEMA = "1.0.0" 

17BLEND_TYPE = "hierarchical" 

18MigrationRegistry.set_current(BLEND_TYPE, CURRENT_SCHEMA) 

19 

20 

21@dataclass(kw_only=True) 

22class HierarchicalBlendData(ScarletBlendBaseData): 

23 """Data for a hierarchical blend. 

24 

25 Attributes 

26 ---------- 

27 blend_type : 

28 The type of blend being stored 

29 children : 

30 Map from blend IDs to child blends. 

31 version : 

32 The schema version of the HierarchicalBlendData. 

33 """ 

34 

35 blend_type: str = BLEND_TYPE 

36 children: dict[int, ScarletBlendBaseData] 

37 version: str = CURRENT_SCHEMA 

38 

39 @property 

40 def bbox(self) -> Box: 

41 """The bounding box of the blend""" 

42 # Compute the bounding box that contains all children 

43 if not self.children: 

44 raise ValueError("HierarchicalBlendData has no children to compute bbox from.") 

45 bboxes = [child.bbox for child in self.children.values()] 

46 min_y = min(bbox.origin[0] for bbox in bboxes) 

47 min_x = min(bbox.origin[1] for bbox in bboxes) 

48 max_y = max(bbox.origin[0] + bbox.shape[0] for bbox in bboxes) 

49 max_x = max(bbox.origin[1] + bbox.shape[1] for bbox in bboxes) 

50 origin = (min_y, min_x) 

51 shape = (max_y - min_y, max_x - min_x) 

52 return Box(shape, origin=origin) 

53 

54 def as_dict(self) -> dict: 

55 """Return the object encoded into a dict for JSON serialization 

56 

57 Returns 

58 ------- 

59 result : 

60 The object encoded as a JSON compatible dict 

61 """ 

62 result: dict[str, Any] = { 

63 "blend_type": self.blend_type, 

64 "children": {bid: child.as_dict() for bid, child in self.children.items()}, 

65 "version": self.version, 

66 } 

67 if self.metadata is not None: 

68 result["metadata"] = encode_metadata(self.metadata) 

69 return result 

70 

71 @classmethod 

72 def from_dict(cls, data: dict, dtype: DTypeLike = np.float32) -> HierarchicalBlendData: 

73 """Reconstruct `HierarchicalBlendData` from JSON compatible dict. 

74 

75 Parameters 

76 ---------- 

77 data : 

78 Dictionary representation of the object 

79 dtype : 

80 Datatype of the resulting model. 

81 

82 Returns 

83 ------- 

84 result : 

85 The reconstructed object 

86 """ 

87 data = MigrationRegistry.migrate(BLEND_TYPE, data) 

88 children: dict[int, ScarletBlendBaseData] = {} 

89 for blend_id, child in data["children"].items(): 

90 try: 

91 children[int(blend_id)] = ScarletBlendBaseData.from_dict(child, dtype=dtype) 

92 except KeyError: 

93 raise PersistenceError(f"Unknown blend type: {child['blend_type']} for blend ID: {blend_id}") 

94 

95 metadata = decode_metadata(data.get("metadata", None)) 

96 return cls(children=children, metadata=metadata) 

97 

98 

99HierarchicalBlendData.register() 

100# Register the legacy blend_type in the blend registry 

101ScarletBlendBaseData.blend_registry["hierarchical_blend"] = HierarchicalBlendData 

102 

103 

104@migration(BLEND_TYPE, PRE_SCHEMA) 

105def _to_1_0_0(data: dict) -> dict: 

106 """Migrate a pre-schema hierarchical blend to schema version 1.0.0 

107 

108 There were no changes to this data model in v1.0.0 but we need 

109 to provide a way to migrate pre-schema data. 

110 

111 Parameters 

112 ---------- 

113 data : 

114 The data to migrate. 

115 

116 Returns 

117 ------- 

118 result : 

119 The migrated data. 

120 """ 

121 data["version"] = "1.0.0" 

122 return data