witwin-radar 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- witwin_radar-0.0.1/.github/workflows/publish-witwin-radar.yml +92 -0
- witwin_radar-0.0.1/.gitignore +229 -0
- witwin_radar-0.0.1/FEATURE_LIST.md +55 -0
- witwin_radar-0.0.1/PERFORMANCE.md +146 -0
- witwin_radar-0.0.1/PKG-INFO +145 -0
- witwin_radar-0.0.1/README.md +133 -0
- witwin_radar-0.0.1/batch_generate.py +352 -0
- witwin_radar-0.0.1/examples/__init__.py +0 -0
- witwin_radar-0.0.1/examples/amass_pointcloud.ipynb +311 -0
- witwin_radar-0.0.1/examples/amass_pointcloud.py +117 -0
- witwin_radar-0.0.1/examples/humanbody.ipynb +428 -0
- witwin_radar-0.0.1/examples/humanbody.py +72 -0
- witwin_radar-0.0.1/examples/mesh_scene.ipynb +151 -0
- witwin_radar-0.0.1/examples/mesh_scene.py +92 -0
- witwin_radar-0.0.1/examples/music_imaging.ipynb +235 -0
- witwin_radar-0.0.1/examples/music_imaging.py +67 -0
- witwin_radar-0.0.1/examples/music_imaging_body.ipynb +391 -0
- witwin_radar-0.0.1/examples/single_point.ipynb +139 -0
- witwin_radar-0.0.1/examples/single_point.py +57 -0
- witwin_radar-0.0.1/examples/timeline.ipynb +237 -0
- witwin_radar-0.0.1/experiments/gen_amass_video.py +270 -0
- witwin_radar-0.0.1/pyproject.toml +27 -0
- witwin_radar-0.0.1/requirements.txt +9 -0
- witwin_radar-0.0.1/tests/benchmark.py +156 -0
- witwin_radar-0.0.1/tests/conftest.py +221 -0
- witwin_radar-0.0.1/tests/core/__init__.py +0 -0
- witwin_radar-0.0.1/tests/core/test_antenna_pattern.py +174 -0
- witwin_radar-0.0.1/tests/core/test_differentiable_to_mesh.py +55 -0
- witwin_radar-0.0.1/tests/core/test_multipath_smoke.py +113 -0
- witwin_radar-0.0.1/tests/core/test_noise_model.py +270 -0
- witwin_radar-0.0.1/tests/core/test_path_runtime.py +386 -0
- witwin_radar-0.0.1/tests/core/test_polarization.py +122 -0
- witwin_radar-0.0.1/tests/core/test_radar_config.py +434 -0
- witwin_radar-0.0.1/tests/core/test_radar_pose.py +147 -0
- witwin_radar-0.0.1/tests/core/test_receiver_chain.py +148 -0
- witwin_radar-0.0.1/tests/core/test_scene_api.py +33 -0
- witwin_radar-0.0.1/tests/core/test_scene_motion.py +342 -0
- witwin_radar-0.0.1/tests/core/test_scene_to_mitsuba.py +242 -0
- witwin_radar-0.0.1/tests/core/test_simulation_smoke.py +280 -0
- witwin_radar-0.0.1/tests/core/test_waveform.py +98 -0
- witwin_radar-0.0.1/tests/debug_offset.py +341 -0
- witwin_radar-0.0.1/tests/sigproc/__init__.py +0 -0
- witwin_radar-0.0.1/tests/sigproc/test_aoa.py +213 -0
- witwin_radar-0.0.1/tests/sigproc/test_cfar.py +218 -0
- witwin_radar-0.0.1/tests/sigproc/test_pointcloud_pipeline.py +153 -0
- witwin_radar-0.0.1/tests/sigproc/test_range_doppler.py +128 -0
- witwin_radar-0.0.1/tests/solvers/__init__.py +0 -0
- witwin_radar-0.0.1/tests/solvers/test_chirp_cross.py +164 -0
- witwin_radar-0.0.1/tests/solvers/test_mimo_cross.py +183 -0
- witwin_radar-0.0.1/tests/solvers/test_solver_edge.py +127 -0
- witwin_radar-0.0.1/tests/test_gradient_flow.py +447 -0
- witwin_radar-0.0.1/tests/validation/__init__.py +0 -0
- witwin_radar-0.0.1/tests/validation/test_multi_target.py +229 -0
- witwin_radar-0.0.1/tests/validation/test_single_target.py +313 -0
- witwin_radar-0.0.1/tests/verify.py +173 -0
- witwin_radar-0.0.1/tests/verify_mimo.py +88 -0
- witwin_radar-0.0.1/tests/verify_triangle.py +175 -0
- witwin_radar-0.0.1/witwin/radar/__init__.py +81 -0
- witwin_radar-0.0.1/witwin/radar/antenna_pattern.py +313 -0
- witwin_radar-0.0.1/witwin/radar/config.py +211 -0
- witwin_radar-0.0.1/witwin/radar/material.py +42 -0
- witwin_radar-0.0.1/witwin/radar/motion.py +181 -0
- witwin_radar-0.0.1/witwin/radar/noise.py +258 -0
- witwin_radar-0.0.1/witwin/radar/polarization.py +156 -0
- witwin_radar-0.0.1/witwin/radar/radar.py +223 -0
- witwin_radar-0.0.1/witwin/radar/receiver_chain.py +173 -0
- witwin_radar-0.0.1/witwin/radar/renderer.py +611 -0
- witwin_radar-0.0.1/witwin/radar/result.py +107 -0
- witwin_radar-0.0.1/witwin/radar/scene.py +728 -0
- witwin_radar-0.0.1/witwin/radar/sensor.py +95 -0
- witwin_radar-0.0.1/witwin/radar/sigproc/__init__.py +39 -0
- witwin_radar-0.0.1/witwin/radar/sigproc/cfar.py +229 -0
- witwin_radar-0.0.1/witwin/radar/sigproc/music.py +146 -0
- witwin_radar-0.0.1/witwin/radar/sigproc/pointcloud.py +608 -0
- witwin_radar-0.0.1/witwin/radar/simulation.py +275 -0
- witwin_radar-0.0.1/witwin/radar/solvers/__init__.py +80 -0
- witwin_radar-0.0.1/witwin/radar/solvers/_runtime.py +223 -0
- witwin_radar-0.0.1/witwin/radar/solvers/dirichlet.slang +439 -0
- witwin_radar-0.0.1/witwin/radar/solvers/radar.slang +237 -0
- witwin_radar-0.0.1/witwin/radar/solvers/solver_dirichlet.py +302 -0
- witwin_radar-0.0.1/witwin/radar/solvers/solver_pytorch.py +68 -0
- witwin_radar-0.0.1/witwin/radar/solvers/solver_slang.py +204 -0
- witwin_radar-0.0.1/witwin/radar/timeline.py +301 -0
- witwin_radar-0.0.1/witwin/radar/types.py +45 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
name: Publish witwin-radar
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
if: startsWith(github.event.release.tag_name, 'witwin-radar-v') && !github.event.release.prerelease
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
id-token: write
|
|
14
|
+
environment:
|
|
15
|
+
name: pypi
|
|
16
|
+
url: https://pypi.org/project/witwin-radar/
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- name: Check out repository
|
|
20
|
+
uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- name: Set up Python
|
|
23
|
+
uses: actions/setup-python@v5
|
|
24
|
+
with:
|
|
25
|
+
python-version: "3.11"
|
|
26
|
+
|
|
27
|
+
- name: Resolve package directory
|
|
28
|
+
id: package_dir
|
|
29
|
+
shell: bash
|
|
30
|
+
run: |
|
|
31
|
+
if [ -f pyproject.toml ]; then
|
|
32
|
+
echo "path=." >> "$GITHUB_OUTPUT"
|
|
33
|
+
elif [ -f radar/pyproject.toml ]; then
|
|
34
|
+
echo "path=radar" >> "$GITHUB_OUTPUT"
|
|
35
|
+
else
|
|
36
|
+
echo "Unable to find pyproject.toml for the witwin-radar package." >&2
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
- name: Validate release tag and package version
|
|
41
|
+
shell: bash
|
|
42
|
+
env:
|
|
43
|
+
PACKAGE_DIR: ${{ steps.package_dir.outputs.path }}
|
|
44
|
+
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
|
45
|
+
run: |
|
|
46
|
+
python - <<'PY'
|
|
47
|
+
import os
|
|
48
|
+
import re
|
|
49
|
+
import sys
|
|
50
|
+
import tomllib
|
|
51
|
+
from pathlib import Path
|
|
52
|
+
|
|
53
|
+
tag = os.environ["RELEASE_TAG"]
|
|
54
|
+
match = re.fullmatch(r"witwin-radar-v(?P<version>[0-9A-Za-z.+_-]+)", tag)
|
|
55
|
+
if match is None:
|
|
56
|
+
print(f"Release tag '{tag}' must match 'witwin-radar-v<version>'.", file=sys.stderr)
|
|
57
|
+
raise SystemExit(1)
|
|
58
|
+
|
|
59
|
+
expected_version = match.group("version")
|
|
60
|
+
pyproject_path = Path(os.environ["PACKAGE_DIR"]) / "pyproject.toml"
|
|
61
|
+
data = tomllib.loads(pyproject_path.read_text(encoding="utf-8"))
|
|
62
|
+
|
|
63
|
+
project = data["project"]
|
|
64
|
+
package_name = project["name"]
|
|
65
|
+
package_version = project["version"]
|
|
66
|
+
|
|
67
|
+
if package_name != "witwin-radar":
|
|
68
|
+
print(f"Expected package name 'witwin-radar', found '{package_name}'.", file=sys.stderr)
|
|
69
|
+
raise SystemExit(1)
|
|
70
|
+
|
|
71
|
+
if package_version != expected_version:
|
|
72
|
+
print(
|
|
73
|
+
f"Release tag version '{expected_version}' does not match pyproject version '{package_version}'.",
|
|
74
|
+
file=sys.stderr,
|
|
75
|
+
)
|
|
76
|
+
raise SystemExit(1)
|
|
77
|
+
|
|
78
|
+
print(f"Validated {package_name} {package_version} from {pyproject_path}.")
|
|
79
|
+
PY
|
|
80
|
+
|
|
81
|
+
- name: Build distributions
|
|
82
|
+
shell: bash
|
|
83
|
+
env:
|
|
84
|
+
PACKAGE_DIR: ${{ steps.package_dir.outputs.path }}
|
|
85
|
+
run: |
|
|
86
|
+
python -m pip install --upgrade pip build
|
|
87
|
+
python -m build "$PACKAGE_DIR"
|
|
88
|
+
|
|
89
|
+
- name: Publish distributions to PyPI
|
|
90
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
91
|
+
with:
|
|
92
|
+
packages-dir: ${{ steps.package_dir.outputs.path }}/dist
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
*.lock
|
|
10
|
+
|
|
11
|
+
# Distribution / packaging
|
|
12
|
+
.Python
|
|
13
|
+
build/
|
|
14
|
+
develop-eggs/
|
|
15
|
+
dist/
|
|
16
|
+
downloads/
|
|
17
|
+
eggs/
|
|
18
|
+
.eggs/
|
|
19
|
+
lib/
|
|
20
|
+
lib64/
|
|
21
|
+
parts/
|
|
22
|
+
sdist/
|
|
23
|
+
var/
|
|
24
|
+
wheels/
|
|
25
|
+
share/python-wheels/
|
|
26
|
+
*.egg-info/
|
|
27
|
+
.installed.cfg
|
|
28
|
+
*.egg
|
|
29
|
+
MANIFEST
|
|
30
|
+
|
|
31
|
+
# PyInstaller
|
|
32
|
+
# Usually these files are written by a python script from a template
|
|
33
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
34
|
+
*.manifest
|
|
35
|
+
*.spec
|
|
36
|
+
|
|
37
|
+
# Installer logs
|
|
38
|
+
pip-log.txt
|
|
39
|
+
pip-delete-this-directory.txt
|
|
40
|
+
|
|
41
|
+
# Unit test / coverage reports
|
|
42
|
+
htmlcov/
|
|
43
|
+
.tox/
|
|
44
|
+
.nox/
|
|
45
|
+
.coverage
|
|
46
|
+
.coverage.*
|
|
47
|
+
.cache
|
|
48
|
+
nosetests.xml
|
|
49
|
+
coverage.xml
|
|
50
|
+
*.cover
|
|
51
|
+
*.py.cover
|
|
52
|
+
.hypothesis/
|
|
53
|
+
.pytest_cache/
|
|
54
|
+
cover/
|
|
55
|
+
|
|
56
|
+
# Translations
|
|
57
|
+
*.mo
|
|
58
|
+
*.pot
|
|
59
|
+
|
|
60
|
+
# Django stuff:
|
|
61
|
+
*.log
|
|
62
|
+
local_settings.py
|
|
63
|
+
db.sqlite3
|
|
64
|
+
db.sqlite3-journal
|
|
65
|
+
|
|
66
|
+
# Flask stuff:
|
|
67
|
+
instance/
|
|
68
|
+
.webassets-cache
|
|
69
|
+
|
|
70
|
+
# Scrapy stuff:
|
|
71
|
+
.scrapy
|
|
72
|
+
|
|
73
|
+
# Sphinx documentation
|
|
74
|
+
docs/_build/
|
|
75
|
+
|
|
76
|
+
# PyBuilder
|
|
77
|
+
.pybuilder/
|
|
78
|
+
target/
|
|
79
|
+
|
|
80
|
+
# Jupyter Notebook
|
|
81
|
+
.ipynb_checkpoints
|
|
82
|
+
|
|
83
|
+
# IPython
|
|
84
|
+
profile_default/
|
|
85
|
+
ipython_config.py
|
|
86
|
+
|
|
87
|
+
# pyenv
|
|
88
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
89
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
90
|
+
# .python-version
|
|
91
|
+
|
|
92
|
+
# pipenv
|
|
93
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
94
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
95
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
96
|
+
# install all needed dependencies.
|
|
97
|
+
# Pipfile.lock
|
|
98
|
+
|
|
99
|
+
# UV
|
|
100
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
101
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
102
|
+
# commonly ignored for libraries.
|
|
103
|
+
# uv.lock
|
|
104
|
+
|
|
105
|
+
# poetry
|
|
106
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
107
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
108
|
+
# commonly ignored for libraries.
|
|
109
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
110
|
+
# poetry.lock
|
|
111
|
+
# poetry.toml
|
|
112
|
+
|
|
113
|
+
# pdm
|
|
114
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
115
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
116
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
117
|
+
# pdm.lock
|
|
118
|
+
# pdm.toml
|
|
119
|
+
.pdm-python
|
|
120
|
+
.pdm-build/
|
|
121
|
+
|
|
122
|
+
# pixi
|
|
123
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
124
|
+
# pixi.lock
|
|
125
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
126
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
127
|
+
.pixi
|
|
128
|
+
|
|
129
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
130
|
+
__pypackages__/
|
|
131
|
+
|
|
132
|
+
# Celery stuff
|
|
133
|
+
celerybeat-schedule
|
|
134
|
+
celerybeat.pid
|
|
135
|
+
|
|
136
|
+
# Redis
|
|
137
|
+
*.rdb
|
|
138
|
+
*.aof
|
|
139
|
+
*.pid
|
|
140
|
+
|
|
141
|
+
# RabbitMQ
|
|
142
|
+
mnesia/
|
|
143
|
+
rabbitmq/
|
|
144
|
+
rabbitmq-data/
|
|
145
|
+
|
|
146
|
+
# ActiveMQ
|
|
147
|
+
activemq-data/
|
|
148
|
+
|
|
149
|
+
# SageMath parsed files
|
|
150
|
+
*.sage.py
|
|
151
|
+
|
|
152
|
+
# Environments
|
|
153
|
+
.env
|
|
154
|
+
.envrc
|
|
155
|
+
.venv
|
|
156
|
+
env/
|
|
157
|
+
venv/
|
|
158
|
+
ENV/
|
|
159
|
+
env.bak/
|
|
160
|
+
venv.bak/
|
|
161
|
+
|
|
162
|
+
# Spyder project settings
|
|
163
|
+
.spyderproject
|
|
164
|
+
.spyproject
|
|
165
|
+
|
|
166
|
+
# Rope project settings
|
|
167
|
+
.ropeproject
|
|
168
|
+
|
|
169
|
+
# mkdocs documentation
|
|
170
|
+
/site
|
|
171
|
+
|
|
172
|
+
# mypy
|
|
173
|
+
.mypy_cache/
|
|
174
|
+
.dmypy.json
|
|
175
|
+
dmypy.json
|
|
176
|
+
|
|
177
|
+
# Pyre type checker
|
|
178
|
+
.pyre/
|
|
179
|
+
|
|
180
|
+
# pytype static type analyzer
|
|
181
|
+
.pytype/
|
|
182
|
+
|
|
183
|
+
# Cython debug symbols
|
|
184
|
+
cython_debug/
|
|
185
|
+
|
|
186
|
+
.slangtorch_cache/
|
|
187
|
+
|
|
188
|
+
# RadarTwin project data
|
|
189
|
+
data/
|
|
190
|
+
output/
|
|
191
|
+
models/
|
|
192
|
+
*.tar.bz2
|
|
193
|
+
*.tar.xz
|
|
194
|
+
*.tar.gz
|
|
195
|
+
activate.sh
|
|
196
|
+
|
|
197
|
+
# PyCharm
|
|
198
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
199
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
200
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
201
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
202
|
+
# .idea/
|
|
203
|
+
|
|
204
|
+
# Abstra
|
|
205
|
+
# Abstra is an AI-powered process automation framework.
|
|
206
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
207
|
+
# Learn more at https://abstra.io/docs
|
|
208
|
+
.abstra/
|
|
209
|
+
|
|
210
|
+
# Visual Studio Code
|
|
211
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
212
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
213
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
214
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
215
|
+
# .vscode/
|
|
216
|
+
|
|
217
|
+
# Ruff stuff:
|
|
218
|
+
.ruff_cache/
|
|
219
|
+
|
|
220
|
+
# PyPI configuration file
|
|
221
|
+
.pypirc
|
|
222
|
+
|
|
223
|
+
# Marimo
|
|
224
|
+
marimo/_static/
|
|
225
|
+
marimo/_lsp/
|
|
226
|
+
__marimo__/
|
|
227
|
+
|
|
228
|
+
# Streamlit
|
|
229
|
+
.streamlit/secrets.toml
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Radar Feature List
|
|
2
|
+
|
|
3
|
+
## Public API
|
|
4
|
+
|
|
5
|
+
- Declarative simulation flow: `Scene -> Simulation -> Result`
|
|
6
|
+
- Radar pose can be controlled explicitly with `Sensor(...)` on `Radar`, `Renderer`, or `Simulation`
|
|
7
|
+
- Scene assembly uses `Scene.set_sensor(...)`, `Scene.add_structure(...)`, `Scene.add_mesh(...)`, `Scene.add_smpl(...)`, and `Scene.add_structure_motion(...)`
|
|
8
|
+
- Multi-radar orchestration is available via `Simulation.mimo_group(...)`, `RadarSpec`, and `MultiResult`
|
|
9
|
+
- Optional per-structure motion is available through `Scene.add_structure_motion(...)`, `Scene.update_structure_motion(...)`, and `Scene.clear_structure_motion(...)`
|
|
10
|
+
- Public string-literal API types: `SolverBackend`, `DetectorType`, `SamplingMode`, and `MotionSampling`
|
|
11
|
+
- Low-level radar solver entrypoint: `Radar.chirp()`, `Radar.frame()`, `Radar.mimo()`, and `Radar.apply_noise()`
|
|
12
|
+
- Ray-tracing entrypoint: `Renderer.trace()` returns `TraceResult(points, intensities)` and also carries `entry_points`, `fixed_path_lengths`, and `depths` for generalized path tracing
|
|
13
|
+
- `Result.signal()`, `Result.trace_points()`, `Result.trace_intensities()`, `Result.trace_entry_points()`, `Result.trace_fixed_path_lengths()`, and `Result.trace_depths()` provide semantic tensor access, with `Result.tensor(...)` retained as a generic fallback
|
|
14
|
+
|
|
15
|
+
## Configuration
|
|
16
|
+
|
|
17
|
+
- `RadarConfig` frozen schema validates required radar fields and antenna layouts
|
|
18
|
+
- Optional `antenna_pattern` config defaults to a broadside dipole and also supports separable `x/y` 1D gain curves or a direct 2D gain map
|
|
19
|
+
- Optional `noise_model` config supports thermal noise, quantization noise, and phase noise with optional deterministic seeding
|
|
20
|
+
- Optional `polarization` config supports simplified TX/RX polarization vectors with alias strings (`horizontal` / `vertical`) or per-element 3D vectors
|
|
21
|
+
- Optional `receiver_chain` config supports `lna`, `agc`, and `adc` stages plus absolute TX-power scaling via `config["power"]`
|
|
22
|
+
- `Radar` accepts `RadarConfig`, `dict`, or JSON config path
|
|
23
|
+
- `Simulation` accepts the same validated config inputs as `Radar`
|
|
24
|
+
- `Renderer(...)` and `Simulation.mimo(...)` accept `multipath`, `max_reflections`, and `ray_batch_size`
|
|
25
|
+
- `Simulation.mimo(...)` and `Simulation.mimo_group(...)` accept `motion_sampling="frame" | "chirp"` for dynamic scenes
|
|
26
|
+
|
|
27
|
+
## Backend Execution
|
|
28
|
+
|
|
29
|
+
- Three solver backends: `pytorch`, `slang`, `dirichlet`
|
|
30
|
+
- Backend-specific runtime state lives on `radar.solver`, including Dirichlet FFT metadata such as `pad_factor` and `N_fft`
|
|
31
|
+
- `Radar(device=...)` validates CUDA availability explicitly; `slang` and `dirichlet` require CUDA, while `pytorch` honors the selected device
|
|
32
|
+
- Time-domain outputs from `Radar.chirp()`, `Radar.frame()`, and `Radar.mimo()` automatically apply `noise_model` when configured; `radar.mimo(..., freq_domain=True)` rejects built-in noise injection
|
|
33
|
+
- Time-domain outputs from `Radar.chirp()`, `Radar.frame()`, and `Radar.mimo()` automatically apply `receiver_chain` when configured; enabling it also moves `Radar.gain` onto an absolute transmit-voltage scale
|
|
34
|
+
- `receiver_chain.adc` and `noise_model.quantization` are mutually exclusive so only one ADC quantizer is active
|
|
35
|
+
|
|
36
|
+
## Rendering And Dynamics
|
|
37
|
+
|
|
38
|
+
- `Renderer.trace()` has a single public signature with no ignored `spp` parameter
|
|
39
|
+
- `Scene.compile_renderables(time=...)` and `Renderer.trace(time=...)` expose time-dependent geometry for dynamic scenes
|
|
40
|
+
- Multipath tracing is available for `sampling="pixel"` and uses radar-center path tracing with configurable maximum specular reflection depth
|
|
41
|
+
- Solver backends consume generalized path samples and apply FSPL from the total `tx -> bounces -> scatter -> rx` distance
|
|
42
|
+
- When `polarization` is configured, traced path normals are propagated through the runtime and used for simplified reflection/projection coupling
|
|
43
|
+
- Shared core geometry constructors default to `device=None`, while radar `Scene(...)` owns device placement and defaults to CUDA
|
|
44
|
+
- `Timeline.from_motion()` uses the renderer trace contract directly
|
|
45
|
+
- Dynamic structure motion supports rigid `translation`, `rotation`, and `parent` inheritance so rotational Doppler can be modeled directly from the scene
|
|
46
|
+
- `radar.mimo(..., freq_domain=True)` remains available for Dirichlet frequency-domain output
|
|
47
|
+
|
|
48
|
+
## Signal Processing
|
|
49
|
+
|
|
50
|
+
- `process_pc(..., detector=...)` accepts the validated detector set `{"cfar", "topk"}`
|
|
51
|
+
- `frame2pointcloud(...)` requires a `radar` argument so TDM-MIMO compensation is never skipped silently
|
|
52
|
+
- `PointCloudProcessConfig` provides the normalized point-cloud extraction config surface
|
|
53
|
+
- `range_fft(...)`, `doppler_fft(...)`, `clutter_removal(...)`, `process_pc(...)`, and `process_rd(...)` keep tensor inputs on the PyTorch path and use `torch.fft` for GPU-native DSP
|
|
54
|
+
- `ca_cfar_2d(...)`, `ca_cfar_2d_fast(...)`, and `os_cfar_2d(...)` return `(detections, threshold_map)` with a consistent CFAR contract
|
|
55
|
+
- `ca_cfar_2d(...)`, `ca_cfar_2d_fast(...)`, and `os_cfar_2d(...)` all accept NumPy arrays and PyTorch tensors; the reference CA/OS paths now stay on the torch device instead of falling back to CPU
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Radar Signal Processing Performance Analysis
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This report compares different parallelization strategies for FMCW radar signal processing:
|
|
6
|
+
- **Slang+FFT**: Compute time-domain chirp signal, then apply FFT
|
|
7
|
+
- **Dirichlet**: Direct frequency-domain computation using Dirichlet kernel (skips FFT)
|
|
8
|
+
|
|
9
|
+
Each method has three variants:
|
|
10
|
+
- **Internal (_i)**: Per-output-bin parallel, each thread loops through ALL targets
|
|
11
|
+
- **Chunked (_c)**: Per-output-bin parallel, targets split into chunks, no atomics
|
|
12
|
+
- **Per-target (_t)**: Per-target parallel, each thread loops through ALL output bins, chunked atomics
|
|
13
|
+
|
|
14
|
+
## Benchmark Results
|
|
15
|
+
|
|
16
|
+
Hardware: NVIDIA GPU with CUDA
|
|
17
|
+
Parameters: `adc_samples=400`, `pad_factor=16`, `N_fft=6400`, `num_bins=3200`
|
|
18
|
+
|
|
19
|
+
| Targets | PyTorch | Slang_i | Slang_c | Slang_t | Dir_i | Dir_c | Dir_t |
|
|
20
|
+
|---------|---------|---------|---------|---------|-------|-------|-------|
|
|
21
|
+
| 4 | 0.20 | 0.15 | 0.50 | 0.23 | 0.07 | 0.08 | 0.16 |
|
|
22
|
+
| 16 | 0.21 | 0.14 | 0.15 | 0.17 | 0.07 | 0.08 | 0.14 |
|
|
23
|
+
| 64 | 0.19 | 0.27 | 0.27 | 0.17 | 0.07 | 0.08 | 0.68 |
|
|
24
|
+
| 256 | 0.21 | 0.74 | 0.74 | 0.24 | 0.08 | 0.12 | 1.46 |
|
|
25
|
+
| 1024 | 0.27 | 2.99 | 0.75 | 0.34 | 0.16 | 0.13 | 1.65 |
|
|
26
|
+
| 4096 | 0.64 | 11.34 | 0.84 | 0.58 | 0.87 | 0.31 | 2.11 |
|
|
27
|
+
| 16384 | 3.69 | 45.86 | 1.91 | 1.25 | 1.77 | 1.33 | 3.79 |
|
|
28
|
+
| 65536 | 15.17 | 177.90 | 5.23 | 5.87 | 7.44 | 4.68 | 12.55 |
|
|
29
|
+
| 262144 | 54.28 | 677.15 | 21.04 | 19.09 | 28.44 | 14.50 | 42.31 |
|
|
30
|
+
| 1048576 | OOM | 2719.41 | 79.22 | 76.41 | 112.38 | **58.12** | 175.29 |
|
|
31
|
+
|
|
32
|
+
*Times in milliseconds (ms). OOM = Out of Memory.*
|
|
33
|
+
|
|
34
|
+
## Method Comparison
|
|
35
|
+
|
|
36
|
+
### 1. PyTorch (Batched Tensor Operations)
|
|
37
|
+
- **Pros**: Simple implementation, fast for small N
|
|
38
|
+
- **Cons**: O(N × T) memory, OOM for N > 262144
|
|
39
|
+
- **Memory**: ~6.7 GB for N=1M (N × T × 16 bytes for complex128)
|
|
40
|
+
|
|
41
|
+
### 2. Slang Internal (Slang_i)
|
|
42
|
+
- **Strategy**: Each thread handles one time sample, loops through ALL targets
|
|
43
|
+
- **Parallelism**: T threads (400)
|
|
44
|
+
- **Pros**: Minimal memory O(T)
|
|
45
|
+
- **Cons**: Poor parallelism, very slow for large N
|
|
46
|
+
- **Bottleneck**: Serial loop over N targets per thread
|
|
47
|
+
|
|
48
|
+
### 3. Slang Chunked (Slang_c)
|
|
49
|
+
- **Strategy**: 2D grid (time_blocks × target_chunks), each thread handles one time sample, loops through targets in its chunk
|
|
50
|
+
- **Parallelism**: T × num_chunks threads
|
|
51
|
+
- **Memory**: O(num_chunks × T) ≈ 26 MB for N=1M
|
|
52
|
+
- **Pros**: Good parallelism, no atomics needed
|
|
53
|
+
- **Performance**: 79.22ms @ 1M targets
|
|
54
|
+
|
|
55
|
+
### 4. Slang Per-target (Slang_t)
|
|
56
|
+
- **Strategy**: Each thread handles one target, loops through ALL time samples
|
|
57
|
+
- **Parallelism**: N threads (chunked)
|
|
58
|
+
- **Memory**: O(num_chunks × T)
|
|
59
|
+
- **Atomics**: Required within each chunk (multiple targets write to same time index)
|
|
60
|
+
- **Performance**: 76.41ms @ 1M targets
|
|
61
|
+
|
|
62
|
+
### 5. Dirichlet Internal (Dir_i)
|
|
63
|
+
- **Strategy**: Each thread handles one frequency bin, loops through ALL targets
|
|
64
|
+
- **Parallelism**: num_bins threads (3200)
|
|
65
|
+
- **Memory**: O(num_bins) ≈ 25 KB
|
|
66
|
+
- **Pros**: Minimal memory, skips FFT
|
|
67
|
+
- **Cons**: Poor parallelism for large N
|
|
68
|
+
|
|
69
|
+
### 6. Dirichlet Chunked (Dir_c) - FASTEST
|
|
70
|
+
- **Strategy**: 2D grid (bin_blocks × target_chunks), each thread handles one bin, loops through targets in its chunk
|
|
71
|
+
- **Parallelism**: num_bins × num_chunks threads
|
|
72
|
+
- **Memory**: O(num_chunks × num_bins) ≈ 105 MB for N=1M
|
|
73
|
+
- **Pros**: Best parallelism, no atomics, skips FFT
|
|
74
|
+
- **Performance**: **58.12ms @ 1M targets**
|
|
75
|
+
|
|
76
|
+
### 7. Dirichlet Per-target (Dir_t)
|
|
77
|
+
- **Strategy**: Each thread handles one target, loops through ALL frequency bins
|
|
78
|
+
- **Parallelism**: N threads (chunked)
|
|
79
|
+
- **Memory**: O(num_chunks × num_bins)
|
|
80
|
+
- **Atomics**: Required within each chunk (multiple targets write to same bin)
|
|
81
|
+
- **Performance**: 175.29ms @ 1M targets
|
|
82
|
+
|
|
83
|
+
## Key Insights
|
|
84
|
+
|
|
85
|
+
### Why Dirichlet is Faster than Slang+FFT
|
|
86
|
+
1. **Skips FFT**: Dirichlet computes frequency-domain result directly
|
|
87
|
+
2. **Same complexity**: Both are O(N × M) where M = num_bins or T
|
|
88
|
+
3. **FFT overhead**: cuFFT adds ~20ms constant overhead
|
|
89
|
+
|
|
90
|
+
### Why Chunked is Faster than Per-target
|
|
91
|
+
1. **No atomics**: Chunked per-bin writes to separate memory locations
|
|
92
|
+
2. **Cache locality**: Sequential bin iteration has better memory access patterns
|
|
93
|
+
3. **Atomic contention**: Per-target has multiple threads writing to same bin indices
|
|
94
|
+
|
|
95
|
+
### Why Per-target Chunked Improved Over Global Atomics
|
|
96
|
+
Previous per-target with global atomics: 132.58ms (Slang_t), 293.73ms (Dir_t)
|
|
97
|
+
Current per-target with chunked atomics: 76.41ms (Slang_t), 175.29ms (Dir_t)
|
|
98
|
+
|
|
99
|
+
**Improvement: ~1.7x faster** by reducing atomic contention scope from global to per-chunk.
|
|
100
|
+
|
|
101
|
+
## Memory vs Performance Trade-off
|
|
102
|
+
|
|
103
|
+
| Method | Memory (N=1M) | Time (ms) | Notes |
|
|
104
|
+
|--------|---------------|-----------|-------|
|
|
105
|
+
| PyTorch | ~6.7 GB | OOM | N × T × 16 bytes |
|
|
106
|
+
| Slang_i | ~6 KB | 2719 | T × 8 bytes |
|
|
107
|
+
| Slang_c | ~26 MB | 79 | chunks × T × 8 bytes |
|
|
108
|
+
| Slang_t | ~26 MB | 76 | chunks × T × 8 bytes |
|
|
109
|
+
| Dir_i | ~25 KB | 112 | bins × 8 bytes |
|
|
110
|
+
| Dir_c | ~105 MB | **58** | chunks × bins × 8 bytes |
|
|
111
|
+
| Dir_t | ~105 MB | 175 | chunks × bins × 8 bytes |
|
|
112
|
+
|
|
113
|
+
## Recommendations
|
|
114
|
+
|
|
115
|
+
1. **For large N (>10K targets)**: Use **Dir_c** (Dirichlet Chunked)
|
|
116
|
+
- Best performance: 58ms @ 1M targets
|
|
117
|
+
- Reasonable memory: ~105 MB
|
|
118
|
+
- No atomic contention
|
|
119
|
+
|
|
120
|
+
2. **For small N (<1K targets)**: Use **Dir_i** (Dirichlet Internal)
|
|
121
|
+
- Minimal memory overhead
|
|
122
|
+
- Fast enough for small N
|
|
123
|
+
|
|
124
|
+
3. **When FFT is required**: Use **Slang_c** or **Slang_t**
|
|
125
|
+
- Similar performance (~76-79ms @ 1M)
|
|
126
|
+
- Slang_t slightly faster for very large N
|
|
127
|
+
|
|
128
|
+
4. **Avoid**: Slang_i for large N (poor parallelism)
|
|
129
|
+
|
|
130
|
+
## Parallelization Strategy Summary
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
Per-bin (loop targets): Per-target (loop bins):
|
|
134
|
+
┌─────────────────────┐ ┌─────────────────────┐
|
|
135
|
+
│ Thread 0: bin 0 │ │ Thread 0: target 0 │
|
|
136
|
+
│ for t in targets: │ │ for b in bins: │
|
|
137
|
+
│ accumulate │ │ atomic_add[b] │
|
|
138
|
+
├─────────────────────┤ ├─────────────────────┤
|
|
139
|
+
│ Thread 1: bin 1 │ │ Thread 1: target 1 │
|
|
140
|
+
│ for t in targets: │ │ for b in bins: │
|
|
141
|
+
│ accumulate │ │ atomic_add[b] │
|
|
142
|
+
└─────────────────────┘ └─────────────────────┘
|
|
143
|
+
No atomics! Atomics required!
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The per-bin approach naturally avoids atomics because each thread writes to a unique output location.
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: witwin-radar
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Witwin Radar - Radar signal simulation
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Requires-Dist: numpy>=1.24
|
|
8
|
+
Requires-Dist: scipy>=1.10
|
|
9
|
+
Requires-Dist: torch>=2.0
|
|
10
|
+
Requires-Dist: witwin>=0.0.1
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# WiTwin Radar
|
|
14
|
+
|
|
15
|
+
A GPU-accelerated, differentiable FMCW radar simulator for generating synthetic radar data from 3D scenes. It combines Mitsuba ray tracing with custom CUDA kernels and exposes a single public workflow:
|
|
16
|
+
|
|
17
|
+
`Scene -> Simulation -> Result`
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
import numpy as np
|
|
23
|
+
import torch
|
|
24
|
+
|
|
25
|
+
from witwin.radar import Radar
|
|
26
|
+
from witwin.radar.sigproc import process_pc, process_rd
|
|
27
|
+
|
|
28
|
+
config = {
|
|
29
|
+
"num_tx": 3,
|
|
30
|
+
"num_rx": 4,
|
|
31
|
+
"fc": 77e9,
|
|
32
|
+
"slope": 60.012,
|
|
33
|
+
"adc_samples": 256,
|
|
34
|
+
"adc_start_time": 6,
|
|
35
|
+
"sample_rate": 4400,
|
|
36
|
+
"idle_time": 7,
|
|
37
|
+
"ramp_end_time": 65,
|
|
38
|
+
"chirp_per_frame": 128,
|
|
39
|
+
"frame_per_second": 10,
|
|
40
|
+
"num_doppler_bins": 128,
|
|
41
|
+
"num_range_bins": 256,
|
|
42
|
+
"num_angle_bins": 64,
|
|
43
|
+
"power": 15,
|
|
44
|
+
"tx_loc": [[0, 0, 0], [4, 0, 0], [2, 1, 0]],
|
|
45
|
+
"rx_loc": [[-6, 0, 0], [-5, 0, 0], [-4, 0, 0], [-3, 0, 0]],
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
radar = Radar(config, backend="pytorch", device="cpu")
|
|
49
|
+
|
|
50
|
+
point = np.array([[0.0, 0.0, -3.0]], dtype=np.float32)
|
|
51
|
+
velocity = np.array([[0.0, 0.0, 0.01]], dtype=np.float32)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def interp(t):
|
|
55
|
+
positions = torch.tensor(point + velocity * t, dtype=torch.float32, device=radar.device)
|
|
56
|
+
intensities = torch.ones((positions.shape[0],), dtype=torch.float32, device=radar.device)
|
|
57
|
+
return intensities, positions
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
frame = radar.mimo(interp, t0=0)
|
|
61
|
+
pc = process_pc(radar, frame)
|
|
62
|
+
rd, _, ranges, vels = process_rd(radar, frame)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Scene API
|
|
66
|
+
|
|
67
|
+
Use `Sensor(...)` to define the radar pose, `Scene.set_sensor(...)` to configure the default scene sensor, and `Scene.add_*` methods for scene assembly.
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from witwin.core import Material, Structure
|
|
71
|
+
from witwin.radar import Scene, Sensor, Simulation
|
|
72
|
+
|
|
73
|
+
scene = Scene(device="cpu").set_sensor(
|
|
74
|
+
origin=(0.0, 0.0, 0.0),
|
|
75
|
+
target=(0.0, 0.0, -1.0),
|
|
76
|
+
up=(0.0, 1.0, 0.0),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
scene.add_structure(
|
|
80
|
+
Structure(
|
|
81
|
+
name="car_body",
|
|
82
|
+
geometry=car_body_mesh,
|
|
83
|
+
material=Material(eps_r=3.0),
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
scene.add_mesh(name="wheel_fl", vertices=wheel_vertices, faces=wheel_faces, dynamic=True)
|
|
87
|
+
scene.add_structure_motion(
|
|
88
|
+
"wheel_fl",
|
|
89
|
+
rotation={
|
|
90
|
+
"axis": (0.0, 1.0, 0.0),
|
|
91
|
+
"angular_velocity": 32.0,
|
|
92
|
+
"origin": (0.0, 0.0, 0.0),
|
|
93
|
+
"space": "local",
|
|
94
|
+
},
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
result = Simulation.mimo(
|
|
98
|
+
scene,
|
|
99
|
+
config=config,
|
|
100
|
+
backend="pytorch",
|
|
101
|
+
sampling="triangle",
|
|
102
|
+
motion_sampling="chirp",
|
|
103
|
+
device="cpu",
|
|
104
|
+
).run()
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Available mutating scene methods:
|
|
108
|
+
|
|
109
|
+
- `Scene.set_sensor(...)`
|
|
110
|
+
- `Scene.add_structure(...)`
|
|
111
|
+
- `Scene.add_mesh(...)`
|
|
112
|
+
- `Scene.add_smpl(...)`
|
|
113
|
+
- `Scene.add_structure_motion(...)`
|
|
114
|
+
- `Scene.update_structure(...)`
|
|
115
|
+
- `Scene.update_structure_motion(...)`
|
|
116
|
+
- `Scene.clear_structure_motion(...)`
|
|
117
|
+
|
|
118
|
+
## Features
|
|
119
|
+
|
|
120
|
+
- Three solver backends: `pytorch`, `slang`, `dirichlet`
|
|
121
|
+
- Ray tracing through Mitsuba with differentiable scene support
|
|
122
|
+
- Shared-core geometry and structure primitives
|
|
123
|
+
- SMPL body support through `Scene.add_smpl(...)`
|
|
124
|
+
- Optional per-structure rigid motion with parent inheritance
|
|
125
|
+
- Multi-radar orchestration through `Simulation.mimo_group(...)`
|
|
126
|
+
- Torch-native DSP pipeline for range/Doppler processing and point-cloud extraction
|
|
127
|
+
- Optional antenna pattern, polarization, noise-model, and receiver-chain configuration
|
|
128
|
+
|
|
129
|
+
## Running Tests
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
cd radar
|
|
133
|
+
pytest tests/
|
|
134
|
+
pytest tests/ --gpu
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Installation
|
|
138
|
+
|
|
139
|
+
Requires Python 3.10+ and a CUDA-capable environment for the CUDA backends.
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
pip install -r requirements.txt
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Core dependencies include `torch`, `numpy`, `slangtorch`, `tqdm`, `matplotlib`, and `scipy`. Optional rendering dependencies are `mitsuba` and `drjit`.
|