Get Started - Deploy Surgical Instrument#
This is a deployment guide for the three-service Docker stack:
surgical-backend(Flask 3 + Ultralytics + OpenVINO bootstrap)surgical-pipeline(GStreamer + DL Streamer runtime)surgical-ui(nginx + React SPA)
Prerequisites#
Before you start, refer to System Requirements to confirm your setup compatibility.
If you work behind a proxy, see how to configure the app for a proxy.
The application does not ship with the medical dataset, trained model binaries, or demo videos. You will need to collect these resources separately, as described in the following steps, and store them locally. This keeps the demo reproducible without any dependency on a specific machine.
1. Clone the repo and download the dataset#
Open PowerShell and run:
git clone --filter=blob:none --sparse https://github.com/open-edge-platform/edge-ai-suites.git `
; cd edge-ai-suites `
; git sparse-checkout set health-and-life-sciences-ai-suite/Surgical_Instrument `
; cd health-and-life-sciences-ai-suite/Surgical_Instrument
Download the dataset and place it in the following local folder for datasets
(create it if it does not exist), for example:
Surgical_Instrument/datasets/ColonDB/raw/.
Archive support:
.zip,.tar,.tar.gz,.tgzcan be consumed directly..rarmust be extracted locally.
The bootstrap will now auto-detect images + masks on the first launch, convert binary
masks to YOLO bounding-box labels, split 70/15/15, and write data.yaml.
2. Train and export the model#
Once the dataset is in the right place, the repo script will download the YOLO11n base model, train it, export it, and cache the final OpenVINO IR.
Model references:
YOLO11n model family: https://docs.ultralytics.com/models/yolo11/
Ultralytics detect task: https://docs.ultralytics.com/tasks/detect/
Base weights (~5.4 MB, downloaded automatically during
make backend-bootstrap): https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo11n.pt
If you already have a trained IR, you can seed it into models/ and skip the training
entirely. The presence of both models/yolo11n_polyp/best_openvino_model/best.xml and
models/yolo11n_polyp/.trained_ok short-circuits the bootstrap to ready in seconds.
make assets # copies best.xml + best.bin from poc/st2_app, if present
2.1 Optional: pre-place yolo11n.pt when GitHub is blocked#
If downloading yolo11n.pt fails during bootstrap (for example, timeout / curl 28),
pre-download it on a machine with GitHub access and copy it into the backend cache:
# On a machine that can access GitHub:
curl -L -o yolo11n.pt \
https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo11n.pt
# On the target host (container must exist from at least one `make up`):
docker cp yolo11n.pt surgical-backend:/cache/weights/yolo11n.pt
docker restart surgical-backend
The backend checks ${CACHE_DIR}/weights/<model_name>.pt (default
/cache/weights/yolo11n.pt) before fallback to auto-download.
2.2 Optional: pre-train and export model before make up#
You can prepare the model artifacts ahead of time:
make backend-venv # one-time Python environment: torch+xpu, Ultralytics, OpenVINO
make backend-bootstrap # prepares ColonDB, trains YOLO11n, exports FP16 OpenVINO IR
backend-bootstrap does the full model preparation in sequence:
reads the dataset from
datasets/ColonDB/raw/extracts the archive if needed
creates the train/validation/test split
downloads the base
yolo11n.ptweights through Ultralyticstrains YOLO11n using the settings in
backend/config/model.yamlexports the best checkpoint to FP16 OpenVINO IR
writes the
.trained_okcache marker
Expected output layout:
models/yolo11n_polyp/
├── .trained_ok
└── best_openvino_model/
├── best.xml
├── best.bin
└── metadata.yaml
2.3 Optional: train elsewhere and import the IR#
If you have trained YOLO11n on ColonDB on another machine:
Export the checkpoint to FP16 OpenVINO IR:
yolo export model=/path/to/best.pt format=openvino half=True imgsz=640
Copy the result into this exact layout:
Surgical_Instrument/models/yolo11n_polyp/ ├── .trained_ok └── best_openvino_model/ ├── best.xml ├── best.bin └── metadata.yamlbest.xmlandbest.binare required.metadata.yamlis recommended because DL Streamer/OpenVINO can use the exported Ultralytics metadata.Create the
.trained_okcache marker (checked bymake upand the backend):mkdir -p models/yolo11n_polyp/best_openvino_model date -Is > models/yolo11n_polyp/.trained_ok
3. Generate demo video (required)#
Fresh clones do not include videos/polyp_test.mp4. Generate it before
running make doctor / make up.
.venv-backend/bin/python scripts/create_endoscopy_video.py \
--images-dir datasets/ColonDB/raw/ColonDB/images \
--output videos/polyp_test.mp4 \
--seconds 60 --fps 60 --width 1920 --height 1080
The script creates or overwrites videos/polyp_test.mp4 using an H.264-compatible codec
(required by the default file-source pipeline). It tries OpenCV first, then falls back to
system ffmpeg (libx264) when needed.
4. Bring the stack up#
Before bringing the stack up, run the preflight check:
make doctor # checks Docker, accelerators, cached IR, demo video, and port 8080
Then discover the camera serial and the P-core set for your CPU:
make list-cameras # prints Basler serial(s) -> SOURCE_ARG
make show-cores # prints the P-core set -> PIPELINE_GST_CORES
make up supports two image sources, controlled by the REGISTRY flag.
4a. Pull images from registry (default)#
REGISTRY=true (the default) pulls the prebuilt images at TAG (default latest) and
starts them with RENDER_GID / VIDEO_GID auto-detected from the host — no local build
needed.
# Live Basler camera, tuned for fixed exposure + GPU detection with a live window.
make up SOURCE_KIND=basler SOURCE_ARG=<SERIAL_NUMBER> \
BASLER_PIXEL_FORMAT=ycbcr422_8 BASLER_FIXED_CAMERA=1 BASLER_EXPOSURE_US=5000 \
BASLER_THROUGHPUT_LIMIT=400000000 DETECT=1 DETECTION_DEVICE=GPU \
AUTOVIDEOSINK=true PIPELINE_VIDEO_SINK=autovideosink
# Minimal live Basler camera (P-core pinned, free-running sink).
make up SOURCE_KIND=basler SOURCE_ARG=<SERIAL_NUMBER> \
PIPELINE_GST_CORES=<P_CORES> PIPELINE_SINK_SYNC=false
# Default file source
make up # trains YOLO11n on first boot if IR is not cached
make logs # optional: follow readiness/startup logs
4b. Build images from source#
REGISTRY=false builds every image locally from its Dockerfile (backend = torch+xpu wheels +
OpenVINO + Ultralytics, UI = Vite build → nginx, pipeline = DL Streamer + gencamsrc).
# Live Basler camera, tuned and built from source
make up SOURCE_KIND=basler SOURCE_ARG=<SERIAL_NUMBER> \
BASLER_PIXEL_FORMAT=ycbcr422_8 BASLER_FIXED_CAMERA=1 BASLER_EXPOSURE_US=5000 \
BASLER_THROUGHPUT_LIMIT=400000000 DETECT=1 DETECTION_DEVICE=GPU \
AUTOVIDEOSINK=true PIPELINE_VIDEO_SINK=autovideosink REGISTRY=false
# Minimal live Basler camera, built from source
make up SOURCE_KIND=basler SOURCE_ARG=<SERIAL_NUMBER> \
PIPELINE_GST_CORES=<P_CORES> PIPELINE_SINK_SYNC=false REGISTRY=false
# Default file source, built from source
make up REGISTRY=false # trains YOLO11n on first boot if IR is not cached
make logs # optional: follow readiness/startup logs
The surgical-ui service declares depends_on: surgical-backend: condition: service_healthy,
so it will not start listening on :8080 until the backend passes its /api/readiness
HEALTHCHECK. The backend healthcheck uses a 45-minute start_period to absorb first-boot
training.
Follow first-boot progress#
make logs
Expect to see the FSM walk through:
[boot] state=initializing
[boot] state=checking_cache
[boot] state=downloading_dataset (skipped if raw/ already populated)
[boot] state=preparing_dataset
[boot] state=downloading_weights (~5 MB yolo11n.pt)
[boot] state=training (~15-25 min, ~50 epochs)
[boot] state=exporting (Ultralytics → OpenVINO IR)
[boot] state=ready
[server] READY
5. Open the UI#
Note that the UI is health-gated on the backend -
the browser tab will not answer until surgical-backend reports /api/readiness → ready.
On the first boot this time window is 20–35 minutes while YOLO11n trains (an estimate for
ColonDB on the Intel® Arc™ iGPU).
Subsequent boots take seconds because the trained IR is cached in ./models/.
Once the backend is healthy the UI starts and answers on http://localhost:8080
(you can override it with make up UI_HOST_PORT=9090).
make up and make run also print the LAN URL so you can access it from the same network.
Use the left Config accordion to pick source (file or basler), source argument,
and device, then click Start to kick off inference.
The right-side KPI blocks begin populating within ~1 second.
See more info in User Interface.
For subsequent boots (IR already cached):
make run
make run requires cached IR files and .trained_ok. If they are missing,
prepare or seed the model artifacts first.
make up and make run auto-detect /dev/video* devices and layer in a
compose override so cameras are available to the pipeline container.
For runtime adjustments — source and device selection via the Settings modal, video upload, and API endpoints for scripting — see Runtime Configuration.
6. Stop / clean up#
If you want to stop or restart the app, you have the following options:
make down # stop + remove containers, keep volumes + IR
make clean # also drop the surgical-cache named volume + built images
The trained IR model under ./models/ is a bind-mount and survives make clean.
Delete it manually to force a full re-train on next boot:
rm -rf models/yolo11n_polyp
Common overrides#
Variable |
Default |
Meaning |
|---|---|---|
|
|
Only host-published port. |
|
|
Set to |
|
auto |
Override if the host has non-standard render/video group IDs. |
Example: run the whole stack CPU-only on port 9000:
make up UI_HOST_PORT=9000 DETECTION_DEVICE=cpu
Next: pipeline tuning#
The DL Streamer pipeline can be tuned at launch time with environment
variables passed to make up, including:
SOURCE_KINDDETECTWATERMARKMINIMALSCHEDULING_POLICYBATCH_SIZEAUTOVIDEOSINK
Three common shapes are used in practice:
File preview
Minimal live camera
Tuned live inference
Basler camera tuning knobs#
For live Basler sources (SOURCE_KIND=basler), the following variables tune the
camera and the display sink. See the tuned command in
4a. Pull images from registry for a full example.
Variable |
Default |
Meaning |
|---|---|---|
|
|
Camera pixel format (e.g. |
|
|
Set to |
|
unset |
Fixed exposure time in microseconds (applied only when |
|
|
|
|
|
Set to |
|
unset |
|
|
|
GStreamer sink element (e.g. |