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.
- driftlessmap/__init__.py +16 -0
- driftlessmap/__main__.py +5 -0
- driftlessmap/about.py +24 -0
- driftlessmap/allen_downloader.py +908 -0
- driftlessmap/app.py +8085 -0
- driftlessmap/atlas_downloader.py +370 -0
- driftlessmap/atlas_loader.py +526 -0
- driftlessmap/atlas_processor.py +784 -0
- driftlessmap/atlas_transform.py +111 -0
- driftlessmap/atlas_view.py +1435 -0
- driftlessmap/cell_detection.py +27 -0
- driftlessmap/coordinate_validation.py +19 -0
- driftlessmap/czi_reader.py +236 -0
- driftlessmap/data/WHS_atlas_labels.pkl +0 -0
- driftlessmap/data/allen_mice_atlas_labels.pkl +0 -0
- driftlessmap/data/atlas_labels.pkl +0 -0
- driftlessmap/data/query.csv +1328 -0
- driftlessmap/download_utils.py +85 -0
- driftlessmap/herbs.png +0 -0
- driftlessmap/icons/backward.svg +15 -0
- driftlessmap/icons/dot.svg +11 -0
- driftlessmap/icons/down-arrow.svg +10 -0
- driftlessmap/icons/fast_backward.svg +14 -0
- driftlessmap/icons/fast_forward.svg +14 -0
- driftlessmap/icons/forward.svg +14 -0
- driftlessmap/icons/layers/add.png +0 -0
- driftlessmap/icons/layers/ai.png +0 -0
- driftlessmap/icons/layers/eye_off.png +0 -0
- driftlessmap/icons/layers/eye_on.png +0 -0
- driftlessmap/icons/layers/eye_white.png +0 -0
- driftlessmap/icons/layers/hi.png +0 -0
- driftlessmap/icons/layers/trash.png +0 -0
- driftlessmap/icons/object.svg +22 -0
- driftlessmap/icons/sidebar/add.png +0 -0
- driftlessmap/icons/sidebar/atlascontrol.png +0 -0
- driftlessmap/icons/sidebar/atlascontrol.svg +32 -0
- driftlessmap/icons/sidebar/bnd.svg +14 -0
- driftlessmap/icons/sidebar/c_section.png +0 -0
- driftlessmap/icons/sidebar/c_section2.png +0 -0
- driftlessmap/icons/sidebar/cell.svg +24 -0
- driftlessmap/icons/sidebar/check.svg +9 -0
- driftlessmap/icons/sidebar/compare.svg +18 -0
- driftlessmap/icons/sidebar/contour.svg +14 -0
- driftlessmap/icons/sidebar/h_section.png +0 -0
- driftlessmap/icons/sidebar/info.svg +14 -0
- driftlessmap/icons/sidebar/layers.png +0 -0
- driftlessmap/icons/sidebar/layers.svg +23 -0
- driftlessmap/icons/sidebar/line.svg +10 -0
- driftlessmap/icons/sidebar/link.svg +18 -0
- driftlessmap/icons/sidebar/link_off.svg +18 -0
- driftlessmap/icons/sidebar/merge.svg +11 -0
- driftlessmap/icons/sidebar/object.svg +13 -0
- driftlessmap/icons/sidebar/probe.png +0 -0
- driftlessmap/icons/sidebar/probe.svg +23 -0
- driftlessmap/icons/sidebar/rotation_horizontal.svg +14 -0
- driftlessmap/icons/sidebar/rotation_vertical.svg +14 -0
- driftlessmap/icons/sidebar/s_section.png +0 -0
- driftlessmap/icons/sidebar/tool.png +0 -0
- driftlessmap/icons/sidebar/tool.svg +19 -0
- driftlessmap/icons/sidebar/trash.png +0 -0
- driftlessmap/icons/sidebar/tree_checked.svg +10 -0
- driftlessmap/icons/sidebar/treeview.png +0 -0
- driftlessmap/icons/sidebar/treeview.svg +10 -0
- driftlessmap/icons/sidebar/treeview2.png +0 -0
- driftlessmap/icons/sidebar/virus.svg +24 -0
- driftlessmap/icons/tdown.svg +16 -0
- driftlessmap/icons/toolbar/accept.svg +18 -0
- driftlessmap/icons/toolbar/accept2.svg +24 -0
- driftlessmap/icons/toolbar/aim.svg +44 -0
- driftlessmap/icons/toolbar/aim_not.svg +28 -0
- driftlessmap/icons/toolbar/anchor.svg +14 -0
- driftlessmap/icons/toolbar/anticlockwise_rotation.svg +12 -0
- driftlessmap/icons/toolbar/atlas_icon.png +0 -0
- driftlessmap/icons/toolbar/boundary_register.svg +18 -0
- driftlessmap/icons/toolbar/cancel.svg +32 -0
- driftlessmap/icons/toolbar/cell_select.svg +22 -0
- driftlessmap/icons/toolbar/cell_select_not.svg +12 -0
- driftlessmap/icons/toolbar/check.svg +9 -0
- driftlessmap/icons/toolbar/clockwise_rotation.svg +11 -0
- driftlessmap/icons/toolbar/closed_path.svg +16 -0
- driftlessmap/icons/toolbar/closed_path2 copy.svg +19 -0
- driftlessmap/icons/toolbar/closed_path2.svg +20 -0
- driftlessmap/icons/toolbar/eraser.png +0 -0
- driftlessmap/icons/toolbar/eraser.svg +19 -0
- driftlessmap/icons/toolbar/eye.svg +17 -0
- driftlessmap/icons/toolbar/eye_closed.svg +19 -0
- driftlessmap/icons/toolbar/fill.svg +12 -0
- driftlessmap/icons/toolbar/gps.svg +15 -0
- driftlessmap/icons/toolbar/h_flip.png +0 -0
- driftlessmap/icons/toolbar/handle.png +0 -0
- driftlessmap/icons/toolbar/image_icon.png +0 -0
- driftlessmap/icons/toolbar/image_icon.svg +25 -0
- driftlessmap/icons/toolbar/info.svg +14 -0
- driftlessmap/icons/toolbar/inpart.png +0 -0
- driftlessmap/icons/toolbar/inpart.svg +17 -0
- driftlessmap/icons/toolbar/lasso.png +0 -0
- driftlessmap/icons/toolbar/lasso.svg +14 -0
- driftlessmap/icons/toolbar/left90.png +0 -0
- driftlessmap/icons/toolbar/line.svg +10 -0
- driftlessmap/icons/toolbar/line_sites.svg +11 -0
- driftlessmap/icons/toolbar/linear_silicon.png +0 -0
- driftlessmap/icons/toolbar/linear_silicon.svg +88 -0
- driftlessmap/icons/toolbar/list.svg +14 -0
- driftlessmap/icons/toolbar/location.svg +14 -0
- driftlessmap/icons/toolbar/magic-wand.svg +16 -0
- driftlessmap/icons/toolbar/magic_white.png +0 -0
- driftlessmap/icons/toolbar/mask.svg +10 -0
- driftlessmap/icons/toolbar/match.svg +20 -0
- driftlessmap/icons/toolbar/matchbnd.svg +18 -0
- driftlessmap/icons/toolbar/matching.svg +36 -0
- driftlessmap/icons/toolbar/merge.svg +11 -0
- driftlessmap/icons/toolbar/move_down.png +0 -0
- driftlessmap/icons/toolbar/move_left.png +0 -0
- driftlessmap/icons/toolbar/move_right.png +0 -0
- driftlessmap/icons/toolbar/move_up.png +0 -0
- driftlessmap/icons/toolbar/moving.png +0 -0
- driftlessmap/icons/toolbar/multi-probe.svg +119 -0
- driftlessmap/icons/toolbar/multi_pencil.svg +23 -0
- driftlessmap/icons/toolbar/multi_probe.png +0 -0
- driftlessmap/icons/toolbar/object.svg +13 -0
- driftlessmap/icons/toolbar/open_path.svg +22 -0
- driftlessmap/icons/toolbar/outpart.png +0 -0
- driftlessmap/icons/toolbar/outpart.svg +26 -0
- driftlessmap/icons/toolbar/pencil.png +0 -0
- driftlessmap/icons/toolbar/pencil.svg +19 -0
- driftlessmap/icons/toolbar/plasso.png +0 -0
- driftlessmap/icons/toolbar/probe.svg +19 -0
- driftlessmap/icons/toolbar/radar.svg +32 -0
- driftlessmap/icons/toolbar/rotation.svg +45 -0
- driftlessmap/icons/toolbar/rotation_clockwise.svg +18 -0
- driftlessmap/icons/toolbar/rotation_counter_clockwise.svg +18 -0
- driftlessmap/icons/toolbar/ruler.svg +27 -0
- driftlessmap/icons/toolbar/separate_sites.svg +13 -0
- driftlessmap/icons/toolbar/single_pencil.svg +16 -0
- driftlessmap/icons/toolbar/toa.svg +11 -0
- driftlessmap/icons/toolbar/toa_delete.svg +36 -0
- driftlessmap/icons/toolbar/toaa.svg +11 -0
- driftlessmap/icons/toolbar/toh.svg +11 -0
- driftlessmap/icons/toolbar/toh_delete.svg +36 -0
- driftlessmap/icons/toolbar/tohh.svg +11 -0
- driftlessmap/icons/toolbar/trans.png +0 -0
- driftlessmap/icons/toolbar/trans.svg +16 -0
- driftlessmap/icons/toolbar/triangulation.svg +10 -0
- driftlessmap/icons/toolbar/two_window.png +0 -0
- driftlessmap/icons/toolbar/unmerge.svg +16 -0
- driftlessmap/icons/toolbar/v_flip.png +0 -0
- driftlessmap/icons/toolbar/virus.svg +24 -0
- driftlessmap/icons/toolbar/virus_register.svg +31 -0
- driftlessmap/icons/toolbar/vis2d.svg +29 -0
- driftlessmap/icons/toolbar/window2.png +0 -0
- driftlessmap/icons/toolbar/window3.png +0 -0
- driftlessmap/icons/toolbar/window4.png +0 -0
- driftlessmap/icons/tree_close.svg +14 -0
- driftlessmap/icons/tree_open.svg +14 -0
- driftlessmap/icons/up-arrow.svg +10 -0
- driftlessmap/image_curves.py +579 -0
- driftlessmap/image_reader.py +186 -0
- driftlessmap/image_stacks.py +359 -0
- driftlessmap/image_view.py +646 -0
- driftlessmap/images_reader.py +5 -0
- driftlessmap/label_tree.py +278 -0
- driftlessmap/layer_validation.py +25 -0
- driftlessmap/layers_control.py +554 -0
- driftlessmap/main_window.ui +883 -0
- driftlessmap/movable_points.py +254 -0
- driftlessmap/obj_items.py +122 -0
- driftlessmap/object_control.py +1762 -0
- driftlessmap/persistence.py +376 -0
- driftlessmap/popup_message.py +16 -0
- driftlessmap/probe_csv.py +319 -0
- driftlessmap/probe_reconstruction.py +409 -0
- driftlessmap/probe_utiles.py +1442 -0
- driftlessmap/qss/atlas_view_group_box.qss +15 -0
- driftlessmap/qss/box_label.qss +7 -0
- driftlessmap/qss/channel_selector.qss +19 -0
- driftlessmap/qss/color_combo.qss +121 -0
- driftlessmap/qss/decor_label.qss +9 -0
- driftlessmap/qss/dialogs.qss +11 -0
- driftlessmap/qss/hidden_line_edit.qss +5 -0
- driftlessmap/qss/label_tree.qss +123 -0
- driftlessmap/qss/main_window.qss +243 -0
- driftlessmap/qss/menu_bar.qss +42 -0
- driftlessmap/qss/multi_handle_slider.qss +14 -0
- driftlessmap/qss/obj_ctrl_bottom_button.qss +29 -0
- driftlessmap/qss/object_text_button.qss +19 -0
- driftlessmap/qss/page_control.qss +36 -0
- driftlessmap/qss/rotation_button.qss +9 -0
- driftlessmap/qss/side_bar.qss +23 -0
- driftlessmap/qss/spinbox.qss +106 -0
- driftlessmap/qss/tabs.qss +211 -0
- driftlessmap/qss/tool_bar.qss +77 -0
- driftlessmap/resources.py +34 -0
- driftlessmap/roi_analysis.py +375 -0
- driftlessmap/run_driftlessmap.py +4 -0
- driftlessmap/slice_stacks.py +268 -0
- driftlessmap/slice_validation.py +20 -0
- driftlessmap/toolbox.py +569 -0
- driftlessmap/triangulation.py +517 -0
- driftlessmap/triangulation_points.py +108 -0
- driftlessmap/user_settings.py +92 -0
- driftlessmap/uuuuuu.py +1168 -0
- driftlessmap/version.py +3 -0
- driftlessmap/widgets_utils.py +140 -0
- driftlessmap/wtiles.py +715 -0
- driftlessmap-1.1.0.dist-info/METADATA +186 -0
- driftlessmap-1.1.0.dist-info/RECORD +210 -0
- driftlessmap-1.1.0.dist-info/WHEEL +5 -0
- driftlessmap-1.1.0.dist-info/entry_points.txt +2 -0
- driftlessmap-1.1.0.dist-info/licenses/LICENSE.txt +23 -0
- 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')
|