146 lines
5.9 KiB
Markdown
146 lines
5.9 KiB
Markdown
# Train Measurement System - Developer Guide
|
|
|
|
## Architecture Overview
|
|
|
|
This is a **configuration-driven, plugin-based point cloud measurement system** for train inspection. The core design principle: measurements are defined in YAML, not hardcoded in Python.
|
|
|
|
### Key Design Decisions
|
|
|
|
**Why configuration-driven?**
|
|
- Users need to measure 20+ different features per train scan
|
|
- Measurement locations are consistent across scans but vary by train model
|
|
- Non-programmers should be able to add new measurement tasks by editing YAML
|
|
|
|
**Why plugin-based measurement types?**
|
|
- Three fundamentally different measurement algorithms: plane-to-plane, point-to-plane, point-to-point
|
|
- Each type has different input requirements (ROI bboxes vs exact coordinates)
|
|
- Easy to add new types (e.g., circle-to-circle) without modifying core logic
|
|
|
|
**Why single large point cloud input?**
|
|
- Train scans are 100M points covering the entire vehicle
|
|
- ROI extraction happens at runtime based on bbox parameters
|
|
- Avoids pre-segmentation and maintains spatial context
|
|
|
|
## Common Development Tasks
|
|
|
|
### Running measurements
|
|
```bash
|
|
conda activate pcc-experiment
|
|
cd train_measurement_system
|
|
python main.py
|
|
```
|
|
|
|
Results: `results/measurement_results_YYYYMMDD_HHMMSS.xlsx`
|
|
Visualizations: `visualizations/*.ply`
|
|
|
|
### Adding a new measurement task
|
|
|
|
1. **Edit `config/measurements.yaml`** - add task definition with appropriate type
|
|
2. **Set `enabled: true`** and provide required parameters for that type
|
|
3. **Run `python main.py`** - the task will execute automatically
|
|
4. **Check Excel results** - verify mean, std dev, inlier ratio
|
|
5. **Inspect PLY files** - confirm ROI extraction is correct
|
|
|
|
### Adding a new measurement type
|
|
|
|
1. **Create `src/measurements/your_type.py`** inheriting from `BaseMeasurement`
|
|
2. **Implement `execute() -> MeasurementResult`** with your algorithm
|
|
3. **Register in `src/measurements/__init__.py`** - add to `__all__`
|
|
4. **Update `src/task_manager.py`** - add branch in `_create_measurement_task()`
|
|
5. **Document in README.md** - add config example and parameter descriptions
|
|
|
|
## Architecture Flow
|
|
|
|
```
|
|
YAML Config → TaskManager → [Measurement Plugin] → Excel + PLY
|
|
↓
|
|
PointCloudLoader (cached)
|
|
↓
|
|
ROIExtractor / PlaneFitter (shared utilities)
|
|
```
|
|
|
|
**TaskManager** orchestrates everything:
|
|
- Loads point cloud once, caches in memory
|
|
- Iterates through enabled tasks
|
|
- Instantiates appropriate measurement class based on `type` field
|
|
- Collects results and delegates to ExcelExporter
|
|
|
|
**Measurement plugins** are stateless:
|
|
- Receive full point cloud + task config in constructor
|
|
- Extract ROI, run algorithm, return `MeasurementResult`
|
|
- No shared state between tasks
|
|
|
|
## Critical Implementation Details
|
|
|
|
### Coordinate System
|
|
- Point cloud uses **global coordinates** (mm units)
|
|
- Z-axis is vertical (up positive)
|
|
- ROI bboxes are in global coordinates
|
|
- No coordinate transformations applied during extraction
|
|
|
|
### RANSAC Plane Fitting
|
|
- `inlier_ratio` in results indicates ROI quality, not measurement accuracy
|
|
- 100% inlier ratio = ROI perfectly contains only the target plane
|
|
- Low inlier ratio (<70%) = ROI includes multiple surfaces or noise
|
|
- **Automatic downsampling**: ROIs >10k points are sampled to 10k before SVD fitting (prevents memory explosion)
|
|
|
|
### Point-to-Plane Distance
|
|
- Two modes: `exact_points` (KDTree nearest neighbor) vs `bbox_filtered` (extract then filter)
|
|
- `exact_points` is faster but requires knowing precise coordinates
|
|
- `bbox_filtered` supports filters like `max_abs_coordinate` for finding extreme points
|
|
|
|
### Plane-to-Plane Distance
|
|
- **Unidirectional sampling**: samples points from source plane, measures to target plane
|
|
- Not symmetric: swapping source/target may give different results if planes aren't parallel
|
|
- Standard deviation indicates parallelism quality
|
|
|
|
## Testing Strategy
|
|
|
|
**When modifying measurement algorithms:**
|
|
1. Disable all tasks except one test case (`enabled: false`)
|
|
2. Run and verify Excel output matches expected values
|
|
3. Open PLY files in CloudCompare/MeshLab to visually verify ROI extraction
|
|
4. Check console output for warnings (low inlier ratio, insufficient points)
|
|
|
|
**When adding new measurement types:**
|
|
1. Start with a simple test case with known ground truth
|
|
2. Verify the `MeasurementResult.data` dictionary contains all required fields
|
|
3. Ensure visualization saves the relevant points (not the entire ROI)
|
|
4. Test with edge cases: empty ROI, single point, coplanar points
|
|
|
|
## Common Pitfalls
|
|
|
|
- **Forgetting to set `enabled: true`** - task silently skipped
|
|
- **Wrong coordinate order** - YAML uses `[x, y, z]`, some tools use `[z, y, x]`
|
|
- **Bbox too large** - includes multiple surfaces, low inlier ratio
|
|
- **Bbox too small** - insufficient points for robust fitting
|
|
- **Missing `visualization_dir`** - no PLY files generated (optional but recommended)
|
|
- **Modifying cached point cloud** - reload by restarting Python process
|
|
|
|
## Performance Notes
|
|
|
|
- Point cloud loading: ~10-30 seconds for 100M points (cached after first load)
|
|
- Single plane-to-plane task: ~5-10 seconds (ROI extraction + RANSAC + sampling)
|
|
- Bottleneck is usually RANSAC iterations, not point cloud I/O
|
|
- Visualization (PLY save) adds ~1-2 seconds per task
|
|
|
|
## Extension Points
|
|
|
|
**Want to add CAD-based ROI extraction?**
|
|
- Modify `roi_extractor.py` to accept plane parameters from CAD
|
|
- Keep bbox-based extraction as fallback
|
|
- Add `roi_source: "cad"` vs `"bbox"` in config
|
|
|
|
**Want to add statistical outlier removal?**
|
|
- Add preprocessing step in measurement plugins before RANSAC
|
|
- Make it optional via `params.remove_outliers: true`
|
|
|
|
**Want to support multiple point cloud files?**
|
|
- Modify `task_manager.py` to accept per-task `pointcloud_file` override
|
|
- Update `pointcloud_loader.py` to cache multiple files by path
|
|
|
|
**Want to add 2D projections or cross-sections?**
|
|
- Create new measurement type `cross_section_profile`
|
|
- Use `roi_extractor` to get 3D points, then project to 2D plane
|
|
- Output profile data as additional sheet in Excel
|