From 1ab49bce1d41ddbb19039feb5f353ba0f0480409 Mon Sep 17 00:00:00 2001 From: Michael Pilosov Date: Sat, 16 May 2026 18:08:34 -0600 Subject: [PATCH] rename project --- Dockerfile | 2 +- Makefile | 2 +- README.md | 8 +- compose.yml | 4 +- pyproject.toml | 6 +- .../__init__.py | 0 .../model.py | 0 .../prompt_segment.py | 0 .../server.py | 0 .../static/index.html | 0 src/rmbg_as_a_service/static/styles.css | 656 ++++++++++++++++++ 11 files changed, 667 insertions(+), 11 deletions(-) rename src/{birefnet_service => rmbg_as_a_service}/__init__.py (100%) rename src/{birefnet_service => rmbg_as_a_service}/model.py (100%) rename src/{birefnet_service => rmbg_as_a_service}/prompt_segment.py (100%) rename src/{birefnet_service => rmbg_as_a_service}/server.py (100%) rename src/{birefnet_service => rmbg_as_a_service}/static/index.html (100%) create mode 100644 src/rmbg_as_a_service/static/styles.css diff --git a/Dockerfile b/Dockerfile index 56b10d1..a6b6ebc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,4 +35,4 @@ RUN --mount=type=cache,target=/root/.cache/uv \ ENV PATH="/app/.venv/bin:${PATH}" EXPOSE 8000 -CMD ["birefnet-service"] +CMD ["rmbg-as-a-service"] diff --git a/Makefile b/Makefile index ba636d1..789842f 100644 --- a/Makefile +++ b/Makefile @@ -45,7 +45,7 @@ sync: ## Install dependencies locally with uv uv sync dev: sync ## Run the service locally (no Docker; needs local CUDA) - uv run birefnet-service + uv run rmbg-as-a-service shell: ## Open a shell inside a fresh container $(COMPOSE) run --rm --entrypoint bash birefnet diff --git a/README.md b/README.md index 6d77e6f..1b20c7d 100644 --- a/README.md +++ b/README.md @@ -105,10 +105,10 @@ make dev # uv sync + run the server locally ## Layout ``` -src/birefnet_service/model.py BiRefNet / RMBG-2.0 wrapper + compositing -src/birefnet_service/prompt_segment.py GroundingDINO + SAM pipeline -src/birefnet_service/server.py LitServe /predict + /segment + web UI -src/birefnet_service/static/ web UI (index.html) +src/rmbg_as_a_service/model.py BiRefNet / RMBG-2.0 wrapper + compositing +src/rmbg_as_a_service/prompt_segment.py GroundingDINO + SAM pipeline +src/rmbg_as_a_service/server.py LitServe /predict + /segment + web UI +src/rmbg_as_a_service/static/ web UI (index.html) scripts/client.py stdlib-only test client Dockerfile / compose.yml CUDA image + nvidia runtime Makefile build / run / test shortcuts diff --git a/compose.yml b/compose.yml index 402dc0f..a1b1f99 100644 --- a/compose.yml +++ b/compose.yml @@ -1,8 +1,8 @@ services: birefnet: build: . - image: birefnet-service:latest - container_name: birefnet-service + image: rmbg-as-a-service:latest + container_name: rmbg-as-a-service ports: - "${PORT:-8000}:8000" environment: diff --git a/pyproject.toml b/pyproject.toml index 1d3c675..79f363e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "rmbg-as-a-service" -version = "0.1.0" +version = "0.0.1" description = "Background removal as a GPU-accelerated API" readme = "README.md" requires-python = ">=3.12,<3.13" @@ -17,7 +17,7 @@ dependencies = [ ] [project.scripts] -birefnet-service = "birefnet_service.server:run" +rmbg-as-a-service = "rmbg_as_a_service.server:run" [dependency-groups] dev = ["ruff>=0.6.0"] @@ -27,7 +27,7 @@ requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] -packages = ["src/birefnet_service"] +packages = ["src/rmbg_as_a_service"] # BiRefNet (torch) needs CUDA wheels; pull torch/torchvision from the PyTorch index. [[tool.uv.index]] diff --git a/src/birefnet_service/__init__.py b/src/rmbg_as_a_service/__init__.py similarity index 100% rename from src/birefnet_service/__init__.py rename to src/rmbg_as_a_service/__init__.py diff --git a/src/birefnet_service/model.py b/src/rmbg_as_a_service/model.py similarity index 100% rename from src/birefnet_service/model.py rename to src/rmbg_as_a_service/model.py diff --git a/src/birefnet_service/prompt_segment.py b/src/rmbg_as_a_service/prompt_segment.py similarity index 100% rename from src/birefnet_service/prompt_segment.py rename to src/rmbg_as_a_service/prompt_segment.py diff --git a/src/birefnet_service/server.py b/src/rmbg_as_a_service/server.py similarity index 100% rename from src/birefnet_service/server.py rename to src/rmbg_as_a_service/server.py diff --git a/src/birefnet_service/static/index.html b/src/rmbg_as_a_service/static/index.html similarity index 100% rename from src/birefnet_service/static/index.html rename to src/rmbg_as_a_service/static/index.html diff --git a/src/rmbg_as_a_service/static/styles.css b/src/rmbg_as_a_service/static/styles.css new file mode 100644 index 0000000..2b7b512 --- /dev/null +++ b/src/rmbg_as_a_service/static/styles.css @@ -0,0 +1,656 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Courier New', 'Lucida Console', monospace; + background: #000000; + color: #8fbc8f; + min-height: 100vh; + padding: 12px; + text-transform: uppercase; +} + +:root { + --sidebar-width: 350px; + --page-padding: 12px; +} + +.container { + max-width: 1400px; + margin: 0 auto; + display: grid; + grid-template-columns: var(--sidebar-width) 1fr; + gap: 12px; + height: calc(100vh - 24px); + position: relative; + /* For toggle positioning */ +} + +/* Allow collapsing at any size */ +.container.sidebar-collapsed { + grid-template-columns: 1fr; +} + +.container.sidebar-collapsed>.controls { + display: none !important; +} + +.container.sidebar-collapsed .preview-container { + grid-column: 1 / -1; +} + +.sidebar-toggle { + display: none; + /* Hidden on desktop */ + position: absolute; + z-index: 100; +} + +@media (max-width: 768px) { + .container { + grid-template-columns: 1fr; + display: block; + position: relative; + } + + .controls { + position: fixed; + left: var(--page-padding); + top: var(--page-padding); + width: var(--sidebar-width); + height: calc(100% - (var(--page-padding) * 2)); + z-index: 100; + transform: translateX(0); + border-right: 2px solid #8fbc8f; + } + + .preview-container { + height: calc(100vh - 24px); + margin-left: calc(var(--sidebar-width) + var(--page-padding)); + width: calc(100% - var(--sidebar-width) - (var(--page-padding) * 2)); + } + + .container.sidebar-collapsed .controls { + transform: translateX(-100%); + } + + .container.sidebar-collapsed .preview-container { + margin-left: var(--page-padding); + width: calc(100% - (var(--page-padding) * 2)); + } + + .sidebar-toggle { + display: flex; + align-items: center; + justify-content: center; + position: fixed; + left: calc(var(--sidebar-width) + var(--page-padding)); + top: 50%; + transform: translateY(-50%); + z-index: 101; + width: 25px; + height: 50px; + background: #8fbc8f; + color: #000000; + border: 2px solid #8fbc8f; + border-radius: 0; + border-left: none; + cursor: pointer; + font-size: 18px; + font-weight: bold; + } + + .container.sidebar-collapsed .sidebar-toggle { + left: var(--page-padding); + } +} + +.panel { + background: #000000; + border: 2px solid #8fbc8f; + padding: 24px; +} + +.controls { + display: flex; + flex-direction: column; + gap: 18px; + overflow-y: auto; + scrollbar-width: none; + /* Firefox: hide scrollbar */ + -ms-overflow-style: none; + /* IE/Edge legacy */ + scrollbar-gutter: stable; + /* Prevent layout shift when scrollability changes */ + min-width: 0; +} + +/* Scrollbar - WebKit */ +.controls::-webkit-scrollbar { + width: 0; + /* Hide scrollbar in Chrome/Safari/Edge */ + height: 0; +} + +.controls::-webkit-scrollbar-track { + background: transparent; +} + +.controls::-webkit-scrollbar-thumb { + background: transparent; + border: none; +} + +.controls::-webkit-scrollbar-thumb:hover { + background: transparent; +} + +.upload-section { + display: flex; + flex-direction: column; + gap: 12px; +} + +.upload-section.drawer:not(.open) { + gap: 0; + padding-bottom: 0; +} + +/* Drawer styling */ +.drawer { + border: 1px solid #8fbc8f; + /* Always present, just controls visibility of content */ +} + +.drawer:not(.open) { + padding-bottom: 0; +} + +.drawer-header { + display: flex; + align-items: center; + gap: 10px; + cursor: pointer; + padding: 6px 6px; + user-select: none; + border-bottom: 1px solid #8fbc8f; + line-height: 1.2; +} + +.drawer-header .section-title { + margin-bottom: 0; +} + +.drawer-caret { + color: #8fbc8f; + font-size: 14px; + width: 14px; + display: inline-block; + transition: transform 0.2s ease; +} + +.drawer .drawer-content { + max-height: 0; + min-height: 0; + overflow: hidden; + transition: max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease; + opacity: 0; + width: 100%; + flex-shrink: 0; + box-sizing: border-box; + padding: 12px 12px 0px 12px; + /* Reduced bottom padding when closed */ +} + +.drawer.open .drawer-content { + max-height: 1000px; + opacity: 1; + padding: 12px; + /* Full padding when open */ +} + +/* Canvas width stays calculated for padding */ +.curve-canvas { + width: 100%; + height: 120px; + background: #000000; + cursor: crosshair; + border: 1px solid #8fbc8f; + display: block; +} + +.upload-box { + position: relative; + border: 2px solid #8fbc8f; + padding: 24px; + text-align: center; + cursor: pointer; + min-height: 120px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background: #000000; +} + +.upload-box:hover { + border-color: #7aa87a; + background: #0a0a0a; +} + +.upload-box.dragover { + border-color: #ff6b6b; + background: #0a0000; +} + +.upload-box.disabled { + border-color: #444; + background: #111; + color: #666; + cursor: not-allowed; +} + +.upload-box.disabled .upload-text, +.upload-box.disabled .upload-hint { + color: #666; +} + +.upload-box input { + position: absolute; + opacity: 0; + width: 100%; + height: 100%; + cursor: pointer; +} + +.upload-text { + color: #8fbc8f; + font-size: 14px; + margin-bottom: 8px; +} + +.upload-hint { + color: #7aa87a; + font-size: 12px; +} + +.preview-thumb { + max-width: 60px; + max-height: 60px; + margin-top: 8px; +} + +.control-group { + display: flex; + flex-direction: column; + gap: 5px; +} + +.control-group.drawer:not(.open) { + gap: 0; + padding-bottom: 0; +} + +.control-label { + color: #8fbc8f; + font-size: 14px; + font-weight: bold; +} + +.slider-container { + display: flex; + flex-direction: column; + gap: 8px; +} + +.slider-container.inline { + flex-direction: row; + align-items: center; + gap: 12px; +} + +.slider { + width: 100%; + height: 8px; + background: #000000; + border: 1px solid #8fbc8f; + outline: none; + appearance: none; + cursor: pointer; +} + +.slider:focus { + outline: none; +} + +.slider:hover { + background: #0a0a0a; +} + +.slider::-webkit-slider-runnable-track { + height: 8px; + background: #000000; + border: 1px solid #8fbc8f; +} + +.slider::-moz-range-track { + height: 8px; + background: #000000; + border: 1px solid #8fbc8f; +} + +.slider::-webkit-slider-thumb { + appearance: none; + width: 18px; + height: 18px; + background: #8fbc8f; + cursor: pointer; + border: 1px solid #000000; + margin-top: -5px; +} + +.slider::-moz-range-thumb { + width: 18px; + height: 18px; + background: #8fbc8f; + cursor: pointer; + border: 1px solid #000000; +} + +.slider-value { + color: #7aa87a; + font-size: 12px; + text-align: center; + font-weight: bold; + /* Add this */ +} + +.curves-container { + background: #000000; + border: 1px solid #8fbc8f; + padding: 16px; + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; +} + + +.preview-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + position: relative; + overflow: hidden; +} + +.loupe-overlay { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + pointer-events: none; +} + +.preview-canvas { + max-width: 100%; + max-height: 100%; + border: 2px solid #8fbc8f; + background: #000000; +} + +.preview-canvas.cursor-hidden { + cursor: none; +} + +.no-preview { + color: #7aa87a; + font-size: 18px; + text-align: center; +} + +.section-title { + color: #8fbc8f; + font-size: 16px; + font-weight: bold; + margin-bottom: 8px; +} + +.reset-btn { + background: #000000; + border: 1px solid #ff6b6b; + color: #ff6b6b; + padding: 8px 16px; + font-size: 12px; + cursor: pointer; + margin-top: 10px; + display: inline-block; +} + +.reset-btn:hover { + background: #0a0000; +} + +.action-btn { + background: #000000; + border: 1px solid #8fbc8f; + color: #8fbc8f; + padding: 10px 16px; + font-size: 12px; + cursor: pointer; +} + +.action-btn:hover { + background: #0a0a0a; +} + +/* Brutalist checkboxes */ +input[type="checkbox"] { + appearance: none; + width: 18px; + height: 18px; + border: 1px solid #8fbc8f; + background: #000000; + cursor: pointer; + vertical-align: middle; +} + +input[type="checkbox"]:hover { + background: #0a0a0a; +} + +input[type="checkbox"]:focus { + outline: 2px solid #8fbc8f; +} + +input[type="checkbox"]:checked { + background: #8fbc8f; + border-color: #8fbc8f; +} + +input[type="checkbox"]:checked::after { + content: "X"; + color: #000000; + font-weight: bold; + font-size: 12px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +/* Brutalist radio buttons */ +input[type="radio"] { + appearance: none; + width: 18px; + height: 18px; + border: 1px solid #8fbc8f; + background: #000000; + cursor: pointer; + vertical-align: middle; + border-radius: 0; + /* Keep square for brutalist look */ +} + +input[type="radio"]:hover { + background: #0a0a0a; +} + +input[type="radio"]:focus { + outline: 2px solid #8fbc8f; +} + +input[type="radio"]:checked { + background: #8fbc8f; + border-color: #8fbc8f; +} + +input[type="radio"]:checked::after { + content: "●"; + color: #000000; + font-weight: bold; + font-size: 12px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + line-height: 1; +} + +/* Brutalist text inputs */ +input[type="text"] { + background: #000000; + border: 1px solid #8fbc8f; + color: #8fbc8f; + padding: 4px 8px; + font-family: inherit; + font-size: 12px; + width: 100%; + box-sizing: border-box; +} + +input[type="text"]:hover { + background: #0a0a0a; +} + +input[type="text"]:focus { + outline: 2px solid #8fbc8f; + background: #0a0a0a; +} + +input[type="text"]::placeholder { + color: #7aa87a; + opacity: 0.7; +} + +/* Brutalist number inputs */ +input[type="number"] { + appearance: none; + background: #000000; + border: 1px solid #8fbc8f; + color: #8fbc8f; + padding: 4px 8px; + font-family: inherit; + font-size: 12px; + text-transform: uppercase; + font-weight: bold; +} + +input[type="number"]:hover { + background: #0a0a0a; +} + +input[type="number"]:focus { + outline: 2px solid #8fbc8f; + background: #0a0a0a; +} + +/* Hide default number input spinners and replace with brutalist ones */ +input[type="number"]::-webkit-outer-spin-button, +input[type="number"]::-webkit-inner-spin-button { + appearance: none; + margin: 0; +} + +/* Firefox */ +input[type="number"] { + -moz-appearance: textfield; + appearance: textfield; +} + +/* We'll use a wrapper approach for the spinner buttons */ +.number-input-wrapper { + position: relative; + display: inline-block; +} + +.number-input-wrapper input[type="number"] { + padding-right: 25px; + /* Make room for buttons */ + position: relative; + width: 100%; + box-sizing: border-box; + height: auto; + min-height: 24px; +} + +.number-spinner { + position: absolute; + right: 0; + top: 1px; + bottom: 1px; + width: 20px; + display: flex; + flex-direction: column; + border-left: 1px solid #8fbc8f; +} + +.number-spinner-btn { + flex: 1; + background: #8fbc8f; + border: none; + color: #000000; + cursor: pointer; + font-size: 8px; + font-weight: bold; + display: flex; + align-items: center; + justify-content: center; + user-select: none; +} + +.number-spinner-btn:first-child { + border-bottom: 1px solid #000000; +} + +.number-spinner-btn:hover { + background: #7aa87a; +} + +.number-spinner-btn:active { + background: #6a9a6a; +} + +/* Ensure buttons also get uppercase text */ +button { + text-transform: uppercase; + font: inherit; + font-weight: bold; +} + +/* Label styling for radio buttons */ +label { + color: #8fbc8f; + cursor: pointer; + user-select: none; +} + +/* Rotate caret when drawer is open */ +.drawer.open .drawer-caret { + transform: rotate(120deg); +}