Semapp 1.0.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.

Potentially problematic release.


This version of Semapp might be problematic. Click here for more details.

@@ -0,0 +1,599 @@
1
+ """
2
+ Module for processing and renaming TIFF files based on CSV coordinates.
3
+ """
4
+
5
+ import json
6
+ import re
7
+ import glob
8
+ import shutil
9
+ import os
10
+ from PIL import Image
11
+ import pandas as pd
12
+
13
+
14
+ class Process:
15
+ """
16
+ A class to handle processing of TIFF files and renaming them based on
17
+ coordinates from a CSV file.
18
+ """
19
+
20
+ def __init__(self, dirname, wafer=None, scale=None):
21
+ """
22
+ Initialize the processing instance with necessary parameters.
23
+
24
+ Args:
25
+ dirname (str): The base directory for the files.
26
+ recipe (str): The CSV file containing coordinates.
27
+ wafer (str): The wafer number (optional).
28
+ scale (str): The path to the settings JSON file (optional).
29
+ """
30
+ self.dirname = dirname
31
+ self.scale_data = scale
32
+ self.wafer_number = str(wafer)
33
+ self.tiff_path = None
34
+ self.coordinates = None
35
+ self.settings = None
36
+ self.output_dir = None
37
+ self.load_json()
38
+ def load_json(self):
39
+ """Load the settings data from a JSON file."""
40
+ try:
41
+ with open(self.scale_data, "r", encoding="utf-8") as file:
42
+ self.settings = json.load(file)
43
+ print("Settings data loaded successfully.")
44
+ except FileNotFoundError:
45
+ print("Settings file not found. Starting fresh.")
46
+ self.settings = []
47
+ except json.JSONDecodeError as error:
48
+ print(f"JSON decoding error: {error}")
49
+ self.settings = []
50
+ except OSError as error:
51
+ print(f"OS error when reading file: {error}")
52
+ self.settings = []
53
+ def extract_positions(self, filepath):
54
+ '''Function to extract positions from a 001 file.'''
55
+ data = {
56
+ "SampleSize": None,
57
+ "DiePitch": {"X": None, "Y": None},
58
+ "DieOrigin": {"X": None, "Y": None},
59
+ "SampleCenterLocation": {"X": None, "Y": None},
60
+ "Defects": []
61
+ }
62
+
63
+ dans_defect_list = False
64
+
65
+ with open(filepath, "r", encoding="utf-8") as f:
66
+ for line in f:
67
+ line = line.strip()
68
+
69
+ if line.startswith("SampleSize"):
70
+ match = re.search(r"SampleSize\s+1\s+(\d+)", line)
71
+ if match:
72
+ data["SampleSize"] = int(match.group(1))
73
+
74
+ elif line.startswith("DiePitch"):
75
+ match = re.search(r"DiePitch\s+([0-9.]+)\s+([0-9.]+);", line)
76
+ if match:
77
+ data["DiePitch"]["X"] = float(match.group(1))
78
+ data["DiePitch"]["Y"] = float(match.group(2))
79
+
80
+ elif line.startswith("DieOrigin"):
81
+ match = re.search(r"DieOrigin\s+([0-9.]+)\s+([0-9.]+);", line)
82
+ if match:
83
+ data["DieOrigin"]["X"] = float(match.group(1))
84
+ data["DieOrigin"]["Y"] = float(match.group(2))
85
+
86
+ elif line.startswith("SampleCenterLocation"):
87
+ match = re.search(r"SampleCenterLocation\s+([0-9.]+)\s+([0-9.]+);", line)
88
+ if match:
89
+ data["SampleCenterLocation"]["X"] = float(match.group(1))
90
+ data["SampleCenterLocation"]["Y"] = float(match.group(2))
91
+
92
+ elif line.startswith("DefectList"):
93
+ dans_defect_list = True
94
+ continue
95
+
96
+ elif dans_defect_list:
97
+ if re.match(r"^\d+\s", line):
98
+ value = line.split()
99
+ if len(value) >= 18:
100
+ defect = {f"val{i+1}": float(val) for i, val in enumerate(value[:18])}
101
+ data["Defects"].append(defect)
102
+
103
+ pitch_x = data["DiePitch"]["X"]
104
+ pitch_y = data["DiePitch"]["Y"]
105
+ x_center = data["SampleCenterLocation"]["X"]
106
+ y_center = data["SampleCenterLocation"]["Y"]
107
+
108
+ corrected_positions = []
109
+
110
+ for d in data["Defects"]:
111
+ val1 = d["val1"]
112
+ val2 = d["val2"]
113
+ val3 = d["val3"]
114
+ val4_scaled = d["val4"] * pitch_x - x_center
115
+ val5_scaled = d["val5"] * pitch_y - y_center
116
+
117
+ x_corr = round((val2 + val4_scaled) / 10000, 1)
118
+ y_corr = round((val3 + val5_scaled) / 10000, 1)
119
+
120
+ corrected_positions.append({"defect_id": val1, "X": x_corr,"Y": y_corr})
121
+
122
+ self.coordinates = pd.DataFrame(corrected_positions, columns=["X", "Y"])
123
+
124
+ return self.coordinates
125
+ def rename(self):
126
+ """
127
+ Rename TIFF files based on the coordinates from the CSV file.
128
+
129
+ This method processes TIFF files in the wafer directory and renames
130
+ them using the corresponding X/Y coordinates and settings values.
131
+ """
132
+ self.output_dir = os.path.join(self.dirname, self.wafer_number)
133
+
134
+ if not os.path.exists(self.output_dir):
135
+ print(f"Directory not found: {self.output_dir}")
136
+ return
137
+
138
+ matching_files = glob.glob(os.path.join(self.output_dir, '*.001'))
139
+
140
+ # If at least one file matches, take the first one
141
+ if matching_files:
142
+ recipe_path = matching_files[0]
143
+ else:
144
+ recipe_path = None
145
+ self.coordinates = self.extract_positions(recipe_path)
146
+
147
+
148
+
149
+ tiff_files = [f for f in os.listdir(self.output_dir)
150
+ if "page" in f and f.lower().endswith(('.tiff', '.tif'))]
151
+ tiff_files.sort()
152
+
153
+ for file in tiff_files:
154
+ # Extract page number from the file name (e.g., data_page_1.tiff)
155
+ file_number = int(file.split('_')[2].split('.')[0])
156
+ print(f"Processing {file}: Page number {file_number}, Total "
157
+ f"settings {len(self.settings)}")
158
+
159
+ # Calculate the corresponding row in the CSV
160
+ csv_row_index = (file_number - 1) // len(self.settings)
161
+ remainder = (file_number - 1) % len(self.settings)
162
+
163
+ # Get X/Y coordinates from the CSV
164
+ x = self.coordinates.iloc[csv_row_index, 0] # First column for X
165
+ y = self.coordinates.iloc[csv_row_index, 1] # Second column for Y
166
+
167
+ # Get settings values (Scale, Image Type)
168
+ scale = self.settings[remainder]["Scale"]
169
+ image_type = self.settings[remainder]["Image Type"]
170
+
171
+ # Construct the new file name
172
+ new_name = f"{scale}_{x}_{y}_{image_type}.tif"
173
+
174
+ old_path = os.path.join(self.output_dir, file)
175
+ new_path = os.path.join(self.output_dir, new_name)
176
+
177
+ # Rename the file
178
+ os.rename(old_path, new_path)
179
+ print(f"Renamed: {file} -> {new_name}")
180
+
181
+ def split_tiff(self):
182
+ """
183
+ Split a merged TIFF file into individual TIFF files.
184
+
185
+ Returns:
186
+ list: List of file paths of the generated TIFF files.
187
+ """
188
+ self.tiff_path = os.path.join(self.dirname,
189
+ self.wafer_number,
190
+ "data.tif")
191
+
192
+
193
+ output_files = []
194
+ page_index = 0
195
+ if not os.path.exists(self.tiff_path):
196
+ print(f"TIFF file not found: {self.tiff_path}")
197
+ return []
198
+
199
+ try:
200
+ img = Image.open(self.tiff_path)
201
+ while True:
202
+ output_file = self.tiff_path.replace(".tif", "") + \
203
+ f"_page_{page_index + 1}.tiff"
204
+ img.save(output_file, format="TIFF")
205
+ output_files.append(output_file)
206
+
207
+ try:
208
+ img.seek(page_index + 1)
209
+ page_index += 1
210
+ except EOFError:
211
+ break
212
+ except Exception as error:
213
+ raise RuntimeError(f"Error splitting TIFF file: {error}") from error
214
+
215
+ return output_files
216
+
217
+ def clean(self):
218
+ """
219
+ Clean up the output directory by deleting any non-conforming TIFF files.
220
+
221
+ This method deletes any files that do not follow the expected naming
222
+ conventions (files not starting with "data" or
223
+ containing the word "page").
224
+ """
225
+ self.output_dir = os.path.join(self.dirname, self.wafer_number)
226
+
227
+ if not os.path.exists(self.output_dir):
228
+ print(f"Error: Directory does not exist: {self.output_dir}")
229
+ return
230
+
231
+ tiff_files = [f for f in os.listdir(self.output_dir)
232
+ if f.lower().endswith(('.tiff', '.tif'))]
233
+
234
+ # Delete non-conforming files
235
+ for file_name in tiff_files:
236
+ if not file_name.startswith("data") or "page" in file_name.lower() or file_name.endswith("001"):
237
+ file_path = os.path.join(self.output_dir, file_name)
238
+ os.remove(file_path)
239
+ print(f"Deleted: {file_path}")
240
+
241
+ def split_tiff_all(self):
242
+ """
243
+ Split all merged TIFF files in the directory (including subdirectories)
244
+ into individual TIFF files.
245
+
246
+ This method will look through all directories and split each `data.tif`
247
+ file into separate pages.
248
+ """
249
+ for subdir, _, _ in os.walk(self.dirname):
250
+ if subdir != self.dirname:
251
+ self.tiff_path = os.path.join(subdir, "data.tif")
252
+ print(f"Processing directory: {subdir}, "
253
+ f"TIFF path: {self.tiff_path}")
254
+
255
+ output_files = []
256
+ page_index = 0
257
+ if not os.path.exists(self.tiff_path):
258
+ print(f"TIFF file not found: {self.tiff_path}")
259
+ continue
260
+
261
+ try:
262
+ img = Image.open(self.tiff_path)
263
+ while True:
264
+ output_file = self.tiff_path.replace(".tif", "") + \
265
+ f"_page_{page_index + 1}.tiff"
266
+ img.save(output_file, format="TIFF")
267
+ output_files.append(output_file)
268
+
269
+ try:
270
+ img.seek(page_index + 1)
271
+ page_index += 1
272
+ except EOFError:
273
+ break
274
+ except Exception as error:
275
+ raise RuntimeError(f"Error splitting "
276
+ f"TIFF file: {error}") from error
277
+
278
+ def rename_all(self):
279
+ """
280
+ Rename all TIFF files based on the coordinates from the
281
+ CSV file in all subdirectories.
282
+
283
+ This method will iterate through all subdirectories,
284
+ loading the CSV and settings, and renaming files accordingly.
285
+ """
286
+
287
+ for subdir, _, _ in os.walk(self.dirname):
288
+ if subdir != self.dirname:
289
+ self.output_dir = os.path.join(self.dirname,
290
+ os.path.basename(subdir))
291
+ print(f"Renaming files in: {self.output_dir}")
292
+
293
+ matching_files = glob.glob(os.path.join(self.output_dir, '*.001'))
294
+
295
+ if matching_files:
296
+ recipe_path = matching_files[0]
297
+ else:
298
+ return
299
+ self.coordinates = self.extract_positions(recipe_path)
300
+
301
+ if self.coordinates is None or self.coordinates.empty:
302
+ raise ValueError("Coordinates have not been loaded or are empty.")
303
+
304
+ tiff_files = [f for f in os.listdir(self.output_dir)
305
+ if "page" in f and
306
+ f.lower().endswith(('.tiff', '.tif'))]
307
+ tiff_files.sort()
308
+
309
+ for file in tiff_files:
310
+ file_number = int(file.split('_')[2].split('.')[0])
311
+ print(f"Processing {file}: Page number {file_number}, "
312
+ f"Total settings {len(self.settings)}")
313
+
314
+ csv_row_index = (file_number - 1) // len(self.settings)
315
+ remainder = (file_number - 1) % len(self.settings)
316
+
317
+ x = self.coordinates.iloc[csv_row_index, 0]
318
+ y = self.coordinates.iloc[csv_row_index, 1]
319
+
320
+ scale = self.settings[remainder]["Scale"]
321
+ image_type = self.settings[remainder]["Image Type"]
322
+
323
+ new_name = f"{scale}_{x}_{y}_{image_type}.tif"
324
+ old_path = os.path.join(self.output_dir, file)
325
+ new_path = os.path.join(self.output_dir, new_name)
326
+
327
+ os.rename(old_path, new_path)
328
+ print(f"Renamed: {file} -> {new_name}")
329
+
330
+ def clean_all(self):
331
+ """
332
+ Delete all non-conforming TIFF files in all subdirectories.
333
+
334
+ This method will remove any files that do not follow the expected
335
+ naming conventions in all directories.
336
+ """
337
+ for subdir, _, _ in os.walk(self.dirname):
338
+ if subdir != self.dirname:
339
+ self.output_dir = os.path.join(self.dirname,
340
+ os.path.basename(subdir))
341
+ print(f"Cleaning directory: {self.output_dir}")
342
+
343
+ if not os.path.exists(self.output_dir):
344
+ print(f"Error: Directory does not exist: {self.output_dir}")
345
+ continue
346
+
347
+ tiff_files = [f for f in os.listdir(self.output_dir)
348
+ if f.lower().endswith(('.tiff', '.tif'))]
349
+ for file_name in tiff_files:
350
+ if not file_name.startswith("data") or \
351
+ "page" in file_name.lower() or file_name.endswith("001"):
352
+ file_path = os.path.join(self.output_dir, file_name)
353
+ os.remove(file_path)
354
+ print(f"Deleted: {file_path}")
355
+
356
+ def organize_and_rename_files(self):
357
+ """
358
+ Organize TIFF files into subfolders based
359
+ on the last split of their name
360
+ and rename the files to 'data.tif' in their respective subfolders.
361
+ """
362
+ if not os.path.exists(self.dirname):
363
+ print(f"Error: The folder {self.dirname} does not exist.")
364
+ return
365
+
366
+ # Iterate through files in the directory
367
+ for file_name in os.listdir(self.dirname):
368
+ if file_name.lower().endswith(".tif"):
369
+ parts = file_name.rsplit("_", 1)
370
+ if len(parts) < 2:
371
+ print(
372
+ f"Skipping file with unexpected format: {file_name}")
373
+ continue
374
+
375
+ # Use the last part (before extension) as the subfolder name
376
+ subfolder_name = parts[-1].split(".")[0]
377
+ subfolder_path = os.path.join(self.dirname, subfolder_name)
378
+
379
+ # Create the subfolder if it does not exist
380
+ os.makedirs(subfolder_path, exist_ok=True)
381
+
382
+ # Move and rename the file
383
+ source_path = os.path.join(self.dirname, file_name)
384
+ destination_path = os.path.join(subfolder_path, "data.tif")
385
+ shutil.move(source_path, destination_path)
386
+
387
+ print(
388
+ f"Moved and renamed: {file_name} -> {destination_path}")
389
+
390
+ if file_name.lower().endswith(".001"):
391
+ parts = file_name.rsplit("_", 1)
392
+ if len(parts) < 2:
393
+ print(
394
+ f"Skipping file with unexpected format: {file_name}")
395
+ continue
396
+
397
+ # Use the last part (before extension) as the subfolder name
398
+ subfolder_name = parts[-1].split(".")[0]
399
+ subfolder_path = os.path.join(self.dirname, subfolder_name)
400
+
401
+ # Create the subfolder if it does not exist
402
+ os.makedirs(subfolder_path, exist_ok=True)
403
+
404
+ # Move and rename the file
405
+ source_path = os.path.join(self.dirname, file_name)
406
+ destination_path = os.path.join(subfolder_path, file_name)
407
+ shutil.move(source_path, destination_path)
408
+
409
+ print(
410
+ f"Moved and renamed: {file_name} -> {destination_path}")
411
+
412
+ def rename_wo_legend_all(self):
413
+ """
414
+ Preprocess all files by renaming them based on coordinates from the CSV.
415
+ This method works on folders containing 'Topography1.tiff'.
416
+ """
417
+ folder_names = []
418
+
419
+ # Find all subfolders containing "Topography1.tiff"
420
+ for subdir, _, files in os.walk(self.dirname):
421
+ for file in files:
422
+ if file.endswith(".001"):
423
+ folder_names.append(subdir)
424
+
425
+ print(folder_names) # debug print
426
+
427
+ # Process each folder found
428
+ for folder in folder_names:
429
+ for subdir, _, files in os.walk(folder):
430
+ for file in files:
431
+ if file.endswith(".tiff"):
432
+ print(subdir)
433
+ print(f"Processing: {file}")
434
+ old_filepath = os.path.join(subdir, file)
435
+
436
+ matching_files = glob.glob(os.path.join(folder, '*.001'))
437
+ print("Found .001 files:", matching_files) # debug print
438
+
439
+ if matching_files:
440
+ recipe_path = matching_files[0]
441
+ self.coordinates = self.extract_positions(recipe_path)
442
+ else:
443
+ return
444
+
445
+
446
+ print(self.coordinates)
447
+
448
+ try:
449
+ defect_part = int(file.split("_")[1]) - 1
450
+ print(f"Defect part index: {defect_part}")
451
+ except (IndexError, ValueError):
452
+ print(
453
+ f"Skipping file due to unexpected format: {file}")
454
+ continue
455
+
456
+ # Check if defect part is within the valid range
457
+
458
+
459
+ # Get X and Y coordinates
460
+
461
+
462
+ if defect_part >= len(self.coordinates):
463
+ print(
464
+ f"Skipping file {file} due to out-of-bounds "
465
+ f"defect part.")
466
+ continue
467
+
468
+ x = self.coordinates.iloc[defect_part, 0]
469
+ y = self.coordinates.iloc[defect_part, 1]
470
+ new_filename = f"{x}_{y}"
471
+
472
+ # Add specific suffix based on the file type
473
+ if "_Class_1_Internal" in file:
474
+ new_filename += "_BSE.tif"
475
+ elif "_Class_1_Topography" in file:
476
+ topo_number = file.split("_Class_1_Topography")[1][
477
+ 0] # Extract Topography number
478
+ new_filename += f"_SE{topo_number}.tiff"
479
+ else:
480
+ print(
481
+ f"Skipping file due to unexpected format: {file}")
482
+ continue
483
+
484
+ # Construct the new file path and rename
485
+ new_filepath = os.path.join(subdir, new_filename)
486
+ os.rename(old_filepath, new_filepath)
487
+ print(f"Renamed: {old_filepath} -> {new_filepath}")
488
+
489
+ def rename_wo_legend(self):
490
+ """
491
+ Preprocess TIFF files for a specific wafer
492
+ by renaming based on coordinates.
493
+ This method processes the folder for the specified wafer.
494
+ """
495
+ wafer_path = os.path.join(self.dirname, self.wafer_number)
496
+ if not os.path.exists(wafer_path):
497
+ print(f"Error: The wafer folder {wafer_path} does not exist.")
498
+ return
499
+
500
+ for subdir, _, files in os.walk(wafer_path):
501
+ for file in files:
502
+ if file.endswith(".tiff"):
503
+ print(f"Processing: {file}")
504
+
505
+ old_filepath = os.path.join(subdir, file)
506
+
507
+ try:
508
+ defect_part = int(file.split("_")[1]) - 1
509
+ print(f"Defect part index: {defect_part}")
510
+ except (IndexError, ValueError):
511
+ print(
512
+ f"Skipping file due to unexpected format: {file}")
513
+ continue
514
+
515
+ matching_files = glob.glob(os.path.join(wafer_path, '*.001'))
516
+
517
+ print("Found .001 files:", matching_files) # debug print
518
+
519
+ if matching_files:
520
+ recipe_path = matching_files[0]
521
+ else:
522
+ return
523
+ self.coordinates = self.extract_positions(recipe_path)
524
+
525
+ # Check if defect part is within the valid range
526
+ if defect_part >= len(self.coordinates):
527
+ print(
528
+ f"Skipping file {file} "
529
+ f"due to out-of-bounds defect part.")
530
+ continue
531
+
532
+ x = self.coordinates.iloc[defect_part, 0]
533
+ y = self.coordinates.iloc[defect_part, 1]
534
+ new_filename = f"{x}_{y}"
535
+
536
+ # Add specific suffix based on the file type
537
+ if "_Class_1_Internal" in file:
538
+ new_filename += "_BSE.tiff"
539
+ elif "_Class_1_Topography" in file:
540
+ topo_number = file.split("_Class_1_Topography")[1][
541
+ 0] # Extract Topography number
542
+ new_filename += f"_SE{topo_number}.tiff"
543
+ else:
544
+ print(
545
+ f"Skipping file due to unexpected format: {file}")
546
+ continue
547
+
548
+ # Construct the new file path and rename
549
+ new_filepath = os.path.join(subdir, new_filename)
550
+ os.rename(old_filepath, new_filepath)
551
+ print(f"Renamed: {old_filepath} -> {new_filepath}")
552
+
553
+ def clean_folders_and_files(self):
554
+ """
555
+ Clean up folders and files by deleting specific TIFF files
556
+ """
557
+ for folder, subfolders, files in os.walk(self.dirname):
558
+ # Rename folders like "w01" -> "1"
559
+ for subfolder in subfolders:
560
+ match = re.fullmatch(r"w0*(\d+)", subfolder)
561
+ if match:
562
+ new_name = match.group(1)
563
+ old_path = os.path.join(folder, subfolder)
564
+ new_path = os.path.join(folder, new_name)
565
+
566
+ # Avoid name conflicts
567
+ if not os.path.exists(new_path):
568
+ os.rename(old_path, new_path)
569
+ print(f"Renamed: {old_path} -> {new_path}")
570
+ else:
571
+ print(f"Conflict: {new_path} already exists. Skipped renaming.")
572
+
573
+ for folder, subfolders, files in os.walk(self.dirname):
574
+ # Delete .tiff files that contain "Raw" in their name
575
+ for file in files:
576
+ if file.endswith(".tiff") and "Raw" in file:
577
+ print(file)
578
+ file_path = os.path.join(folder, file)
579
+ os.remove(file_path)
580
+ print(f"Deleted: {file_path}")
581
+
582
+ if __name__ == "__main__":
583
+ DIRNAME = r"C:\Users\TM273821\Desktop\SEM\RAW"
584
+ SCALE = r"C:\Users\TM273821\SEM\settings_data.json"
585
+
586
+ processor = Process(DIRNAME, wafer=18, scale=SCALE)
587
+
588
+ # Process files
589
+ # processor.organize_and_rename_files() # Organize and rename files
590
+ # processor.rename_wo_legend_all() # Preprocess all files in the directory
591
+ # processor.clean_folders_and_files()
592
+ processor.rename_wo_legend() # Preprocess specific wafer
593
+
594
+ # processor.split_tiff_all() # Preprocess specific wafer
595
+ # processor.split_tiff_all() # Preprocess specific wafer
596
+ # processor.split_tiff() # Preprocess specific wafer
597
+ # processor.rename_all() # Preprocess specific wafer
598
+
599
+
semapp/__init__.py ADDED
File without changes
semapp/main.py ADDED
@@ -0,0 +1,86 @@
1
+ """
2
+ GUI for data visualization.
3
+
4
+ This module implements the main window and core functionality
5
+ for the SEM data visualization application.
6
+ """
7
+
8
+ import sys
9
+ from PyQt5.QtCore import QTimer
10
+ from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout
11
+ from semapp.Layout.main_window_att import LayoutFrame
12
+ from semapp.Layout.create_button import ButtonFrame
13
+ from semapp.Plot.frame_attributes import PlotFrame
14
+
15
+ # Constants
16
+ TIMER_INTERVAL = 200 # Milliseconds
17
+ BACKGROUND_COLOR = "#F5F5F5"
18
+
19
+
20
+ class MainWindow(QWidget): # pylint: disable=R0903
21
+ """
22
+ Main window for data visualization.
23
+
24
+ This class handles the main application window and initializes all UI components.
25
+ It manages the layout, plotting area, and button controls for the application.
26
+
27
+ Attributes:
28
+ canvas_widget (QWidget): The main widget container
29
+ canvas_layout (QGridLayout): The main layout manager
30
+ layout_frame (LayoutFrame): Handles layout configuration
31
+ button_frame (ButtonFrame): Contains all button controls
32
+ plot_frame (PlotFrame): Manages the plotting area
33
+ timer (QTimer): Updates the scroll area size
34
+ """
35
+
36
+ def __init__(self):
37
+ """Initialize the main window and UI components."""
38
+ super().__init__()
39
+ self.canvas_widget = None
40
+ self.canvas_layout = None
41
+ self.layout_frame = None
42
+ self.button_frame = None
43
+ self.plot_frame = None
44
+ self.timer = None
45
+ self.init_ui()
46
+
47
+ def init_ui(self):
48
+ """
49
+ Initialize the user interface.
50
+
51
+ Sets up the window properties, layouts, and all UI components.
52
+ Configures the main layout, creates frames for different sections,
53
+ and initializes the update timer.
54
+ """
55
+ self.setWindowTitle("Data Visualization")
56
+ self.setStyleSheet(f"background-color: {BACKGROUND_COLOR};")
57
+
58
+ # Create the main layout (canvas_layout)
59
+ self.canvas_widget = QWidget(self)
60
+ self.canvas_layout = QGridLayout(self.canvas_widget)
61
+
62
+ # Use LayoutFrame for layout configuration
63
+ self.layout_frame = LayoutFrame(self)
64
+ self.layout_frame.setup_layout(self.canvas_widget, self.canvas_layout)
65
+
66
+ self.button_frame = ButtonFrame(self.canvas_layout)
67
+ self.plot_frame = PlotFrame(self.canvas_layout, self.button_frame)
68
+
69
+ # Set/adapt the maximum window size
70
+ self.layout_frame.set_max_window_size()
71
+ self.layout_frame.position_window_top_left()
72
+
73
+ self.timer = QTimer(self)
74
+ self.timer.timeout.connect(self.layout_frame.adjust_scroll_area_size)
75
+ self.timer.start(TIMER_INTERVAL)
76
+
77
+
78
+ def main():
79
+ print("SEMapp launched")
80
+ app = QApplication(sys.argv)
81
+ window = MainWindow()
82
+ window.show()
83
+ sys.exit(app.exec_())
84
+
85
+ if __name__ == "__main__":
86
+ main()