| name | nanodevice_flakedetect_combine |
| description | Transform detection results into the common full_stack coordinate system, build unified traces.json, and draw overlay images. Use after align and detect steps are complete. |
nanodevice_flakedetect_combine — Coordinate Transforms & Overlays
Transforms all per-material detections into the full_stack coordinate system, produces the unified traces.json, and draws contour overlays on raw/LUT images.
Prerequisites
- Conda env
instrMCPdev with opencv, numpy, shapely (rank_candidate_pairs.py uses shapely)
- Completed align step (warp matrices, footprint mask)
- Completed detect step (per-material masks/contours, detections.json)
- All scripts run via
${PYTHON_PATH:-conda run -n instrMCPdev python} <script>
Scripts
ecc_register.py — ECC raw-to-LUT translation alignment
${PYTHON_PATH:-conda run -n instrMCPdev python} skills/nanodevice_flakedetect_combine/scripts/ecc_register.py \
--raw <full_stack_raw_image> \
--lut <full_stack_lut_image> \
--output-dir <path>
--raw — Full stack raw image
--lut — Full stack LUT (color-enhanced) image
--output-dir — Output directory
Computes ECC translation alignment between raw and LUT images. The LUT image typically has a spatial offset (~71px dx, ~56px dy) from raw.
Outputs:
- Creates
combine_report.json with raw2lut section: dx, dy, ecc_correlation
Note: If no LUT image is available, skip this script. The overlay script handles the absence gracefully.
transform.py — Coordinate transforms for all materials
${PYTHON_PATH:-conda run -n instrMCPdev python} skills/nanodevice_flakedetect_combine/scripts/transform.py \
--detections <detect/detections.json> \
--align-dir <align/> \
--image <full_stack_raw_image> \
--pixel-size <um_per_px> \
--output-dir <path> \
[--min-area 500]
--detections — Path to detections.json from detect step
--align-dir — Directory containing warp matrices and footprint from align step
--image — Full stack raw image (for size reference)
--pixel-size — Microns per pixel
--output-dir — Output directory
--min-area — Minimum component area in pixels for keep_largest_n on the warped/clipped graphene mask. Default 500 px (≈ 5.6 µm² at 0.106 µm/px). Lower this when working at finer pixel sizes or with intentionally small flakes; raise it to reject more aggressive noise.
Transform rules by material:
| Material | Coordinate System | Transform |
|---|
| graphite | bottom_part | Invert warp_sift_bottom.npy, apply to contour |
| graphene | top_part (mirrored) | Apply warp_top.npy to mask (INTER_NEAREST), clip to footprint, morph clean, re-extract contour |
| bottom_hBN | full_stack | Pass through (already in target coords) |
| top_hBN | full_stack | Pass through (= footprint) |
All materials get smooth_material() applied after transform.
Input files read from --align-dir:
warp_sift_bottom.npy — inverted before use (bottom_part→full_stack)
warp_top.npy — applied directly (top_part→full_stack)
footprint_mask.png — for graphene clipping
Outputs:
traces.json — unified traces with all contours in full_stack pixel coordinates
graphite_full.png, graphene_full.png, bottom_hbn_full.png, top_hbn_full.png — transformed masks
- Appends
transform_summary section to combine_report.json
- Appends
transform_diagnostics section to combine_report.json — per-material per-stage pixel counts (see below)
Per-stage diagnostics (transform_diagnostics):
Every material reports its pixel count at each pipeline stage so the orchestrator can tell the difference between "detect produced nothing" and "transform zeroed a valid input at stage X". Stage keys vary by material:
| Material | Stage keys |
|---|
| graphite | input_pixels, post_keep_largest_pixels |
| graphene | input_pixels, post_warp_pixels, post_bitwise_and_pixels, post_morph_pixels, post_keep_largest_pixels |
| bottom_hBN, top_hBN | input_pixels, post_keep_largest_pixels |
Example success entry:
"transform_diagnostics": {
"graphene": {
"input_pixels": 18234,
"post_warp_pixels": 17890,
"post_bitwise_and_pixels": 12044,
"post_morph_pixels": 11820,
"post_keep_largest_pixels": 11820
}
}
Refusal on zeroed valid input: if input_pixels > 0 but the mask drops to zero pixels at any stage, transform.py exits non-zero (code 3) with the partial counts persisted to combine_report.json under both transform_diagnostics.<material> and a top-level transform_error block:
"transform_error": {
"material": "graphene",
"dropped_at_stage": "bitwise_and",
"stage_counts": { "input_pixels": 18234, "post_warp_pixels": 17890, "post_bitwise_and_pixels": 0 }
}
dropped_at_stage is one of warp, bitwise_and, morph, keep_largest. The orchestrator can then re-run detect with a different cluster choice or escalate to vision-review without re-deriving which stage failed.
This refusal composes with the alignment-status refusal: transform.py already exits with code 2 when alignment_report.status (or footprint.status) is failed / needs_rotation_selection. Per-stage diagnostics only run after that gate passes.
overlay.py — Contour overlay visualization
${PYTHON_PATH:-conda run -n instrMCPdev python} skills/nanodevice_flakedetect_combine/scripts/overlay.py \
--traces <combine/traces.json> \
--raw <full_stack_raw_image> \
[--lut <full_stack_lut_image>] \
[--combine-report <combine/combine_report.json>] \
--output-dir <path>
--traces — Path to traces.json from transform step
--raw — Full stack raw image
--lut — (optional) Full stack LUT image
--combine-report — (optional) Path to combine_report.json (for raw→LUT shift)
--output-dir — Output directory
Draws material contours on desaturated background images using the BGR color palette:
- top_hBN: green (0, 200, 0)
- graphene: red (0, 0, 255)
- bottom_hBN: blue-ish (255, 100, 0)
- graphite: yellow (0, 200, 255)
For LUT overlay: reads dx, dy from combine_report.json and shifts contours before drawing.
Outputs:
overlay_raw.png — all material contours on raw image
overlay_lut.png — all material contours on LUT image (if --lut provided)
mask_composite.png — all material masks color-coded at 50% alpha
- Appends
overlay_files section to combine_report.json
rank_candidate_pairs.py — rank material pairs by overlap (default graphene × graphite)
Ranks every pair across two detected material lists by intersection area. Useful whenever a downstream design step requires a material pair with non-trivial overlap and the rank-0 detections do not always satisfy that constraint. Defaults to --material-a graphene --material-b graphite for the vdW-Hall-bar workflow, but any two material keys that exist in traces.json will work (e.g. --material-a top_hBN --material-b bottom_hBN to rank encapsulation pairs).
${PYTHON_PATH:-conda run -n instrMCPdev python} skills/nanodevice_flakedetect_combine/scripts/rank_candidate_pairs.py \
--traces <combine/traces.json> \
[--output candidate_ranking.json] \
[--top-k 5] \
[--material-a graphene] [--material-b graphite]
--traces — Path to traces.json (works on both pre- and post-gdsalign traces: uses contour_gds if present, falls back to contour_um)
--output — Path to the ranking JSON (default: alongside traces.json)
--top-k — How many top pairs to echo on stdout (default 5). The JSON file always contains all pairs.
--material-a, --material-b — Material keys to pair (default graphene × graphite)
Computes polygon intersection area between every (material_a, material_b) candidate pair, ranks by overlap area descending, and writes an ordered list.
Outputs:
candidate_ranking.json — ordered list with fields: {rank, graphene_id, graphite_id, overlap_um2, graphene_area_um2, graphite_area_um2, centroid_distance_um}
When to run: When a first-pass design session discovers the auto-picked material-A × material-B pair has overlap_um2 ≈ 0. The ranking surfaces a pair with non-trivial overlap without brute-force iteration. (Observed failure mode: ml09 / ml11 benchmarks where default graphene × graphite had no overlap.)
Workflow
Run in order:
ecc_register.py (if LUT image available)
transform.py
overlay.py
rank_candidate_pairs.py — only when the auto-picked pair has zero overlap; otherwise skip.
The combine_report.json is a multi-writer file: ecc_register creates it, transform adds to it, overlay adds to it. Each script reads the existing file and appends its section. rank_candidate_pairs.py writes a separate candidate_ranking.json and does not touch combine_report.json.
Output Files
All outputs go in the combine output directory:
traces.json — the main pipeline output consumed by review and commit
combine_report.json — metadata for diagnostics
candidate_ranking.json — (optional) ranking of candidate material pairs
- Per-material transformed masks and overlay images