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
instrMCPdevwith opencv, numpy, shapely (rank_candidate_pairs.pyuses 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.jsonwithraw2lutsection: 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 forkeep_largest_non the warped/clipped graphene mask. Default500px (≈ 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 coordinatesgraphite_full.png,graphene_full.png,bottom_hbn_full.png,top_hbn_full.png— transformed masks- Appends
transform_summarysection tocombine_report.json - Appends
transform_diagnosticssection tocombine_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 imageoverlay_lut.png— all material contours on LUT image (if --lut provided)mask_composite.png— all material masks color-coded at 50% alpha- Appends
overlay_filessection tocombine_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: usescontour_gdsif present, falls back tocontour_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.pyoverlay.pyrank_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 commitcombine_report.json— metadata for diagnosticscandidate_ranking.json— (optional) ranking of candidate material pairs- Per-material transformed masks and overlay images