This commit is contained in:
beabigegg
2025-07-30 11:24:58 +08:00
parent ede5af22f8
commit 9f7040ece9
10 changed files with 1667 additions and 175 deletions

344
app.py
View File

@@ -35,17 +35,66 @@ def index():
resp.set_cookie('session_id', session_id)
return resp
@app.route('/compare')
def compare_index():
"""Serves the comparison HTML page."""
session_id = get_session_id()
resp = make_response(render_template('compare.html'))
resp.set_cookie('session_id', session_id)
# Clean up previous comparison data if any
if session_id in session_data and 'maps' in session_data[session_id]:
del session_data[session_id]['maps']
return resp
@app.route('/upload', methods=['POST'])
def upload_file():
"""Handles file upload, saves it, and returns unique characters."""
if 'file' not in request.files:
return jsonify({"error": "No file part"}), 400
file = request.files['file']
if file.filename == '':
return jsonify({"error": "No selected file"}), 400
"""
Handles both single file upload for the editor and multiple file uploads for comparison.
It distinguishes between them by checking the 'name' attribute of the file input.
"""
session_id = get_session_id()
# Case 1: Multiple files from comparison page (input name="files")
if 'files' in request.files:
files = request.files.getlist('files')
if not files or files[0].filename == '':
return jsonify({"error": "No files selected for comparison."}), 400
session_data[session_id] = {'maps': {}}
all_unique_chars = set()
for i, file in enumerate(files):
fid = f"map_{i}"
filename = f"{session_id}_{fid}_{file.filename}"
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(filepath)
unique_chars = wp.get_unique_chars(filepath)
if not unique_chars:
continue
all_unique_chars.update(unique_chars)
session_data[session_id]['maps'][fid] = {
'filepath': filepath,
'filename': file.filename,
'wafer_map': None,
'reference': None
}
if not session_data[session_id]['maps']:
return jsonify({"error": "None of the files could be processed."}), 400
return jsonify({
"unique_chars": sorted(list(all_unique_chars)),
"maps": {fid: data['filename'] for fid, data in session_data[session_id]['maps'].items()}
})
# Case 2: Single file from main editor page (input name="file")
elif 'file' in request.files:
file = request.files['file']
if file.filename == '':
return jsonify({"error": "No selected file"}), 400
if file:
session_id = get_session_id()
filename = f"{session_id}_{file.filename}"
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(filepath)
@@ -57,37 +106,176 @@ def upload_file():
session_data[session_id] = {'filepath': filepath}
return jsonify({"unique_chars": unique_chars})
return jsonify({"error": "File upload failed"}), 500
# Case 3: No valid file part found
return jsonify({"error": "No file part in the request."}), 400
@app.route('/generate_map', methods=['POST'])
def generate_map():
"""Generates the initial wafer map data from the file and bin definitions."""
"""
Generates wafer map data. For comparison mode, it generates for all uploaded maps.
For editor mode, it also initializes the undo/redo history.
"""
session_id = get_session_id()
if session_id not in session_data:
return jsonify({"error": "Session not found. Please upload a file first."}), 400
return jsonify({"error": "Session not found."}), 400
data = request.json
char_map = data.get('char_map')
bin_type_to_value = {'good': 2, 'ng': 1, 'dummy': 0, 'ignore': -1}
char_to_bin_mapping = {char: bin_type_to_value[bin_type] for char, bin_type in char_map.items()}
# Comparison mode logic
if 'maps' in session_data[session_id]:
maps_data = {}
for fid, map_info in session_data[session_id]['maps'].items():
wafer_map = wp.read_wafer_map(map_info['filepath'], char_to_bin_mapping)
if wafer_map.size == 0:
continue
session_data[session_id]['maps'][fid]['wafer_map'] = wafer_map.tolist()
maps_data[fid] = {
"wafer_map": wafer_map.tolist(),
"rows": wafer_map.shape[0],
"cols": wafer_map.shape[1]
}
session_data[session_id]['char_to_bin_mapping'] = char_to_bin_mapping
return jsonify({"maps_data": maps_data})
# Editor mode logic
filepath = session_data[session_id]['filepath']
wafer_map = wp.read_wafer_map(filepath, char_to_bin_mapping)
if wafer_map.size == 0:
return jsonify({"error": "Failed to read wafer map from file."}), 500
return jsonify({"error": "Failed to read wafer map."}), 500
# Store data in session
session_data[session_id]['wafer_map'] = wafer_map.tolist()
wafer_map_list = wafer_map.tolist()
session_data[session_id]['wafer_map'] = wafer_map_list
session_data[session_id]['char_to_bin_mapping'] = char_to_bin_mapping
# Initialize history for undo/redo
session_data[session_id]['history'] = [wafer_map_list]
session_data[session_id]['history_index'] = 0
return jsonify({
"wafer_map": wafer_map.tolist(),
"wafer_map": wafer_map_list,
"rows": wafer_map.shape[0],
"cols": wafer_map.shape[1]
})
def add_to_history(session_id, new_map_state):
"""Adds a new state to the history stack for undo/redo."""
history = session_data[session_id].get('history', [])
index = session_data[session_id].get('history_index', -1)
# If we undo and then make a new change, truncate the future history
if index < len(history) - 1:
history = history[:index + 1]
history.append(new_map_state)
session_data[session_id]['history'] = history
session_data[session_id]['history_index'] = len(history) - 1
@app.route('/set_reference', methods=['POST'])
def set_reference():
"""Sets the reference point for a specific map in comparison mode."""
session_id = get_session_id()
if session_id not in session_data or 'maps' not in session_data[session_id]:
return jsonify({"error": "Not in comparison mode or session expired."}), 400
data = request.json
fid = data.get('fid')
reference = data.get('reference') # Expected: [row, col]
if fid not in session_data[session_id]['maps']:
return jsonify({"error": "Invalid map ID."}), 400
session_data[session_id]['maps'][fid]['reference'] = reference
return jsonify({"success": True, "fid": fid, "reference": reference})
@app.route('/run_comparison', methods=['POST'])
def run_comparison():
"""Runs the comparison algorithm on all maps with set references."""
session_id = get_session_id()
if session_id not in session_data or 'maps' not in session_data[session_id]:
return jsonify({"error": "Not in comparison mode or session expired."}), 400
maps_to_compare = []
references = []
for fid, map_info in session_data[session_id]['maps'].items():
if map_info.get('wafer_map') is not None and map_info.get('reference') is not None:
maps_to_compare.append(np.array(map_info['wafer_map']))
references.append(map_info['reference'])
else:
return jsonify({"error": f"Map '{map_info['filename']}' is missing data or a reference point."}), 400
if len(maps_to_compare) < 2:
return jsonify({"error": "At least two maps with reference points are required for comparison."}), 400
comparison_result, legend, detailed_grid = wp.compare_wafer_maps(maps_to_compare, references)
# Convert numpy types in detailed_grid to standard Python ints for JSON serialization
serializable_grid = [[[int(b) for b in cell] for cell in row] for row in detailed_grid]
# Store the detailed grid in the session for potential later use if needed
session_data[session_id]['comparison_details'] = {
'grid': serializable_grid,
'map_names': [info['filename'] for info in session_data[session_id]['maps'].values() if info.get('wafer_map') is not None and info.get('reference') is not None]
}
return jsonify({
"result_map": comparison_result.tolist(),
"legend": legend,
"detailed_grid": serializable_grid,
"map_names": session_data[session_id]['comparison_details']['map_names'],
"rows": comparison_result.shape[0],
"cols": comparison_result.shape[1]
})
@app.route('/compare/rotate', methods=['POST'])
def compare_rotate():
"""Rotates a single map within the comparison session."""
session_id = get_session_id()
if session_id not in session_data or 'maps' not in session_data[session_id]:
return jsonify({"error": "Not in comparison mode or session expired."}), 400
data = request.json
fid = data.get('fid')
if fid not in session_data[session_id]['maps']:
return jsonify({"error": "Invalid map ID."}), 400
map_info = session_data[session_id]['maps'][fid]
wafer_map = np.array(map_info['wafer_map'])
rotated_map = np.rot90(wafer_map, k=-1)
map_info['wafer_map'] = rotated_map.tolist()
# Clear reference if it was set, as rotation changes coordinates
map_info['reference'] = None
return jsonify({
"fid": fid,
"wafer_map": rotated_map.tolist(),
"rows": rotated_map.shape[0],
"cols": rotated_map.shape[1]
})
@app.route('/compare/clear_reference', methods=['POST'])
def compare_clear_reference():
"""Clears the reference point for a specific map."""
session_id = get_session_id()
if session_id not in session_data or 'maps' not in session_data[session_id]:
return jsonify({"error": "Not in comparison mode or session expired."}), 400
data = request.json
fid = data.get('fid')
if fid not in session_data[session_id]['maps']:
return jsonify({"error": "Invalid map ID."}), 400
session_data[session_id]['maps'][fid]['reference'] = None
return jsonify({"success": True, "fid": fid})
@app.route('/update_map', methods=['POST'])
def update_map():
"""Updates specified dies in the map and returns the full updated map."""
@@ -96,7 +284,7 @@ def update_map():
return jsonify({"error": "Session expired or invalid."}), 400
data = request.json
dies_to_update = data.get('dies') # Expected format: [[row, col], [row, col], ...]
dies_to_update = data.get('dies')
new_bin_type = data.get('bin_type')
if not dies_to_update:
@@ -107,14 +295,15 @@ def update_map():
wafer_map = np.array(session_data[session_id]['wafer_map'])
# Update the map based on the list of die coordinates
for row, col in dies_to_update:
if 0 <= row < wafer_map.shape[0] and 0 <= col < wafer_map.shape[1]:
wafer_map[row, col] = new_bin_value
session_data[session_id]['wafer_map'] = wafer_map.tolist()
wafer_map_list = wafer_map.tolist()
session_data[session_id]['wafer_map'] = wafer_map_list
add_to_history(session_id, wafer_map_list)
return jsonify({"wafer_map": wafer_map.tolist()})
return jsonify({"wafer_map": wafer_map_list})
@app.route('/rotate_map', methods=['POST'])
def rotate_map():
@@ -124,23 +313,124 @@ def rotate_map():
return jsonify({"error": "Session expired or invalid."}), 400
wafer_map = np.array(session_data[session_id]['wafer_map'])
# np.rot90 rotates counter-clockwise, so k=-1 rotates clockwise
rotated_map = np.rot90(wafer_map, k=-1)
rotated_map_list = rotated_map.tolist()
session_data[session_id]['wafer_map'] = rotated_map.tolist()
session_data[session_id]['wafer_map'] = rotated_map_list
add_to_history(session_id, rotated_map_list)
return jsonify({
"wafer_map": rotated_map.tolist(),
"wafer_map": rotated_map_list,
"rows": rotated_map.shape[0],
"cols": rotated_map.shape[1]
})
@app.route('/inset_map', methods=['POST'])
def inset_map():
"""Applies an inset to the wafer map."""
session_id = get_session_id()
if session_id not in session_data:
return jsonify({"error": "Session expired or invalid."}), 400
data = request.json
layers = int(data.get('layers', 1))
from_bin_name = data.get('from_bin')
to_bin_name = data.get('to_bin')
bin_type_to_value = {'good': 2, 'ng': 1, 'dummy': 0, 'ignore': -1}
from_bin_value = bin_type_to_value.get(from_bin_name)
to_bin_value = bin_type_to_value.get(to_bin_name)
if from_bin_value is None or to_bin_value is None:
return jsonify({"error": "Invalid 'from' or 'to' bin type specified."}), 400
wafer_map = np.array(session_data[session_id]['wafer_map'])
inset_map_array = wp.inset_wafer_map(wafer_map, layers, from_bin_value, to_bin_value)
inset_map_list = inset_map_array.tolist()
session_data[session_id]['wafer_map'] = inset_map_list
add_to_history(session_id, inset_map_list)
return jsonify({
"wafer_map": inset_map_list,
"rows": inset_map_array.shape[0],
"cols": inset_map_array.shape[1]
})
@app.route('/undo', methods=['POST'])
def undo():
session_id = get_session_id()
if session_id not in session_data or not session_data[session_id].get('history'):
return jsonify({"error": "No history to undo."}), 400
history = session_data[session_id]['history']
index = session_data[session_id]['history_index']
if index > 0:
index -= 1
session_data[session_id]['history_index'] = index
wafer_map = history[index]
session_data[session_id]['wafer_map'] = wafer_map
return jsonify({
"wafer_map": wafer_map,
"rows": len(wafer_map),
"cols": len(wafer_map[0]) if wafer_map else 0,
"can_undo": index > 0,
"can_redo": True
})
return jsonify({"error": "Already at the beginning of history."}), 400
@app.route('/redo', methods=['POST'])
def redo():
session_id = get_session_id()
if session_id not in session_data or not session_data[session_id].get('history'):
return jsonify({"error": "No history to redo."}), 400
history = session_data[session_id]['history']
index = session_data[session_id]['history_index']
if index < len(history) - 1:
index += 1
session_data[session_id]['history_index'] = index
wafer_map = history[index]
session_data[session_id]['wafer_map'] = wafer_map
return jsonify({
"wafer_map": wafer_map,
"rows": len(wafer_map),
"cols": len(wafer_map[0]) if wafer_map else 0,
"can_undo": True,
"can_redo": index < len(history) - 1
})
return jsonify({"error": "Already at the end of history."}), 400
@app.route('/reset_map', methods=['POST'])
def reset_map():
"""Resets the map to its original state (the first state in history)."""
session_id = get_session_id()
if session_id not in session_data or not session_data[session_id].get('history'):
return jsonify({"error": "No history available to reset from."}), 400
history = session_data[session_id]['history']
original_map = history[0]
# Reset history to only the original state
session_data[session_id]['history'] = [original_map]
session_data[session_id]['history_index'] = 0
session_data[session_id]['wafer_map'] = original_map
return jsonify({
"wafer_map": original_map,
"rows": len(original_map),
"cols": len(original_map[0]) if original_map else 0,
"can_undo": False,
"can_redo": False
})
@app.route('/download', methods=['GET'])
def download_file():
"""Saves the current wafer map to a text file and serves it for download."""
session_id = get_session_id()
if session_id not in session_data:
if session_id not in session_data or 'wafer_map' not in session_data[session_id]:
return jsonify({"error": "No data to download."}), 400
wafer_map = np.array(session_data[session_id]['wafer_map'])