Skip to content

About

Desktop tool for bi-temporal remote-sensing image change inspection: A/B flicker comparison, 256-color mask annotation, and offline MobileSAM AI-assisted selection. Electron + onnxruntime.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

9 Commits

Folders and files

Repository files navigation

DT-Change-Inspector

A desktop tool for bi-temporal remote-sensing image change inspection — flicker comparison, 256-color mask annotation, and offline AI-assisted selection.

Version License: MIT Platform Built with

English | 简体中文

Annotating bi-temporal change-detection data means flipping back and forth between two phases of large orthoimages, spotting what changed, and painting per-class masks. Generic image editors don't fit this loop: they lack phase-synced comparison, and they don't tie mask pixels to a label color system. DT-Change-Inspector was built for exactly this loop and is used in daily annotation production.

Main UI: A/B phases side by side with annotation outlines and the full toolbar

Main UI — A/B phases side by side, annotation outlines, and the full toolbar

Everything runs fully offline — images never leave the machine.

Features

Bi-temporal comparison

  • A/B phases side by side, or rapid flicker at 5 speeds (150 / 250 / 350 / 600 / 1000 ms)
  • Swap phase order per image (Space/Tab), or physically swap the A/B files on disk (X) to turn "disappeared" into "appeared"
  • Pan / zoom with middle-drag and wheel; reset with R

Annotation

  • 256 labels, each with its own color, name, and a custom hotkey (single keys or combos like Ctrl+Alt+1)
  • Polygon annotation (Q) with guide line and edge snapping; Enter or right-click to commit, Backspace to remove the last point
  • Connected-region delete by color (E), plus Shift+click to erase with a polygon
  • Labels live in the per-user config directory (mask_labels.json); the bundled file is only the first-run seed, so upgrades do not overwrite custom labels

AI-assisted selection (MobileSAM, offline)

  • Bundled MobileSAM ONNX models (encoder + quantized decoder) run locally via onnxruntime-node — no Python, no GPU, no upload
  • F to start: left-click inside the target as a positive point, Shift+click / right-click as negative points to trim the candidate, Enter to write it into the current label color
  • The first click computes the image embedding; follow-up point refinements are much faster
  • SAM outlines what the clicks suggest — whether it is a real temporal change stays a human decision

Safe data workflow

  • Delete moves the current A/B/Mask group into deleted_pairs/ (recycle bin); U restores it, including after an application restart
  • Ctrl+Z undoes the latest mask edit on the current image within the current session; when no mask edit is available it falls back to restoring the last deleted group
  • Shift+Delete removes permanently with confirmation
  • Masks use the same stem as the A/B pair and are written as lossless PNG; legacy JPG/BMP masks are converted to PNG on their first edit

Production workflow

  • Dataset-level review state in review_state.json; the status bar shows annotated/reviewed counts; N jumps to the next unprocessed image, M marks or clears “reviewed / unchanged”
  • Load summary reports A/B/Mask file counts and unmatched files; the sibling Mask folder is auto-detected; recent datasets and drag-and-drop dataset roots are supported
  • Label configurations can be imported/exported; changing a label color warns that existing masks are not recolored
  • Current-mask statistics break pixels down by label and export to CSV, with off-palette colors listed separately
  • Window bounds are remembered; the tray menu opens the local log directory

Built for 4K imagery

  • Rapid page-through renders only the frame you stop on, renders are cancelable, and interaction uses a lightweight preview that snaps back to full resolution when you pause
  • Mask fill and red-outline layers are cached per view, so flicker, opacity changes and mask toggling reuse them instead of reprocessing every pixel
  • Recently decoded images use a 384 MB byte-budget LRU (preview bitmaps included), so memory stays bounded on large datasets
  • Mask edits use a main-process raw-mask cache and bounding-box compositing; large connected-region deletes run asynchronously
  • Canvas renders at the device pixel ratio for crisp imagery on 125%/150%/200% Windows scaling
  • SAM previews and IPC use the model-resolution mask instead of expanding to the source size

Data layout

dataset/
├─ A/                 # phase-1 images
├─ B/                 # phase-2 images (same filenames as A/)
├─ Mask/              # output masks (same stem as A/B, lossless PNG)
├─ review_state.json  # reviewed/unchanged state (optional, generated)
└─ deleted_pairs/     # recycle folder + manifest.json (generated)

Supported formats: png, jpg, jpeg, tif, tiff, bmp, webp. Existing Mask folders and mask_labels.json from older versions work as-is. When A and B have different pixel dimensions, annotation is restricted to phase A to prevent silent mask-grid distortion.

Mask format

  • A label's color is its class value: annotation pixels are written in the label's exact RGB.
  • New masks are always written as lossless PNG, even when the source pair is .jpg (tile.jpg → Mask/tile.png).
  • A legacy .jpg/.jpeg/.bmp mask is copied into a lossless PNG on its first edit; the original file is kept.
  • If an existing mask has a different size than phase A, the app asks for confirmation before nearest-neighbor rescaling it to the A grid.
  • Background is always pure black (0, 0, 0).
  • The red outline shown in the UI is view-only and never written to the mask.
  • Any non-black color can be used as a label, including pure white.
  • Legacy JPEG masks may contain palette drift from earlier lossy writes. Audit them with pnpm mask:convert <MaskDir> --dry-run, then run the same command without --dry-run to rewrite them as exact-color PNG.

Getting started

End users

Grab the latest installer (NSIS, ~141 MB with models bundled) from the Releases page and follow the wizard. The app maximizes on first launch and then remembers its window bounds. Closing the window hides it to the system tray — right-click the tray icon to quit for real.

Developers

# Electron client
pnpm install

# Python side (legacy tkinter viewer / mask_core.py)
pip install -r requirements.txt

# Run
pnpm start
pnpm test         # Node unit tests
pnpm test:smoke   # in-app self test
pnpm mask:convert <MaskDir> --dry-run  # audit mask palette
pnpm mask:convert <MaskDir>            # convert legacy JPG/BMP masks to PNG
pnpm build        # Windows NSIS installer -> release/

Basic workflow

  1. Launch and pick the A, B and Mask directories.
  2. Flicker (B) or compare (V) to locate a change.
  3. Switch to the phase where the target is visible (Space/Tab) — B phase for additions, A phase for removals — and press F.
  4. Click inside the target, refine with negative clicks if needed, Enter to commit with the current label.
  5. Page through with A/D and repeat. Toggle mask visibility with S to review.

Keyboard shortcuts

Key Action
A / ← Previous image
D / → Next image (rapid paging renders only the final frame)
N Next unannotated/unreviewed image
M Mark or clear the current image's review state
Space / Tab Toggle A/B phase
B Flicker A/B
V Side-by-side comparison
X Swap A/B files on disk (press again to revert)
S Show / hide mask
C Label manager
Q Polygon annotation
E Delete annotation (click a connected region; Shift+click erases with a polygon)
F AI selection (MobileSAM)
Delete Move group to deleted_pairs/
Shift+Delete Delete permanently (confirmed)
U Undo the last delete
Ctrl+Z Undo the latest mask edit in this session; fall back to undoing a delete
R / 0 Reset view
+ / - / wheel Zoom
Middle-drag Pan
Enter Commit annotation
Backspace Remove last point
Esc Cancel current operation

Per-label hotkeys are recorded in the label manager (C) and work during polygon editing without losing painted points.

Project structure

├─ electron/              # Electron app
│  ├─ main.js             # main process: windows, tray, dialogs, mask IO
│  ├─ core/               # pairs, labels, shortcuts, mask-service, review, logger, window state
│  ├─ models/             # MobileSAM ONNX weights + license (committed)
│  └─ tests/              # node --test unit tests
├─ .github/workflows/     # CI: unit tests + Electron self test
├─ scripts/convert-masks.js # batch palette audit / legacy mask conversion
├─ pair_change_viewer.py  # legacy tkinter version (v2.x), kept for reference
├─ mask_core.py           # shared mask logic for the Python side (pytest)
├─ mask_labels.json       # default 256-label palette
├─ .requirements/         # per-feature requirement docs, one folder per change
└─ release/               # build output (not committed)

Tech stack

Electron 43 · onnxruntime-node 1.29 · sharp 0.35 · Node unit tests + pytest · electron-builder / NSIS

License

Code is released under the MIT License. The bundled MobileSAM weights follow their own license — see electron/models/LICENSE-MobileSAM-in-the-Browser.txt. MobileSAM: ChaoningZhang/MobileSAM.

About

Desktop tool for bi-temporal remote-sensing image change inspection: A/B flicker comparison, 256-color mask annotation, and offline MobileSAM AI-assisted selection. Electron + onnxruntime.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages