driftlessmap 1.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. driftlessmap/__init__.py +16 -0
  2. driftlessmap/__main__.py +5 -0
  3. driftlessmap/about.py +24 -0
  4. driftlessmap/allen_downloader.py +908 -0
  5. driftlessmap/app.py +8085 -0
  6. driftlessmap/atlas_downloader.py +370 -0
  7. driftlessmap/atlas_loader.py +526 -0
  8. driftlessmap/atlas_processor.py +784 -0
  9. driftlessmap/atlas_transform.py +111 -0
  10. driftlessmap/atlas_view.py +1435 -0
  11. driftlessmap/cell_detection.py +27 -0
  12. driftlessmap/coordinate_validation.py +19 -0
  13. driftlessmap/czi_reader.py +236 -0
  14. driftlessmap/data/WHS_atlas_labels.pkl +0 -0
  15. driftlessmap/data/allen_mice_atlas_labels.pkl +0 -0
  16. driftlessmap/data/atlas_labels.pkl +0 -0
  17. driftlessmap/data/query.csv +1328 -0
  18. driftlessmap/download_utils.py +85 -0
  19. driftlessmap/herbs.png +0 -0
  20. driftlessmap/icons/backward.svg +15 -0
  21. driftlessmap/icons/dot.svg +11 -0
  22. driftlessmap/icons/down-arrow.svg +10 -0
  23. driftlessmap/icons/fast_backward.svg +14 -0
  24. driftlessmap/icons/fast_forward.svg +14 -0
  25. driftlessmap/icons/forward.svg +14 -0
  26. driftlessmap/icons/layers/add.png +0 -0
  27. driftlessmap/icons/layers/ai.png +0 -0
  28. driftlessmap/icons/layers/eye_off.png +0 -0
  29. driftlessmap/icons/layers/eye_on.png +0 -0
  30. driftlessmap/icons/layers/eye_white.png +0 -0
  31. driftlessmap/icons/layers/hi.png +0 -0
  32. driftlessmap/icons/layers/trash.png +0 -0
  33. driftlessmap/icons/object.svg +22 -0
  34. driftlessmap/icons/sidebar/add.png +0 -0
  35. driftlessmap/icons/sidebar/atlascontrol.png +0 -0
  36. driftlessmap/icons/sidebar/atlascontrol.svg +32 -0
  37. driftlessmap/icons/sidebar/bnd.svg +14 -0
  38. driftlessmap/icons/sidebar/c_section.png +0 -0
  39. driftlessmap/icons/sidebar/c_section2.png +0 -0
  40. driftlessmap/icons/sidebar/cell.svg +24 -0
  41. driftlessmap/icons/sidebar/check.svg +9 -0
  42. driftlessmap/icons/sidebar/compare.svg +18 -0
  43. driftlessmap/icons/sidebar/contour.svg +14 -0
  44. driftlessmap/icons/sidebar/h_section.png +0 -0
  45. driftlessmap/icons/sidebar/info.svg +14 -0
  46. driftlessmap/icons/sidebar/layers.png +0 -0
  47. driftlessmap/icons/sidebar/layers.svg +23 -0
  48. driftlessmap/icons/sidebar/line.svg +10 -0
  49. driftlessmap/icons/sidebar/link.svg +18 -0
  50. driftlessmap/icons/sidebar/link_off.svg +18 -0
  51. driftlessmap/icons/sidebar/merge.svg +11 -0
  52. driftlessmap/icons/sidebar/object.svg +13 -0
  53. driftlessmap/icons/sidebar/probe.png +0 -0
  54. driftlessmap/icons/sidebar/probe.svg +23 -0
  55. driftlessmap/icons/sidebar/rotation_horizontal.svg +14 -0
  56. driftlessmap/icons/sidebar/rotation_vertical.svg +14 -0
  57. driftlessmap/icons/sidebar/s_section.png +0 -0
  58. driftlessmap/icons/sidebar/tool.png +0 -0
  59. driftlessmap/icons/sidebar/tool.svg +19 -0
  60. driftlessmap/icons/sidebar/trash.png +0 -0
  61. driftlessmap/icons/sidebar/tree_checked.svg +10 -0
  62. driftlessmap/icons/sidebar/treeview.png +0 -0
  63. driftlessmap/icons/sidebar/treeview.svg +10 -0
  64. driftlessmap/icons/sidebar/treeview2.png +0 -0
  65. driftlessmap/icons/sidebar/virus.svg +24 -0
  66. driftlessmap/icons/tdown.svg +16 -0
  67. driftlessmap/icons/toolbar/accept.svg +18 -0
  68. driftlessmap/icons/toolbar/accept2.svg +24 -0
  69. driftlessmap/icons/toolbar/aim.svg +44 -0
  70. driftlessmap/icons/toolbar/aim_not.svg +28 -0
  71. driftlessmap/icons/toolbar/anchor.svg +14 -0
  72. driftlessmap/icons/toolbar/anticlockwise_rotation.svg +12 -0
  73. driftlessmap/icons/toolbar/atlas_icon.png +0 -0
  74. driftlessmap/icons/toolbar/boundary_register.svg +18 -0
  75. driftlessmap/icons/toolbar/cancel.svg +32 -0
  76. driftlessmap/icons/toolbar/cell_select.svg +22 -0
  77. driftlessmap/icons/toolbar/cell_select_not.svg +12 -0
  78. driftlessmap/icons/toolbar/check.svg +9 -0
  79. driftlessmap/icons/toolbar/clockwise_rotation.svg +11 -0
  80. driftlessmap/icons/toolbar/closed_path.svg +16 -0
  81. driftlessmap/icons/toolbar/closed_path2 copy.svg +19 -0
  82. driftlessmap/icons/toolbar/closed_path2.svg +20 -0
  83. driftlessmap/icons/toolbar/eraser.png +0 -0
  84. driftlessmap/icons/toolbar/eraser.svg +19 -0
  85. driftlessmap/icons/toolbar/eye.svg +17 -0
  86. driftlessmap/icons/toolbar/eye_closed.svg +19 -0
  87. driftlessmap/icons/toolbar/fill.svg +12 -0
  88. driftlessmap/icons/toolbar/gps.svg +15 -0
  89. driftlessmap/icons/toolbar/h_flip.png +0 -0
  90. driftlessmap/icons/toolbar/handle.png +0 -0
  91. driftlessmap/icons/toolbar/image_icon.png +0 -0
  92. driftlessmap/icons/toolbar/image_icon.svg +25 -0
  93. driftlessmap/icons/toolbar/info.svg +14 -0
  94. driftlessmap/icons/toolbar/inpart.png +0 -0
  95. driftlessmap/icons/toolbar/inpart.svg +17 -0
  96. driftlessmap/icons/toolbar/lasso.png +0 -0
  97. driftlessmap/icons/toolbar/lasso.svg +14 -0
  98. driftlessmap/icons/toolbar/left90.png +0 -0
  99. driftlessmap/icons/toolbar/line.svg +10 -0
  100. driftlessmap/icons/toolbar/line_sites.svg +11 -0
  101. driftlessmap/icons/toolbar/linear_silicon.png +0 -0
  102. driftlessmap/icons/toolbar/linear_silicon.svg +88 -0
  103. driftlessmap/icons/toolbar/list.svg +14 -0
  104. driftlessmap/icons/toolbar/location.svg +14 -0
  105. driftlessmap/icons/toolbar/magic-wand.svg +16 -0
  106. driftlessmap/icons/toolbar/magic_white.png +0 -0
  107. driftlessmap/icons/toolbar/mask.svg +10 -0
  108. driftlessmap/icons/toolbar/match.svg +20 -0
  109. driftlessmap/icons/toolbar/matchbnd.svg +18 -0
  110. driftlessmap/icons/toolbar/matching.svg +36 -0
  111. driftlessmap/icons/toolbar/merge.svg +11 -0
  112. driftlessmap/icons/toolbar/move_down.png +0 -0
  113. driftlessmap/icons/toolbar/move_left.png +0 -0
  114. driftlessmap/icons/toolbar/move_right.png +0 -0
  115. driftlessmap/icons/toolbar/move_up.png +0 -0
  116. driftlessmap/icons/toolbar/moving.png +0 -0
  117. driftlessmap/icons/toolbar/multi-probe.svg +119 -0
  118. driftlessmap/icons/toolbar/multi_pencil.svg +23 -0
  119. driftlessmap/icons/toolbar/multi_probe.png +0 -0
  120. driftlessmap/icons/toolbar/object.svg +13 -0
  121. driftlessmap/icons/toolbar/open_path.svg +22 -0
  122. driftlessmap/icons/toolbar/outpart.png +0 -0
  123. driftlessmap/icons/toolbar/outpart.svg +26 -0
  124. driftlessmap/icons/toolbar/pencil.png +0 -0
  125. driftlessmap/icons/toolbar/pencil.svg +19 -0
  126. driftlessmap/icons/toolbar/plasso.png +0 -0
  127. driftlessmap/icons/toolbar/probe.svg +19 -0
  128. driftlessmap/icons/toolbar/radar.svg +32 -0
  129. driftlessmap/icons/toolbar/rotation.svg +45 -0
  130. driftlessmap/icons/toolbar/rotation_clockwise.svg +18 -0
  131. driftlessmap/icons/toolbar/rotation_counter_clockwise.svg +18 -0
  132. driftlessmap/icons/toolbar/ruler.svg +27 -0
  133. driftlessmap/icons/toolbar/separate_sites.svg +13 -0
  134. driftlessmap/icons/toolbar/single_pencil.svg +16 -0
  135. driftlessmap/icons/toolbar/toa.svg +11 -0
  136. driftlessmap/icons/toolbar/toa_delete.svg +36 -0
  137. driftlessmap/icons/toolbar/toaa.svg +11 -0
  138. driftlessmap/icons/toolbar/toh.svg +11 -0
  139. driftlessmap/icons/toolbar/toh_delete.svg +36 -0
  140. driftlessmap/icons/toolbar/tohh.svg +11 -0
  141. driftlessmap/icons/toolbar/trans.png +0 -0
  142. driftlessmap/icons/toolbar/trans.svg +16 -0
  143. driftlessmap/icons/toolbar/triangulation.svg +10 -0
  144. driftlessmap/icons/toolbar/two_window.png +0 -0
  145. driftlessmap/icons/toolbar/unmerge.svg +16 -0
  146. driftlessmap/icons/toolbar/v_flip.png +0 -0
  147. driftlessmap/icons/toolbar/virus.svg +24 -0
  148. driftlessmap/icons/toolbar/virus_register.svg +31 -0
  149. driftlessmap/icons/toolbar/vis2d.svg +29 -0
  150. driftlessmap/icons/toolbar/window2.png +0 -0
  151. driftlessmap/icons/toolbar/window3.png +0 -0
  152. driftlessmap/icons/toolbar/window4.png +0 -0
  153. driftlessmap/icons/tree_close.svg +14 -0
  154. driftlessmap/icons/tree_open.svg +14 -0
  155. driftlessmap/icons/up-arrow.svg +10 -0
  156. driftlessmap/image_curves.py +579 -0
  157. driftlessmap/image_reader.py +186 -0
  158. driftlessmap/image_stacks.py +359 -0
  159. driftlessmap/image_view.py +646 -0
  160. driftlessmap/images_reader.py +5 -0
  161. driftlessmap/label_tree.py +278 -0
  162. driftlessmap/layer_validation.py +25 -0
  163. driftlessmap/layers_control.py +554 -0
  164. driftlessmap/main_window.ui +883 -0
  165. driftlessmap/movable_points.py +254 -0
  166. driftlessmap/obj_items.py +122 -0
  167. driftlessmap/object_control.py +1762 -0
  168. driftlessmap/persistence.py +376 -0
  169. driftlessmap/popup_message.py +16 -0
  170. driftlessmap/probe_csv.py +319 -0
  171. driftlessmap/probe_reconstruction.py +409 -0
  172. driftlessmap/probe_utiles.py +1442 -0
  173. driftlessmap/qss/atlas_view_group_box.qss +15 -0
  174. driftlessmap/qss/box_label.qss +7 -0
  175. driftlessmap/qss/channel_selector.qss +19 -0
  176. driftlessmap/qss/color_combo.qss +121 -0
  177. driftlessmap/qss/decor_label.qss +9 -0
  178. driftlessmap/qss/dialogs.qss +11 -0
  179. driftlessmap/qss/hidden_line_edit.qss +5 -0
  180. driftlessmap/qss/label_tree.qss +123 -0
  181. driftlessmap/qss/main_window.qss +243 -0
  182. driftlessmap/qss/menu_bar.qss +42 -0
  183. driftlessmap/qss/multi_handle_slider.qss +14 -0
  184. driftlessmap/qss/obj_ctrl_bottom_button.qss +29 -0
  185. driftlessmap/qss/object_text_button.qss +19 -0
  186. driftlessmap/qss/page_control.qss +36 -0
  187. driftlessmap/qss/rotation_button.qss +9 -0
  188. driftlessmap/qss/side_bar.qss +23 -0
  189. driftlessmap/qss/spinbox.qss +106 -0
  190. driftlessmap/qss/tabs.qss +211 -0
  191. driftlessmap/qss/tool_bar.qss +77 -0
  192. driftlessmap/resources.py +34 -0
  193. driftlessmap/roi_analysis.py +375 -0
  194. driftlessmap/run_driftlessmap.py +4 -0
  195. driftlessmap/slice_stacks.py +268 -0
  196. driftlessmap/slice_validation.py +20 -0
  197. driftlessmap/toolbox.py +569 -0
  198. driftlessmap/triangulation.py +517 -0
  199. driftlessmap/triangulation_points.py +108 -0
  200. driftlessmap/user_settings.py +92 -0
  201. driftlessmap/uuuuuu.py +1168 -0
  202. driftlessmap/version.py +3 -0
  203. driftlessmap/widgets_utils.py +140 -0
  204. driftlessmap/wtiles.py +715 -0
  205. driftlessmap-1.1.0.dist-info/METADATA +186 -0
  206. driftlessmap-1.1.0.dist-info/RECORD +210 -0
  207. driftlessmap-1.1.0.dist-info/WHEEL +5 -0
  208. driftlessmap-1.1.0.dist-info/entry_points.txt +2 -0
  209. driftlessmap-1.1.0.dist-info/licenses/LICENSE.txt +23 -0
  210. driftlessmap-1.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,376 @@
1
+ """Versioned, non-executable persistence for DriftlessMap user data."""
2
+
3
+ import io
4
+ import importlib
5
+ import json
6
+ import os
7
+ from pathlib import Path
8
+ import pickle
9
+ import tempfile
10
+ import zipfile
11
+
12
+ import numpy as np
13
+
14
+
15
+ FORMAT_NAME = "DriftlessMap"
16
+ LEGACY_FORMAT_NAMES = frozenset({"HERBS"})
17
+ SUPPORTED_FORMAT_NAMES = LEGACY_FORMAT_NAMES | {FORMAT_NAME}
18
+ FORMAT_VERSION = 1
19
+ MANIFEST_NAME = "manifest.json"
20
+ MAX_MANIFEST_BYTES = 16 * 1024 * 1024
21
+ MAX_ARCHIVE_BYTES = 8 * 1024 * 1024 * 1024
22
+
23
+ REQUIRED_KEYS = {
24
+ "layer": {"layer_link", "data", "color", "thumbnail"},
25
+ "object": {"type", "data", "name"},
26
+ "project": {
27
+ "atlas_path",
28
+ "img_path",
29
+ "current_atlas",
30
+ "num_windows",
31
+ "probe_settings",
32
+ "np_onside",
33
+ "processing_slice",
34
+ "processing_img",
35
+ "overlay_img",
36
+ "atlas_control",
37
+ "img_ctrl_data",
38
+ "setting_data",
39
+ "tool_data",
40
+ "layer_data",
41
+ "working_img_data",
42
+ "working_atlas_data",
43
+ "object_data",
44
+ },
45
+ "slice": {"data", "cut", "width", "height", "distance", "Bregma", "ready"},
46
+ "triangulation": {
47
+ "atlas_corner_points",
48
+ "atlas_side_lines",
49
+ "atlas_tri_data",
50
+ "atlas_tri_inside_data",
51
+ "atlas_tri_onside_data",
52
+ "atlas_display",
53
+ },
54
+ }
55
+
56
+
57
+ def _numpy_pickle_globals():
58
+ """Return inert NumPy constructors used by supported pickle versions."""
59
+ safe_globals = {
60
+ ("numpy", "dtype"): np.dtype,
61
+ ("numpy", "ndarray"): np.ndarray,
62
+ ("numpy.core.multiarray", "_reconstruct"): np.core.multiarray._reconstruct,
63
+ ("numpy.core.multiarray", "scalar"): np.core.multiarray.scalar,
64
+ ("numpy._core.multiarray", "_reconstruct"): np.core.multiarray._reconstruct,
65
+ ("numpy._core.multiarray", "scalar"): np.core.multiarray.scalar,
66
+ }
67
+ frombuffer = None
68
+ for module_name in ("numpy._core.numeric", "numpy.core.numeric"):
69
+ try:
70
+ numeric_module = importlib.import_module(module_name)
71
+ frombuffer = numeric_module._frombuffer
72
+ break
73
+ except (AttributeError, ImportError):
74
+ continue
75
+ if frombuffer is not None:
76
+ for module_name in ("numpy.core.numeric", "numpy._core.numeric"):
77
+ safe_globals[(module_name, "_frombuffer")] = frombuffer
78
+ return safe_globals
79
+
80
+
81
+ class _LegacyPandasNA:
82
+ """Inert marker for pandas' missing-string singleton."""
83
+
84
+ __slots__ = ()
85
+
86
+
87
+ _LEGACY_PANDAS_NA = _LegacyPandasNA()
88
+
89
+
90
+ class _LegacyPandasStringDtype:
91
+ """Inert stand-in used while decoding a pandas string-array pickle."""
92
+
93
+ __slots__ = ()
94
+
95
+ def __init__(self, storage="python", na_value=np.nan):
96
+ valid_nan = isinstance(na_value, float) and np.isnan(na_value)
97
+ if storage != "python" or (
98
+ not valid_nan and na_value is not _LEGACY_PANDAS_NA
99
+ ):
100
+ raise pickle.UnpicklingError(
101
+ "Unsupported legacy pandas string dtype."
102
+ )
103
+
104
+
105
+ class _LegacyPandasStringArray:
106
+ """Capture the NumPy payload of a legacy pandas StringArray safely."""
107
+
108
+ __slots__ = ("values",)
109
+
110
+ def __init__(self):
111
+ self.values = None
112
+
113
+ def __setstate__(self, state):
114
+ if (
115
+ not isinstance(state, tuple)
116
+ or len(state) not in (2, 3)
117
+ or not isinstance(state[0], _LegacyPandasStringDtype)
118
+ or not isinstance(state[1], np.ndarray)
119
+ or (len(state) == 3 and state[2] != {})
120
+ ):
121
+ raise pickle.UnpicklingError("Invalid legacy pandas string-array state.")
122
+ self.values = state[1]
123
+
124
+
125
+ def _reconstruct_legacy_pandas_string_array(array_type, checksum, state):
126
+ """Replace pandas' Cython reconstruction function with an inert shim."""
127
+ if (
128
+ array_type is not _LegacyPandasStringArray
129
+ or not isinstance(checksum, int)
130
+ or state is not None
131
+ ):
132
+ raise pickle.UnpicklingError(
133
+ "Invalid legacy pandas string-array constructor."
134
+ )
135
+ return _LegacyPandasStringArray()
136
+
137
+
138
+ def _normalize_legacy_pandas_arrays(value):
139
+ if isinstance(value, _LegacyPandasStringArray):
140
+ array = np.asarray(value.values)
141
+ if array.ndim != 1 or array.dtype.kind not in ("O", "S", "U"):
142
+ raise pickle.UnpicklingError(
143
+ "Invalid legacy pandas string-array payload."
144
+ )
145
+ if array.dtype.kind == "O" and any(
146
+ not isinstance(item, (str, np.str_)) for item in array
147
+ ):
148
+ raise pickle.UnpicklingError(
149
+ "Legacy pandas string array contains non-string values."
150
+ )
151
+ return array.astype(str)
152
+ if isinstance(value, dict):
153
+ return {
154
+ _normalize_legacy_pandas_arrays(key): _normalize_legacy_pandas_arrays(item)
155
+ for key, item in value.items()
156
+ }
157
+ if isinstance(value, list):
158
+ return [_normalize_legacy_pandas_arrays(item) for item in value]
159
+ if isinstance(value, tuple):
160
+ return tuple(_normalize_legacy_pandas_arrays(item) for item in value)
161
+ if isinstance(value, set):
162
+ return {_normalize_legacy_pandas_arrays(item) for item in value}
163
+ if isinstance(value, frozenset):
164
+ return frozenset(_normalize_legacy_pandas_arrays(item) for item in value)
165
+ return value
166
+
167
+
168
+ class RestrictedUnpickler(pickle.Unpickler):
169
+ """Legacy reader limited to inert builtins and NumPy array machinery."""
170
+
171
+ SAFE_GLOBALS = {
172
+ ("builtins", "complex"): complex,
173
+ ("builtins", "frozenset"): frozenset,
174
+ ("builtins", "set"): set,
175
+ ("builtins", "slice"): slice,
176
+ (
177
+ "pandas._libs.arrays",
178
+ "__pyx_unpickle_NDArrayBacked",
179
+ ): _reconstruct_legacy_pandas_string_array,
180
+ ("pandas.arrays", "StringArray"): _LegacyPandasStringArray,
181
+ ("pandas.core.arrays.string_", "StringArray"): _LegacyPandasStringArray,
182
+ ("pandas.core.arrays.string_", "StringDtype"): _LegacyPandasStringDtype,
183
+ ("pandas", "StringDtype"): _LegacyPandasStringDtype,
184
+ ("pandas", "NA"): _LEGACY_PANDAS_NA,
185
+ ("pandas._libs.missing", "NA"): _LEGACY_PANDAS_NA,
186
+ **_numpy_pickle_globals(),
187
+ }
188
+
189
+ def find_class(self, module, name):
190
+ try:
191
+ return self.SAFE_GLOBALS[(module, name)]
192
+ except KeyError as exc:
193
+ raise pickle.UnpicklingError(
194
+ "Legacy file contains unsupported type {}.{}".format(module, name)
195
+ ) from exc
196
+
197
+
198
+ def load_legacy_pickle(file_path):
199
+ """Load inert data from a legacy pickle with a consistent result tuple."""
200
+ try:
201
+ with open(file_path, "rb") as infile:
202
+ data = RestrictedUnpickler(infile).load()
203
+ return _normalize_legacy_pandas_arrays(data), None
204
+ except OSError as exc:
205
+ return None, "Unable to read file: {}".format(exc)
206
+ except Exception as exc:
207
+ return None, "Invalid or unsupported legacy HERBS file: {}".format(exc)
208
+
209
+
210
+ def _validate_payload(data, kind):
211
+ required = REQUIRED_KEYS.get(kind)
212
+ if required is not None and (
213
+ not isinstance(data, dict) or not required.issubset(data)
214
+ ):
215
+ raise ValueError("File does not contain a complete {} payload.".format(kind))
216
+ return data
217
+
218
+
219
+ def _encode(value, arrays):
220
+ if value is None or isinstance(value, (bool, int, str)):
221
+ return value
222
+ if isinstance(value, float):
223
+ if np.isfinite(value):
224
+ return value
225
+ return {"__type__": "float", "value": repr(value)}
226
+ if isinstance(value, np.generic):
227
+ return _encode(value.item(), arrays)
228
+ if isinstance(value, np.ndarray):
229
+ if value.dtype.hasobject:
230
+ return {"__type__": "object_array", "value": _encode(value.tolist(), arrays)}
231
+ name = "arrays/{:08d}.npy".format(len(arrays))
232
+ arrays.append((name, value))
233
+ return {"__type__": "ndarray", "name": name}
234
+ if isinstance(value, dict):
235
+ return {
236
+ "__type__": "dict",
237
+ "items": [[_encode(key, arrays), _encode(item, arrays)] for key, item in value.items()],
238
+ }
239
+ if isinstance(value, list):
240
+ return {"__type__": "list", "items": [_encode(item, arrays) for item in value]}
241
+ if isinstance(value, tuple):
242
+ return {"__type__": "tuple", "items": [_encode(item, arrays) for item in value]}
243
+ if isinstance(value, Path):
244
+ return {"__type__": "path", "value": str(value)}
245
+ if value.__class__.__name__ == "QColor" and hasattr(value, "getRgb"):
246
+ return {"__type__": "color", "rgba": list(value.getRgb())}
247
+ raise TypeError(
248
+ "Unsupported DriftlessMap data type: {}".format(type(value).__name__)
249
+ )
250
+
251
+
252
+ def _decode(value, archive):
253
+ if not isinstance(value, dict) or "__type__" not in value:
254
+ return value
255
+ value_type = value["__type__"]
256
+ if value_type == "float":
257
+ return float(value["value"])
258
+ if value_type == "ndarray":
259
+ name = value["name"]
260
+ if name not in archive.namelist() or not name.startswith("arrays/"):
261
+ raise ValueError("Archive references a missing array: {}".format(name))
262
+ with archive.open(name) as stream:
263
+ return np.lib.format.read_array(stream, allow_pickle=False)
264
+ if value_type == "object_array":
265
+ return np.asarray(_decode(value["value"], archive), dtype=object)
266
+ if value_type == "dict":
267
+ return {
268
+ _decode(key, archive): _decode(item, archive)
269
+ for key, item in value["items"]
270
+ }
271
+ if value_type == "list":
272
+ return [_decode(item, archive) for item in value["items"]]
273
+ if value_type == "tuple":
274
+ return tuple(_decode(item, archive) for item in value["items"])
275
+ if value_type == "path":
276
+ return Path(value["value"])
277
+ if value_type == "color":
278
+ return tuple(value["rgba"])
279
+ raise ValueError(
280
+ "Unknown DriftlessMap archive value type: {}".format(value_type)
281
+ )
282
+
283
+
284
+ def save_driftlessmap_file(file_path, data, kind):
285
+ """Atomically save data in the versioned DriftlessMap archive format."""
286
+ destination = Path(file_path)
287
+ arrays = []
288
+ try:
289
+ _validate_payload(data, kind)
290
+ encoded = _encode(data, arrays)
291
+ manifest = json.dumps(
292
+ {
293
+ "format": FORMAT_NAME,
294
+ "version": FORMAT_VERSION,
295
+ "kind": kind,
296
+ "data": encoded,
297
+ },
298
+ allow_nan=False,
299
+ separators=(",", ":"),
300
+ ).encode("utf-8")
301
+ destination.parent.mkdir(parents=True, exist_ok=True)
302
+ with tempfile.NamedTemporaryFile(
303
+ dir=str(destination.parent),
304
+ prefix=".driftlessmap-",
305
+ suffix=".tmp",
306
+ delete=False,
307
+ ) as temporary:
308
+ temporary_path = Path(temporary.name)
309
+ try:
310
+ with zipfile.ZipFile(
311
+ temporary_path, "w", compression=zipfile.ZIP_DEFLATED, allowZip64=True
312
+ ) as archive:
313
+ archive.writestr(MANIFEST_NAME, manifest)
314
+ for name, array in arrays:
315
+ with archive.open(name, "w", force_zip64=True) as stream:
316
+ np.lib.format.write_array(stream, array, allow_pickle=False)
317
+ os.replace(str(temporary_path), str(destination))
318
+ except Exception:
319
+ temporary_path.unlink(missing_ok=True)
320
+ raise
321
+ except Exception as exc:
322
+ return False, "Unable to save DriftlessMap file: {}".format(exc)
323
+ return True, None
324
+
325
+
326
+ def load_driftlessmap_file(file_path, expected_kind=None):
327
+ """Load a DriftlessMap or legacy HERBS archive safely."""
328
+ try:
329
+ if not zipfile.is_zipfile(file_path):
330
+ data, error = load_legacy_pickle(file_path)
331
+ if error is None and expected_kind is not None:
332
+ try:
333
+ return _validate_payload(data, expected_kind), None
334
+ except ValueError as exc:
335
+ return None, "Invalid DriftlessMap file: {}".format(exc)
336
+ return data, error
337
+
338
+ with zipfile.ZipFile(file_path, "r") as archive:
339
+ infos = archive.infolist()
340
+ names = [info.filename for info in infos]
341
+ if len(names) != len(set(names)):
342
+ raise ValueError("Archive contains duplicate entries.")
343
+ total_size = sum(info.file_size for info in infos)
344
+ if total_size > MAX_ARCHIVE_BYTES:
345
+ raise ValueError("Archive expands beyond the supported size limit.")
346
+ if MANIFEST_NAME not in names:
347
+ raise ValueError("Archive has no DriftlessMap manifest.")
348
+ manifest_info = archive.getinfo(MANIFEST_NAME)
349
+ if manifest_info.file_size > MAX_MANIFEST_BYTES:
350
+ raise ValueError("DriftlessMap manifest is too large.")
351
+ manifest = json.loads(archive.read(MANIFEST_NAME).decode("utf-8"))
352
+ if manifest.get("format") not in SUPPORTED_FORMAT_NAMES:
353
+ raise ValueError("File is not a DriftlessMap or HERBS archive.")
354
+ if manifest.get("version") != FORMAT_VERSION:
355
+ raise ValueError(
356
+ "Unsupported DriftlessMap archive version: {}".format(
357
+ manifest.get("version")
358
+ )
359
+ )
360
+ if expected_kind is not None and manifest.get("kind") != expected_kind:
361
+ raise ValueError(
362
+ "Expected a {} file, found {}.".format(
363
+ expected_kind, manifest.get("kind")
364
+ )
365
+ )
366
+ data = _decode(manifest["data"], archive)
367
+ if expected_kind is not None:
368
+ data = _validate_payload(data, expected_kind)
369
+ return data, None
370
+ except Exception as exc:
371
+ return None, "Invalid DriftlessMap file: {}".format(exc)
372
+
373
+
374
+ # Backward compatibility for the public helper names used by HERBS scripts.
375
+ save_herbs_file = save_driftlessmap_file
376
+ load_herbs_file = load_driftlessmap_file
@@ -0,0 +1,16 @@
1
+ import PyQt6
2
+ from PyQt6.QtWidgets import *
3
+ from PyQt6.QtCore import *
4
+ from PyQt6.QtGui import *
5
+
6
+
7
+ class PopupMessage(QMessageBox):
8
+
9
+ def __init__(self, parent=None):
10
+ QMessageBox.__init__(self)
11
+
12
+ self.setWindowTitle("Caution!")
13
+ self.setText('Histological image: is oversized.')
14
+ button = self.exec()
15
+ if button == QMessageBox.StandardButton.Ok:
16
+ print('222')