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.
@@ -0,0 +1,686 @@
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
+ from semapp.Processing.klarf_reader import extract_positions
13
+ from semapp.Processing.rename_tif import rename_files, rename_files_all
14
+ from semapp.Processing.split_tif import split_tiff, split_tiff_all
15
+
16
+
17
+ class Process:
18
+ """
19
+ A class to handle processing of TIFF files and renaming them based on
20
+ coordinates from KLARF files.
21
+
22
+ This class supports three modes:
23
+ - Normal mode: Standard KLARF format with subdirectories
24
+ - COMPLUS4T mode: Multi-wafer KLARF files in parent directory
25
+ - KRONOS mode: Special format with OCR-based number detection
26
+ """
27
+
28
+ def __init__(self, dirname, wafer=None, scale=None):
29
+ """
30
+ Initialize the processing instance with necessary parameters.
31
+
32
+ Args:
33
+ dirname (str): The base directory for the files.
34
+ wafer (int, optional): The wafer number. Defaults to None.
35
+ scale (str, optional): The path to the settings JSON file. Defaults to None.
36
+ """
37
+ self.dirname = dirname
38
+ self.scale_data = scale
39
+ self.wafer_number = str(wafer)
40
+ self.tiff_path = None
41
+ self.coordinates = None
42
+ self.settings = None
43
+ self.output_dir = None
44
+ self.load_json()
45
+ def load_json(self):
46
+ """
47
+ Load the settings data from a JSON file.
48
+
49
+ The settings file should contain a list of dictionaries with
50
+ 'Scale' and 'Image Type' keys. If the file is not found or
51
+ invalid, an empty list is used.
52
+ """
53
+ if not self.scale_data:
54
+ self.settings = []
55
+ return
56
+
57
+ try:
58
+ with open(self.scale_data, "r", encoding="utf-8") as file:
59
+ self.settings = json.load(file)
60
+ except FileNotFoundError:
61
+ # Settings file not found, starting fresh
62
+ self.settings = []
63
+ except json.JSONDecodeError:
64
+ # JSON decoding error
65
+ self.settings = []
66
+ except OSError:
67
+ # OS error when reading file
68
+ self.settings = []
69
+ def extract_positions(self, filepath):
70
+ """
71
+ Extract defect positions from KLARF file.
72
+
73
+ Wrapper method that calls the klarf_reader module. Automatically
74
+ detects the mode (KRONOS, COMPLUS4T, or normal) and extracts
75
+ coordinates accordingly.
76
+
77
+ Args:
78
+ filepath (str): Path to the KLARF (.001) file
79
+
80
+ Returns:
81
+ pd.DataFrame: DataFrame with columns ["defect_id", "X", "Y", "defect_size"]
82
+ Returns empty DataFrame if file cannot be parsed.
83
+ """
84
+ # Convert self.wafer_number to int if it's not None (for COMPLUS4T mode)
85
+ wafer_id = None
86
+ if self.wafer_number and self.wafer_number != "None":
87
+ try:
88
+ wafer_id = int(self.wafer_number)
89
+ except (ValueError, TypeError):
90
+ wafer_id = None
91
+
92
+ # Call the klarf_reader function
93
+ self.coordinates = extract_positions(filepath, wafer_id=wafer_id)
94
+ return self.coordinates
95
+
96
+ def rename(self):
97
+ """
98
+ Rename TIFF files based on coordinates from KLARF file.
99
+
100
+ Wrapper method that calls the rename_tif module. Automatically
101
+ detects the mode and uses the appropriate renaming scheme:
102
+ - Normal mode: Uses scale and image type from settings
103
+ - COMPLUS4T mode: Uses only X and Y coordinates
104
+ - KRONOS mode: Uses only X and Y coordinates
105
+
106
+ The method verifies that no duplicate scale/image_type combinations
107
+ exist before proceeding.
108
+ """
109
+ print("\n" + "="*80)
110
+ print("[DEBUG] rename() called")
111
+ print(f"[DEBUG] dirname: {self.dirname}")
112
+ print(f"[DEBUG] wafer_number: {self.wafer_number}")
113
+ print("="*80)
114
+
115
+ # Security check: verify no duplicate scale/image_type combinations
116
+ scale_image_combinations = []
117
+ for setting in self.settings:
118
+ combination = f"{setting['Scale']}_{setting['Image Type']}"
119
+ scale_image_combinations.append(combination)
120
+
121
+ # Check for duplicates
122
+ if len(scale_image_combinations) != len(set(scale_image_combinations)):
123
+ duplicate_combinations = []
124
+ seen = set()
125
+ for combo in scale_image_combinations:
126
+ if combo in seen:
127
+ duplicate_combinations.append(combo)
128
+ else:
129
+ seen.add(combo)
130
+
131
+ print(f"Warning: Duplicate scale/image_type combinations found: {duplicate_combinations}. "
132
+ f"Skipping rename operation.")
133
+ return
134
+
135
+ self.output_dir = os.path.join(self.dirname, self.wafer_number)
136
+ print(f"[DEBUG] output_dir: {self.output_dir}")
137
+
138
+ if not os.path.exists(self.output_dir):
139
+ print(f"[DEBUG] output_dir does not exist, returning")
140
+ return
141
+
142
+ # Check if COMPLUS4T mode: .001 files are in parent directory
143
+ is_complus4t = self._check_complus4t_mode()
144
+ print(f"[DEBUG] is_complus4t: {is_complus4t}")
145
+
146
+ if is_complus4t:
147
+ # COMPLUS4T mode: .001 file is in parent directory
148
+ matching_files = glob.glob(os.path.join(self.dirname, '*.001'))
149
+ print(f"[DEBUG] COMPLUS4T: Found {len(matching_files)} .001 files in parent directory")
150
+ recipe_path = None
151
+
152
+ # Find the .001 file that contains the selected wafer ID
153
+ for file_path in matching_files:
154
+ print(f"[DEBUG] Checking file: {os.path.basename(file_path)} for wafer {self.wafer_number}")
155
+ if self._is_wafer_in_klarf(file_path, int(self.wafer_number)):
156
+ recipe_path = file_path
157
+ print(f"[DEBUG] Found matching .001 file: {recipe_path}")
158
+ break
159
+ else:
160
+ # Normal/KRONOS mode: .001 file is in wafer subdirectory
161
+ matching_files = glob.glob(os.path.join(self.output_dir, '*.001'))
162
+ print(f"[DEBUG] Normal/KRONOS: Found {len(matching_files)} .001 files in output_dir")
163
+ if matching_files:
164
+ recipe_path = matching_files[0]
165
+ print(f"[DEBUG] Using .001 file: {recipe_path}")
166
+ else:
167
+ recipe_path = None
168
+
169
+ if recipe_path:
170
+ # Check if KRONOS mode
171
+ is_kronos = self._check_kronos_mode(recipe_path)
172
+ print(f"[DEBUG] is_kronos: {is_kronos}")
173
+ # For COMPLUS4T, pass wafer_id explicitly. For normal mode, pass None
174
+ if is_complus4t:
175
+ # Temporarily store original wafer_number and set it for extract_positions
176
+ original_wafer_number = self.wafer_number
177
+ print(f"[DEBUG] COMPLUS4T: Setting wafer_number to {self.wafer_number} for extract_positions")
178
+ self.wafer_number = str(self.wafer_number)
179
+ self.coordinates = self.extract_positions(recipe_path)
180
+ self.wafer_number = original_wafer_number
181
+ print(f"[DEBUG] COMPLUS4T: Extracted {len(self.coordinates)} coordinates")
182
+ else:
183
+ # Normal mode: set wafer_number to None so extract_positions reads all defects
184
+ original_wafer_number = self.wafer_number
185
+ print(f"[DEBUG] Normal/KRONOS: Setting wafer_number to None for extract_positions")
186
+ self.wafer_number = None
187
+ self.coordinates = self.extract_positions(recipe_path)
188
+ self.wafer_number = original_wafer_number
189
+ print(f"[DEBUG] Normal/KRONOS: Extracted {len(self.coordinates)} coordinates")
190
+ else:
191
+ print(f"[DEBUG] Warning: No .001 file found for wafer {self.wafer_number}")
192
+ return
193
+
194
+ # Check if KRONOS mode (if not already checked)
195
+ if not is_complus4t and recipe_path:
196
+ is_kronos = self._check_kronos_mode(recipe_path)
197
+ else:
198
+ is_kronos = False
199
+
200
+ print(f"[DEBUG] Final is_kronos: {is_kronos}")
201
+ print(f"[DEBUG] Coordinates DataFrame:")
202
+ print(self.coordinates)
203
+
204
+ # Call the rename_tif module function
205
+ renamed_count = rename_files(
206
+ output_dir=self.output_dir,
207
+ coordinates=self.coordinates,
208
+ settings=self.settings,
209
+ is_kronos=is_kronos,
210
+ is_complus4t=is_complus4t
211
+ )
212
+
213
+ print(f"[DEBUG] Total files renamed: {renamed_count}")
214
+ print("="*80 + "\n")
215
+
216
+ def split_tiff(self):
217
+ """
218
+ Split a merged TIFF file into individual TIFF files.
219
+ Wrapper method that calls the split_tif module.
220
+
221
+ Returns:
222
+ list: List of file paths of the generated TIFF files.
223
+ """
224
+ # Check if COMPLUS4T mode: TIFF file is in parent directory
225
+ is_complus4t = self._check_complus4t_mode()
226
+
227
+ if is_complus4t:
228
+ # COMPLUS4T mode: TIFF file is in parent directory
229
+ tiff_files = glob.glob(os.path.join(self.dirname, '*.tiff'))
230
+ if not tiff_files:
231
+ tiff_files = glob.glob(os.path.join(self.dirname, '*.tif'))
232
+ if tiff_files:
233
+ self.tiff_path = tiff_files[0] # Use first TIFF file found
234
+ else:
235
+ return []
236
+ # Output directory is the wafer subdirectory
237
+ output_dir = os.path.join(self.dirname, self.wafer_number)
238
+
239
+ # For COMPLUS4T: extract positions first to get defect_id list
240
+ matching_files = glob.glob(os.path.join(self.dirname, '*.001'))
241
+ recipe_path = None
242
+
243
+ # Find the .001 file that contains the selected wafer ID
244
+ for file_path in matching_files:
245
+ if self._is_wafer_in_klarf(file_path, int(self.wafer_number)):
246
+ recipe_path = file_path
247
+ break
248
+
249
+ if not recipe_path:
250
+ print(f"Warning: No .001 file found for wafer {self.wafer_number}")
251
+ return []
252
+
253
+ # Extract coordinates to get defect_id list
254
+ self.coordinates = self.extract_positions(recipe_path)
255
+
256
+ if self.coordinates is None or self.coordinates.empty:
257
+ print(f"Warning: No coordinates found for wafer {self.wafer_number}")
258
+ return []
259
+ else:
260
+ # Normal/KRONOS mode: TIFF file is in wafer subdirectory
261
+ self.tiff_path = os.path.join(self.dirname,
262
+ self.wafer_number,
263
+ "data.tif")
264
+ output_dir = os.path.join(self.dirname, self.wafer_number)
265
+ self.coordinates = None # Not needed for normal/KRONOS mode
266
+
267
+ # Check if KRONOS mode
268
+ is_kronos = False
269
+ if not is_complus4t:
270
+ # Check for KRONOS in the wafer subdirectory
271
+ matching_files = glob.glob(os.path.join(output_dir, '*.001'))
272
+ if matching_files:
273
+ is_kronos = self._check_kronos_mode(matching_files[0])
274
+
275
+ # Call the split_tif module function
276
+ output_files = split_tiff(
277
+ tiff_path=self.tiff_path,
278
+ output_dir=output_dir,
279
+ coordinates=self.coordinates,
280
+ is_kronos=is_kronos,
281
+ is_complus4t=is_complus4t
282
+ )
283
+
284
+ return output_files
285
+
286
+ def clean(self):
287
+ """
288
+ Clean up the output directory by deleting any non-conforming TIFF files.
289
+ This method deletes any files that do not follow the expected naming
290
+ conventions (files not starting with "data" or
291
+ containing the word "page").
292
+ """
293
+ self.output_dir = os.path.join(self.dirname, self.wafer_number)
294
+
295
+ if not os.path.exists(self.output_dir):
296
+ return
297
+
298
+ tiff_files = [f for f in os.listdir(self.output_dir)
299
+ if f.lower().endswith(('.tiff', '.tif'))]
300
+
301
+ # Delete non-conforming files
302
+ for file_name in tiff_files:
303
+ if not file_name.startswith("data") or "page" in file_name.lower() or file_name.endswith("001"):
304
+ file_path = os.path.join(self.output_dir, file_name)
305
+ os.remove(file_path)
306
+
307
+ def split_tiff_all(self):
308
+ """
309
+ Split all merged TIFF files in the directory (including subdirectories)
310
+ into individual TIFF files.
311
+ Wrapper method that calls the split_tif module.
312
+
313
+ This method will look through all directories and split each `data.tif`
314
+ file into separate pages.
315
+ """
316
+ # Check if COMPLUS4T mode
317
+ is_complus4t = self._check_complus4t_mode()
318
+
319
+ coordinates_dict = None
320
+ is_kronos = False
321
+
322
+ if is_complus4t:
323
+ # COMPLUS4T mode: need to extract coordinates for all wafers
324
+ matching_files = glob.glob(os.path.join(self.dirname, '*.001'))
325
+ if not matching_files:
326
+ print("Warning: No .001 file found in parent directory for COMPLUS4T mode")
327
+ return []
328
+
329
+ parent_recipe_path = matching_files[0]
330
+ coordinates_dict = {}
331
+
332
+ # Extract coordinates for each wafer
333
+ for subdir, _, _ in os.walk(self.dirname):
334
+ if subdir == self.dirname:
335
+ continue
336
+
337
+ try:
338
+ wafer_num = int(os.path.basename(subdir))
339
+ except ValueError:
340
+ continue
341
+
342
+ original_wafer_number = self.wafer_number
343
+ self.wafer_number = str(wafer_num)
344
+ coordinates = self.extract_positions(parent_recipe_path)
345
+ self.wafer_number = original_wafer_number
346
+
347
+ if coordinates is not None and not coordinates.empty:
348
+ coordinates_dict[wafer_num] = coordinates
349
+ else:
350
+ # Normal/KRONOS mode: check for KRONOS in first subdirectory
351
+ for subdir, _, _ in os.walk(self.dirname):
352
+ if subdir == self.dirname:
353
+ continue
354
+
355
+ matching_files = glob.glob(os.path.join(subdir, '*.001'))
356
+ if matching_files:
357
+ is_kronos = self._check_kronos_mode(matching_files[0])
358
+ break
359
+
360
+ # Call the split_tif module function
361
+ output_files = split_tiff_all(
362
+ dirname=self.dirname,
363
+ coordinates_dict=coordinates_dict,
364
+ is_kronos=is_kronos,
365
+ is_complus4t=is_complus4t
366
+ )
367
+
368
+ return output_files
369
+
370
+ def rename_all(self):
371
+ """
372
+ Rename all TIFF files based on the coordinates from the
373
+ CSV file in all subdirectories.
374
+ Wrapper method that calls the rename_tif module.
375
+
376
+ This method will iterate through all subdirectories,
377
+ loading the CSV and settings, and renaming files accordingly.
378
+ """
379
+ print("\n" + "="*80)
380
+ print("[DEBUG] rename_all() called")
381
+ print(f"[DEBUG] dirname: {self.dirname}")
382
+ print("="*80)
383
+
384
+ # Security check: verify no duplicate scale/image_type combinations
385
+ scale_image_combinations = []
386
+ for setting in self.settings:
387
+ combination = f"{setting['Scale']}_{setting['Image Type']}"
388
+ scale_image_combinations.append(combination)
389
+
390
+ # Check for duplicates
391
+ if len(scale_image_combinations) != len(set(scale_image_combinations)):
392
+ duplicate_combinations = []
393
+ seen = set()
394
+ for combo in scale_image_combinations:
395
+ if combo in seen:
396
+ duplicate_combinations.append(combo)
397
+ else:
398
+ seen.add(combo)
399
+
400
+ print(f"Warning: Duplicate scale/image_type combinations found: {duplicate_combinations}. "
401
+ f"Skipping rename operation.")
402
+ return
403
+
404
+ # Check if COMPLUS4T mode: .001 files are in parent directory
405
+ is_complus4t = self._check_complus4t_mode()
406
+ print(f"[DEBUG] is_complus4t: {is_complus4t}")
407
+
408
+ if is_complus4t:
409
+ # COMPLUS4T mode: .001 file is in parent directory, find it once
410
+ matching_files = glob.glob(os.path.join(self.dirname, '*.001'))
411
+ print(f"[DEBUG] COMPLUS4T: Found {len(matching_files)} .001 files in parent directory")
412
+ if not matching_files:
413
+ print("[DEBUG] Warning: No .001 file found in parent directory for COMPLUS4T mode")
414
+ return
415
+ parent_recipe_path = matching_files[0]
416
+ print(f"[DEBUG] COMPLUS4T: Using parent recipe_path: {parent_recipe_path}")
417
+
418
+ # Build coordinates dictionary for all wafers
419
+ coordinates_dict = {}
420
+ is_kronos = False
421
+
422
+ for subdir, _, _ in os.walk(self.dirname):
423
+ if subdir == self.dirname:
424
+ continue
425
+
426
+ output_dir = os.path.join(self.dirname, os.path.basename(subdir))
427
+
428
+ try:
429
+ wafer_num = int(os.path.basename(subdir))
430
+ except ValueError:
431
+ continue
432
+
433
+ print(f"\n[DEBUG] Processing subdirectory: {subdir}")
434
+ print(f"[DEBUG] output_dir: {output_dir}")
435
+ print(f"[DEBUG] Extracted wafer_num: {wafer_num}")
436
+
437
+ if is_complus4t:
438
+ # COMPLUS4T mode: use parent .001 file and pass wafer_id
439
+ recipe_path = parent_recipe_path
440
+ print(f"[DEBUG] COMPLUS4T: Using recipe_path: {recipe_path} for wafer {wafer_num}")
441
+ original_wafer_number = self.wafer_number
442
+ self.wafer_number = str(wafer_num)
443
+ print(f"[DEBUG] COMPLUS4T: Setting wafer_number to {self.wafer_number} for extract_positions")
444
+ coordinates = self.extract_positions(recipe_path)
445
+ self.wafer_number = original_wafer_number
446
+ print(f"[DEBUG] COMPLUS4T: Extracted {len(coordinates)} coordinates for wafer {wafer_num}")
447
+ else:
448
+ # Normal/KRONOS mode: .001 file is in wafer subdirectory
449
+ matching_files = glob.glob(os.path.join(output_dir, '*.001'))
450
+ print(f"[DEBUG] Normal/KRONOS: Found {len(matching_files)} .001 files in output_dir")
451
+ if matching_files:
452
+ recipe_path = matching_files[0]
453
+ is_kronos = self._check_kronos_mode(recipe_path)
454
+ print(f"[DEBUG] is_kronos: {is_kronos}")
455
+ original_wafer_number = self.wafer_number
456
+ self.wafer_number = None
457
+ coordinates = self.extract_positions(recipe_path)
458
+ self.wafer_number = original_wafer_number
459
+ print(f"[DEBUG] Normal/KRONOS: Extracted {len(coordinates)} coordinates")
460
+ else:
461
+ print(f"[DEBUG] No .001 file found in {output_dir}, skipping")
462
+ continue
463
+
464
+ if coordinates is None or coordinates.empty:
465
+ print(f"[DEBUG] Warning: Coordinates are None or empty for wafer {wafer_num}")
466
+ continue
467
+
468
+ coordinates_dict[wafer_num] = coordinates
469
+
470
+ # Call the rename_tif module function
471
+ total_renamed = rename_files_all(
472
+ dirname=self.dirname,
473
+ coordinates_dict=coordinates_dict,
474
+ settings=self.settings,
475
+ is_kronos=is_kronos,
476
+ is_complus4t=is_complus4t
477
+ )
478
+
479
+ print(f"\n[DEBUG] Total files renamed across all directories: {total_renamed}")
480
+ print("="*80 + "\n")
481
+
482
+ def clean_all(self):
483
+ """
484
+ Delete all non-conforming TIFF files in all subdirectories.
485
+
486
+ This method will remove any files that do not follow the expected
487
+ naming conventions in all directories.
488
+ """
489
+ for subdir, _, _ in os.walk(self.dirname):
490
+ if subdir != self.dirname:
491
+ self.output_dir = os.path.join(self.dirname,
492
+ os.path.basename(subdir))
493
+
494
+ if not os.path.exists(self.output_dir):
495
+ continue
496
+
497
+ tiff_files = [f for f in os.listdir(self.output_dir)
498
+ if f.lower().endswith(('.tiff', '.tif'))]
499
+ for file_name in tiff_files:
500
+ if not file_name.startswith("data") or \
501
+ "page" in file_name.lower() or file_name.endswith("001"):
502
+ file_path = os.path.join(self.output_dir, file_name)
503
+ os.remove(file_path)
504
+
505
+ def organize_and_rename_files(self):
506
+ """
507
+ Organize TIFF files into subfolders based
508
+ on the last split of their name
509
+ and rename the files to 'data.tif' in their respective subfolders.
510
+ """
511
+ if not os.path.exists(self.dirname):
512
+ return
513
+
514
+ # Check if there are subdirectories
515
+ subdirs = [d for d in os.listdir(self.dirname) if
516
+ os.path.isdir(os.path.join(self.dirname, d))]
517
+
518
+ # Check if there are .tif files
519
+ tif_files = [f for f in os.listdir(self.dirname)
520
+ if f.lower().endswith(".tif") and os.path.isfile(os.path.join(self.dirname, f))]
521
+
522
+ # Iterate through files in the directory
523
+ for file_name in os.listdir(self.dirname):
524
+ if file_name.lower().endswith(".tif"):
525
+ parts = file_name.rsplit("_", 1)
526
+ if len(parts) < 2:
527
+ # Skip file with unexpected format
528
+ continue
529
+
530
+ # Use the last part (before extension) as the subfolder name
531
+ subfolder_name = parts[-1].split(".")[0]
532
+ subfolder_path = os.path.join(self.dirname, subfolder_name)
533
+
534
+ # Create the subfolder if it does not exist
535
+ os.makedirs(subfolder_path, exist_ok=True)
536
+
537
+ # Move and rename the file
538
+ source_path = os.path.join(self.dirname, file_name)
539
+ destination_path = os.path.join(subfolder_path, "data.tif")
540
+ shutil.move(source_path, destination_path)
541
+
542
+
543
+ if file_name.lower().endswith(".001"):
544
+ parts = file_name.rsplit("_", 1)
545
+ if len(parts) < 2:
546
+ # Skip file with unexpected format
547
+ continue
548
+
549
+ # Use the last part (before extension) as the subfolder name
550
+ subfolder_name = parts[-1].split(".")[0]
551
+ subfolder_path = os.path.join(self.dirname, subfolder_name)
552
+
553
+ # Create the subfolder if it does not exist
554
+ os.makedirs(subfolder_path, exist_ok=True)
555
+
556
+ # Move and rename the file
557
+ source_path = os.path.join(self.dirname, file_name)
558
+ destination_path = os.path.join(subfolder_path, file_name)
559
+ shutil.move(source_path, destination_path)
560
+
561
+
562
+ # If no subdirectories and no .tif files, create folders from KLARF files
563
+ if not subdirs and not tif_files:
564
+ wafer_ids = self.extract_wafer_ids_from_klarf()
565
+
566
+ if wafer_ids:
567
+ for wafer_id in wafer_ids:
568
+ subfolder_path = os.path.join(self.dirname, str(wafer_id))
569
+ os.makedirs(subfolder_path, exist_ok=True)
570
+
571
+ def _check_complus4t_mode(self):
572
+ """Check if we are in COMPLUS4T mode (.001 files with COMPLUS4T in parent directory)."""
573
+ if not self.dirname or not os.path.exists(self.dirname):
574
+ return False
575
+
576
+ # Check for .001 files with COMPLUS4T in the parent directory
577
+ matching_files = glob.glob(os.path.join(self.dirname, '*.001'))
578
+ for file_path in matching_files:
579
+ try:
580
+ with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
581
+ content = f.read()
582
+ if 'COMPLUS4T' in content:
583
+ return True
584
+ except Exception:
585
+ pass
586
+
587
+ return False
588
+
589
+ def _check_kronos_mode(self, filepath=None):
590
+ """Check if we are in KRONOS mode (.001 files with KRONOS format)."""
591
+ if filepath:
592
+ # Check specific file
593
+ try:
594
+ with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
595
+ for line in f:
596
+ if 'KRONOS' in line or re.search(r'WaferID\s+"Read Failed\.(\d+)"', line):
597
+ return True
598
+ except Exception:
599
+ pass
600
+ else:
601
+ # Check parent directory
602
+ if not self.dirname or not os.path.exists(self.dirname):
603
+ return False
604
+
605
+ matching_files = glob.glob(os.path.join(self.dirname, '*.001'))
606
+ for file_path in matching_files:
607
+ try:
608
+ with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
609
+ for line in f:
610
+ if 'KRONOS' in line or re.search(r'WaferID\s+"Read Failed\.(\d+)"', line):
611
+ return True
612
+ except Exception:
613
+ pass
614
+
615
+ return False
616
+
617
+ def _is_wafer_in_klarf(self, file_path, wafer_id):
618
+ """Check if a specific wafer ID is in the KLARF file."""
619
+ try:
620
+ with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
621
+ for line in f:
622
+ # Check for COMPLUS4T format: WaferID "@11"
623
+ match = re.search(r'WaferID\s+"@(\d+)"', line)
624
+ if match:
625
+ if int(match.group(1)) == wafer_id:
626
+ return True
627
+ except Exception:
628
+ pass
629
+ return False
630
+
631
+ def extract_wafer_ids_from_klarf(self):
632
+ """Extract wafer IDs from KLARF files (.001) that contain COMPLUS4T."""
633
+ wafer_ids = []
634
+
635
+ if not self.dirname:
636
+ return wafer_ids
637
+
638
+ # Search for .001 files
639
+ try:
640
+ files = [f for f in os.listdir(self.dirname)
641
+ if f.endswith('.001') and os.path.isfile(os.path.join(self.dirname, f))]
642
+
643
+ for file in files:
644
+ file_path = os.path.join(self.dirname, file)
645
+ try:
646
+ with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
647
+ content = f.read()
648
+
649
+ # Check if file contains "COMPLUS4T"
650
+ if 'COMPLUS4T' in content:
651
+ # Search for all lines with WaferID
652
+ # Pattern to extract number in quotes after WaferID
653
+ pattern = r'WaferID\s+"@(\d+)"'
654
+ matches = re.findall(pattern, content)
655
+
656
+ # Add found IDs (converted to int)
657
+ for match in matches:
658
+ wafer_id = int(match)
659
+ if wafer_id not in wafer_ids and 1 <= wafer_id <= 26:
660
+ wafer_ids.append(wafer_id)
661
+ except Exception:
662
+ pass # Error reading file
663
+
664
+ except Exception:
665
+ pass # Error listing files
666
+
667
+ return wafer_ids
668
+
669
+ if __name__ == "__main__":
670
+ DIRNAME = r"C:\Users\TM273821\Desktop\SEM\D25S2039_200_MOS2_SIO2_API"
671
+ SCALE = r"C:\Users\TM273821\SEM\settings_data.json"
672
+
673
+ processor = Process(DIRNAME, wafer=18, scale=SCALE)
674
+
675
+ # Process files
676
+ # processor.organize_and_rename_files() # Organize and rename files
677
+ # processor.rename_wo_legend_all() # Preprocess all files in the directory
678
+ # processor.rename_wo_legend() # Preprocess specific wafer
679
+
680
+ # processor.split_tiff_all() # Preprocess specific wafer
681
+ # processor.split_tiff_all() # Preprocess specific wafer
682
+ # processor.split_tiff() # Preprocess specific wafer
683
+ processor.rename_all() # Preprocess specific wafer
684
+
685
+
686
+