4. Python environment setup

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.

If a vendor SDK ships as a wheel outside PyPI, host it in an internal package index or a 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.

Measurement Automation Environment & Data Flow Isolated Environment .venv (Python 3.11) requirements.txt (pinned) pip-sync → exact versions vendor wheels in vendor/ pyproject.toml (editable) .python-version pinned Reproducible interpreter Configuration (YAML) test_id, vehicle camera serials + intrinsics baseline_mm = 320.0 ROI polygon thresholds (mm) Pydantic validation Parameters as data src/deform Package io/camera.py, pointcloud.py calibration/stereo.py measurement/intrusion.py measurement/deformation.py cli.py entry point tests/ + fixtures/ Importable, testable Measurement Pipeline (python -m deform.cli measure) raw capture → stereo rectify → triangulate → ROI mask → intrusion / deformation results.parquet per-point mm values intrusion, deformation summary.json max values vs thresholds pass / fail verdict run.log resolved versions config hash, timestamps

Practical Checklist Before First Measurement Run

ItemCheckFailure Symptom
Interpretersys.executable points inside .venvVendor SDK import fails
Lock filepip-sync exits 0Version conflict warning
Config schemaPydantic loads YAML without errorUnit mismatch, wrong baseline
Calibration files.npz paths resolveTriangulation returns NaN
Smoke testpytest tests/ -q passesRegression in ROI or threshold logic
Output dirsdata/processed/<test_id>/ writableSilent 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.