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.
- semapp/Layout/__init__.py +26 -0
- semapp/Layout/create_button.py +496 -0
- semapp/Layout/main_window_att.py +54 -0
- semapp/Layout/settings.py +170 -0
- semapp/Layout/styles.py +152 -0
- semapp/Plot/__init__.py +8 -0
- semapp/Plot/frame_attributes.py +408 -0
- semapp/Plot/styles.py +40 -0
- semapp/Plot/utils.py +93 -0
- semapp/Processing/__init__.py +4 -0
- semapp/Processing/processing.py +599 -0
- semapp/__init__.py +0 -0
- semapp/main.py +86 -0
- semapp-1.0.0.dist-info/METADATA +21 -0
- semapp-1.0.0.dist-info/RECORD +19 -0
- semapp-1.0.0.dist-info/WHEEL +5 -0
- semapp-1.0.0.dist-info/entry_points.txt +2 -0
- semapp-1.0.0.dist-info/licenses/LICENSE +674 -0
- semapp-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -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()
|