{"id":50669267,"url":"https://github.com/magliaral/railshot","last_synced_at":"2026-06-08T09:30:59.879Z","repository":{"id":356257714,"uuid":"1231715398","full_name":"magliaral/railshot","owner":"magliaral","description":"Prepare model railway photos for digital control software (Rocrail, etc). Background removal, scale-accurate sizing, optional rail underlay.","archived":false,"fork":false,"pushed_at":"2026-05-07T09:40:47.000Z","size":52,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-07T10:27:54.905Z","etag":null,"topics":["background-removal","image-processing","model-railway","n-scale","rembg","rocrail"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/magliaral.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-07T08:07:56.000Z","updated_at":"2026-05-07T09:40:51.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/magliaral/railshot","commit_stats":null,"previous_names":["magliaral/railshot"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/magliaral/railshot","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/magliaral%2Frailshot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/magliaral%2Frailshot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/magliaral%2Frailshot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/magliaral%2Frailshot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/magliaral","download_url":"https://codeload.github.com/magliaral/railshot/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/magliaral%2Frailshot/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34057158,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-08T02:00:07.615Z","response_time":111,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["background-removal","image-processing","model-railway","n-scale","rembg","rocrail"],"created_at":"2026-06-08T09:30:56.099Z","updated_at":"2026-06-08T09:30:59.870Z","avatar_url":"https://github.com/magliaral.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"assets/logo-dark.png\"\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"assets/logo-light.png\"\u003e\n    \u003cimg src=\"assets/logo-light.png\" alt=\"railshot logo\" width=\"320\"\u003e\n  \u003c/picture\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eScaled. Clean. Ready for your layout.\u003c/strong\u003e\n\u003c/p\u003e\n\n---\n\n# railshot\n\n**Prepare model railway photos for digital control software** like Rocrail or\niTrain. Background removal, scale-accurate sizing, optional rail underlay —\nPython CLI for batch processing.\n\nDesigned for **N-scale collections** (1:160) but works for any scale where\nyour scaling factor (px/mm) is consistent across the roster.\n\n## What it does\n\n- Pixel-accurate left/right cropping (buffer beam to buffer beam)\n- Scale-accurate sizing across the entire collection\n- Default 80 px output height (Rocrail wiki standard)\n- Wheels aligned on a common ground line\n- Optional digital rail underlay (consistent across the whole roster)\n- Optional auto-rotation (level the underframe)\n- Optional auto-perspective (straighten end faces)\n- Optional pre-crop (studio ROI before background removal)\n- Transparent PNG output\n\n## Setup\n\n```bash\npip install \"rembg[cpu]\" pillow numpy\npip install opencv-python      # only for --auto-perspective\npip install scipy              # optional, helps with --pre-crop auto\n```\n\nOr just install everything from `requirements.txt`:\n\n```bash\npip install -r requirements.txt\n```\n\nOn first run, `rembg` automatically downloads its ONNX model (~170 MB for\n`u2net`, cached in `~/.u2net/`).\n\n## Web UI (optional)\n\nA minimal Streamlit-based web UI is included as `app.py`. Run it locally:\n\n```bash\nstreamlit run app.py\n```\n\nYour browser opens automatically at `http://localhost:8501`. Upload a photo,\nadjust the sliders in the sidebar, click **Process**, download the result.\n\nThe web UI is also designed to deploy directly to **Hugging Face Spaces**\n(see `README_huggingface.md` and the deployment section at the bottom of\nthis file).\n\n## Quickstart (CLI)\n\n```powershell\npython railshot.py coach.jpg -o coach.png `\n    --mode scale --px-per-mm 2.0 --length-mm 165 --auto-rotate --rail\n```\n\nThis produces a transparent PNG (~330×80 px when rail is enabled) with the\ncoach sitting bottom-aligned on a rail.\n\n## Defining the scale\n\nThe most important parameter is **`--px-per-mm`**: how many pixels per\nmillimeter of model length. Set this **once** for your entire collection and\nuse the same value for every image. Otherwise vehicles won't be in the same\nscale anymore.\n\n### Method A: Define via height (recommended for locos with pantograph)\n\nIn this method, you use the tallest locomotive in your collection as the\nanchor for the 80 px Rocrail height. This guarantees no loco gets too tall\nand the pantograph space is used optimally.\n\n1. **Measure the tallest loco** (rail head to roof with pantograph\n   retracted). E.g. Re 460 ≈ 30 mm.\n2. **Choose a target height:** say 60 px (leaves 20 px reserve at top for\n   raised pantograph).\n3. **Calculate:** `60 px / 30 mm = 2.0 px/mm`\n\nThis gives you for the rest of the collection:\n\n| Vehicle              | Length (mm) | Output width (at 2.0 px/mm) |\n|----------------------|-------------|------------------------------|\n| Astoro power car     | 172         | 344 px                       |\n| EW IV                | 165         | 330 px                       |\n| RAe TEE control car  | 158         | 316 px                       |\n| Re 460               | 116         | 232 px                       |\n| Eem 923 (Tigerli)    |  58         | 116 px                       |\n\n### Method B: Define via length\n\n1. **Measure the main coach** (e.g. EW IV ≈ 165 mm).\n2. **Choose a target width** (e.g. 250 px).\n3. **Calculate:** `250 px / 165 mm = 1.515 px/mm`\n\n| px-per-mm | EW IV (165 mm) | Re 460 (116 mm) | Tigerli (58 mm) |\n|-----------|----------------|-----------------|------------------|\n| 1.515     | 250 px         | 176 px          | 88 px            |\n| 1.82      | 300 px         | 211 px          | 105 px           |\n| 2.0       | 330 px         | 232 px          | 116 px           |\n\n## Studio setup for clean results\n\n1. **Clear background** around the model — no other vehicles, no dark walls,\n   no monitors in the field of view.\n2. **Frame-filling** — model should fill ~90% of the photo width, so rembg\n   has enough pixels to recognize the subject.\n3. **Frontal view** — camera parallel to the model. Then `--auto-perspective`\n   isn't needed.\n4. **Constant lighting** — avoids harsh shadows under the model.\n5. **Plain underlay** — don't photograph a real rail underneath! The script\n   adds a digital rail later (see next section).\n\n## Digital rail underlay\n\nInstead of photographing a real rail (which can confuse rembg), the script\noverlays a **consistent digital rail** under each model. Advantages:\n\n- Rail is **pixel-perfect identical** on all images\n- rembg doesn't have to deal with rail details\n- Rail is **cropped to model width** (not scaled) — sleeper spacing stays\n  constant\n- When models are placed adjacent in Rocrail, the rail forms a continuous line\n\n### Usage\n\n```powershell\npython railshot.py coach.jpg -o coach.png `\n    --mode scale --px-per-mm 2.0 --length-mm 165 --auto-rotate `\n    --rail\n```\n\nThe script expects a file named `rail.png` in the same folder as the script.\nA default version is included in this repository (4 px high, 800 px wide).\n\n### Customizing the rail\n\nIf the bundled rail doesn't suit your taste, create your own PNG with these\nproperties:\n\n- **Height:** 3–8 px (subtle — too tall and it dominates the image)\n- **Width:** any, at least as wide as your longest model\n- **Format:** PNG with alpha channel\n- **Content:** sleepers, rail head, ballast — your choice\n\nUse it via `--rail-image my_rail.png`.\n\n### Canvas behavior\n\n- **Default (no flag):** the rail is **overlaid** on the bottom edge of the\n  wheels. The wheels visually rest on the rail (this is the realistic look).\n- **`--rail-extend`:** the canvas grows downwards by the rail height. The\n  rail hangs **below** the wheels.\n\n## Pre-crop: eliminate noise outside the studio\n\nIf your photo contains things outside the studio area (other locos, cables,\nwall edges), a pre-crop **before** rembg helps a lot:\n\n```powershell\npython railshot.py coach.jpg -o coach.png `\n    --mode scale --px-per-mm 2.0 --length-mm 165 `\n    --pre-crop \"170,80,1965,820\"\n```\n\nFormat: `\"X1,Y1,X2,Y2\"` in pixels of the original photo. Read coordinates in\nPaint, IrfanView, GIMP, or any image viewer that shows the cursor position.\n\nFor a fixed studio setup, you measure the ROI **once** and reuse it for all\nphotos.\n\nAuto variant (finds the brightest region):\n\n```powershell\n--pre-crop \"auto\"\n--pre-crop \"auto 180\"   # custom brightness threshold\n```\n\nWorks when the studio is clearly brighter than its surroundings.\n\n## Auto-rotation and auto-perspective\n\n```powershell\n# Auto-level the bottom of the model (safe)\n--auto-rotate\n\n# Plus straighten end faces (experimental, requires opencv)\n--auto-rotate --auto-perspective\n```\n\nBoth corrections have built-in safety limits: corrections \u003e5° rotation or\n\u003e30 px perspective are ignored, leaving the original unchanged. So\nauto-correction can only help or do nothing — never break things.\n\nThe terminal output tells you what happened:\n\n```\nOK  ew4.jpg  -\u003e  ew4.png  (330 x 80 px)  [rot -0.64°, bbox 1478x233 (aspect 6.34)]\n```\n\n## Batch processing with lengths.json\n\nCreate a `lengths.json` mapping filenames to model lengths:\n\n```json\n{\n  \"ew4_a_184-7\": 165,\n  \"ew4_b_xxx\": 165,\n  \"re460_001\": 116,\n  \"tigerli\": 58,\n  \"shimms_454\": 103\n}\n```\n\nKeys = filename **without** extension (or with — both work).\nValues = model length in mm.\n\nRun:\n\n```powershell\npython railshot.py ./photos -o ./out `\n    --mode scale --px-per-mm 2.0 --lengths lengths.json `\n    --pre-crop \"170,80,1965,820\" --auto-rotate --rail\n```\n\nMissing entries fall back to `--length-mm` if provided, otherwise raise an\nerror.\n\n## PowerShell shortcuts for daily use\n\nAdd to your PowerShell profile (`notepad $PROFILE`):\n\n```powershell\nfunction rrp-one {\n    param(\n        [Parameter(Mandatory)][string]$In,\n        [Parameter(Mandatory)][string]$Out,\n        [Parameter(Mandatory)][int]$LengthMm\n    )\n    python railshot.py $In -o $Out `\n        --mode scale --px-per-mm 2.0 --length-mm $LengthMm `\n        --pre-crop \"170,80,1965,820\" --auto-rotate --rail\n}\n\nfunction rrp-batch {\n    param(\n        [string]$In = \"./photos\",\n        [string]$Out = \"./out\"\n    )\n    python railshot.py $In -o $Out `\n        --mode scale --px-per-mm 2.0 --lengths lengths.json `\n        --pre-crop \"170,80,1965,820\" --auto-rotate --rail\n}\n```\n\nThen just:\n\n```powershell\nrrp-one -In coach.jpg -Out coach.png -LengthMm 165\nrrp-batch\n```\n\n## Debug mode\n\nIf something goes wrong, dump all intermediate steps:\n\n```powershell\npython railshot.py coach.jpg -o coach.png `\n    [...other options...] `\n    --debug-dir ./debug --verbose\n```\n\nCreates `./debug/coach/` with numbered PNGs:\n\n| File                          | Content                          |\n|-------------------------------|----------------------------------|\n| `00_input.png`                | Original photo                   |\n| `01_pre_crop.png`             | After pre-crop                   |\n| `02_rembg.png`                | After background removal         |\n| `03_edge_clean.png`           | Halo cleanup                     |\n| `04_rotated_+0.64deg.png`     | After auto-rotation              |\n| `05_perspective.png`          | After auto-perspective           |\n| `06_cropped_NNNNxNNN.png`     | After bbox crop (with size!)     |\n| `07_scaled_330x52.png`        | After scaling                    |\n| `08_with_rail_330x52.png`     | After rail overlay               |\n| `09_final_330x80.png`         | Final on canvas                  |\n\nThis shows exactly where in the pipeline things go wrong.\n\n## Verifying the output\n\nSome image viewers don't display transparency correctly (showing it as\nblack or white). To verify:\n\n```powershell\npython -c \"from PIL import Image; img = Image.open('out.png'); print('Mode:', img.mode)\"\n```\n\nExpected: `Mode: RGBA`. If `Mode: RGB`, the alpha channel is missing.\n\nVisually: PyCharm shows transparency as a **gray checkerboard pattern**.\nGIMP, Paint.NET, and modern browsers all do this correctly.\n\n## Rocrail image specs\n\nPer the Rocrail wiki:\n- **Height:** 80 px (standard)\n- **Max file size:** 50 KB\n- **Format:** PNG with transparent background\n\nFor typical N-scale photos at 200–400 px width × 80 px height, output PNGs\nland at 15–35 KB — well below the 50 KB limit.\n\n**Important on train assembly:** in Rocrail you define each wagon with its\nmodel length in mm. Rocrail then composes trains at runtime using these\nlengths. Your individual wagon image doesn't need to contain a full train —\nthat's done by Rocrail dynamically.\n\n## All options\n\n| Flag                    | Default             | Purpose |\n|-------------------------|---------------------|---------|\n| `--mode`                | `scale`             | `height` or `scale` |\n| `--canvas-height`       | `80`                | Output height in px (Rocrail standard) |\n| `--max-width`           | —                   | Hard cap on width (height mode) |\n| `--px-per-mm`           | —                   | **Scale: pixels per mm of model length** |\n| `--length-mm`           | —                   | Length of current vehicle in mm |\n| `--lengths`             | —                   | JSON with per-file lengths |\n| `--pre-crop`            | —                   | ROI before rembg (`X1,Y1,X2,Y2` or `auto`) |\n| `--pre-crop-padding`    | `20`                | Safety padding around ROI |\n| `--auto-rotate`         | off                 | Level the bottom edge |\n| `--min-rotation-deg`    | `0.2`               | Lower threshold (no rotation below) |\n| `--max-rotation-deg`    | `5.0`               | Upper threshold (probably error) |\n| `--auto-perspective`    | off                 | Straighten end faces |\n| `--min-perspective-px`  | `1.5`               | Lower threshold |\n| `--max-perspective-px`  | `30`                | Upper threshold |\n| `--h-alpha-threshold`   | `128`               | Strict horizontal threshold |\n| `--v-alpha-threshold`   | `32`                | Lenient vertical threshold |\n| `--h-min-column-pixels` | `3`                 | Filter against cutout artefacts |\n| `--edge-clean-threshold`| `64`                | Halo cleanup threshold |\n| `--pad-left`            | `0`                 | Padding left (= 0 for Rocrail!) |\n| `--pad-right`           | `0`                 | Padding right (= 0 for Rocrail!) |\n| `--pad-top`             | `1`                 | Padding top |\n| `--pad-bottom`          | `0`                 | Padding bottom |\n| **`--rail`**            | off                 | **Place rail under model** |\n| **`--rail-image`**      | `rail.png`          | **Path to rail template** |\n| **`--rail-extend`**     | off                 | **Extend canvas instead of overlay** |\n| `--align`               | `bottom`            | Vertical alignment in canvas |\n| `--model`               | `u2net`             | rembg model |\n| `--debug-dir`           | —                   | Save intermediate steps as PNG |\n| `-v` / `--verbose`      | off                 | More verbose error output |\n\n## rembg model recommendations\n\n- `u2net` — robust classic, default — best general results in practice\n- `isnet-general-use` — alternative all-rounder, slightly slower\n- `u2netp` — fast and small, slightly less accurate\n\nFor most loco/wagon shots, `u2net` produces the cleanest cutout. Switch via\n`--model` (CLI) or the sidebar dropdown (Web UI) if a specific image needs it.\n\n## Troubleshooting\n\n### No transparent background (everything black)\n\nKnown rembg quirk. The script has a workaround built in that activates\nwhen needed. If still black:\n\n```bash\npip install \"rembg[cpu]\" --upgrade\n```\n\n### Model gets cut off left/right\n\n```powershell\n--h-alpha-threshold 96       # less strict (default 128)\n--pad-left 1 --pad-right 1   # 1 px safety\n```\n\n### Pantograph/antenna missing at top\n\n```powershell\n--v-alpha-threshold 16    # more lenient (default 32)\n--pad-top 3\n```\n\n### Halo around model\n\n```powershell\n--edge-clean-threshold 96    # stricter (default 64)\n```\n\n### Gaps between coaches in Rocrail\n\n```powershell\n--edge-clean-threshold 96 --h-alpha-threshold 160    # both stricter\n```\n\n### Model too small/large in output\n\nAdjust `--px-per-mm`. **But:** if you change this mid-collection, **all**\nmodels must be regenerated with the new value — otherwise the shared\nscale breaks.\n\n### Rail too prominent\n\nReplace the bundled `rail.png` with a slimmer custom version. Make it\nthinner (3 px instead of 4 px), use muted colors, less sleeper contrast.\n\n### Rail file not found\n\n```\nFileNotFoundError: Rail file not found: ...\n```\n\n`rail.png` must be in the same folder as `railshot.py` — or pass an\nexplicit path with `--rail-image C:\\path\\to\\my_rail.png`.\n\n### Auto-perspective makes the image worse\n\nDrop `--auto-perspective`. End-face detection is unreliable on rounded\ntransitions — better to shoot straight mechanically.\n\n### Pre-crop cuts off the model\n\nRe-measure ROI coordinates in Paint/GIMP. Buffer beams must be **inside**\nthe ROI. Tip: `--pre-crop-padding 30` gives extra safety.\n\n## Recommended workflow for a complete collection\n\n1. **Build a fixed studio setup** and don't change it.\n2. **Measure the tallest loco** → determines `--px-per-mm`.\n3. **Measure the pre-crop ROI** — once, reused for everything.\n4. **Process a test image**, view in Rocrail, adjust scale if needed.\n5. **Customize rail.png** if the default doesn't fit.\n6. **Set up PowerShell shortcuts** with your fixed parameters.\n7. **Maintain `lengths.json`** for all models.\n8. **Run the batch**, copy results to your Rocrail image folder.\n\n## What this tool does NOT do\n\n- Detect multiple vehicles in one photo — please, one model per photo.\n- Mirror/flip for the \"other side\" — shoot both sides or use mirror options\n  in your control software.\n- Color or brightness correction — best done at capture time (white balance,\n  even lighting).\n- Compose full trains — that's done by Rocrail/iTrain at runtime using your\n  per-model length definitions.\n\n## Deploy to Hugging Face Spaces\n\nThe web UI can be deployed to [Hugging Face Spaces](https://huggingface.co/spaces)\nfor free, giving you (or anyone) a public URL to use the tool without any\nlocal installation.\n\n**Steps:**\n\n1. Create a free account at [huggingface.co](https://huggingface.co)\n2. Click \"New Space\", choose **Streamlit** as SDK, name it `railshot` (or whatever you prefer), free CPU tier is sufficient\n3. Clone your new Space repository:\n   ```bash\n   git clone https://huggingface.co/spaces/YOUR_USERNAME/railshot\n   cd railshot\n   ```\n4. Copy these files from this repo into the cloned Space repo:\n   - `app.py`\n   - `railshot.py`\n   - `rail.png`\n   - `requirements.txt`\n   - **Rename** `README_huggingface.md` to `README.md` (it has the YAML\n     config header that HF needs)\n5. Push to HF:\n   ```bash\n   git add .\n   git commit -m \"Initial deploy\"\n   git push\n   ```\n6. Wait ~3-5 minutes for the build, then your Space is live at\n   `https://huggingface.co/spaces/YOUR_USERNAME/railshot`\n\n**Note:** the first model download (~170 MB rembg model) happens on the\nfirst user request and takes ~30 seconds. After that it's cached for the\nlifetime of the Space.\n\n## Acknowledgements\n\nThis tool stands on the shoulders of excellent open-source projects:\n\n- **[rembg](https://github.com/danielgatis/rembg)** (MIT) — AI-based\n  background removal using ONNX runtime\n- **[Pillow](https://python-pillow.org/)** (HPND) — image manipulation\n- **[NumPy](https://numpy.org/)** (BSD-3) — numerical operations\n- **[Streamlit](https://streamlit.io/)** (Apache 2.0) — web UI framework\n- **[OpenCV](https://opencv.org/)** (Apache 2.0, optional) — for\n  perspective correction\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n\nThe dependencies above retain their respective licenses and are not\nredistributed as part of this repository — they are installed via pip.\n\n## Contributing\n\nIssues, ideas, and pull requests welcome. The tool was developed for\nSBB-themed N-scale photography but should work for any scale and railway\nnetwork.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmagliaral%2Frailshot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmagliaral%2Frailshot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmagliaral%2Frailshot/lists"}