coralnet-toolbox 0.0.73__py2.py3-none-any.whl → 0.0.74__py2.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.
- coralnet_toolbox/Annotations/QtAnnotation.py +28 -69
- coralnet_toolbox/Annotations/QtMaskAnnotation.py +408 -0
- coralnet_toolbox/Annotations/QtMultiPolygonAnnotation.py +72 -56
- coralnet_toolbox/Annotations/QtPatchAnnotation.py +165 -216
- coralnet_toolbox/Annotations/QtPolygonAnnotation.py +497 -353
- coralnet_toolbox/Annotations/QtRectangleAnnotation.py +126 -116
- coralnet_toolbox/CoralNet/QtDownload.py +2 -1
- coralnet_toolbox/Explorer/QtExplorer.py +16 -14
- coralnet_toolbox/Explorer/QtSettingsWidgets.py +114 -82
- coralnet_toolbox/IO/QtExportTagLabAnnotations.py +30 -10
- coralnet_toolbox/IO/QtImportTagLabAnnotations.py +21 -15
- coralnet_toolbox/IO/QtOpenProject.py +46 -78
- coralnet_toolbox/IO/QtSaveProject.py +18 -43
- coralnet_toolbox/MachineLearning/ExportDataset/QtBase.py +1 -1
- coralnet_toolbox/MachineLearning/ImportDataset/QtBase.py +42 -22
- coralnet_toolbox/MachineLearning/VideoInference/QtBase.py +0 -4
- coralnet_toolbox/QtEventFilter.py +11 -0
- coralnet_toolbox/QtImageWindow.py +117 -68
- coralnet_toolbox/QtLabelWindow.py +13 -1
- coralnet_toolbox/QtMainWindow.py +5 -27
- coralnet_toolbox/QtProgressBar.py +52 -27
- coralnet_toolbox/Rasters/RasterTableModel.py +8 -8
- coralnet_toolbox/SAM/QtDeployPredictor.py +10 -0
- coralnet_toolbox/SeeAnything/QtDeployGenerator.py +779 -161
- coralnet_toolbox/SeeAnything/QtDeployPredictor.py +86 -149
- coralnet_toolbox/Tools/QtCutSubTool.py +18 -2
- coralnet_toolbox/Tools/QtResizeSubTool.py +19 -2
- coralnet_toolbox/Tools/QtSAMTool.py +72 -50
- coralnet_toolbox/Tools/QtSeeAnythingTool.py +8 -5
- coralnet_toolbox/Tools/QtSelectTool.py +27 -3
- coralnet_toolbox/Tools/QtSubtractSubTool.py +66 -0
- coralnet_toolbox/Tools/__init__.py +2 -0
- coralnet_toolbox/__init__.py +1 -1
- coralnet_toolbox/utilities.py +137 -47
- coralnet_toolbox-0.0.74.dist-info/METADATA +375 -0
- {coralnet_toolbox-0.0.73.dist-info → coralnet_toolbox-0.0.74.dist-info}/RECORD +40 -38
- coralnet_toolbox-0.0.73.dist-info/METADATA +0 -341
- {coralnet_toolbox-0.0.73.dist-info → coralnet_toolbox-0.0.74.dist-info}/WHEEL +0 -0
- {coralnet_toolbox-0.0.73.dist-info → coralnet_toolbox-0.0.74.dist-info}/entry_points.txt +0 -0
- {coralnet_toolbox-0.0.73.dist-info → coralnet_toolbox-0.0.74.dist-info}/licenses/LICENSE.txt +0 -0
- {coralnet_toolbox-0.0.73.dist-info → coralnet_toolbox-0.0.74.dist-info}/top_level.txt +0 -0
@@ -2,13 +2,14 @@ import warnings
|
|
2
2
|
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
3
3
|
|
4
4
|
from rasterio.windows import Window
|
5
|
-
|
6
|
-
from shapely.
|
5
|
+
|
6
|
+
from shapely.ops import split, unary_union
|
7
|
+
from shapely.geometry import Point, LineString, box
|
7
8
|
|
8
9
|
from PyQt5.QtCore import Qt, QPointF
|
9
|
-
from PyQt5.QtWidgets import QGraphicsScene,
|
10
|
-
from PyQt5.QtGui import (QPixmap, QColor, QPen, QBrush, QPolygonF,
|
11
|
-
QImage, QRegion)
|
10
|
+
from PyQt5.QtWidgets import QGraphicsScene, QGraphicsPathItem
|
11
|
+
from PyQt5.QtGui import (QPixmap, QColor, QPen, QBrush, QPolygonF,
|
12
|
+
QPainter, QImage, QRegion, QPainterPath)
|
12
13
|
|
13
14
|
from coralnet_toolbox.Annotations.QtAnnotation import Annotation
|
14
15
|
|
@@ -67,21 +68,60 @@ class RectangleAnnotation(Annotation):
|
|
67
68
|
self.annotation_size = int(max(width, height))
|
68
69
|
|
69
70
|
def contains_point(self, point: QPointF) -> bool:
|
70
|
-
"""Check if the given point is within the rectangle."""
|
71
|
-
|
72
|
-
|
71
|
+
"""Check if the given point is within the rectangle using Shapely."""
|
72
|
+
try:
|
73
|
+
# Create a shapely box from the rectangle's corner coordinates
|
74
|
+
shapely_rect = box(self.top_left.x(),
|
75
|
+
self.top_left.y(),
|
76
|
+
self.bottom_right.x(),
|
77
|
+
self.bottom_right.y())
|
78
|
+
|
79
|
+
# Convert the input QPointF to a Shapely Point
|
80
|
+
shapely_point = Point(point.x(), point.y())
|
81
|
+
|
82
|
+
# Return Shapely's boolean result for the containment check
|
83
|
+
return shapely_rect.contains(shapely_point)
|
84
|
+
|
85
|
+
except Exception:
|
86
|
+
# Fallback to the original implementation if Shapely fails
|
87
|
+
return (self.top_left.x() <= point.x() <= self.bottom_right.x() and
|
88
|
+
self.top_left.y() <= point.y() <= self.bottom_right.y())
|
73
89
|
|
74
90
|
def get_centroid(self):
|
75
91
|
"""Get the centroid of the annotation."""
|
76
92
|
return (float(self.center_xy.x()), float(self.center_xy.y()))
|
77
93
|
|
78
94
|
def get_area(self):
|
79
|
-
"""Calculate the area of the rectangle."""
|
80
|
-
|
95
|
+
"""Calculate the area of the rectangle using Shapely."""
|
96
|
+
try:
|
97
|
+
# Create a shapely box from the rectangle's corner coordinates
|
98
|
+
shapely_rect = box(self.top_left.x(),
|
99
|
+
self.top_left.y(),
|
100
|
+
self.bottom_right.x(),
|
101
|
+
self.bottom_right.y())
|
102
|
+
return shapely_rect.area
|
103
|
+
|
104
|
+
except Exception:
|
105
|
+
# Fallback to the original implementation if Shapely fails
|
106
|
+
width = self.bottom_right.x() - self.top_left.x()
|
107
|
+
height = self.bottom_right.y() - self.top_left.y()
|
108
|
+
return width * height
|
81
109
|
|
82
110
|
def get_perimeter(self):
|
83
|
-
"""Calculate the perimeter of the rectangle."""
|
84
|
-
|
111
|
+
"""Calculate the perimeter of the rectangle using Shapely."""
|
112
|
+
try:
|
113
|
+
# Create a shapely box from the rectangle's corner coordinates
|
114
|
+
shapely_rect = box(self.top_left.x(),
|
115
|
+
self.top_left.y(),
|
116
|
+
self.bottom_right.x(),
|
117
|
+
self.bottom_right.y())
|
118
|
+
return shapely_rect.length
|
119
|
+
|
120
|
+
except Exception:
|
121
|
+
# Fallback to the original implementation if Shapely fails
|
122
|
+
width = self.bottom_right.x() - self.top_left.x()
|
123
|
+
height = self.bottom_right.y() - self.top_left.y()
|
124
|
+
return 2 * width + 2 * height
|
85
125
|
|
86
126
|
def get_polygon(self):
|
87
127
|
"""Get the polygon representation of this rectangle."""
|
@@ -92,6 +132,15 @@ class RectangleAnnotation(Annotation):
|
|
92
132
|
QPointF(self.top_left.x(), self.bottom_right.y())
|
93
133
|
]
|
94
134
|
return QPolygonF(points)
|
135
|
+
|
136
|
+
def get_painter_path(self) -> QPainterPath:
|
137
|
+
"""
|
138
|
+
Get a QPainterPath representation of the annotation.
|
139
|
+
"""
|
140
|
+
path = QPainterPath()
|
141
|
+
polygon = self.get_polygon()
|
142
|
+
path.addPolygon(polygon)
|
143
|
+
return path
|
95
144
|
|
96
145
|
def get_bounding_box_top_left(self):
|
97
146
|
"""Get the top-left corner of the bounding box."""
|
@@ -102,69 +151,55 @@ class RectangleAnnotation(Annotation):
|
|
102
151
|
return self.bottom_right
|
103
152
|
|
104
153
|
def get_cropped_image_graphic(self):
|
105
|
-
"""Create a cropped image with a mask and
|
154
|
+
"""Create a cropped image with a mask and solid outline."""
|
106
155
|
if self.cropped_image is None:
|
107
156
|
return None
|
108
157
|
|
109
158
|
# Create a QImage with transparent background for the mask
|
110
159
|
masked_image = QImage(self.cropped_image.size(), QImage.Format_ARGB32)
|
111
|
-
masked_image.fill(Qt.transparent)
|
112
|
-
|
113
|
-
# Create a QPainter to draw the polygon onto the mask
|
114
|
-
painter = QPainter(masked_image)
|
115
|
-
painter.setRenderHint(QPainter.Antialiasing)
|
116
|
-
painter.setBrush(QBrush(Qt.white)) # White fill for the mask area
|
117
|
-
painter.setPen(Qt.NoPen)
|
160
|
+
masked_image.fill(Qt.transparent)
|
118
161
|
|
119
|
-
#
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
QPointF(self.top_left.x(), self.bottom_right.y())
|
125
|
-
]
|
126
|
-
|
127
|
-
# Create a copy of the points that are transformed to be relative to the cropped_image
|
128
|
-
cropped_points = [QPointF(point.x() - self.cropped_bbox[0],
|
129
|
-
point.y() - self.cropped_bbox[1]) for point in rectangle_points]
|
162
|
+
# Create a painter to draw the path onto the mask
|
163
|
+
mask_painter = QPainter(masked_image)
|
164
|
+
mask_painter.setRenderHint(QPainter.Antialiasing)
|
165
|
+
mask_painter.setBrush(QBrush(Qt.white))
|
166
|
+
mask_painter.setPen(Qt.NoPen)
|
130
167
|
|
131
|
-
# Create a
|
132
|
-
|
168
|
+
# Create a path from the points relative to the cropped image
|
169
|
+
path = QPainterPath()
|
170
|
+
# The cropped image is the same size as the rectangle, so the path is from (0,0)
|
171
|
+
path.addRect(0, 0, self.cropped_image.width(), self.cropped_image.height())
|
133
172
|
|
134
|
-
# Draw the
|
135
|
-
|
136
|
-
|
173
|
+
# Draw the path onto the mask
|
174
|
+
mask_painter.drawPath(path)
|
175
|
+
mask_painter.end()
|
137
176
|
|
138
177
|
# Convert the mask QImage to QPixmap and create a bitmap mask
|
139
|
-
# We want the inside of the polygon to show the image, so we DON'T use MaskInColor
|
140
178
|
mask_pixmap = QPixmap.fromImage(masked_image)
|
141
179
|
mask_bitmap = mask_pixmap.createMaskFromColor(Qt.white, Qt.MaskOutColor)
|
142
|
-
|
143
|
-
# Convert bitmap to region for clipping
|
144
180
|
mask_region = QRegion(mask_bitmap)
|
145
181
|
|
146
182
|
# Create the result image
|
147
183
|
cropped_image_graphic = QPixmap(self.cropped_image.size())
|
148
|
-
|
149
|
-
# First draw the entire original image at 50% opacity (for area outside polygon)
|
150
184
|
result_painter = QPainter(cropped_image_graphic)
|
151
185
|
result_painter.setRenderHint(QPainter.Antialiasing)
|
152
|
-
|
186
|
+
|
187
|
+
# Draw the background at 50% opacity
|
188
|
+
result_painter.setOpacity(0.5)
|
153
189
|
result_painter.drawPixmap(0, 0, self.cropped_image)
|
154
190
|
|
155
|
-
#
|
156
|
-
result_painter.setOpacity(1.0)
|
191
|
+
# Draw the full-opacity image inside the masked region
|
192
|
+
result_painter.setOpacity(1.0)
|
157
193
|
result_painter.setClipRegion(mask_region)
|
158
194
|
result_painter.drawPixmap(0, 0, self.cropped_image)
|
159
195
|
|
160
|
-
# Draw the
|
196
|
+
# Draw the solid line outline on top
|
161
197
|
pen = QPen(Qt.black)
|
162
|
-
pen.setStyle(Qt.SolidLine)
|
163
|
-
pen.setWidth(1)
|
198
|
+
pen.setStyle(Qt.SolidLine)
|
199
|
+
pen.setWidth(1)
|
164
200
|
result_painter.setPen(pen)
|
165
|
-
result_painter.setClipping(False)
|
166
|
-
result_painter.
|
167
|
-
|
201
|
+
result_painter.setClipping(False)
|
202
|
+
result_painter.drawPath(path)
|
168
203
|
result_painter.end()
|
169
204
|
|
170
205
|
return cropped_image_graphic
|
@@ -198,29 +233,25 @@ class RectangleAnnotation(Annotation):
|
|
198
233
|
self.annotationUpdated.emit(self) # Notify update
|
199
234
|
|
200
235
|
def create_graphics_item(self, scene: QGraphicsScene):
|
201
|
-
"""Create all graphics items for the
|
202
|
-
#
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
self.graphics_item = QGraphicsPolygonItem(QPolygonF(points))
|
210
|
-
# Call parent to handle group and helpers
|
236
|
+
"""Create all graphics items for the annotation and add them to the scene."""
|
237
|
+
# Get the complete shape as a QPainterPath.
|
238
|
+
path = self.get_painter_path()
|
239
|
+
|
240
|
+
# Use a QGraphicsPathItem for rendering.
|
241
|
+
self.graphics_item = QGraphicsPathItem(path)
|
242
|
+
|
243
|
+
# Call the parent class method to handle grouping, styling, and adding to the scene.
|
211
244
|
super().create_graphics_item(scene)
|
212
245
|
|
213
246
|
def update_graphics_item(self):
|
214
247
|
"""Update the graphical representation of the rectangle annotation."""
|
215
|
-
#
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
self.graphics_item = QGraphicsPolygonItem(QPolygonF(points))
|
223
|
-
# Call parent to handle group and helpers
|
248
|
+
# Get the complete shape as a QPainterPath.
|
249
|
+
path = self.get_painter_path()
|
250
|
+
|
251
|
+
# Use a QGraphicsPathItem to correctly represent the shape.
|
252
|
+
self.graphics_item = QGraphicsPathItem(path)
|
253
|
+
|
254
|
+
# Call the parent class method to handle rebuilding the graphics group.
|
224
255
|
super().update_graphics_item()
|
225
256
|
|
226
257
|
def update_polygon(self, delta):
|
@@ -311,75 +342,54 @@ class RectangleAnnotation(Annotation):
|
|
311
342
|
|
312
343
|
@classmethod
|
313
344
|
def combine(cls, annotations: list):
|
314
|
-
"""Combine multiple rectangle annotations into a single encompassing rectangle
|
315
|
-
but only if every annotation overlaps with at least one other
|
316
|
-
|
317
|
-
Args:
|
318
|
-
annotations: List of RectangleAnnotations objects to combine.
|
319
|
-
|
320
|
-
Returns:
|
321
|
-
A new RectangleAnnotations that encompasses all input rectangles if every
|
322
|
-
annotation overlaps with at least one other, otherwise None.
|
345
|
+
"""Combine multiple rectangle annotations into a single encompassing rectangle
|
346
|
+
using Shapely, but only if every annotation overlaps with at least one other.
|
323
347
|
"""
|
324
348
|
if not annotations:
|
325
349
|
return None
|
326
|
-
|
327
350
|
if len(annotations) == 1:
|
328
351
|
return annotations[0]
|
329
352
|
|
330
|
-
#
|
331
|
-
|
353
|
+
# Convert all annotations to Shapely boxes
|
354
|
+
shapely_rects = []
|
355
|
+
for anno in annotations:
|
356
|
+
shaped_rect = box(anno.top_left.x(),
|
357
|
+
anno.top_left.y(),
|
358
|
+
anno.bottom_right.x(),
|
359
|
+
anno.bottom_right.y())
|
360
|
+
|
361
|
+
shapely_rects.append(shaped_rect)
|
362
|
+
|
363
|
+
# 1. Perform the overlap check using Shapely's `intersects`
|
364
|
+
for i, rect_i in enumerate(shapely_rects):
|
332
365
|
has_overlap = False
|
333
|
-
for j,
|
334
|
-
if i
|
335
|
-
continue
|
336
|
-
|
337
|
-
# Check if these two rectangles overlap
|
338
|
-
if (anno_i.top_left.x() < anno_j.bottom_right.x() and
|
339
|
-
anno_i.bottom_right.x() > anno_j.top_left.x() and
|
340
|
-
anno_i.top_left.y() < anno_j.bottom_right.y() and
|
341
|
-
anno_i.bottom_right.y() > anno_j.top_left.y()):
|
366
|
+
for j, rect_j in enumerate(shapely_rects):
|
367
|
+
if i != j and rect_i.intersects(rect_j):
|
342
368
|
has_overlap = True
|
343
369
|
break
|
344
|
-
|
345
|
-
# If any annotation doesn't overlap with any other, return None
|
346
370
|
if not has_overlap:
|
347
|
-
return None
|
371
|
+
return None # An annotation is isolated, cancel the combine
|
348
372
|
|
349
|
-
#
|
350
|
-
|
351
|
-
min_y =
|
352
|
-
max_x = max(anno.bottom_right.x() for anno in annotations)
|
353
|
-
max_y = max(anno.bottom_right.y() for anno in annotations)
|
373
|
+
# 2. Get the encompassing bounding box using Shapely's union and bounds
|
374
|
+
merged_geom = unary_union(shapely_rects)
|
375
|
+
min_x, min_y, max_x, max_y = merged_geom.bounds
|
354
376
|
|
355
377
|
# Create new rectangle with these bounds
|
356
378
|
top_left = QPointF(min_x, min_y)
|
357
379
|
bottom_right = QPointF(max_x, max_y)
|
358
380
|
|
359
381
|
# Extract info from the first annotation
|
360
|
-
|
361
|
-
long_label_code = annotations[0].label.long_label_code
|
362
|
-
color = annotations[0].label.color
|
363
|
-
image_path = annotations[0].image_path
|
364
|
-
label_id = annotations[0].label.id
|
365
|
-
|
366
|
-
# Create a new annotation with the merged points
|
382
|
+
first_anno = annotations[0]
|
367
383
|
new_annotation = cls(
|
368
384
|
top_left=top_left,
|
369
385
|
bottom_right=bottom_right,
|
370
|
-
short_label_code=short_label_code,
|
371
|
-
long_label_code=long_label_code,
|
372
|
-
color=color,
|
373
|
-
image_path=image_path,
|
374
|
-
label_id=
|
386
|
+
short_label_code=first_anno.label.short_label_code,
|
387
|
+
long_label_code=first_anno.label.long_label_code,
|
388
|
+
color=first_anno.label.color,
|
389
|
+
image_path=first_anno.image_path,
|
390
|
+
label_id=first_anno.label.id
|
375
391
|
)
|
376
392
|
|
377
|
-
# All input annotations have the same rasterio source, use it for the new one
|
378
|
-
if all(hasattr(anno, 'rasterio_src') and anno.rasterio_src is not None for anno in annotations):
|
379
|
-
if len(set(id(anno.rasterio_src) for anno in annotations)) == 1:
|
380
|
-
new_annotation.rasterio_src = annotations[0].rasterio_src
|
381
|
-
new_annotation.create_cropped_image(new_annotation.rasterio_src)
|
382
|
-
|
383
393
|
return new_annotation
|
384
394
|
|
385
395
|
@classmethod
|
@@ -946,7 +946,8 @@ class DownloadDialog(QDialog):
|
|
946
946
|
|
947
947
|
try:
|
948
948
|
# Check if there is a next page button and it's enabled
|
949
|
-
|
949
|
+
# ---- THIS IS THE MODIFIED LINE ----
|
950
|
+
element_text = 'a[title="Next page"]'
|
950
951
|
|
951
952
|
try:
|
952
953
|
next_button = self.driver.find_element(By.CSS_SELECTOR, element_text)
|
@@ -1705,37 +1705,27 @@ class ExplorerWindow(QMainWindow):
|
|
1705
1705
|
if child.widget():
|
1706
1706
|
child.widget().setParent(None)
|
1707
1707
|
|
1708
|
-
# Lazily initialize the settings and viewer widgets
|
1709
|
-
# This ensures that the widgets are only created once per ExplorerWindow instance.
|
1710
|
-
|
1711
|
-
# Annotation settings panel (filters by image, type, label)
|
1708
|
+
# Lazily initialize the settings and viewer widgets
|
1712
1709
|
if self.annotation_settings_widget is None:
|
1713
1710
|
self.annotation_settings_widget = AnnotationSettingsWidget(self.main_window, self)
|
1714
|
-
|
1715
|
-
# Model selection panel (choose feature extraction model)
|
1716
1711
|
if self.model_settings_widget is None:
|
1717
1712
|
self.model_settings_widget = ModelSettingsWidget(self.main_window, self)
|
1718
|
-
|
1719
|
-
# Embedding settings panel (choose dimensionality reduction method)
|
1720
1713
|
if self.embedding_settings_widget is None:
|
1721
1714
|
self.embedding_settings_widget = EmbeddingSettingsWidget(self.main_window, self)
|
1722
|
-
|
1723
|
-
# Annotation viewer (shows annotation image crops in a grid)
|
1724
1715
|
if self.annotation_viewer is None:
|
1725
1716
|
self.annotation_viewer = AnnotationViewer(self)
|
1726
|
-
|
1727
|
-
# Embedding viewer (shows 2D embedding scatter plot)
|
1728
1717
|
if self.embedding_viewer is None:
|
1729
1718
|
self.embedding_viewer = EmbeddingViewer(self)
|
1730
1719
|
|
1720
|
+
# Horizontal layout for the three settings panels (original horizontal layout)
|
1731
1721
|
top_layout = QHBoxLayout()
|
1732
1722
|
top_layout.addWidget(self.annotation_settings_widget, 2)
|
1733
1723
|
top_layout.addWidget(self.model_settings_widget, 1)
|
1734
1724
|
top_layout.addWidget(self.embedding_settings_widget, 1)
|
1735
1725
|
top_container = QWidget()
|
1736
1726
|
top_container.setLayout(top_layout)
|
1737
|
-
self.main_layout.addWidget(top_container)
|
1738
1727
|
|
1728
|
+
# Horizontal splitter for the two main viewer panels
|
1739
1729
|
middle_splitter = QSplitter(Qt.Horizontal)
|
1740
1730
|
annotation_group = QGroupBox("Annotation Viewer")
|
1741
1731
|
annotation_layout = QVBoxLayout(annotation_group)
|
@@ -1747,7 +1737,19 @@ class ExplorerWindow(QMainWindow):
|
|
1747
1737
|
embedding_layout.addWidget(self.embedding_viewer)
|
1748
1738
|
middle_splitter.addWidget(embedding_group)
|
1749
1739
|
middle_splitter.setSizes([500, 500])
|
1750
|
-
|
1740
|
+
|
1741
|
+
# Create a VERTICAL splitter to manage the height between the settings and viewers.
|
1742
|
+
# This makes the top settings panel vertically resizable.
|
1743
|
+
main_splitter = QSplitter(Qt.Vertical)
|
1744
|
+
main_splitter.addWidget(top_container)
|
1745
|
+
main_splitter.addWidget(middle_splitter)
|
1746
|
+
|
1747
|
+
# Set initial heights to give the settings panel a bit more space by default
|
1748
|
+
main_splitter.setSizes([250, 750])
|
1749
|
+
|
1750
|
+
# Add the new main splitter to the layout instead of the individual components
|
1751
|
+
self.main_layout.addWidget(main_splitter, 1)
|
1752
|
+
|
1751
1753
|
self.main_layout.addWidget(self.label_window)
|
1752
1754
|
|
1753
1755
|
self.buttons_layout = QHBoxLayout()
|