Files
wafer_map_webui/wafer_processor.py
beabigegg 9f7040ece9 ver 2
2025-07-30 11:24:58 +08:00

221 lines
7.9 KiB
Python

import numpy as np
import os
def get_unique_chars(file_path):
"""
Reads a wafer map file and returns all unique characters found,
excluding whitespace.
"""
unique_chars = set()
try:
with open(file_path, 'r', encoding='utf-8') as file:
lines = file.readlines()
# Filter out empty lines before calculating max_length
non_empty_lines = [line.strip() for line in lines if line.strip()]
if not non_empty_lines:
return []
max_length = max(len(line) for line in non_empty_lines)
for line in non_empty_lines:
# Process only lines that have the max length
if len(line) == max_length:
for char in line:
if char != ' ':
unique_chars.add(char)
return sorted(list(unique_chars))
except Exception as e:
print(f"Error reading unique chars: {e}")
return []
def read_wafer_map(file_path, char_to_bin_mapping):
"""
Reads a wafer map file and converts it to a numerical numpy array based on
the provided character-to-bin mapping.
"""
wafer_map = []
try:
with open(file_path, 'r', encoding='utf-8') as file:
lines = file.readlines()
non_empty_lines = [line.strip() for line in lines if line.strip()]
if not non_empty_lines:
return np.array([])
max_length = max(len(line) for line in non_empty_lines)
for line in non_empty_lines:
# Pad the line to ensure uniform length for all processed rows
padded_line = line.ljust(max_length, ' ')
row = [char_to_bin_mapping.get(char, -1) for char in padded_line]
wafer_map.append(row)
return np.array(wafer_map)
except Exception as e:
print(f"Error reading wafer map: {e}")
return np.array([])
def save_wafer_map(wafer_map, bin_to_char_mapping, file_path):
"""
Saves the numerical wafer map back to a character-based file.
"""
with open(file_path, 'w', encoding='utf-8') as file:
for row in wafer_map:
file.write(''.join([bin_to_char_mapping.get(int(bin_code), ' ') for bin_code in row]) + '\n')
def get_bin_counts(wafer_map):
"""
Calculates the count of each bin type in the wafer map.
"""
unique, counts = np.unique(wafer_map, return_counts=True)
return dict(zip(unique, counts))
def inset_wafer_map(wafer_map, layers, from_bin_value, to_bin_value):
"""
Finds the outer N layers of the main die area, defined by proximity to
'ignore' bins, and changes them from 'from_bin_value' to 'to_bin_value'.
"""
if layers <= 0:
return wafer_map
rows, cols = wafer_map.shape
# Mask for all dies that are candidates for changing.
target_mask = (wafer_map == from_bin_value)
# Mask for dies that are NOT part of the wafer (e.g., ignore bins)
background_mask = (wafer_map == -1)
# This will hold all dies that have been identified for changing.
final_change_mask = np.zeros_like(wafer_map, dtype=bool)
# This mask represents the "peeling front". Initially, it's the background.
peel_front_mask = np.copy(background_mask)
for _ in range(layers):
# Find dies in our target_mask that are adjacent to the current peel_front_mask
newly_found_edge_mask = np.zeros_like(wafer_map, dtype=bool)
# Iterate only over the candidates to be more efficient
for r, c in np.argwhere(target_mask):
# Check neighbors
for dr, dc in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
nr, nc = r + dr, c + dc
# If a neighbor is part of the current "peel front"
if (0 <= nr < rows and 0 <= nc < cols and peel_front_mask[nr, nc]):
newly_found_edge_mask[r, c] = True
break # Found it's an edge, no need to check other neighbors
# If we didn't find any new edge dies, we're done.
if not np.any(newly_found_edge_mask):
break
# Add the newly found layer to our final mask of dies to change.
final_change_mask |= newly_found_edge_mask
# The layer we just found becomes the new "peel front" for the next iteration.
peel_front_mask |= newly_found_edge_mask
# Remove the dies we just processed from the pool of candidates.
target_mask &= ~newly_found_edge_mask
# Apply the change to a copy of the original map
wafer_map_copy = np.copy(wafer_map)
wafer_map_copy[final_change_mask] = to_bin_value
return wafer_map_copy
def compare_wafer_maps(maps, references):
"""
Compares multiple wafer maps by aligning them based on reference points.
"""
if not maps or len(maps) != len(references):
return np.array([]), {}
# Determine the bounds of the combined map
max_up = 0
max_down = 0
max_left = 0
max_right = 0
for i, (map_arr, ref) in enumerate(zip(maps, references)):
ref_r, ref_c = ref
rows, cols = map_arr.shape
max_up = max(max_up, ref_r)
max_down = max(max_down, rows - ref_r - 1)
max_left = max(max_left, ref_c)
max_right = max(max_right, cols - ref_c - 1)
# Total dimensions of the comparison grid
total_rows = max_up + max_down + 1
total_cols = max_left + max_right + 1
# A grid where each cell holds a list of bin codes from all maps at that position
# Initialize with a special value for "no map data"
NO_DATA = -10
comparison_grid = [[[NO_DATA] * len(maps) for _ in range(total_cols)] for _ in range(total_rows)]
# Place each map onto the comparison grid
for i, (map_arr, ref) in enumerate(zip(maps, references)):
ref_r, ref_c = ref
rows, cols = map_arr.shape
offset_r = max_up - ref_r
offset_c = max_left - ref_c
for r in range(rows):
for c in range(cols):
# Only consider non-ignore bins
if map_arr[r, c] != -1:
comparison_grid[offset_r + r][offset_c + c][i] = map_arr[r, c]
# --- Process the grid to create a final result map ---
# Result codes:
# -1: No data from any map (Ignore)
# 0: Match (Good)
# 1: Match (NG)
# 2: Match (Dummy)
# 3: Mismatch (mix of Good, NG, Dummy)
# 4: Partial data (some maps have data, others don't)
result_map = np.full((total_rows, total_cols), -1, dtype=int)
for r in range(total_rows):
for c in range(total_cols):
bins = [b for b in comparison_grid[r][c] if b != NO_DATA]
if not bins: # No map has data here
result_map[r, c] = -1
continue
if len(bins) < len(maps): # Some maps have data, others don't
result_map[r, c] = 4
continue
# All maps have data, check for match or mismatch
first_bin = bins[0]
is_match = all(b == first_bin for b in bins)
if is_match:
if first_bin == 2: # Good
result_map[r, c] = 0
elif first_bin == 1: # NG
result_map[r, c] = 1
elif first_bin == 0: # Dummy
result_map[r, c] = 2
else: # Mismatch
result_map[r, c] = 3
legend = {
-1: {"label": "No Data", "color": "#3e3e42"},
0: {"label": "Match (Good)", "color": "#28a745"},
1: {"label": "Match (NG)", "color": "#4a90e2"}, # Different color for match NG
2: {"label": "Match (Dummy)", "color": "#7f8c8d"},
3: {"label": "Mismatch", "color": "#dc3545"},
4: {"label": "Partial Data", "color": "#f39c12"}
}
# Also return the detailed grid for tooltip purposes
return result_map, legend, comparison_grid