slotsgeltool 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. slotsgeltool-1.0.0/CHANGELOG.md +50 -0
  2. slotsgeltool-1.0.0/Install_Windows.bat +56 -0
  3. slotsgeltool-1.0.0/Install_macOS.command +56 -0
  4. slotsgeltool-1.0.0/LICENSE +21 -0
  5. slotsgeltool-1.0.0/MANIFEST.in +16 -0
  6. slotsgeltool-1.0.0/PKG-INFO +285 -0
  7. slotsgeltool-1.0.0/README.md +251 -0
  8. slotsgeltool-1.0.0/gel_annotator/__init__.py +28 -0
  9. slotsgeltool-1.0.0/gel_annotator/__main__.py +19 -0
  10. slotsgeltool-1.0.0/gel_annotator/assets/README.md +33 -0
  11. slotsgeltool-1.0.0/gel_annotator/assets/android-chrome-192x192.png +0 -0
  12. slotsgeltool-1.0.0/gel_annotator/assets/android-chrome-512x512.png +0 -0
  13. slotsgeltool-1.0.0/gel_annotator/assets/apple-touch-icon.png +0 -0
  14. slotsgeltool-1.0.0/gel_annotator/assets/favicon-16x16.png +0 -0
  15. slotsgeltool-1.0.0/gel_annotator/assets/favicon-32x32.png +0 -0
  16. slotsgeltool-1.0.0/gel_annotator/assets/favicon.ico +0 -0
  17. slotsgeltool-1.0.0/gel_annotator/assets/icon-192.png +0 -0
  18. slotsgeltool-1.0.0/gel_annotator/assets/icon-512.png +0 -0
  19. slotsgeltool-1.0.0/gel_annotator/assets/icon.ico +0 -0
  20. slotsgeltool-1.0.0/gel_annotator/assets/site.webmanifest +1 -0
  21. slotsgeltool-1.0.0/gel_annotator/cli.py +625 -0
  22. slotsgeltool-1.0.0/gel_annotator/frontend/app.js +5063 -0
  23. slotsgeltool-1.0.0/gel_annotator/frontend/index.html +328 -0
  24. slotsgeltool-1.0.0/gel_annotator/frontend/style.css +598 -0
  25. slotsgeltool-1.0.0/gel_annotator/icon.py +90 -0
  26. slotsgeltool-1.0.0/gel_annotator/install_shortcut.py +254 -0
  27. slotsgeltool-1.0.0/gel_annotator/server/__init__.py +0 -0
  28. slotsgeltool-1.0.0/gel_annotator/server/main.py +590 -0
  29. slotsgeltool-1.0.0/pyproject.toml +87 -0
  30. slotsgeltool-1.0.0/setup.cfg +4 -0
  31. slotsgeltool-1.0.0/slotsgeltool.egg-info/PKG-INFO +285 -0
  32. slotsgeltool-1.0.0/slotsgeltool.egg-info/SOURCES.txt +34 -0
  33. slotsgeltool-1.0.0/slotsgeltool.egg-info/dependency_links.txt +1 -0
  34. slotsgeltool-1.0.0/slotsgeltool.egg-info/entry_points.txt +3 -0
  35. slotsgeltool-1.0.0/slotsgeltool.egg-info/requires.txt +7 -0
  36. slotsgeltool-1.0.0/slotsgeltool.egg-info/top_level.txt +1 -0
@@ -0,0 +1,50 @@
1
+ # Changelog
2
+
3
+ All notable changes to Slots Gel Annotator are recorded here.
4
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
5
+ and this project adheres to [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [1.0.0] — 2026-05-10
8
+
9
+ Initial public release as **Slots Gel Annotator** on PyPI as
10
+ `slotsgeltool`, with the `slots` console-script entry point.
11
+
12
+ ### Highlights
13
+
14
+ * Vector-native rendering — the on-screen SVG IS the export.
15
+ * Multi-format input (PNG / JPEG / TIFF / 16-bit raw / 16-bit TIFF), with
16
+ channel-max RGB→grayscale conversion that preserves imager-baked red
17
+ saturation contours from Bio-Rad / GelDoc / ChemiDoc TIFs.
18
+ * Auto-stretched LUT (p1 .. p99.5) so dark gels are visible on first
19
+ load.
20
+ * Saturation overlay tracks the source dynamic range (not the
21
+ auto-stretched preview), so flagged pixels are the imager's intent.
22
+ * Smart bracket rotation — minimum number of labels rotated to 90°
23
+ when overlap would otherwise occur.
24
+ * Multi-ladder support: any combination of left-flank, right-flank, or
25
+ internal ladders. Hide-Ladders collapses flanks only.
26
+ * Spreadsheet-style metadata table: drag-select cell ranges,
27
+ type-to-fill, click-out clears, Tab navigation respects ranges.
28
+ * Drag-to-reorder columns; per-column / per-lane visibility toggles.
29
+ * Undo / Redo for every state-changing action.
30
+ * Smooth (delta-proportional) wheel zoom + right-click drag pan.
31
+ * Single-instance launcher: re-running `slots` reuses the existing
32
+ window.
33
+ * Self-contained sessions: image embedded in the saved JSON.
34
+
35
+ ### Bundled
36
+
37
+ * `slots` CLI with `--port`, `--host`, `--no-browser`, `--install`,
38
+ `--update`, `--no-update`, `--version`.
39
+ * Desktop-shortcut installer for Windows / macOS / Linux.
40
+ * `Install_Windows.bat` and `Install_macOS.command` wrappers around
41
+ `pip install -e .`.
42
+
43
+ ### Known limitations
44
+
45
+ * The shortcut installer requires `tkinter`. Most Python distributions
46
+ include it; on minimal Linux installs run
47
+ `apt install python3-tk` (or your distro's equivalent) first.
48
+ * macOS `.app` icons require the system `sips` tool (always present on
49
+ macOS) for PNG → ICNS conversion. Failure is non-fatal — the
50
+ bundle works without an icon.
@@ -0,0 +1,56 @@
1
+ @echo off
2
+ REM ===============================================================
3
+ REM Slots Gel Annotator — Windows installer
4
+ REM Installs the package from this source tree (editable mode)
5
+ REM and launches the annotator. Re-run any time to update.
6
+ REM ===============================================================
7
+ SETLOCAL ENABLEDELAYEDEXPANSION
8
+
9
+ echo.
10
+ echo ==============================================
11
+ echo Slots Gel Annotator -- installing
12
+ echo ==============================================
13
+ echo.
14
+
15
+ REM Resolve a Python interpreter. py.exe is preferred on Windows;
16
+ REM fall back to "python" on PATH if absent.
17
+ set "PY="
18
+ where py >NUL 2>&1
19
+ if %ERRORLEVEL% EQU 0 (
20
+ set "PY=py -3"
21
+ ) else (
22
+ where python >NUL 2>&1
23
+ if %ERRORLEVEL% EQU 0 (
24
+ set "PY=python"
25
+ ) else (
26
+ echo ERROR: Python 3.10+ not found on PATH.
27
+ echo Install Python from https://www.python.org/downloads/ then re-run this installer.
28
+ pause
29
+ exit /b 1
30
+ )
31
+ )
32
+
33
+ REM Install / upgrade the package from this directory in editable mode
34
+ REM so that pulling new commits (`git pull`) reflects immediately.
35
+ echo Running: %PY% -m pip install -U pip
36
+ %PY% -m pip install -U pip
37
+ if %ERRORLEVEL% NEQ 0 (
38
+ echo Pip self-upgrade failed. Continuing anyway.
39
+ )
40
+
41
+ echo.
42
+ echo Running: %PY% -m pip install -e "%~dp0"
43
+ %PY% -m pip install -e "%~dp0"
44
+ if %ERRORLEVEL% NEQ 0 (
45
+ echo.
46
+ echo Install failed. Try:
47
+ echo %PY% -m pip install -e "%~dp0" --user
48
+ pause
49
+ exit /b 1
50
+ )
51
+
52
+ echo.
53
+ echo Install OK. Launching Slots Gel Annotator...
54
+ echo.
55
+ %PY% -m gel_annotator
56
+ ENDLOCAL
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env bash
2
+ # ===============================================================
3
+ # Slots Gel Annotator — macOS / Linux installer
4
+ # Installs the package from this source tree (editable mode)
5
+ # and launches the annotator. Re-run any time to update.
6
+ # ===============================================================
7
+ set -e
8
+
9
+ # Move into this script's directory so `pip install -e .` resolves
10
+ # correctly regardless of where the user double-clicked from.
11
+ cd "$(dirname "$0")"
12
+
13
+ echo
14
+ echo " =============================================="
15
+ echo " Slots Gel Annotator -- installing"
16
+ echo " =============================================="
17
+ echo
18
+
19
+ # Resolve a Python interpreter. macOS ships /usr/bin/python3 (Catalina+);
20
+ # Linux distros use python3 by convention. Refuse to use Python 2.
21
+ PY=""
22
+ for candidate in python3 python3.13 python3.12 python3.11 python3.10 python; do
23
+ if command -v "$candidate" >/dev/null 2>&1; then
24
+ ver="$("$candidate" -c 'import sys; print(sys.version_info.major)' 2>/dev/null || echo 0)"
25
+ if [ "$ver" = "3" ]; then
26
+ PY="$candidate"
27
+ break
28
+ fi
29
+ fi
30
+ done
31
+
32
+ if [ -z "$PY" ]; then
33
+ echo " ERROR: Python 3.10+ not found."
34
+ echo " macOS: brew install python (or download from https://www.python.org)"
35
+ echo " Linux: sudo apt install python3 python3-pip python3-tk"
36
+ exit 1
37
+ fi
38
+
39
+ echo " Using $PY ($("$PY" --version 2>&1))"
40
+ echo
41
+
42
+ echo " Running: $PY -m pip install -U pip"
43
+ "$PY" -m pip install -U pip || echo " Pip self-upgrade failed. Continuing anyway."
44
+
45
+ echo
46
+ echo " Running: $PY -m pip install -e ."
47
+ if ! "$PY" -m pip install -e . ; then
48
+ echo
49
+ echo " System-wide install failed. Retrying with --user ..."
50
+ "$PY" -m pip install -e . --user
51
+ fi
52
+
53
+ echo
54
+ echo " Install OK. Launching Slots Gel Annotator..."
55
+ echo
56
+ exec "$PY" -m gel_annotator
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 billy-ngo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,16 @@
1
+ include README.md
2
+ include CHANGELOG.md
3
+ include LICENSE
4
+ include MANIFEST.in
5
+ include Install_Windows.bat
6
+ include Install_macOS.command
7
+
8
+ # Frontend assets — required at runtime by the FastAPI server.
9
+ recursive-include gel_annotator/frontend *.html *.js *.css *.png *.svg *.ico
10
+
11
+ # Branding assets — optional but useful when present (loaded by
12
+ # gel_annotator.icon and the desktop-shortcut installer).
13
+ recursive-include gel_annotator/assets *.png *.ico *.icns *.svg README.md
14
+
15
+ # Source-tree extras the developer might want to ship.
16
+ include pyproject.toml
@@ -0,0 +1,285 @@
1
+ Metadata-Version: 2.4
2
+ Name: slotsgeltool
3
+ Version: 1.0.0
4
+ Summary: Slots Gel Annotator — vector-native gel annotation tool with publication-ready SVG / PNG export.
5
+ Author-email: Billy Ngo <billy.ngo0108@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/billy-ngo/slots-gel-annotator
8
+ Project-URL: Repository, https://github.com/billy-ngo/slots-gel-annotator
9
+ Project-URL: Issues, https://github.com/billy-ngo/slots-gel-annotator/issues
10
+ Project-URL: Changelog, https://github.com/billy-ngo/slots-gel-annotator/blob/main/CHANGELOG.md
11
+ Keywords: gel,electrophoresis,annotation,biology,bioinformatics,molecular-biology,ladder,SVG,publication,slots
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
15
+ Classifier: Topic :: Scientific/Engineering :: Image Processing
16
+ Classifier: Topic :: Scientific/Engineering :: Visualization
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Operating System :: OS Independent
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: numpy>=1.24
27
+ Requires-Dist: Pillow>=10.0
28
+ Requires-Dist: tifffile>=2023.1.0
29
+ Requires-Dist: scipy>=1.10
30
+ Requires-Dist: fastapi>=0.111
31
+ Requires-Dist: uvicorn[standard]>=0.29
32
+ Requires-Dist: python-multipart>=0.0.9
33
+ Dynamic: license-file
34
+
35
+ # Slots Gel Annotator
36
+
37
+ A vector-native, browser-based tool for annotating gel-electrophoresis images and exporting publication-ready figures. Designed for working biologists: drag-and-drop a gel image, draw an analysis region, label your lanes with brackets and ladder bands, and export the result as an SVG (vector — editable in Illustrator, Inkscape, or Figma) or a PNG (raster).
38
+
39
+ ---
40
+
41
+ ## Installation
42
+
43
+ ### Recommended (PyPI)
44
+
45
+ ```bash
46
+ pip install slotsgeltool
47
+ ```
48
+
49
+ Requires Python 3.10 or newer and a modern browser (Chrome, Firefox, Safari, or Edge).
50
+
51
+ ### Bundled installers (from the source distribution)
52
+
53
+ If you cloned the repository or downloaded a release archive:
54
+
55
+ * **Windows** — Double-click `Install_Windows.bat`
56
+ * **macOS** — Double-click `Install_macOS.command` (or run `bash Install_macOS.command`)
57
+
58
+ Both scripts call `pip install -e .` against the bundled source and then launch the annotator.
59
+
60
+ ### From source
61
+
62
+ ```bash
63
+ git clone https://github.com/billy-ngo/slots-gel-annotator
64
+ cd slots-gel-annotator
65
+ pip install -e .
66
+ ```
67
+
68
+ ---
69
+
70
+ ## Quick start
71
+
72
+ ```bash
73
+ slots # launch the annotator on port 8062; opens your browser
74
+ slots image.tif # launch and auto-load a gel image
75
+ slots --port 9000 # use a custom port
76
+ slots --no-browser # don't open the browser (useful on a headless host)
77
+ slots --install # create a desktop shortcut
78
+ slots --update # check PyPI for an update and install if available
79
+ slots --version # print version and exit
80
+ ```
81
+
82
+ The annotator single-instances itself: re-running `slots` while it is already running brings the existing tab to the front instead of starting a duplicate server.
83
+
84
+ ---
85
+
86
+ ## Workflow
87
+
88
+ 1. **Open** — Drag a gel image onto the canvas, or click *Open* to browse. PNG, JPEG, TIFF, and 16-bit raw / TIFF are supported. Bio-Rad / GelDoc / ChemiDoc TIFs (which often embed a red saturation contour) are auto-decoded with channel-max conversion so the imager's clipped-pixel marks survive.
89
+ 2. **Draw region** — Click *Draw Region* (or press **D**) and trace the analysis area. Lane positions are auto-distributed across the region's width; you can adjust the per-lane separators by dragging.
90
+ 3. **Label lanes** — Mark ladder lanes with the Ladder checkbox in the metadata table; click the lane on the gel to drop ladder-size bands. Type values in the metadata cells; consecutive cells with the same value automatically merge into a bracket above the gel.
91
+ 4. **Add free-form annotation** — `+Text (T)` for text labels, `+Line (L)` for arrows or underlines, `Rotate (R)` to spin the image or a selected element Illustrator-style.
92
+ 5. **Tune the LUT** — Click *LUT* to drag the histogram handles; saturated pixels are auto-flagged in red. The original pixel data is preserved on the server so re-tuning is non-destructive.
93
+ 6. **Export** — Save as **SVG** (vector, editable, every text element preserved as text) or **PNG** (raster, 2× scale by default). Or save the whole project as a JSON file (image embedded) and reload it later from the same point.
94
+
95
+ A status bar at the bottom of the window narrates the last action and surfaces errors. All buttons have keyboard shortcuts (shown in their titles).
96
+
97
+ ---
98
+
99
+ ## Features
100
+
101
+ | Feature | Notes |
102
+ |---|---|
103
+ | **Vector-native rendering** | The on-screen SVG IS the export. No drift between preview and final figure. |
104
+ | **Multi-format input** | PNG / JPEG / TIFF (8-bit) / 16-bit raw / 16-bit TIFF. RGB → grayscale via channel-max so imager-baked red highlights survive. |
105
+ | **Auto-stretched LUT** | Default LUT clips at the central 98 % of the histogram (p1 .. p99.5) so dark gels become visible without manual tweaking. |
106
+ | **Saturation overlay** | Truly clipped pixels show in red. Threshold is relative to the source dynamic range so it tracks the imager's intent (not the auto-stretched preview). |
107
+ | **Multiple ladders** | Mark any number of lanes as ladders — left flank, right flank, internal. Hide-Ladders collapses flank ladders only; internal ladders stay visible with their labels routed to the left. |
108
+ | **Smart bracket rotation** | When metadata column labels would otherwise overlap, only the offending labels rotate to 90° — the minimum needed for legibility. |
109
+ | **Cell editing** | Spreadsheet-style multi-cell drag-fill, Tab navigation, Esc to cancel, Delete to clear. Click outside the range and the selection clears so the next keystroke can't accidentally overwrite cells you weren't pointing at. |
110
+ | **Column reorder** | Drag column headers to reorder; the brackets on the gel follow. |
111
+ | **Per-column / per-lane visibility** | A "Show" row lets you toggle lane numbers or any metadata column off without losing its data. |
112
+ | **Undo / Redo** | Every state-changing action is undoable (Ctrl+Z / Ctrl+Y). |
113
+ | **Smooth zoom + pan** | Mouse wheel zooms (proportional to delta — trackpad-friendly); right-click drag pans. |
114
+ | **Dark mode (Invert)** | One-click colour inversion of the gel image and ink colours, for posters with dark backgrounds. |
115
+ | **Single-instance** | Re-running `slots` reuses the existing window instead of starting a duplicate server. |
116
+ | **Self-contained sessions** | Save → JSON file with the image embedded. Reload anywhere, anytime, no original file needed. |
117
+
118
+ ---
119
+
120
+ ## File-format support
121
+
122
+ Slots Gel Annotator targets 16-bit raw images from gel-imaging systems (Bio-Rad, Azure, GE, etc.) but tolerates many real-world variations:
123
+
124
+ | Format | Notes |
125
+ |---|---|
126
+ | **PNG** | 8-bit and 16-bit grayscale or RGB. RGB is collapsed to grayscale via channel-max (preserves imager-baked red highlights). |
127
+ | **JPEG** | 8-bit RGB. Some features (LUT editing, saturation toggle) are disabled — JPEG can't reach the dynamic range needed for them — and an alert explains so on upload. |
128
+ | **TIFF** | 8-bit and 16-bit grayscale or RGB. Read via `tifffile` first, with PIL as a fallback for unusual TIFF flavours. |
129
+ | **`.raw16`** | 16-bit raw passed through `tifffile`. The format most modern imaging platforms produce. |
130
+ | **`.bmp`** | 8-bit. Same caveats as JPEG. |
131
+
132
+ 8-bit uploads automatically enable the saturation overlay so clipped pixels are visible without further configuration. 16-bit uploads keep the LUT controls and saturation toggle enabled for fine-tuning.
133
+
134
+ ---
135
+
136
+ ## Branding & icons
137
+
138
+ The desktop shortcut (Windows `.lnk`, macOS `.app`, Linux `.desktop`) and the in-app favicon load their icons from `gel_annotator/assets/`. To customize:
139
+
140
+ | File | Purpose | Recommended size |
141
+ |----------------|--------------------------------------------------|------------------|
142
+ | `icon.ico` | Windows shortcut + window icon | multi-resolution `.ico` containing 16 / 32 / 48 / 64 / 128 / 256 |
143
+ | `icon.icns` | macOS `.app` bundle icon (skip if absent — macOS converts `icon-512.png` via `sips`) | 1024×1024 |
144
+ | `icon-512.png` | High-resolution PNG used for Linux `.desktop`, web favicon, and macOS fallback | 512×512 |
145
+ | `icon-192.png` | Smaller PNG (web manifest, web favicon) | 192×192 |
146
+ | `logo-wide.png`| Banner for the README header (optional, in-app header) | 1200×200 |
147
+
148
+ PNGs MUST be transparent-background. ICOs/ICNS should bundle multiple resolutions. If a file is missing the loader silently substitutes a 1×1 transparent PNG and the OS falls back to its default app icon.
149
+
150
+ See [`gel_annotator/assets/README.md`](gel_annotator/assets/README.md) for the `iconutil` / `sips` recipe to generate `.icns` from a single 1024×1024 source.
151
+
152
+ ---
153
+
154
+ ## Sessions
155
+
156
+ Use the **Save** button to download a JSON file containing the full project state: the image (base64-embedded), the region, lane separators, ladder lanes / bands, metadata column data, every annotation (text, line, custom rotation, custom dx/dy), per-column visibility, the LUT settings, the bit-depth-derived feature gates, and any per-lane ladder shifts.
157
+
158
+ Saved sessions are **fully self-contained** — the image is embedded as a `data:` URL so the file reloads on a different machine without needing the original raw image.
159
+
160
+ ---
161
+
162
+ ## Robustness
163
+
164
+ A few things designed to keep you out of trouble:
165
+
166
+ * **Drag-cancel invariants.** Every in-flight drag (region-draw, region-resize, line-draw, annotation-move, label-move, tick-move, rotation, marquee, multi-move) cleans up through a single `cancelDrag()` path on Esc, blur, pointer-cancel, or any state mutation that would invalidate the drag's coordinates. There is no second mode for the user to be stuck in.
167
+ * **Per-feature isolation.** A bug in (say) bracket rotation can't break ladder-band rendering; each rendering pass is isolated. The whole canvas never goes blank because of one bad annotation.
168
+ * **Saturation auto-on.** Every new image starts with the saturation overlay enabled so clipped pixels are visible from the moment the image is loaded.
169
+ * **Cell range memory.** Drag-select a range, click a cell INSIDE the range to type-fill all of them — Excel-style. Click OUTSIDE the range and the selection clears so the next keystroke can't accidentally overwrite cells you weren't pointing at.
170
+ * **Undo across structural changes.** Per-render selection state (`_cellRange`, `_regionSelected`, `_selectedTick`) is never persisted in undo snapshots, so an undo can never restore a "ghost" selection that no longer matches the rendered DOM.
171
+
172
+ ---
173
+
174
+ ## Architecture
175
+
176
+ ```
177
+ ┌─────────────────┐ ┌──────────────────┐
178
+ │ FastAPI server │ HTTP │ Browser (SVG) │
179
+ │ │ ──────> │ │
180
+ │ - upload │ │ - draw region │
181
+ │ - LUT preview │ │ - lanes/bands │
182
+ │ - rotate │ │ - brackets │
183
+ │ - saturation │ │ - export │
184
+ │ │ │ │
185
+ │ NO renderer │ │ the renderer │
186
+ └─────────────────┘ └──────────────────┘
187
+ ```
188
+
189
+ * **Backend** (`gel_annotator/server/main.py`): handles image upload, RGB→grayscale collapse, LUT-stretched preview generation, saturation-overlay generation, destructive image rotation. Does not generate exports.
190
+ * **Frontend** (`gel_annotator/frontend/`): a single `<svg>` element, built and re-rendered from a state object. All annotations are real SVG primitives (`<rect>`, `<line>`, `<text>`, `<image>`, `<foreignObject>` for inline edit). PNG/SVG export operate on the same DOM nodes the user sees.
191
+
192
+ Why this design: an earlier iteration kept the on-screen renderer (canvas-based JavaScript) and the export renderer (Pillow + svgwrite, in Python) separate. Every UI tweak had to be ported twice, and the two paths drifted on every release. This rebuild has one renderer. Export is:
193
+
194
+ ```javascript
195
+ // SVG: 2 lines
196
+ const xml = new XMLSerializer().serializeToString(svgEl);
197
+ download(xml, 'figure.svg', 'image/svg+xml');
198
+
199
+ // PNG: ~12 lines (rasterize the same SVG via a temporary canvas)
200
+ ```
201
+
202
+ That's the entire export module.
203
+
204
+ ---
205
+
206
+ ## Troubleshooting
207
+
208
+ **The browser doesn't open** — Slots polls `/healthz` for up to 10 s before opening. If the browser still doesn't open, navigate to `http://localhost:8062` (or your `--port`) manually.
209
+
210
+ **"Address already in use"** — Slots single-instances itself, but if it crashed leaving a stale lock file, delete `~/.slots-gel-annotator/server.lock` and try again. Or just use a different `--port`.
211
+
212
+ **LUT and Saturation buttons are grayed out** — You uploaded an 8-bit image (PNG / JPEG / 8-bit TIFF). Those features need the wider dynamic range of a 16-bit raw to be useful. The hover tooltip says so. Upload a `.raw16` / 16-bit TIFF to unlock them.
213
+
214
+ **A figure exported as PNG looks pixelated** — PNG export defaults to 2× scale. Re-export as SVG and rasterize it at higher resolution in Inkscape / Illustrator if needed.
215
+
216
+ **A bracket label rotates 90° unexpectedly** — Smart-rotation kicks in when a label is wider than its bracket. To prevent it, shorten the label or widen the lane.
217
+
218
+ ---
219
+
220
+ ## Project file format (v2)
221
+
222
+ ```json
223
+ {
224
+ "version": 2,
225
+ "image_filename": "western_blot_2026-05.tif",
226
+ "image_data_url": "data:image/png;base64,iVBORw0...",
227
+ "image_width": 2480,
228
+ "image_height": 1653,
229
+ "raw_min": 0.0, "raw_max": 255.0,
230
+ "bit_depth": 8,
231
+ "lut": { "min": 0.0, "max": 165.0, "gamma": 1.0 },
232
+ "invert_image": false,
233
+ "region": { "x": 200, "y": 100, "w": 2050, "h": 1400 },
234
+ "region_outline": true,
235
+ "cropped_to_region": false,
236
+ "region_border_width": 2,
237
+ "tick_width": 2,
238
+ "lane_count": 8,
239
+ "ladder": [true, false, false, false, false, false, false, true],
240
+ "lane_separators": null,
241
+ "tick_height": 10,
242
+ "ladder_label_dx": {},
243
+ "columns": [{"id": "col_xyz", "name": "Treatment"}, ...],
244
+ "cells": {"col_xyz": ["Ctrl", "Ctrl", "T1", "T1", "T2", "T2"]},
245
+ "column_visible": {"col_xyz": true},
246
+ "show_lane_numbers": true,
247
+ "bands": {"0": [{"id": "b0a", "y_center": 400, "label": "50"}]},
248
+ "annotations": [],
249
+ "label_overrides": {},
250
+ "hide_ladders": false,
251
+ "bracket_line_style": "solid",
252
+ "line_cap": "butt"
253
+ }
254
+ ```
255
+
256
+ Sessions saved in v1 are silently upgraded on load.
257
+
258
+ ---
259
+
260
+ ## Citation
261
+
262
+ If Slots Gel Annotator contributes to a publication, please cite it as:
263
+
264
+ ```
265
+ Ngo, B.M. (2026). Slots Gel Annotator [Software].
266
+ Available at https://github.com/billy-ngo/slots-gel-annotator
267
+ ```
268
+
269
+ ---
270
+
271
+ ## Version history
272
+
273
+ See [CHANGELOG.md](CHANGELOG.md) for the full per-version history. Current release is shown via `slots --version`.
274
+
275
+ ---
276
+
277
+ ## License
278
+
279
+ MIT. See [LICENSE](LICENSE).
280
+
281
+ ---
282
+
283
+ ## Author
284
+
285
+ Billy M Ngo · billy.ngo0108@gmail.com · [github.com/billy-ngo](https://github.com/billy-ngo)