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,554 @@
1
+ import os
2
+ import sys
3
+ import numpy as np
4
+ import pyqtgraph as pg
5
+ import pyqtgraph.functions as fn
6
+ from PyQt6.QtWidgets import *
7
+ from PyQt6.QtGui import *
8
+ from PyQt6.QtCore import *
9
+
10
+ import cv2
11
+
12
+ from .wtiles import *
13
+ from .resources import resource_path
14
+
15
+
16
+ DEFAULT_BLEND_MODE = "Overlay"
17
+
18
+
19
+ btm_style = '''
20
+
21
+ QPushButton {
22
+ border : None;
23
+ background: transparent;
24
+ margin: 0px;
25
+ padding-top: 0px;
26
+ border-radius: 0px;
27
+ min-width: 24px;
28
+ min-height: 24px;
29
+ }
30
+ QPushButton:checked {
31
+ background-color: #383838;
32
+ border: 1px solid #636363;
33
+ }
34
+
35
+ QPushButton:pressed {
36
+ background-color: #383838;
37
+ border: 1px solid #636363;
38
+ }
39
+
40
+ QPushButton:hover {
41
+ background-color: #383838;
42
+ border: 1px solid #636363;
43
+ }
44
+
45
+ '''
46
+
47
+
48
+ eye_button_style = '''
49
+ border-left: None;
50
+ border-right: 1px solid rgb(128, 128, 128);
51
+ border-top: None;
52
+ border-bottom: None;
53
+ border-radius:0px;
54
+ background: transparent;
55
+ '''
56
+
57
+ text_btn_style = '''
58
+ QDoubleButton {
59
+ background: transparent;
60
+ border: None;
61
+ border-radius:0px;
62
+ text-align:left;
63
+ padding-left: 5px;
64
+ padding-right: 0px;
65
+ color:rgb(240, 240, 240);
66
+ margin: 0px;
67
+ height: 60px;
68
+ width: 160px;
69
+ }
70
+
71
+ QDoubleButton:disabled {
72
+ color: rgb(190, 190, 190);
73
+ }
74
+ '''
75
+
76
+ line_edit_style = '''
77
+ QLineEdit {
78
+ background: transparent;
79
+ border: 1px solid green;
80
+ border-radius:0px;
81
+ text-align:left;
82
+ color: white;
83
+ padding-left: 5px;
84
+ margin: 0px;
85
+ height: 60px;
86
+ width: 160px;
87
+ }
88
+ '''
89
+
90
+
91
+ class SingleLayer(QWidget):
92
+ sig_clicked = pyqtSignal(object)
93
+ eye_clicked = pyqtSignal(object)
94
+ sig_delete = pyqtSignal(object)
95
+
96
+ def __init__(self, parent=None, layer_id=0, link='None'):
97
+ QWidget.__init__(self, parent=parent)
98
+
99
+ self.inactive_style = 'QFrame{background-color:rgb(83, 83, 83); border: 1px solid rgb(128, 128, 128);}'
100
+ self.active_style = 'QFrame{background-color:rgb(107, 107, 107); border: 1px solid rgb(128, 128, 128);}'
101
+
102
+ # self.setFixedWidth(280)
103
+ self.id = layer_id
104
+ self.link = link
105
+ self.active = True
106
+ self.vis = True
107
+ self.thumbnail_data = None
108
+ self.eye_button = QPushButton()
109
+ self.eye_button.setFixedSize(QSize(30, 60))
110
+ self.eye_button.setStyleSheet(eye_button_style)
111
+ self.eye_button.setCheckable(True)
112
+ eye_icon = QIcon()
113
+ eye_icon.addPixmap(QPixmap(resource_path("icons/layers/eye_on.png")), QIcon.Mode.Normal, QIcon.State.Off)
114
+ eye_icon.addPixmap(QPixmap(resource_path("icons/layers/eye_off.png")), QIcon.Mode.Normal, QIcon.State.On)
115
+ self.eye_button.setIcon(eye_icon)
116
+ self.eye_button.setIconSize(QSize(20, 20))
117
+ self.eye_button.clicked.connect(self.eye_on_click)
118
+
119
+ self.tbnail = QPushButton()
120
+ self.tbnail.setStyleSheet('border:1px solid black; border-radius:0px;')
121
+ self.tbnail.setFixedWidth(50)
122
+ self.tbnail.clicked.connect(self.on_click)
123
+
124
+ self.text_btn = QPushButton()
125
+ self.text_btn.setStyleSheet(text_btn_style)
126
+ self.text_btn.setText(link)
127
+ self.text_btn.setFixedSize(QSize(140, 60))
128
+ self.text_btn.clicked.connect(self.on_click)
129
+ # self.text_btn.double_clicked.connect(self.on_doubleclick)
130
+
131
+ # self.l_line_edit = QLineEdit()
132
+ # self.l_line_edit.setStyleSheet(line_edit_style)
133
+ # self.l_line_edit.setFixedSize(QSize(140, 60))
134
+ # self.l_line_edit.editingFinished.connect(self.enter_pressed)
135
+ # self.l_line_edit.setVisible(False)
136
+
137
+ self.trash_button = QPushButton()
138
+ self.trash_button.setFixedSize(QSize(25, 60))
139
+ self.trash_button.setStyleSheet(eye_button_style)
140
+ self.trash_button.setIcon(QIcon(resource_path('icons/sidebar/trash.png')))
141
+ self.trash_button.setIconSize(QSize(20, 20))
142
+ self.trash_button.clicked.connect(self.on_delete)
143
+
144
+ self.inner_frame = QFrame()
145
+ self.inner_frame.setStyleSheet(self.active_style)
146
+ self.inner_layout = QHBoxLayout(self.inner_frame)
147
+ self.inner_layout.setContentsMargins(0, 0, 0, 0)
148
+ self.inner_layout.setSpacing(0)
149
+ self.inner_layout.setAlignment(Qt.AlignmentFlag.AlignVCenter)
150
+ self.inner_layout.addWidget(self.eye_button)
151
+ self.inner_layout.addSpacing(5)
152
+ self.inner_layout.addWidget(self.tbnail)
153
+ self.inner_layout.addSpacing(5)
154
+ self.inner_layout.addWidget(self.text_btn)
155
+ # self.inner_layout.addWidget(self.l_line_edit)
156
+ self.inner_layout.addWidget(self.trash_button)
157
+ self.inner_layout.addStretch()
158
+
159
+ outer_layout = QHBoxLayout()
160
+ outer_layout.setContentsMargins(0, 0, 0, 0)
161
+ outer_layout.setSpacing(0)
162
+ outer_layout.setAlignment(Qt.AlignmentFlag.AlignVCenter)
163
+ outer_layout.addWidget(self.inner_frame)
164
+
165
+ self.setLayout(outer_layout)
166
+ self.setFixedHeight(60)
167
+
168
+ def set_checked(self, check):
169
+ self.active = check
170
+ if not self.active:
171
+ self.inner_frame.setStyleSheet(self.inactive_style)
172
+ else:
173
+ self.inner_frame.setStyleSheet(self.active_style)
174
+
175
+ def on_click(self):
176
+ self.set_checked(True)
177
+ self.sig_clicked.emit(self.id)
178
+
179
+ def eye_on_click(self):
180
+ if self.eye_button.isChecked():
181
+ self.vis = False
182
+ else:
183
+ self.vis = True
184
+ self.tbnail.setEnabled(self.vis)
185
+ self.text_btn.setEnabled(self.vis)
186
+ # self.l_line_edit.setEnabled(self.vis)
187
+ self.eye_clicked.emit((self.id, self.link, self.vis))
188
+
189
+ def on_delete(self):
190
+ self.sig_delete.emit(self.id)
191
+
192
+ # @pyqtSlot()
193
+ # def on_doubleclick(self):
194
+ # self.l_line_edit.setText(self.text_btn.text())
195
+ # self.l_line_edit.setVisible(True)
196
+ # self.text_btn.setVisible(False)
197
+ # self.l_line_edit.setFocus(True)
198
+ # self.on_click()
199
+ #
200
+ # def enter_pressed(self):
201
+ # self.l_line_edit.setVisible(False)
202
+ # self.text_btn.setText(self.l_line_edit.text())
203
+ # self.text_btn.setVisible(True)
204
+
205
+ def set_thumbnail_data(self, thumbnail_data):
206
+ if len(thumbnail_data.shape) == 2:
207
+ im = np.zeros((thumbnail_data.shape[0], thumbnail_data.shape[1], 3)).astype(np.uint8)
208
+ # im = thumbnail_data.copy()
209
+ im[:, :, 0] = thumbnail_data
210
+ im[:, :, 1] = thumbnail_data
211
+ im[:, :, 2] = thumbnail_data
212
+ else:
213
+ im = thumbnail_data.astype('uint8')
214
+
215
+ self.thumbnail_data = im.copy()
216
+
217
+ im_shape = np.ravel(im.shape[:2])
218
+
219
+ size = QSize(int(im_shape[1] / 2), int(im_shape[0] / 2))
220
+ image = QImage(im.data, im_shape[1], im_shape[0], im.strides[0], QImage.Format.Format_RGB888)
221
+ pixmap = QPixmap(image)
222
+
223
+ self.tbnail.setIcon(QIcon(pixmap.scaled(size)))
224
+ self.tbnail.setIconSize(size)
225
+
226
+ def is_checked(self):
227
+ return self.active
228
+
229
+ def get_link(self):
230
+ return self.link
231
+
232
+
233
+ class LayersControl(QWidget):
234
+
235
+ class SignalProxy(QObject):
236
+ sigOpacityChanged = pyqtSignal(object)
237
+ sigVisChanged = pyqtSignal(object)
238
+ sigBlendModeChanged = pyqtSignal(object)
239
+ sigDeleteLayer = pyqtSignal(object)
240
+
241
+ def __init__(self, parent=None):
242
+
243
+ self._sigprox = LayersControl.SignalProxy()
244
+ self.sig_opacity_changed = self._sigprox.sigOpacityChanged
245
+ self.sig_visible_changed = self._sigprox.sigVisChanged
246
+ self.sig_blend_mode_changed = self._sigprox.sigBlendModeChanged
247
+ self.sig_layer_deleted = self._sigprox.sigDeleteLayer
248
+
249
+ QWidget.__init__(self)
250
+
251
+ self.valid_blendable_link = ['img-mask', 'img-virus', 'img-contour', 'img-overlay', 'Image', 'atlas-overlay']
252
+
253
+ self.current_layer_index = []
254
+
255
+ self.layer_list = []
256
+ self.layer_id = []
257
+ self.layer_link = []
258
+ self.layer_opacity = []
259
+ self.layer_blend_mode = []
260
+ self.layer_color = []
261
+
262
+ self.layer_count = 0
263
+
264
+ blend_frame = QFrame()
265
+ blend_layout = QHBoxLayout(blend_frame)
266
+ blend_layout.setContentsMargins(0, 0, 0, 0)
267
+ blend_layout.setSpacing(5)
268
+
269
+ combo_label = QLabel('Composition:')
270
+ self.layer_blend_combo = QComboBox()
271
+ self.layer_blend_combo.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
272
+ self.layer_blend_combo.setEditable(False)
273
+ combo_value = ['Plus', 'Multiply', 'Overlay', 'SourceOver']
274
+ self.layer_blend_combo.addItems(combo_value)
275
+ self.layer_blend_combo.setCurrentText(DEFAULT_BLEND_MODE)
276
+ self.layer_blend_combo.currentTextChanged.connect(self.blend_mode_changed)
277
+
278
+ blend_layout.addWidget(combo_label)
279
+ blend_layout.addWidget(self.layer_blend_combo)
280
+
281
+ # opacity wrap
282
+ opacity_frame = QFrame()
283
+ opacity_layout = QHBoxLayout(opacity_frame)
284
+ opacity_layout.setContentsMargins(0, 0, 0, 0)
285
+ opacity_layout.setSpacing(5)
286
+
287
+ layer_opacity_label = QLabel('Opacity:')
288
+ self.layer_opacity_slider = QSlider(Qt.Orientation.Horizontal)
289
+ # self.layer_opacity_slider.setFixedWidth(100)
290
+ self.layer_opacity_slider.setMaximum(100)
291
+ self.layer_opacity_slider.setMinimum(0)
292
+ self.layer_opacity_slider.setValue(100)
293
+ self.layer_opacity_slider.sliderMoved.connect(self.opacity_slider_moved)
294
+ self.layer_opacity_slider.valueChanged.connect(self.change_opacity_label_value)
295
+ self.layer_opacity_val_label = QLabel('100%')
296
+
297
+ opacity_layout.addWidget(layer_opacity_label)
298
+ opacity_layout.addWidget(self.layer_opacity_slider)
299
+ opacity_layout.addWidget(self.layer_opacity_val_label)
300
+
301
+ self.layer_frame = QFrame()
302
+ self.layer_frame.setStyleSheet('background: transparent; border: 0px;')
303
+ self.layer_frame.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
304
+ self.layer_layout = QBoxLayout(QBoxLayout.Direction.BottomToTop, self.layer_frame)
305
+ self.layer_layout.setAlignment(Qt.AlignmentFlag.AlignBottom)
306
+ self.layer_layout.setContentsMargins(0, 0, 0, 0)
307
+ self.layer_layout.setSpacing(0)
308
+
309
+ self.layer_scroll = QScrollArea()
310
+ self.layer_scroll.setStyleSheet('background: transparent; border: 0px;')
311
+ self.layer_scroll.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
312
+ self.layer_scroll.setWidget(self.layer_frame)
313
+ self.layer_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
314
+ self.layer_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
315
+ self.layer_scroll.setWidgetResizable(True)
316
+
317
+ mid_frame = QFrame()
318
+ mid_frame.setStyleSheet('background: transparent; border: 1px solid rgb(128, 128, 128);')
319
+ mid_frame.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
320
+ mid_layout = QGridLayout(mid_frame)
321
+ mid_layout.setContentsMargins(0, 0, 0, 0)
322
+ mid_layout.setSpacing(0)
323
+ mid_layout.setAlignment(Qt.AlignmentFlag.AlignBottom)
324
+ mid_layout.addWidget(self.layer_scroll, 0, 0, 1, 1)
325
+
326
+ # extra btn
327
+ self.add_layer_btn = QPushButton()
328
+ self.add_layer_btn.setFixedSize(24, 24)
329
+ self.add_layer_btn.setStyleSheet(btm_style)
330
+ self.add_layer_btn.setIcon(QIcon(resource_path('icons/layers/add.png')))
331
+ self.add_layer_btn.setIconSize(QSize(20, 20))
332
+ self.add_layer_btn.clicked.connect(lambda: self.add_layer('Layer', []))
333
+
334
+ self.delete_layer_btn = QPushButton()
335
+ self.delete_layer_btn.setFixedSize(24, 24)
336
+ self.delete_layer_btn.setStyleSheet(btm_style)
337
+ self.delete_layer_btn.setIcon(QIcon(resource_path('icons/layers/trash.png')))
338
+ self.delete_layer_btn.setIconSize(QSize(20, 20))
339
+ self.delete_layer_btn.clicked.connect(self.delete_current_layers)
340
+
341
+ button_frame = QFrame()
342
+ button_layout = QHBoxLayout(button_frame)
343
+ button_layout.setContentsMargins(0, 0, 0, 0)
344
+ button_layout.addWidget(self.add_layer_btn)
345
+ button_layout.addWidget(self.delete_layer_btn)
346
+ button_layout.addStretch()
347
+
348
+ outer_layout = QVBoxLayout()
349
+ outer_layout.setSpacing(10)
350
+ outer_layout.addWidget(blend_frame)
351
+ outer_layout.addWidget(opacity_frame)
352
+ outer_layout.addWidget(mid_frame)
353
+ outer_layout.addWidget(button_frame)
354
+
355
+ self.setLayout(outer_layout)
356
+
357
+ def add_layer(self, widget_link, color):
358
+ self.layer_id.append(self.layer_count)
359
+ self.layer_link.append(widget_link)
360
+ self.layer_opacity.append(100)
361
+ self.layer_blend_mode.append(DEFAULT_BLEND_MODE)
362
+ self.sig_blend_mode_changed.emit((widget_link,
363
+ self.layer_blend_mode[-1]))
364
+ self.layer_color.append(color)
365
+ # self.layer_data.append(data)
366
+
367
+ new_layer = SingleLayer(layer_id=self.layer_count, link=widget_link)
368
+ new_layer.sig_clicked.connect(self.layer_clicked)
369
+ new_layer.eye_clicked.connect(self.layer_eye_clicked)
370
+ new_layer.sig_delete.connect(self.delete_layer_btn_clicked)
371
+
372
+ active_index = len(self.layer_id) - 1
373
+ if self.current_layer_index:
374
+ for da_ind in self.current_layer_index:
375
+ self.layer_list[da_ind].set_checked(False)
376
+ self.current_layer_index = [active_index]
377
+
378
+ self.layer_list.append(new_layer)
379
+ self.layer_layout.addWidget(self.layer_list[-1])
380
+ self.layer_count += 1
381
+
382
+ if self.layer_opacity[-1] != self.layer_opacity_slider.value():
383
+ self.layer_opacity_slider.setValue(self.layer_opacity[-1])
384
+ if self.layer_blend_mode[-1] != self.layer_blend_combo.currentText():
385
+ self.layer_blend_combo.setCurrentText(self.layer_blend_mode[-1])
386
+
387
+ def master_layers(self, res, layer_type, color):
388
+ if layer_type not in self.layer_link:
389
+ self.add_layer(layer_type, color)
390
+ self.layer_list[-1].set_thumbnail_data(res)
391
+ else:
392
+ da_ind = np.where(np.ravel(self.layer_link) == layer_type)[0][0]
393
+ self.layer_list[da_ind].set_thumbnail_data(res)
394
+
395
+ def delete_layer_btn_clicked(self, clicked_id):
396
+ matching_indexes = np.where(np.ravel(self.layer_id) == clicked_id)[0]
397
+ if len(matching_indexes) == 0:
398
+ return
399
+ delete_index = matching_indexes[0]
400
+ self.sig_layer_deleted.emit(self.layer_link[delete_index])
401
+ self.delete_layer(delete_index)
402
+
403
+ def delete_current_layers(self):
404
+ for delete_index in sorted(self.current_layer_index, reverse=True):
405
+ self.sig_layer_deleted.emit(self.layer_link[delete_index])
406
+ self.delete_layer(delete_index)
407
+
408
+ def delete_layer(self, delete_index):
409
+ self.layer_layout.removeWidget(self.layer_list[delete_index])
410
+ self.layer_list[delete_index].deleteLater()
411
+ del self.layer_list[delete_index]
412
+ del self.layer_id[delete_index]
413
+ del self.layer_link[delete_index]
414
+ del self.layer_opacity[delete_index]
415
+ del self.layer_blend_mode[delete_index]
416
+ del self.layer_color[delete_index]
417
+ # del self.layer_data[delete_index]
418
+ self.correct_current_layer_index(delete_index)
419
+
420
+ def correct_current_layer_index(self, delete_index):
421
+ if delete_index in self.current_layer_index:
422
+ remove_ind = np.where(np.ravel(self.current_layer_index) == delete_index)[0][0]
423
+ del self.current_layer_index[remove_ind]
424
+ for i in range(len(self.current_layer_index)):
425
+ if self.current_layer_index[i] > delete_index:
426
+ self.current_layer_index[i] -= 1
427
+
428
+ def layer_clicked(self, clicked_id):
429
+ clicked_index = np.where(np.ravel(self.layer_id) == clicked_id)[0][0]
430
+
431
+ modifiers = QApplication.keyboardModifiers()
432
+ if modifiers == Qt.KeyboardModifier.ControlModifier:
433
+ if clicked_index in self.current_layer_index:
434
+ self.layer_list[clicked_index].set_checked(False)
435
+ remove_ind = np.where(np.ravel(self.current_layer_index) == clicked_index)[0][0]
436
+ self.current_layer_index.pop(remove_ind)
437
+ else:
438
+ self.layer_list[clicked_index].set_checked(True)
439
+ self.current_layer_index.append(clicked_index)
440
+ else:
441
+ for da_ind in self.current_layer_index:
442
+ self.layer_list[da_ind].set_checked(False)
443
+ self.layer_list[clicked_index].set_checked(True)
444
+ self.current_layer_index = [clicked_index]
445
+
446
+ if self.layer_opacity[clicked_index] != self.layer_opacity_slider.value():
447
+ self.layer_opacity_slider.setValue(self.layer_opacity[clicked_index])
448
+ if self.layer_blend_mode[clicked_index] != self.layer_blend_combo.currentText():
449
+ self.layer_blend_combo.setCurrentText(self.layer_blend_mode[clicked_index])
450
+
451
+ def layer_eye_clicked(self, event):
452
+ self.sig_visible_changed.emit(event)
453
+
454
+ def blend_mode_changed(self):
455
+ if not self.current_layer_index:
456
+ return
457
+ if len(self.current_layer_index) > 1:
458
+ return
459
+ blend_mode = self.layer_blend_combo.currentText()
460
+ if blend_mode != self.layer_blend_mode[self.current_layer_index[0]]:
461
+ current_link = self.layer_link[self.current_layer_index[0]]
462
+ self.layer_blend_mode[self.current_layer_index[0]] = blend_mode
463
+ self.sig_blend_mode_changed.emit((current_link, blend_mode))
464
+
465
+ def opacity_slider_moved(self):
466
+ if not self.current_layer_index:
467
+ return
468
+ if len(self.current_layer_index) > 1:
469
+ return
470
+ val = self.layer_opacity_slider.value()
471
+ if val != self.layer_opacity[self.current_layer_index[0]]:
472
+ current_link = self.layer_link[self.current_layer_index[0]]
473
+ current_color = self.layer_color[self.current_layer_index[0]]
474
+ self.layer_opacity[self.current_layer_index[0]] = val
475
+ self.sig_opacity_changed.emit((current_link, current_color, val))
476
+
477
+ def change_opacity_label_value(self):
478
+ da_val = self.layer_opacity_slider.value()
479
+ self.layer_opacity_val_label.setText('{} %'.format(da_val))
480
+
481
+ def get_layer_data(self):
482
+ tb_list = []
483
+ for i in range(len(self.layer_link)):
484
+ da_tb = self.layer_list[i].thumbnail_data
485
+ tb_list.append(da_tb)
486
+ data = {'layer_link': self.layer_link,
487
+ 'layer_color': self.layer_color,
488
+ 'layer_thumbnail': tb_list,
489
+ 'layer_opacity': self.layer_opacity,
490
+ 'layer_blend_mode': self.layer_blend_mode,
491
+ 'current_layer_index': self.current_layer_index}
492
+ return data
493
+
494
+ def set_layer_data(self, data):
495
+ required_keys = (
496
+ 'layer_link', 'layer_color', 'layer_thumbnail', 'layer_opacity',
497
+ 'layer_blend_mode', 'current_layer_index'
498
+ )
499
+ missing_keys = [key for key in required_keys if key not in data]
500
+ if missing_keys:
501
+ raise ValueError('Layer data is missing: {}.'.format(', '.join(missing_keys)))
502
+
503
+ layer_count = len(data['layer_link'])
504
+ parallel_keys = ('layer_color', 'layer_thumbnail', 'layer_opacity', 'layer_blend_mode')
505
+ if any(len(data[key]) != layer_count for key in parallel_keys):
506
+ raise ValueError('Saved layer properties have inconsistent lengths.')
507
+ if len(set(data['layer_link'])) != layer_count:
508
+ raise ValueError('Saved layer links must be unique.')
509
+
510
+ self.clear_all()
511
+ for i in range(layer_count):
512
+ self.master_layers(
513
+ data['layer_thumbnail'][i], data['layer_link'][i], data['layer_color'][i]
514
+ )
515
+ self.layer_color = list(data['layer_color'])
516
+ self.layer_opacity = list(data['layer_opacity'])
517
+ self.layer_blend_mode = list(data['layer_blend_mode'])
518
+
519
+ for layer in self.layer_list:
520
+ layer.set_checked(False)
521
+ selected_indexes = []
522
+ for saved_index in data['current_layer_index']:
523
+ index = int(saved_index)
524
+ if 0 <= index < layer_count and index not in selected_indexes:
525
+ selected_indexes.append(index)
526
+ self.layer_list[index].set_checked(True)
527
+ self.current_layer_index = selected_indexes
528
+
529
+ if len(selected_indexes) == 1:
530
+ index = selected_indexes[0]
531
+ self.layer_opacity_slider.setValue(self.layer_opacity[index])
532
+ self.layer_blend_combo.setCurrentText(self.layer_blend_mode[index])
533
+
534
+ def clear_all(self):
535
+ ind = np.arange(len(self.layer_list))[::-1]
536
+ for i in ind:
537
+ self.layer_layout.removeWidget(self.layer_list[i])
538
+ self.layer_list[i].deleteLater()
539
+ del self.layer_list[i]
540
+ self.layer_id = []
541
+ self.layer_link = []
542
+ self.layer_color = []
543
+ self.layer_opacity = []
544
+ self.layer_blend_mode = []
545
+ self.current_layer_index = []
546
+ self.layer_count = 0
547
+
548
+
549
+
550
+
551
+
552
+
553
+
554
+