Semapp 1.0.5__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.
semapp/Plot/utils.py ADDED
@@ -0,0 +1,295 @@
1
+ """
2
+ Utility functions for frame management and screenshot functionality.
3
+
4
+ This module provides functions to create save buttons and manage frame layouts
5
+ in the application. It includes functionality for capturing and saving
6
+ combined screenshots of multiple frames.
7
+ """
8
+
9
+ import os
10
+ import csv
11
+ import pandas as pd
12
+ from PyQt5.QtCore import Qt
13
+ from PyQt5.QtWidgets import QPushButton, QFileDialog
14
+ from PyQt5.QtGui import QPixmap, QPainter
15
+
16
+ # Style constants
17
+ SAVE_BUTTON_STYLE = """
18
+ QPushButton {
19
+ font-size: 16px;
20
+ background-color: #e1bee7;
21
+ border: 2px solid #8c8c8c;
22
+ border-radius: 10px;
23
+ padding: 5px;
24
+ height: 20px;
25
+ }
26
+ QPushButton:hover {
27
+ background-color: #ce93d8;
28
+ }
29
+ """
30
+
31
+ BUTTON_POSITION = {
32
+ 'row': 5, # Shifted down by 1 row
33
+ 'column': 0,
34
+ 'row_span': 1,
35
+ 'column_span': 6
36
+ }
37
+
38
+
39
+ def update_merged_csv_results(wafer_folder, filename, result, image_size_um):
40
+ """
41
+ Update the merged_threshold_results.csv file by replacing the row with the same filename.
42
+
43
+ Args:
44
+ wafer_folder: Path to the wafer folder containing the CSV
45
+ filename: Name of the processed image file
46
+ result: Result dictionary from SEMThresholdProcessor.process_single_image
47
+ image_size_um: Image size in micrometers
48
+ """
49
+ csv_path = os.path.join(wafer_folder, "merged_threshold_results.csv")
50
+
51
+ # Calculate statistics from the result
52
+ particles = result['detected_particles']
53
+ binary_image = result['binary_image']
54
+
55
+ # Calculate statistics for this file
56
+ import numpy as np
57
+ import cv2
58
+
59
+ mask_particles = np.zeros_like(binary_image, dtype=np.uint8)
60
+ for particle in particles:
61
+ cv2.fillPoly(mask_particles, [particle['contour']], 200)
62
+
63
+ num_particles = len(particles)
64
+ total_area_pixels = int(np.count_nonzero(mask_particles == 200))
65
+ avg_area_pixels = total_area_pixels / num_particles if num_particles > 0 else 0
66
+
67
+ # Calculate areas in µm²
68
+ height, width = binary_image.shape
69
+ pixel_area_um2 = (image_size_um ** 2) / (height * width)
70
+
71
+ total_area_um2 = total_area_pixels * pixel_area_um2
72
+ avg_area_um2 = avg_area_pixels * pixel_area_um2
73
+ total_area_percentage = (total_area_pixels / (height * width)) * 100
74
+
75
+ # Calculate density
76
+ surface_area_um2 = image_size_um ** 2
77
+ density = num_particles / surface_area_um2 if surface_area_um2 > 0 else 0
78
+
79
+ # Create new row data
80
+ new_row = {
81
+ 'Filename': filename,
82
+ 'Page_Index': 1, # Single image, so page index is 1
83
+ 'Density': density,
84
+ 'Avg_area_um2': avg_area_um2,
85
+ 'Total_area_percentage': total_area_percentage,
86
+ 'Num_particles': num_particles,
87
+ 'Total_area_um2': total_area_um2
88
+ }
89
+
90
+ # Check if CSV file exists
91
+ if os.path.exists(csv_path):
92
+ # Read existing CSV
93
+ df = pd.read_csv(csv_path)
94
+
95
+ # Check if filename already exists
96
+ existing_row_index = df[df['Filename'] == filename].index
97
+
98
+ if len(existing_row_index) > 0:
99
+ # Replace existing row
100
+ df.loc[existing_row_index[0]] = new_row
101
+ print(f"Updated existing row for {filename} in merged_threshold_results.csv")
102
+ else:
103
+ # Add new row
104
+ df = pd.concat([df, pd.DataFrame([new_row])], ignore_index=True)
105
+ print(f"Added new row for {filename} in merged_threshold_results.csv")
106
+
107
+ # Save updated CSV
108
+ df.to_csv(csv_path, index=False)
109
+ print(f"Updated CSV saved: {csv_path}")
110
+ else:
111
+ # Create new CSV file
112
+ df = pd.DataFrame([new_row])
113
+ df.to_csv(csv_path, index=False)
114
+ print(f"Created new CSV file: {csv_path}")
115
+
116
+
117
+ def create_savebutton(layout, frame_left, frame_right, button_frame=None):
118
+ """
119
+ Create save buttons to capture frames and process images.
120
+
121
+ Args:
122
+ layout: The layout where the save buttons will be added
123
+ frame_left: The left frame to capture
124
+ frame_right: The right frame to capture
125
+ button_frame: Reference to button frame for accessing sliders and parameters
126
+
127
+ Returns:
128
+ None
129
+ """
130
+ def save_image():
131
+ """
132
+ Capture and save specific frames as a combined image.
133
+
134
+ Creates a screenshot of both left and right frames, combines them
135
+ horizontally and saves the result as a PNG file.
136
+ """
137
+ screen_left = frame_left.grab()
138
+ screen_right = frame_right.grab()
139
+
140
+ file_name, _ = QFileDialog.getSaveFileName(
141
+ parent=None,
142
+ caption="Save screenshot",
143
+ directory="",
144
+ filter="PNG Files (*.png);;All Files (*)"
145
+ )
146
+
147
+ if file_name:
148
+ combined_width = screen_left.width() + screen_right.width()
149
+ combined_height = max(screen_left.height(), screen_right.height())
150
+ combined_pixmap = QPixmap(combined_width, combined_height)
151
+
152
+ # Fill the combined QPixmap with a white background
153
+ combined_pixmap.fill(Qt.white)
154
+
155
+ painter = QPainter(combined_pixmap)
156
+ painter.drawPixmap(0, 0, screen_left)
157
+ painter.drawPixmap(screen_left.width(), 0, screen_right)
158
+ painter.end()
159
+
160
+ # Save the combined image
161
+ combined_pixmap.save(file_name, "PNG")
162
+
163
+ def save_processed_image():
164
+ """
165
+ Process and save the current image using SEMThresholdProcessor.
166
+ """
167
+ if not button_frame:
168
+ return
169
+
170
+ # Get parameters from sliders
171
+ threshold = button_frame.get_threshold_value()
172
+ min_size = button_frame.get_min_size_value()
173
+
174
+ # Get image size from settings data
175
+ image_size_um = 5.0 # Default value
176
+ if hasattr(button_frame, 'settings_window') and button_frame.settings_window:
177
+ settings_data = button_frame.settings_window.data
178
+ if settings_data and len(settings_data) > 0:
179
+ # Get the first entry's scale value
180
+ first_entry = settings_data[0]
181
+ if "Scale" in first_entry:
182
+ scale_str = first_entry["Scale"]
183
+ # Extract numeric value from scale (e.g., "5x5" -> 5.0)
184
+ try:
185
+ # Handle formats like "5x5", "5", "5.0x5.0"
186
+ if 'x' in scale_str:
187
+ image_size_um = float(scale_str.split('x')[0])
188
+ else:
189
+ image_size_um = float(scale_str)
190
+ except (ValueError, IndexError):
191
+ image_size_um = 5.0 # Fallback to default
192
+ print(f"Using image size from settings: {image_size_um} um")
193
+ else:
194
+ print("No Scale found in settings data")
195
+ else:
196
+ print("No settings data available")
197
+ else:
198
+ print("No settings window available")
199
+
200
+ # Get selected wafer
201
+ selected_wafer = button_frame.get_selected_option()
202
+ if not selected_wafer:
203
+ return
204
+
205
+ # Get current image path from plot frame
206
+ current_image_path = None
207
+ if hasattr(button_frame, 'plot_frame') and button_frame.plot_frame:
208
+ current_image_path = button_frame.plot_frame.get_current_image_path()
209
+
210
+ if current_image_path:
211
+ # Get current position from plot frame
212
+ current_x = None
213
+ current_y = None
214
+ if hasattr(button_frame, 'plot_frame') and button_frame.plot_frame:
215
+ # Try to get the last clicked position
216
+ if hasattr(button_frame.plot_frame, 'last_clicked_position'):
217
+ current_x, current_y = button_frame.plot_frame.last_clicked_position
218
+ else:
219
+ print("No clicked position available")
220
+ return
221
+
222
+ if current_x is not None and current_y is not None:
223
+ # Create custom filename using template (without "pos")
224
+ filename = f"{image_size_um}_{current_x:.1f}_{current_y:.1f}.tiff"
225
+
226
+ # Save in wafer subfolder with custom filename
227
+ wafer_folder = os.path.join(button_frame.dirname, str(selected_wafer))
228
+ os.makedirs(wafer_folder, exist_ok=True)
229
+ custom_path = os.path.join(wafer_folder, filename)
230
+
231
+ # Get the currently displayed image (with threshold applied)
232
+ if hasattr(button_frame.plot_frame, 'image_list') and button_frame.plot_frame.image_list:
233
+ current_index = button_frame.plot_frame.current_index
234
+ if 0 <= current_index < len(button_frame.plot_frame.image_list):
235
+ # Get the processed image (with threshold applied)
236
+ processed_image = button_frame.plot_frame.image_list[current_index]
237
+
238
+ # Apply threshold processing to get the final image
239
+ thresholded_image = button_frame.plot_frame._apply_threshold(processed_image, threshold)
240
+
241
+ # Save the thresholded image directly
242
+ thresholded_image.save(custom_path)
243
+ print(f"Processed image saved to: {custom_path}")
244
+
245
+ # Now process with SEMThresholdProcessor to generate CSV
246
+ from semapp.Processing.threshold import SEMThresholdProcessor
247
+ processor = SEMThresholdProcessor(
248
+ threshold=threshold,
249
+ min_size=min_size,
250
+ image_size_um=image_size_um,
251
+ save_results=True,
252
+ verbose=True
253
+ )
254
+
255
+ # Process the saved image to generate CSV
256
+ result = processor.process_single_image(custom_path, show_result=False)
257
+ if result:
258
+ print(f"CSV results saved for: {custom_path}")
259
+
260
+ # Update the merged_threshold_results.csv file
261
+ update_merged_csv_results(wafer_folder, filename, result, image_size_um)
262
+ else:
263
+ print("Failed to generate CSV results")
264
+ else:
265
+ print("Invalid image index")
266
+ else:
267
+ print("No image list available")
268
+ else:
269
+ print("No valid position coordinates available")
270
+
271
+ # Create and configure the save buttons
272
+ processed_button = QPushButton("Save processed image")
273
+ processed_button.setStyleSheet(SAVE_BUTTON_STYLE)
274
+ processed_button.clicked.connect(save_processed_image)
275
+
276
+ save_button = QPushButton("Screenshot")
277
+ save_button.setStyleSheet(SAVE_BUTTON_STYLE)
278
+ save_button.clicked.connect(save_image)
279
+
280
+ # Add buttons to the layout
281
+ layout.addWidget(
282
+ processed_button,
283
+ BUTTON_POSITION['row'],
284
+ BUTTON_POSITION['column'],
285
+ 1, # row_span
286
+ 3 # column_span (half width)
287
+ )
288
+
289
+ layout.addWidget(
290
+ save_button,
291
+ BUTTON_POSITION['row'],
292
+ BUTTON_POSITION['column'] + 3, # Start after processed button
293
+ 1, # row_span
294
+ 3 # column_span (half width)
295
+ )
@@ -0,0 +1,4 @@
1
+ """Processing module initialization"""
2
+ from .processing import Process
3
+
4
+ __all__ = ['Process']