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,186 @@
1
+ """Readers that normalize supported image files for :class:`ImageView`."""
2
+
3
+ import colorsys
4
+ from pathlib import Path
5
+
6
+ import cv2
7
+ import numpy as np
8
+ import tifffile
9
+
10
+
11
+ MAX_CHANNELS = 4
12
+ RGB_COLORS = [(255, 0, 0), (0, 255, 0), (0, 0, 255)]
13
+ CHANNEL_COLORS = [(128, 128, 128), (255, 0, 0), (0, 255, 0), (0, 0, 255)]
14
+ CHANNEL_NAMES = ["Gray", "Red", "Green", "Blue"]
15
+ HISTOLOGY_IMAGE_FILTERS = (
16
+ "TIFF (*.tif *.tiff)",
17
+ "CZI (*.czi)",
18
+ "JPEG (*.jpg *.jpeg)",
19
+ "PNG (*.png)",
20
+ "BMP (*.bmp)",
21
+ )
22
+ HISTOLOGY_IMAGE_FILTER = ";;".join(HISTOLOGY_IMAGE_FILTERS)
23
+
24
+
25
+ def _hsv_colors(rgb_colors):
26
+ result = []
27
+ for red, green, blue in rgb_colors:
28
+ hue, saturation, value = colorsys.rgb_to_hsv(red, green, blue)
29
+ result.append((hue, saturation, value / 255))
30
+ return result
31
+
32
+
33
+ def _set_channel_metadata(reader, rgb_colors, channel_names):
34
+ reader.rgb_colors = list(rgb_colors)
35
+ reader.channel_name = list(channel_names)
36
+ reader.hsv_colors = _hsv_colors(reader.rgb_colors)
37
+ reader.gamma_val = []
38
+
39
+
40
+ class ImageReader(object):
41
+ """Read a conventional bitmap as an eight-bit RGB image."""
42
+
43
+ def __init__(self, image_file_path):
44
+ self.error_index = 0
45
+ self.is_czi = False
46
+ self.file_name_list = [str(Path(image_file_path).with_suffix(""))]
47
+ self.n_scenes = 1
48
+ self.n_pages = 1
49
+ self.scaling_val = None
50
+ self.is_rgb = True
51
+ self.pixel_type = "rgb24"
52
+ self.level = 255
53
+ self.n_channels = 3
54
+ self.data_type = "uint8"
55
+ _set_channel_metadata(self, RGB_COLORS, ["Red", "Green", "Blue"])
56
+
57
+ image = cv2.imread(str(image_file_path), cv2.IMREAD_COLOR)
58
+ if image is None:
59
+ raise ValueError("OpenCV could not decode the selected image.")
60
+ self.data = {"scene 0": cv2.cvtColor(image, cv2.COLOR_BGR2RGB)}
61
+ self.scale = {"scene 0": 1.0}
62
+
63
+
64
+ class TIFFReader(object):
65
+ """Read grayscale, RGB, channel, or page-stack TIFF data."""
66
+
67
+ def __init__(self, image_file_path):
68
+ self.error_index = 0
69
+ self.is_czi = False
70
+ self.file_name_list = [str(Path(image_file_path).with_suffix(""))]
71
+ self.n_scenes = 0
72
+ self.n_pages = 1
73
+ self.scaling_val = None
74
+ self.software = None
75
+ self.is_imagej = False
76
+ self.is_rgb = False
77
+ self.pixel_type = None
78
+ self.level = None
79
+ self.n_channels = 0
80
+ self.data_type = None
81
+ self.data = {}
82
+ self.scale = {}
83
+ _set_channel_metadata(self, [], [])
84
+
85
+ with tifffile.TiffFile(image_file_path) as tiff_file:
86
+ self.n_scenes = len(tiff_file.series)
87
+ self.is_imagej = tiff_file.is_imagej
88
+ if tiff_file.pages:
89
+ self.software = tiff_file.pages[0].software
90
+ if self.n_scenes != 1:
91
+ self.error_index = 1
92
+ return
93
+
94
+ series = tiff_file.series[0]
95
+ image = np.asarray(series.asarray())
96
+ axes = series.axes
97
+
98
+ if image.dtype not in (np.dtype("uint8"), np.dtype("uint16")):
99
+ self.error_index = 2
100
+ return
101
+
102
+ self.data_type = image.dtype.name
103
+ self.level = int(np.iinfo(image.dtype).max)
104
+ bit_depth = image.dtype.itemsize * 8
105
+
106
+ if image.ndim == 2:
107
+ image = image[..., None]
108
+ self._set_grayscale(image, bit_depth, 1)
109
+ elif image.ndim == 3 and image.shape[-1] in (3, 4) and axes.endswith("S"):
110
+ self.is_rgb = True
111
+ self.pixel_type = "rgb{}".format(bit_depth * 3)
112
+ self.n_channels = 3
113
+ image = image[..., :3]
114
+ _set_channel_metadata(self, RGB_COLORS, ["Red", "Green", "Blue"])
115
+ elif image.ndim == 3 and "C" in axes:
116
+ channel_axis = axes.index("C")
117
+ image = np.moveaxis(image, channel_axis, -1)
118
+ self._set_grayscale(image, bit_depth, image.shape[-1])
119
+ elif image.ndim == 3 and axes.endswith("YX"):
120
+ self.n_pages = image.shape[0]
121
+ self.is_rgb = False
122
+ self.pixel_type = "gray{}".format(bit_depth)
123
+ self.n_channels = 1
124
+ _set_channel_metadata(self, CHANNEL_COLORS[:1], CHANNEL_NAMES[:1])
125
+ else:
126
+ self.error_index = 7
127
+ return
128
+
129
+ if self.n_channels > MAX_CHANNELS:
130
+ self.error_index = 8
131
+ return
132
+
133
+ self.data["scene 0"] = image
134
+ self.scale["scene 0"] = 1.0
135
+
136
+ def _set_grayscale(self, image, bit_depth, n_channels):
137
+ self.is_rgb = False
138
+ self.pixel_type = "gray{}".format(bit_depth)
139
+ self.n_channels = n_channels
140
+ _set_channel_metadata(
141
+ self, CHANNEL_COLORS[:n_channels], CHANNEL_NAMES[:n_channels]
142
+ )
143
+
144
+
145
+ class ImagesReader(object):
146
+ """Read a folder of conventional images as deterministic RGB scenes."""
147
+
148
+ SUPPORTED_SUFFIXES = {".bmp", ".jpg", ".jpeg", ".png", ".tif", ".tiff"}
149
+
150
+ def __init__(self, folder_path):
151
+ self.error_index = 0
152
+ self.is_czi = False
153
+ self.is_rgb = True
154
+ self.n_channels = 3
155
+ self.n_pages = 1
156
+ self.level = 255
157
+ self.data_type = "uint8"
158
+ self.pixel_type = "rgb24"
159
+ self.scaling_val = None
160
+ _set_channel_metadata(self, RGB_COLORS, ["Red", "Green", "Blue"])
161
+ self.file_name_list = []
162
+ self.data = {}
163
+ self.scale = {}
164
+
165
+ paths = sorted(
166
+ (
167
+ path
168
+ for path in Path(folder_path).iterdir()
169
+ if path.is_file() and path.suffix.lower() in self.SUPPORTED_SUFFIXES
170
+ ),
171
+ key=lambda path: path.name.casefold(),
172
+ )
173
+ if not paths:
174
+ raise ValueError("The selected folder contains no supported images.")
175
+
176
+ for scene_id, path in enumerate(paths):
177
+ image = cv2.imread(str(path), cv2.IMREAD_COLOR)
178
+ if image is None:
179
+ raise ValueError("Could not decode image: {}".format(path.name))
180
+ self.file_name_list.append(path.stem)
181
+ self.data["scene {}".format(scene_id)] = cv2.cvtColor(
182
+ image, cv2.COLOR_BGR2RGB
183
+ )
184
+ self.scale["scene {}".format(scene_id)] = 1.0
185
+
186
+ self.n_scenes = len(self.data)
@@ -0,0 +1,359 @@
1
+ import colorsys
2
+ import os
3
+ import sys
4
+ import numpy as np
5
+ from PyQt6.QtWidgets import *
6
+ from PyQt6.QtCore import *
7
+ from PyQt6.QtGui import *
8
+ import pyqtgraph as pg
9
+ from pyqtgraph.Qt import QtGui, QtCore
10
+ from .movable_points import TriangulationPoints
11
+
12
+
13
+ class ClickableImage(pg.ImageItem):
14
+ class SignalProxy(QObject):
15
+ mouseHovered = pyqtSignal(object) # id
16
+ mouseClicked = pyqtSignal(object) # id
17
+
18
+ def __init__(self):
19
+ self._sigprox = ClickableImage.SignalProxy()
20
+ self.mouseHovered = self._sigprox.mouseHovered
21
+ self.mouseClicked = self._sigprox.mouseClicked
22
+
23
+ pg.ImageItem.__init__(self)
24
+
25
+ self.hist_data = None
26
+
27
+ self.setAcceptHoverEvents(True)
28
+ # self.setOpts(axisOrder='row-major')
29
+
30
+ def set_data(self, hist_image_data, scale=None):
31
+ # self.label_data = label
32
+ self.hist_data = hist_image_data
33
+ if scale is not None:
34
+ self.resetTransform()
35
+ self.scale(*scale)
36
+ self.setImage(self.hist_data, autoLevels=False)
37
+ # self.label_img.setImage(self.label_data, autoLevels=False)
38
+
39
+ def hoverEvent(self, event):
40
+ if event.isExit():
41
+ return
42
+ try:
43
+ pos = (event.pos())
44
+ id = 1 # self.label_data[int(event.pos().x()), int(event.pos().y())]
45
+ except (IndexError, AttributeError):
46
+ return
47
+ self.mouseHovered.emit(event)
48
+
49
+ def mouseClickEvent(self, event):
50
+ pos = (event.pos().x(), event.pos().y())
51
+ # This item owns background-image clicks. Accept before emitting because
52
+ # the callback may update or remove plot items that pyqtgraph also found
53
+ # under the cursor for this same click.
54
+ event.accept()
55
+ # print(pos)
56
+ # id = self.label_data[int(event.pos().x()), int(event.pos().y())]
57
+ self.mouseClicked.emit(pos)
58
+ # self.mouseClicked.emit([event, id])
59
+
60
+
61
+ class SliceStack(pg.GraphicsLayoutWidget):
62
+ class SignalProxy(QObject):
63
+ mouseHovered = pyqtSignal(object) # id
64
+ mouseClicked = pyqtSignal(object) # id
65
+ sigKeyPressed = pyqtSignal(object) # event
66
+
67
+ def __init__(self):
68
+ self._sigprox = SliceStack.SignalProxy()
69
+ self.sig_mouse_hovered = self._sigprox.mouseHovered
70
+ self.sig_mouse_clicked = self._sigprox.mouseClicked
71
+ self.sig_key_pressed = self._sigprox.sigKeyPressed
72
+
73
+ pg.GraphicsLayoutWidget.__init__(self)
74
+
75
+ self.data = None
76
+ self.vb = self.addViewBox()
77
+ self.setBackground('k')
78
+ self.vb.setAspectLocked()
79
+ self.vb.invertY(True)
80
+ self.vb.setAcceptHoverEvents(True)
81
+ # self.vb.enableAutoRange(enable=False)
82
+
83
+ self.base_layer = ClickableImage()
84
+ self.base_layer.mouseClicked.connect(self.mouse_clicked)
85
+ self.base_layer.mouseHovered.connect(self.mouse_hovered)
86
+
87
+ self.img_layer = pg.ImageItem()
88
+ self.img_layer.setCompositionMode(QPainter.CompositionMode.CompositionMode_Plus)
89
+
90
+ circle_follow = pg.PlotDataItem(pen=pg.mkPen('r', width=2, style=Qt.PenStyle.DashLine))
91
+ lasso_path = pg.PlotDataItem(pen=pg.mkPen(color=(0, 255, 255), width=3, style=Qt.PenStyle.DashLine),
92
+ symbolPen=(0, 255, 255), symbol='o', symbolSize=4)
93
+
94
+ ruler_path = pg.PlotDataItem(pen=pg.mkPen(color='y', width=3, style=Qt.PenStyle.DashLine),
95
+ symbolPen='y', symbolBrush='y', symbol='s', symbolSize=4)
96
+
97
+ overlay_img = pg.ImageItem()
98
+ overlay_img.setCompositionMode(QPainter.CompositionMode.CompositionMode_Plus)
99
+ overlay_contour = pg.ImageItem()
100
+ overlay_contour.setLevels(levels=(0, 1))
101
+
102
+ mask_img = pg.ImageItem()
103
+ mask_img.setLevels(levels=(0, 1))
104
+
105
+ tri_pnts = TriangulationPoints()
106
+ tri_pnts.setVisible(False)
107
+ self.tri_lines_list = []
108
+
109
+ grid_lines = pg.GridItem(pen=(128, 128, 128))
110
+ grid_lines.setVisible(False)
111
+
112
+ cell_pnts = pg.ScatterPlotItem(pen=(0, 255, 0), brush=(0, 255, 0), size=5, hoverSize=8)
113
+ probe_pnts = pg.ScatterPlotItem(pen=(0, 0, 255), brush=(0, 0, 255), symbol='s', size=5, hoverSize=8)
114
+ bregma_pnt = pg.ScatterPlotItem(pen=(180, 112, 57), brush=(180, 112, 57), symbol='star', size=10, hoverSize=15)
115
+
116
+ drawing_pnts = pg.PlotDataItem(pen=pg.mkPen(color=(255, 102, 0), width=2), brush=None)
117
+ contour_pnts = pg.PlotDataItem(pen=pg.mkPen(color=(255, 0, 255), width=3), brush=None)
118
+
119
+ probe_trajectory = pg.PlotDataItem(pen=pg.mkPen(color=(0, 0, 255), width=2), brush=None)
120
+
121
+ self.pre_trajectory_list = [pg.PlotDataItem(pen=pg.mkPen(color=(0, 0, 255), width=2), brush=None)]
122
+
123
+ self.image_dict = {'atlas-overlay': overlay_img,
124
+ 'overlay_contour': overlay_contour,
125
+ 'atlas-drawing': drawing_pnts,
126
+ 'atlas-contour': contour_pnts,
127
+ 'atlas-mask': mask_img,
128
+ 'grid_lines': grid_lines,
129
+ 'tri_pnts': tri_pnts,
130
+ 'circle_follow': circle_follow,
131
+ 'lasso_path': lasso_path,
132
+ 'atlas-probe': probe_pnts,
133
+ 'bregma_pnt': bregma_pnt,
134
+ 'ruler_path': ruler_path,
135
+ 'atlas-cells': cell_pnts,
136
+ 'atlas-trajectory': probe_trajectory}
137
+ self.image_dict_keys = list(self.image_dict.keys())
138
+
139
+ self.vb.addItem(self.base_layer)
140
+ self.vb.addItem(self.img_layer)
141
+
142
+ for i in range(len(self.image_dict)):
143
+ self.vb.addItem(self.image_dict[self.image_dict_keys[i]])
144
+
145
+ self.vb.addItem(self.pre_trajectory_list[0])
146
+
147
+ def set_data(self, data, scale=None):
148
+ if data.ndim != 3:
149
+ raise ValueError('Image data must have shape (height, width, channels).')
150
+ if data.shape[2] > len(self.image_list):
151
+ raise ValueError(
152
+ 'DriftlessMap supports at most {} image channels.'.format(
153
+ len(self.image_list)
154
+ )
155
+ )
156
+ self.data = data
157
+ if scale is not None:
158
+ self.resetTransform()
159
+ self.scale(*scale)
160
+ base_img = np.zeros(data.shape[:2], 'uint8')
161
+ self.base_layer.setImage(base_img)
162
+ self.img_layer.setImage(data)
163
+
164
+ def set_pre_design_vis_data(self, data):
165
+ n_probe = len(data)
166
+ exist_n_probes = len(self.pre_trajectory_list)
167
+ if n_probe < exist_n_probes:
168
+ del_ind = np.arange(n_probe, exist_n_probes)[::-1]
169
+ for i in del_ind:
170
+ self.vb.removeItem(self.pre_trajectory_list[i])
171
+ self.pre_trajectory_list[i].deleteLater()
172
+ del self.pre_trajectory_list[i]
173
+ elif n_probe > exist_n_probes:
174
+ for i in range(exist_n_probes, n_probe):
175
+ self.pre_trajectory_list.append(pg.PlotDataItem(pen=pg.mkPen(color=(0, 0, 255), width=2), brush=None))
176
+ self.vb.addItem(self.pre_trajectory_list[-1])
177
+
178
+ for i in range(n_probe):
179
+ self.pre_trajectory_list[i].setData(data[i])
180
+
181
+ def pre_trajectory_color_changed(self, col, lw):
182
+ for i in range(len(self.pre_trajectory_list)):
183
+ self.pre_trajectory_list[i].setPen(pg.mkPen(color=col, width=lw))
184
+
185
+ def remove_pre_trajectories_vis_lines(self):
186
+ del_ind = np.arange(len(self.pre_trajectory_list))[::-1]
187
+ for i in del_ind:
188
+ self.vb.removeItem(self.pre_trajectory_list[i])
189
+ self.pre_trajectory_list[i].deleteLater()
190
+ del self.pre_trajectory_list[i]
191
+ # self.pre_trajectory_list[i].updateItems(True)
192
+ self.pre_trajectory_list.clear()
193
+
194
+ def boundingRect(self):
195
+ return self.base_layer.boundingRect()
196
+
197
+ def mouse_hovered(self, event):
198
+ if event.isExit():
199
+ return
200
+ try:
201
+ pos = (event.pos())
202
+ id = 1 # self.label_data[int(event.pos().x()), int(event.pos().y())]
203
+ except (IndexError, AttributeError):
204
+ return
205
+ self.sig_mouse_hovered.emit(event)
206
+
207
+ def mouse_clicked(self, pos):
208
+ self.sig_mouse_clicked.emit(pos)
209
+
210
+ def keyPressEvent(self, event):
211
+ if event.key() == QtCore.Qt.Key.Key_Backspace:
212
+ print("Killing")
213
+ self.sig_key_pressed.emit('delete')
214
+
215
+
216
+ class ImageStacks(pg.GraphicsLayoutWidget):
217
+ class SignalProxy(QObject):
218
+ sigMouseHovered = pyqtSignal(object) # id
219
+ sigMouseClicked = pyqtSignal(object) # id
220
+ sigKeyPressed = pyqtSignal(object) # event
221
+
222
+ def __init__(self):
223
+ self._sigprox = ImageStacks.SignalProxy()
224
+ self.sig_mouse_hovered = self._sigprox.sigMouseHovered
225
+ self.sig_mouse_clicked = self._sigprox.sigMouseClicked
226
+ self.sig_key_pressed = self._sigprox.sigKeyPressed
227
+
228
+ pg.GraphicsLayoutWidget.__init__(self)
229
+
230
+ self.data = None
231
+ self.vb = self.addViewBox()
232
+ self.setBackground('k')
233
+ self.vb.setAspectLocked()
234
+ self.vb.invertY(True)
235
+ self.vb.setAcceptHoverEvents(True)
236
+ # self.vb.enableAutoRange(enable=False)
237
+
238
+ self.base_layer = ClickableImage()
239
+ self.base_layer.mouseClicked.connect(self.mouse_clicked)
240
+ self.base_layer.mouseHovered.connect(self.mouse_hovered)
241
+
242
+ img1 = pg.ImageItem()
243
+ img1.setCompositionMode(QPainter.CompositionMode.CompositionMode_Plus)
244
+ img2 = pg.ImageItem()
245
+ img2.setCompositionMode(QPainter.CompositionMode.CompositionMode_Plus)
246
+ img3 = pg.ImageItem()
247
+ img3.setCompositionMode(QPainter.CompositionMode.CompositionMode_Plus)
248
+ img4 = pg.ImageItem()
249
+ img4.setCompositionMode(QPainter.CompositionMode.CompositionMode_Plus)
250
+
251
+ circle_follow = pg.PlotDataItem(pen=pg.mkPen('r', width=2, style=Qt.PenStyle.DashLine))
252
+ lasso_path = pg.PlotDataItem(pen=pg.mkPen(color=(0, 255, 255), width=3, style=Qt.PenStyle.DashLine),
253
+ symbolPen=(0, 255, 255), symbol='o', symbolSize=4)
254
+ ruler_path = pg.PlotDataItem(pen=pg.mkPen(color='y', width=3, style=Qt.PenStyle.DashLine),
255
+ symbolPen='y', symbolBrush='y', symbol='s', symbolSize=4)
256
+
257
+ overlay_img = pg.ImageItem()
258
+ overlay_img.setCompositionMode(QPainter.CompositionMode.CompositionMode_Plus)
259
+ overlay_contour = pg.ImageItem()
260
+ overlay_contour.setLevels(levels=(0, 1))
261
+
262
+ mask_img = pg.ImageItem()
263
+ mask_img.setLevels(levels=(0, 1))
264
+
265
+ tri_pnts = TriangulationPoints()
266
+ tri_pnts.setVisible(False)
267
+ self.tri_lines_list = []
268
+
269
+ grid_lines = pg.GridItem(pen=(128, 128, 128))
270
+ grid_lines.setVisible(False)
271
+
272
+ probe_pnts = pg.ScatterPlotItem(pen=(0, 0, 255), brush=(0, 0, 255), symbol='s', size=5, hoverSize=8)
273
+ cell_pnts = pg.ScatterPlotItem(pen=(0, 255, 0), brush=(0, 255, 0), size=5, hoverSize=8)
274
+ blob_pnts = pg.ScatterPlotItem(pen=(55, 55, 55), brush=(55, 55, 55), symbol='s', size=5)
275
+ drawing_pnts = pg.PlotDataItem(pen=pg.mkPen(color=(255, 102, 0), width=2), brush=None)
276
+ contour_pnts = pg.PlotDataItem(pen=pg.mkPen(color=(255, 0, 255), width=3), brush=None)
277
+
278
+ probe_trajectory = pg.PlotDataItem(pen=pg.mkPen(color=(0, 0, 255), width=2), brush=None)
279
+
280
+ virus_img = pg.ImageItem()
281
+ virus_img.setLevels(levels=(0, 1))
282
+
283
+ self.image_list = [img1, img2, img3, img4]
284
+ self.image_dict = {'img-overlay': overlay_img,
285
+ 'overlay_contour': overlay_contour,
286
+ 'img-virus': virus_img,
287
+ 'img-contour': contour_pnts,
288
+ 'img-mask': mask_img,
289
+ 'grid_lines': grid_lines,
290
+ 'tri_pnts': tri_pnts,
291
+ 'circle_follow': circle_follow,
292
+ 'lasso_path': lasso_path,
293
+ 'ruler_path': ruler_path,
294
+ 'img-probe': probe_pnts,
295
+ 'img-cells': cell_pnts,
296
+ 'img-blob': blob_pnts,
297
+ 'img-drawing': drawing_pnts,
298
+ 'img-trajectory': probe_trajectory}
299
+ self.image_dict_keys = list(self.image_dict.keys())
300
+
301
+ self.vb.addItem(self.base_layer)
302
+ for i in range(4):
303
+ self.vb.addItem(self.image_list[i])
304
+ self.image_list[i].setVisible(False)
305
+
306
+ for i in range(len(self.image_dict)):
307
+ self.vb.addItem(self.image_dict[self.image_dict_keys[i]])
308
+
309
+ def set_data(self, data, scale=None):
310
+ self.data = data
311
+ if scale is not None:
312
+ self.resetTransform()
313
+ self.scale(*scale)
314
+ base_img = np.zeros(data.shape[:2], 'uint8')
315
+ self.base_layer.setImage(base_img)
316
+ for i in range(self.data.shape[2]):
317
+ self.image_list[i].setImage(self.data[:, :, i], autoLevels=False)
318
+ self.image_list[i].setVisible(True)
319
+ for i in range(self.data.shape[2], len(self.image_list)):
320
+ self.image_list[i].clear()
321
+ self.image_list[i].setVisible(False)
322
+
323
+ def set_lut(self, lut_list, bit_level):
324
+ for i in range(self.data.shape[2]):
325
+ self.image_list[i].setLevels(levels=(0, bit_level))
326
+ self.image_list[i].setLookupTable(lut_list[i])
327
+
328
+ def set_opacity(self, val):
329
+ if self.data is None:
330
+ return
331
+ for i in range(self.data.shape[2]):
332
+ self.image_list[i].setOpts(opacity=val)
333
+
334
+ def boundingRect(self):
335
+ return self.base_layer.boundingRect()
336
+
337
+ def mouse_hovered(self, event):
338
+ if event.isExit():
339
+ return
340
+ try:
341
+ pos = (event.pos())
342
+ id = 1 # self.label_data[int(event.pos().x()), int(event.pos().y())]
343
+ except (IndexError, AttributeError):
344
+ return
345
+ self.sig_mouse_hovered.emit(event)
346
+
347
+ def mouse_clicked(self, pos):
348
+ self.sig_mouse_clicked.emit(pos)
349
+
350
+ def change_background_color(self, color: tuple):
351
+ self.setBackground(color)
352
+
353
+ def keyPressEvent(self, event):
354
+ if event.key() == Qt.Key.Key_Backspace or event.key() == Qt.Key.Key_Delete:
355
+ print("Killing")
356
+ self.sig_key_pressed.emit('delete')
357
+ # elif event.key() == Qt.Key.Key_Enter or event.key() == Qt.Key.Key_Return:
358
+ # print('enter')
359
+ # self.sig_key_pressed.emit('enter')