4. Python environment setup
Vehicle intrusion and deformation measurement automation typically combines several fragile dependencies: camera SDKs, LiDAR drivers, photogrammetry libraries, numerical stacks, and file-format parsers. A measurement script that runs on one engineer's workstation may fail on a test cell PC because OpenCV was upgraded, a vendor SDK was installed globally, or NumPy resolved to a different build. This chapter establishes a reproducible Python environment and a project layout that keeps measurement code, configuration, and raw data separated and version-controlled.
Why a Virtual Environment Is Non-Negotiable
A virtual environment isolates the interpreter and site-packages for a single project. For deformation measurement, this matters because:
- Vendor SDKs (e.g., a stereo camera API) often pin specific NumPy or OpenCV versions.
- Photogrammetry and point-cloud libraries may conflict on compiled dependencies.
- Measurement results must be reproducible months later during a crash investigation or homologation audit.
Use venv for a lightweight, standard-library solution, or conda/mamba when you need non-Python binaries such as GDAL or specific BLAS builds. For most vehicle measurement projects, venv plus pip is sufficient and easier to audit.
# Create and activate a virtual environment (Windows PowerShell)
py -3.11 -m venv .venv
.\.venv\Scripts\Activate.ps1
# Linux / macOS
python3.11 -m venv .venv
source .venv/bin/activate
# Confirm the interpreter is isolated
python -c "import sys; print(sys.executable)"
Pin the Python version explicitly (3.10–3.12 are safe choices for the scientific stack). Record it in .python-version or a README so every operator uses the same interpreter.
Dependency Management with Pinned Requirements
Two files are maintained: requirements.in for human-edited top-level dependencies, and requirements.txt for the fully resolved, pinned lock file. Use pip-tools to generate the lock file.
# requirements.in
numpy==1.26.4
opencv-python==4.9.0.80
open3d==0.18.0
pyarrow==15.0.2
pydantic==2.6.4
pyyaml==6.0.1
pytest==8.1.1
# Generate the lock file
pip install pip-tools
pip-compile requirements.in --output-file requirements.txt
pip-sync requirements.txt
For measurement work, always pin exact versions (==), never ranges. A minor OpenCV upgrade can change corner-detection subpixel behavior and shift deformation readings by fractions of a millimeter — enough to invalidate a comparison against a baseline.
vendor/ directory and reference it with a relative path in requirements.in, e.g. ./vendor/camera_sdk-2.4.1-py3-none-any.whl.Recommended Project Structure
The layout below separates importable code, configuration, raw captures, and derived results. Raw measurement data is never committed to Git; only code, configs, and small fixtures are.
vehicle-deform/
├── .venv/
├── .python-version
├── pyproject.toml
├── requirements.in
├── requirements.txt
├── README.md
├── configs/
│ ├── baseline.yaml
│ └── test_2024-06-12.yaml
├── src/
│ └── deform/
│ ├── __init__.py
│ ├── io/
│ │ ├── camera.py
│ │ └── pointcloud.py
│ ├── calibration/
│ │ └── stereo.py
│ ├── measurement/
│ │ ├── intrusion.py
│ │ └── deformation.py
│ └── cli.py
├── data/
│ ├── raw/ # gitignored
│ ├── interim/ # gitignored
│ └── processed/ # gitignored
├── tests/
│ ├── test_intrusion.py
│ └── fixtures/
└── notebooks/
└── exploratory.ipynb
Install the package in editable mode so imports resolve consistently:
pip install -e .
# pyproject.toml excerpt
[project]
name = "deform"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = ["numpy", "opencv-python", "open3d", "pyarrow", "pydantic", "pyyaml"]
Configuration as Data, Not Code
Measurement parameters — camera intrinsics, baseline distance, ROI polygons, deformation thresholds — belong in YAML, validated by Pydantic. This lets a test engineer adjust a threshold without editing Python.
# configs/test_2024-06-12.yaml
test_id: "2024-06-12_A"
vehicle: "sedan_prototype_03"
cameras:
left: {serial: "CAM-L-0417", intrinsics: "calib/left_2024-06-10.npz"}
right: {serial: "CAM-R-0418", intrinsics: "calib/right_2024-06-10.npz"}
baseline_mm: 320.0
roi:
polygon: [[120, 340], [880, 340], [880, 720], [120, 720]]
thresholds:
intrusion_mm: 5.0
deformation_mm: 2.0
from pydantic import BaseModel
from pathlib import Path
import yaml
class Thresholds(BaseModel):
intrusion_mm: float
deformation_mm: float
class TestConfig(BaseModel):
test_id: str
vehicle: str
baseline_mm: float
thresholds: Thresholds
def load_config(path: Path) -> TestConfig:
with path.open() as f:
return TestConfig(**yaml.safe_load(f))
Validation catches unit mistakes early: if someone writes baseline_mm: 0.32 intending meters, the downstream triangulation will produce obviously wrong depths, and the schema makes the unit explicit.
Reproducibility and Verification
After setup, verify the environment with a smoke test that exercises the actual measurement path on a small fixture, not just imports:
pytest tests/ -q
python -m deform.cli measure --config configs/test_2024-06-12.yaml \
--input data/raw/2024-06-12_A/ --output data/processed/2024-06-12_A/
Expected observable outputs: a results.parquet with per-point intrusion and deformation values, a summary.json containing max intrusion, max deformation, and pass/fail against thresholds, and a run.log recording the resolved dependency versions. If pip-sync reports a conflict, the lock file is stale — regenerate it rather than installing ad hoc. If the smoke test fails with an ImportError for a vendor module, confirm the wheel was installed into the active .venv and not the system interpreter.
Practical Checklist Before First Measurement Run
| Item | Check | Failure Symptom |
|---|---|---|
| Interpreter | sys.executable points inside .venv | Vendor SDK import fails |
| Lock file | pip-sync exits 0 | Version conflict warning |
| Config schema | Pydantic loads YAML without error | Unit mismatch, wrong baseline |
| Calibration files | .npz paths resolve | Triangulation returns NaN |
| Smoke test | pytest tests/ -q passes | Regression in ROI or threshold logic |
| Output dirs | data/processed/<test_id>/ writable | Silent write failure |
With the environment pinned, configuration externalized, and the package installed in editable mode, the measurement pipeline becomes a single reproducible command. The next step is to populate src/deform/io/ with the actual capture readers and confirm that a known-good fixture produces the expected intrusion and deformation values before any vehicle test is run.