A visual physics engine for extracting real-world scientific measurements from a static 2D image.
PhysiLens lets you upload a photograph, calibrate it against a reference object of known size, isolate a subject with AI-powered segmentation, and receive a scientific report covering that subject's real-world dimensions, distance, and — where motion is visible — kinematics.
- Spatial calibration — Draw a line over a reference object of known real-world height (a person, a doorway, a tire) and enter its length. PhysiLens derives a meters-per-pixel scale factor from that single reference.
- AI perception — Segment Anything (SAM) isolates your chosen subject with a single click. Depth-Anything-V2 estimates relative scene depth for Z-axis context.
- Deterministic physics — Every physical quantity in the final report (height, width, surface area, volume, speed, mass, kinetic energy) is computed with closed-form mathematics and least-squares regression — never guessed by a model. See
engine/core/physics.pyfor the full derivations. - Scientific report — Results are compiled into a shareable PDF report, generated entirely client-side.
PhysiLens/
├── engine/ FastAPI backend — image processing & physics
│ ├── main.py API routes, session handling, validation
│ ├── schemas.py Pydantic request/response contracts
│ ├── config.py Environment-driven settings
│ ├── core/
│ │ ├── physics.py Pure deterministic math (no AI)
│ │ └── analyzer.py SAM + Depth-Anything-V2 wrappers (lazy-loaded)
│ └── utils/
│ └── report_gen.py Report assembly / confidence scoring
│
└── interface/ Next.js 16 frontend
├── app/ App Router pages & layout
├── components/
│ ├── AnalysisDashboard.tsx Workflow orchestration
│ ├── CanvasOverlay.tsx Interactive measurement canvas
│ ├── MetricsPanel.tsx Live data readout
│ ├── ReportModal.tsx Report preview
│ └── ExportButton.tsx PDF export trigger
└── lib/
├── utils.ts API client, formatters
├── types.ts Shared TypeScript contracts
└── export-pdf.ts jsPDF report generation
Design principle: the AI models (SAM, Depth-Anything-V2) are used strictly for perception — "where is the subject" and "what's the relative depth." Every physical number reported to the user is then derived by explicit, auditable formulas in physics.py, never inferred directly by a model. This keeps results reproducible and explainable.
- Backend: Python 3.11+, a CUDA-capable GPU recommended (CPU inference works but is slow for SAM/Depth-Anything)
- Frontend: Node.js 20+, pnpm 9+
cd engine
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env # then edit values as neededDownload a SAM checkpoint (the vit_b variant is the smallest/fastest) from the official repository and place it at the path referenced by PHYSILENS_SAM_CHECKPOINT in your .env (default: engine/weights/sam_vit_b_01ec64.pth).
uvicorn main:app --reload --port 8000The API will be available at http://localhost:8000, with interactive docs at /docs.
cd interface
pnpm install
cp .env.example .env.local # then edit NEXT_PUBLIC_ENGINE_API_URL if needed
pnpm devThe app will be available at http://localhost:3000.
SAM and Depth-Anything-V2 are lazy-loaded: they're only loaded into memory the first time /api/segment or /api/report is called, not at server startup. This keeps the server lightweight to start, but means the first subject-selection click or report generation after a fresh server start can take anywhere from several seconds to over a minute (model weights + inference on CPU are slow). The frontend surfaces this explicitly with a "loading model, first run only" message so it isn't mistaken for the click doing nothing.
Two ways to avoid this surprising a user in production:
-
Call
/api/warmupright after starting the server (e.g. in a deploy hook,docker-composehealthcheck script, or CI smoke test):curl -X POST http://localhost:8000/api/warmup
This forces both models to load immediately and returns their status, so you control when the cold-load cost happens.
-
Set
PHYSILENS_WARMUP_ON_STARTUP=truein your.envto load both models automatically during server startup, before the app starts accepting requests. This delays server readiness by the full model-load time — make sure your container orchestrator's startup/readiness probes account for that (a generousstart_periodin the DockerfileHEALTHCHECK, or an equivalent readiness delay in Kubernetes).
- CORS on the backend is restricted to an explicit origin allow-list (
PHYSILENS_CORS_ORIGINS_RAW) — never a wildcard. - Uploaded images are validated for content-type, byte size, and pixel dimensions before any processing occurs.
- Analysis sessions live in memory with a configurable TTL (
PHYSILENS_SESSION_TTL_SECONDS) and are never persisted to disk. - No secrets are hardcoded anywhere in this repository; all configuration is environment-driven. Copy the provided
.env.examplefiles and fill in your own values locally. - The frontend sets
X-Frame-Options,X-Content-Type-Options, and a restrictivePermissions-Policyon every response.
Before deploying, review engine/config.py and set PHYSILENS_ENABLE_DOCS=false and PHYSILENS_DEBUG=false in production environments.
The in-memory SessionStore in engine/main.py is intentionally minimal (a get/set/update interface) so it can be swapped for a Redis-backed implementation in horizontally-scaled deployments without touching route logic.
PhysiLens produces estimates, not certified measurements. Accuracy depends entirely on:
- The precision of the user-drawn calibration line and the accuracy of the entered reference length,
- Monocular depth estimation being inherently relative (absolute distance requires an explicit reference anchor),
- Published average densities/masses being used as fallbacks when an object's specific material properties are unknown.
Every generated report includes an explicit methodology and disclaimers section.
Released under the MIT License.
Issues and pull requests are welcome. Please ensure any contribution touching engine/core/physics.py includes the underlying derivation or citation for the formula used — this module intentionally has no "black box" math.
Created and maintained by BZDevelopments (@BZDevelopments) — BzDev.