Upload standalone NLOS model, inference code, and verified package
Browse files- .gitattributes +0 -34
- .gitignore +4 -0
- README.md +157 -1
- SHA256SUMS +10 -0
- best.pt +3 -0
- config.json +19 -0
- inference.py +64 -0
- model.py +148 -0
- preprocess_stats.json +7 -0
- requirements.txt +4 -0
- verification.json +16 -0
.gitattributes
CHANGED
|
@@ -1,35 +1 @@
|
|
| 1 |
-
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitignore
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.pyc
|
| 3 |
+
.venv/
|
| 4 |
+
.cache/
|
README.md
CHANGED
|
@@ -1,3 +1,159 @@
|
|
| 1 |
---
|
| 2 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
library_name: pytorch
|
| 3 |
+
tags:
|
| 4 |
+
- lidar
|
| 5 |
+
- nlos
|
| 6 |
+
- binary-classification
|
| 7 |
+
- histogram
|
| 8 |
+
- pytorch
|
| 9 |
+
datasets:
|
| 10 |
+
- PeizhaoEdinburgh/Occlusion_Based_NLOS
|
| 11 |
---
|
| 12 |
+
|
| 13 |
+
# Occlusion-Based NLOS Model
|
| 14 |
+
|
| 15 |
+
Standalone PyTorch implementation and checkpoint for binary classification of
|
| 16 |
+
32 × 32 × 64 histograms. This package includes the complete inference architecture,
|
| 17 |
+
weights, and saved preprocessing statistics. It does not require the original
|
| 18 |
+
`lidarx` repository and is not a Transformers `AutoModel` package.
|
| 19 |
+
|
| 20 |
+
## Model
|
| 21 |
+
|
| 22 |
+
- Architecture: `HistCoarseMultiScaleMLP`; 549,641 parameters.
|
| 23 |
+
- Multiscale coarse histogram projections (32×8×8, 16×8×8, 32×4×4), global
|
| 24 |
+
statistics, temporal statistics, and spatial projections.
|
| 25 |
+
- Batch normalization, a 128-dimensional projection, gated residual layers,
|
| 26 |
+
and a 128→64→1 classification head; SiLU activation and dropout 0.2.
|
| 27 |
+
- Output: one logit; sigmoid produces the positive-class score.
|
| 28 |
+
- Threshold: 0.5. Class 0 is negative; class 1 is positive.
|
| 29 |
+
- `best.pt` contains the original state dictionary, training configuration,
|
| 30 |
+
preprocessing statistics, selected epoch, and validation loss.
|
| 31 |
+
|
| 32 |
+
The precise physical definition of positive/negative and histogram timing/range
|
| 33 |
+
units require author documentation. This package preserves the original labels.
|
| 34 |
+
|
| 35 |
+
## Files
|
| 36 |
+
|
| 37 |
+
| File | Purpose |
|
| 38 |
+
|---|---|
|
| 39 |
+
| `best.pt` | Original checkpoint, unchanged |
|
| 40 |
+
| `model.py` | Architecture and feature extraction |
|
| 41 |
+
| `inference.py` | Input validation, preprocessing, model loading, and prediction CLI |
|
| 42 |
+
| `config.json` | Inference configuration and class labels |
|
| 43 |
+
| `preprocess_stats.json` | Saved normalization statistics |
|
| 44 |
+
| `requirements.txt` | Runtime dependencies |
|
| 45 |
+
| `verification.json` | Original-versus-standalone parity results and tested versions |
|
| 46 |
+
| `SHA256SUMS` | Integrity hashes for package files |
|
| 47 |
+
|
| 48 |
+
## Local inference
|
| 49 |
+
|
| 50 |
+
Use Python 3.10 or newer. Install a PyTorch build suitable for your hardware if
|
| 51 |
+
needed, then install the remaining requirements:
|
| 52 |
+
|
| 53 |
+
```bash
|
| 54 |
+
python -m pip install -r requirements.txt
|
| 55 |
+
python inference.py /path/to/sample.h5
|
| 56 |
+
# Optional CUDA inference:
|
| 57 |
+
python inference.py /path/to/sample.h5 --device cuda
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
The HDF5 key must be `stamped_histogram`, with raw shape `(32, 32, 64)` in
|
| 61 |
+
`(height, width, time_bin)` order. Values must be finite, nonnegative counts.
|
| 62 |
+
Do not pass already normalized data. No test-time augmentation is applied.
|
| 63 |
+
The output JSON contains the logit, positive probability, numeric label, and label name.
|
| 64 |
+
|
| 65 |
+
From Python, with this folder on the Python import path:
|
| 66 |
+
|
| 67 |
+
```python
|
| 68 |
+
import h5py
|
| 69 |
+
from inference import load_model, predict
|
| 70 |
+
|
| 71 |
+
model, config, stats = load_model()
|
| 72 |
+
with h5py.File("sample.h5", "r") as f:
|
| 73 |
+
result = predict(f["stamped_histogram"][()], model, config, stats)
|
| 74 |
+
print(result)
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
## Downloading from Hugging Face
|
| 78 |
+
|
| 79 |
+
Download this model package from
|
| 80 |
+
[`PeizhaoEdinburgh/Occlusion_Based_NLOS_model`](https://huggingface.co/PeizhaoEdinburgh/Occlusion_Based_NLOS_model).
|
| 81 |
+
|
| 82 |
+
```python
|
| 83 |
+
from huggingface_hub import snapshot_download
|
| 84 |
+
|
| 85 |
+
folder = snapshot_download(
|
| 86 |
+
repo_id="PeizhaoEdinburgh/Occlusion_Based_NLOS_model",
|
| 87 |
+
repo_type="model",
|
| 88 |
+
# revision="COMMIT_HASH", # Pin a published commit for reproducibility.
|
| 89 |
+
)
|
| 90 |
+
print(folder)
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
Run `python /downloaded/folder/inference.py /path/to/sample.h5`. The downloaded
|
| 94 |
+
Python files execute locally, so review the source before running them.
|
| 95 |
+
|
| 96 |
+
## Exact preprocessing
|
| 97 |
+
|
| 98 |
+
1. Cast raw counts to NumPy float32.
|
| 99 |
+
2. Compute the total using float64 accumulation. For a positive total, multiply
|
| 100 |
+
all bins by `10000 / total`; an all-zero histogram remains zero at this step.
|
| 101 |
+
3. Apply `log1p`.
|
| 102 |
+
4. Subtract `0.10141183639534776` and divide by `0.22149132767394814`.
|
| 103 |
+
5. Transpose to `(64, 32, 32)` and add batch/channel axes: `(B, 1, 64, 32, 32)`.
|
| 104 |
+
|
| 105 |
+
The original checkpoint also records `pixel_photon_count` and `count_power`;
|
| 106 |
+
those settings are inactive for this preprocessing mode.
|
| 107 |
+
|
| 108 |
+
## Training and recorded evaluation
|
| 109 |
+
|
| 110 |
+
Source run:
|
| 111 |
+
`20260726_212202_tmf8829_36noise_seed_lhs_plus30_36_global_log_count_coarse_multiscale_h128_d4_aug_standard_hist_coarse_multiscale_mlp`.
|
| 112 |
+
|
| 113 |
+
Training used the synthetic dataset with 26,600 training, 5,700 validation,
|
| 114 |
+
and 5,700 test samples, seed 42, batch size 64, learning rate 0.001,
|
| 115 |
+
weight decay 0.0001, and D4 spatial augmentation. The selected checkpoint is
|
| 116 |
+
from epoch 50, with the lowest run validation loss, **0.3138319095**.
|
| 117 |
+
No real experimental samples were used for training according to the run artifacts.
|
| 118 |
+
|
| 119 |
+
| Evaluation | Recorded result |
|
| 120 |
+
|---|---:|
|
| 121 |
+
| Synthetic test accuracy | 0.8626316 |
|
| 122 |
+
| Synthetic test precision | 0.8627589 |
|
| 123 |
+
| Synthetic test recall | 0.8624561 |
|
| 124 |
+
| Synthetic test F1 | 0.8626075 |
|
| 125 |
+
| Synthetic test ROC-AUC | 0.9465309 |
|
| 126 |
+
| Experimental groups 1–5 | 88/100 correct |
|
| 127 |
+
| Additional left/right/long/short groups | 68/80 correct |
|
| 128 |
+
| Combined experimental accuracy | 156/180 = 86.67% |
|
| 129 |
+
|
| 130 |
+
Metrics above are historical artifacts, not newly recomputed benchmark scores.
|
| 131 |
+
“Best” means lowest validation loss within this run, not best across all models.
|
| 132 |
+
Experimental data use during model selection has not been audited. The synthetic
|
| 133 |
+
split does not establish independence between related clean/noisy scenes.
|
| 134 |
+
|
| 135 |
+
## Verification
|
| 136 |
+
|
| 137 |
+
`verification.json` records exact CPU agreement with the original implementation
|
| 138 |
+
for preprocessing and logits on all 180 experimental samples and six synthetic
|
| 139 |
+
samples spanning both labels and clean/noise_1/noise_36 conditions. Boundary
|
| 140 |
+
checks cover zero counts, constant counts, wrong shape, negative counts, and NaNs.
|
| 141 |
+
|
| 142 |
+
```bash
|
| 143 |
+
sha256sum --check SHA256SUMS
|
| 144 |
+
```
|
| 145 |
+
|
| 146 |
+
## Intended use and limitations
|
| 147 |
+
|
| 148 |
+
Research on NLOS histogram classification. Generalization to different sensors,
|
| 149 |
+
scene geometry, or timing calibration is not established. Probability outputs
|
| 150 |
+
have not been shown to be calibrated. The original checkpoint contains provenance
|
| 151 |
+
paths that are not used by the standalone loader.
|
| 152 |
+
|
| 153 |
+
## License and attribution
|
| 154 |
+
|
| 155 |
+
A license for these model weights and source code has not been selected. The
|
| 156 |
+
associated dataset's MIT metadata does not automatically license this model.
|
| 157 |
+
Author attribution and a paper citation have not been supplied.
|
| 158 |
+
|
| 159 |
+
The card follows the [Hugging Face model card format](https://huggingface.co/docs/hub/model-cards).
|
SHA256SUMS
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
4009e8f5faf67ca3607a646c9b12350d8237e344e6853c59bf9dbad697db3952 .gitattributes
|
| 2 |
+
3e7f345273a9999a6c5e379f8418d7bbc46b7bee5899436384b03bfde7a4e044 .gitignore
|
| 3 |
+
46b438bea76f30462c2164095addfed08cbeb27cec8f856e9be3e0b9767f0fc5 README.md
|
| 4 |
+
6e6b8bf4adbd61f7cdda117498c916c9c098d9a49ef0edecb224a4cac8690f5d best.pt
|
| 5 |
+
543bfb9a13bfdfde410ed42116267cb8b291c1dfd69f664d214638eca6057526 config.json
|
| 6 |
+
a8a513e9231546bc0b64612673677a466e562a0e8eccafe6b040c042a589069f inference.py
|
| 7 |
+
52b036003ee5f5a0d4b62f66f5281e60f7775cfa95f003f588c77195272ab240 model.py
|
| 8 |
+
db61d5a67ecfc6fea45be741ea0e185d2add1d7a30c77d197569371a18489ddf preprocess_stats.json
|
| 9 |
+
0abfce5b440789fe8c39e170023071816ad44e9fa899f8f1e71b164da382d0c7 requirements.txt
|
| 10 |
+
0a3fe2f23144ec52342a3c6096ad8e8646256e6260f2a0a4cb94fbf09801569b verification.json
|
best.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6e6b8bf4adbd61f7cdda117498c916c9c098d9a49ef0edecb224a4cac8690f5d
|
| 3 |
+
size 2236504
|
config.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architecture": "HistCoarseMultiScaleMLP",
|
| 3 |
+
"hidden_features": 128,
|
| 4 |
+
"dropout": 0.2,
|
| 5 |
+
"input_shape": [
|
| 6 |
+
32,
|
| 7 |
+
32,
|
| 8 |
+
64
|
| 9 |
+
],
|
| 10 |
+
"h5_key": "stamped_histogram",
|
| 11 |
+
"threshold": 0.5,
|
| 12 |
+
"id2label": {
|
| 13 |
+
"0": "negative",
|
| 14 |
+
"1": "positive"
|
| 15 |
+
},
|
| 16 |
+
"epoch": 50,
|
| 17 |
+
"validation_loss": 0.3138319095184928,
|
| 18 |
+
"parameters": 549641
|
| 19 |
+
}
|
inference.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Inference on raw (height, width, time-bin) HDF5 histograms."""
|
| 2 |
+
import argparse
|
| 3 |
+
import json
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
import h5py
|
| 7 |
+
import numpy as np
|
| 8 |
+
import torch
|
| 9 |
+
from model import HistCoarseMultiScaleMLP
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def preprocess(histogram, stats):
|
| 13 |
+
data = np.asarray(histogram, dtype=np.float32)
|
| 14 |
+
if data.shape != (32, 32, 64):
|
| 15 |
+
raise ValueError(f'Expected (32, 32, 64), got {data.shape}')
|
| 16 |
+
if not np.isfinite(data).all() or (data < 0).any():
|
| 17 |
+
raise ValueError('Histogram counts must be finite and nonnegative')
|
| 18 |
+
if stats['mode'] != 'photon_count_10000_log1p_standardize':
|
| 19 |
+
raise ValueError('Unsupported preprocessing mode')
|
| 20 |
+
total = float(data.sum(dtype=np.float64))
|
| 21 |
+
if total > 0:
|
| 22 |
+
data = data * (10000.0 / total)
|
| 23 |
+
data = np.log1p(data)
|
| 24 |
+
data = (data - stats['mean']) / max(stats['std'], 1e-6)
|
| 25 |
+
return torch.from_numpy(np.transpose(data, (2, 0, 1)).copy()).unsqueeze(0).unsqueeze(0)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def load_model(model_dir=None, device='cpu'):
|
| 29 |
+
root = Path(model_dir) if model_dir else Path(__file__).resolve().parent
|
| 30 |
+
config = json.loads((root / 'config.json').read_text())
|
| 31 |
+
stats = json.loads((root / 'preprocess_stats.json').read_text())
|
| 32 |
+
checkpoint = torch.load(root / 'best.pt', map_location='cpu', weights_only=True)
|
| 33 |
+
if stats != checkpoint['preprocess_stats']:
|
| 34 |
+
raise ValueError('Preprocessing statistics differ from checkpoint')
|
| 35 |
+
model = HistCoarseMultiScaleMLP(config['hidden_features'], config['dropout'])
|
| 36 |
+
model.load_state_dict(checkpoint['model_state'], strict=True)
|
| 37 |
+
model.to(device).eval()
|
| 38 |
+
return model, config, stats
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def predict(histogram, model, config, stats):
|
| 42 |
+
device = next(model.parameters()).device
|
| 43 |
+
with torch.inference_mode():
|
| 44 |
+
logit = model(preprocess(histogram, stats).to(device)).item()
|
| 45 |
+
probability = torch.sigmoid(torch.tensor(logit)).item()
|
| 46 |
+
label = int(probability >= config['threshold'])
|
| 47 |
+
return {'logit': logit, 'probability_positive': probability,
|
| 48 |
+
'label': label, 'label_name': config['id2label'][str(label)]}
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def main():
|
| 52 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 53 |
+
parser.add_argument('histogram', type=Path)
|
| 54 |
+
parser.add_argument('--model-dir', type=Path, default=Path(__file__).resolve().parent)
|
| 55 |
+
parser.add_argument('--device', default='cpu')
|
| 56 |
+
args = parser.parse_args()
|
| 57 |
+
model, config, stats = load_model(args.model_dir, args.device)
|
| 58 |
+
with h5py.File(args.histogram, 'r') as f:
|
| 59 |
+
histogram = f[config['h5_key']][()]
|
| 60 |
+
print(json.dumps(predict(histogram, model, config, stats), indent=2))
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
if __name__ == '__main__':
|
| 64 |
+
main()
|
model.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Standalone architecture extracted from the original lidarx implementation."""
|
| 2 |
+
import torch
|
| 3 |
+
from torch import nn
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def coarse_projection_features(x: torch.Tensor) -> torch.Tensor:
|
| 7 |
+
"""Return `projection_coarse_32x8x8` features for `[B, 1, D, H, W]` tensors."""
|
| 8 |
+
if x.ndim != 5:
|
| 9 |
+
msg = f"expected input with shape [batch, channel, depth, height, width], got {x.shape}"
|
| 10 |
+
raise ValueError(msg)
|
| 11 |
+
if x.shape[1] != 1:
|
| 12 |
+
msg = f"expected one histogram channel, got {x.shape[1]}"
|
| 13 |
+
raise ValueError(msg)
|
| 14 |
+
|
| 15 |
+
x = x.squeeze(1)
|
| 16 |
+
depth = x.shape[1]
|
| 17 |
+
height = x.shape[2]
|
| 18 |
+
width = x.shape[3]
|
| 19 |
+
if depth % 32 != 0 or height % 8 != 0 or width % 8 != 0:
|
| 20 |
+
msg = (
|
| 21 |
+
"hist coarse projection requires depth divisible by 32 and "
|
| 22 |
+
f"height/width divisible by 8, got {(depth, height, width)}"
|
| 23 |
+
)
|
| 24 |
+
raise ValueError(msg)
|
| 25 |
+
|
| 26 |
+
global_features = torch.cat(
|
| 27 |
+
[
|
| 28 |
+
x.mean(dim=(1, 2, 3), keepdim=False).unsqueeze(1),
|
| 29 |
+
x.std(dim=(1, 2, 3), unbiased=False, keepdim=False).unsqueeze(1),
|
| 30 |
+
x.amin(dim=(1, 2, 3), keepdim=False).unsqueeze(1),
|
| 31 |
+
x.amax(dim=(1, 2, 3), keepdim=False).unsqueeze(1),
|
| 32 |
+
],
|
| 33 |
+
dim=1,
|
| 34 |
+
)
|
| 35 |
+
depth_features = torch.cat(
|
| 36 |
+
[
|
| 37 |
+
x.mean(dim=(2, 3)),
|
| 38 |
+
x.std(dim=(2, 3), unbiased=False),
|
| 39 |
+
],
|
| 40 |
+
dim=1,
|
| 41 |
+
)
|
| 42 |
+
row_features = x.mean(dim=(1, 3))
|
| 43 |
+
col_features = x.mean(dim=(1, 2))
|
| 44 |
+
coarse = (
|
| 45 |
+
x.reshape(
|
| 46 |
+
x.shape[0],
|
| 47 |
+
32,
|
| 48 |
+
depth // 32,
|
| 49 |
+
8,
|
| 50 |
+
height // 8,
|
| 51 |
+
8,
|
| 52 |
+
width // 8,
|
| 53 |
+
)
|
| 54 |
+
.mean(dim=(2, 4, 6))
|
| 55 |
+
.reshape(x.shape[0], -1)
|
| 56 |
+
)
|
| 57 |
+
return torch.cat(
|
| 58 |
+
[global_features, depth_features, row_features, col_features, coarse],
|
| 59 |
+
dim=1,
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def _coarse_grid_features(
|
| 64 |
+
x: torch.Tensor,
|
| 65 |
+
*,
|
| 66 |
+
depth_bins: int,
|
| 67 |
+
row_bins: int,
|
| 68 |
+
col_bins: int,
|
| 69 |
+
) -> torch.Tensor:
|
| 70 |
+
depth = x.shape[1]
|
| 71 |
+
height = x.shape[2]
|
| 72 |
+
width = x.shape[3]
|
| 73 |
+
if depth % depth_bins != 0 or height % row_bins != 0 or width % col_bins != 0:
|
| 74 |
+
msg = (
|
| 75 |
+
"hist coarse grid requires dimensions divisible by requested bins, "
|
| 76 |
+
f"got volume {(depth, height, width)} and bins {(depth_bins, row_bins, col_bins)}"
|
| 77 |
+
)
|
| 78 |
+
raise ValueError(msg)
|
| 79 |
+
|
| 80 |
+
return (
|
| 81 |
+
x.reshape(
|
| 82 |
+
x.shape[0],
|
| 83 |
+
depth_bins,
|
| 84 |
+
depth // depth_bins,
|
| 85 |
+
row_bins,
|
| 86 |
+
height // row_bins,
|
| 87 |
+
col_bins,
|
| 88 |
+
width // col_bins,
|
| 89 |
+
)
|
| 90 |
+
.mean(dim=(2, 4, 6))
|
| 91 |
+
.reshape(x.shape[0], -1)
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def multiscale_coarse_projection_features(x: torch.Tensor) -> torch.Tensor:
|
| 96 |
+
"""Return coarse histogram features with added low-resolution context views."""
|
| 97 |
+
base = coarse_projection_features(x)
|
| 98 |
+
volume = x.squeeze(1)
|
| 99 |
+
depth_context = _coarse_grid_features(volume, depth_bins=16, row_bins=8, col_bins=8)
|
| 100 |
+
spatial_context = _coarse_grid_features(volume, depth_bins=32, row_bins=4, col_bins=4)
|
| 101 |
+
return torch.cat([base, depth_context, spatial_context], dim=1)
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
class HistCoarseMultiScaleMLP(nn.Module):
|
| 105 |
+
"""Small supervised MLP over multi-scale coarse histogram projections.
|
| 106 |
+
|
| 107 |
+
This keeps the strong non-backbone inductive bias of the coarse-only model,
|
| 108 |
+
but gives the classifier both the best 32x8x8 block view and two coarser
|
| 109 |
+
context views before a lightweight gated residual head.
|
| 110 |
+
"""
|
| 111 |
+
|
| 112 |
+
def __init__(self, hidden_features: int = 128, dropout: float = 0.2) -> None:
|
| 113 |
+
super().__init__()
|
| 114 |
+
bottleneck_features = max(1, hidden_features // 2)
|
| 115 |
+
self.input_norm = nn.LazyBatchNorm1d()
|
| 116 |
+
self.input_projection = nn.Sequential(
|
| 117 |
+
nn.LazyLinear(hidden_features),
|
| 118 |
+
nn.SiLU(inplace=True),
|
| 119 |
+
nn.Dropout(dropout),
|
| 120 |
+
)
|
| 121 |
+
self.residual = nn.Sequential(
|
| 122 |
+
nn.Linear(hidden_features, hidden_features),
|
| 123 |
+
nn.SiLU(inplace=True),
|
| 124 |
+
nn.Dropout(dropout),
|
| 125 |
+
nn.Linear(hidden_features, hidden_features),
|
| 126 |
+
)
|
| 127 |
+
self.gate = nn.Sequential(
|
| 128 |
+
nn.Linear(hidden_features, hidden_features),
|
| 129 |
+
nn.Sigmoid(),
|
| 130 |
+
)
|
| 131 |
+
self.head = nn.Sequential(
|
| 132 |
+
nn.LayerNorm(hidden_features),
|
| 133 |
+
nn.SiLU(inplace=True),
|
| 134 |
+
nn.Dropout(dropout),
|
| 135 |
+
nn.Linear(hidden_features, bottleneck_features),
|
| 136 |
+
nn.SiLU(inplace=True),
|
| 137 |
+
nn.Dropout(dropout),
|
| 138 |
+
nn.Linear(bottleneck_features, 1),
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
def forward_features(self, x: torch.Tensor) -> torch.Tensor:
|
| 142 |
+
"""Return the learned 128-D representation immediately before the classifier."""
|
| 143 |
+
features = self.input_norm(multiscale_coarse_projection_features(x))
|
| 144 |
+
embedding = self.input_projection(features)
|
| 145 |
+
return embedding + self.gate(embedding) * self.residual(embedding)
|
| 146 |
+
|
| 147 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 148 |
+
return self.head(self.forward_features(x))
|
preprocess_stats.json
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"mode": "photon_count_10000_log1p_standardize",
|
| 3 |
+
"mean": 0.10141183639534776,
|
| 4 |
+
"std": 0.22149132767394814,
|
| 5 |
+
"pixel_photon_count": 1000.0,
|
| 6 |
+
"count_power": 0.75
|
| 7 |
+
}
|
requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch>=2.5.1
|
| 2 |
+
numpy>=1.24
|
| 3 |
+
h5py>=3.8
|
| 4 |
+
huggingface_hub>=0.24
|
verification.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"samples_compared": 186,
|
| 3 |
+
"experimental_samples": 180,
|
| 4 |
+
"synthetic_samples": 6,
|
| 5 |
+
"preprocessing_exact_match": true,
|
| 6 |
+
"logits_exact_match": true,
|
| 7 |
+
"maximum_absolute_logit_difference": 0,
|
| 8 |
+
"checkpoint_sha256": "6e6b8bf4adbd61f7cdda117498c916c9c098d9a49ef0edecb224a4cac8690f5d",
|
| 9 |
+
"parameters": 549641,
|
| 10 |
+
"validation_device": "cpu",
|
| 11 |
+
"tested_versions": {
|
| 12 |
+
"torch": "2.5.1+cu121",
|
| 13 |
+
"numpy": "2.4.6",
|
| 14 |
+
"h5py": "3.16.0"
|
| 15 |
+
}
|
| 16 |
+
}
|