reconstruct3d 0.1.0__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.
Files changed (42) hide show
  1. reconstruct3d-0.1.0/.github/workflows/publish.yml +49 -0
  2. reconstruct3d-0.1.0/.gitignore +33 -0
  3. reconstruct3d-0.1.0/.python-version +1 -0
  4. reconstruct3d-0.1.0/CLAUDE.md +203 -0
  5. reconstruct3d-0.1.0/LICENSE +21 -0
  6. reconstruct3d-0.1.0/PKG-INFO +416 -0
  7. reconstruct3d-0.1.0/README.md +380 -0
  8. reconstruct3d-0.1.0/backend/.gitignore +1 -0
  9. reconstruct3d-0.1.0/backend/__init__.py +0 -0
  10. reconstruct3d-0.1.0/backend/app.py +175 -0
  11. reconstruct3d-0.1.0/camera.example.json +14 -0
  12. reconstruct3d-0.1.0/camera.json +12 -0
  13. reconstruct3d-0.1.0/data/.gitkeep +0 -0
  14. reconstruct3d-0.1.0/docs.md +254 -0
  15. reconstruct3d-0.1.0/frontend/.gitignore +2 -0
  16. reconstruct3d-0.1.0/frontend/index.html +12 -0
  17. reconstruct3d-0.1.0/frontend/package-lock.json +1687 -0
  18. reconstruct3d-0.1.0/frontend/package.json +20 -0
  19. reconstruct3d-0.1.0/frontend/src/App.jsx +196 -0
  20. reconstruct3d-0.1.0/frontend/src/Viewer.jsx +131 -0
  21. reconstruct3d-0.1.0/frontend/src/api.js +24 -0
  22. reconstruct3d-0.1.0/frontend/src/main.jsx +10 -0
  23. reconstruct3d-0.1.0/frontend/src/styles.css +77 -0
  24. reconstruct3d-0.1.0/frontend/vite.config.js +13 -0
  25. reconstruct3d-0.1.0/pipeline.py +10 -0
  26. reconstruct3d-0.1.0/pyproject.toml +81 -0
  27. reconstruct3d-0.1.0/reconstruct3d/__init__.py +17 -0
  28. reconstruct3d-0.1.0/reconstruct3d/api.py +218 -0
  29. reconstruct3d-0.1.0/reconstruct3d/bundle_adjust.py +370 -0
  30. reconstruct3d-0.1.0/reconstruct3d/calibrate.py +199 -0
  31. reconstruct3d-0.1.0/reconstruct3d/cgal_mesh/.gitignore +1 -0
  32. reconstruct3d-0.1.0/reconstruct3d/cgal_mesh/CMakeLists.txt +22 -0
  33. reconstruct3d-0.1.0/reconstruct3d/cgal_mesh/mesh_reconstruct.cpp +312 -0
  34. reconstruct3d-0.1.0/reconstruct3d/chunked.py +335 -0
  35. reconstruct3d-0.1.0/reconstruct3d/cli.py +448 -0
  36. reconstruct3d-0.1.0/reconstruct3d/core.py +515 -0
  37. reconstruct3d-0.1.0/reconstruct3d/dense_mvs.py +256 -0
  38. reconstruct3d-0.1.0/reconstruct3d/init_sfm.py +151 -0
  39. reconstruct3d-0.1.0/reconstruct3d/mesh.py +99 -0
  40. reconstruct3d-0.1.0/reconstruct3d/track_sfm.py +253 -0
  41. reconstruct3d-0.1.0/reconstruct3d/viewer.py +108 -0
  42. reconstruct3d-0.1.0/uv.lock +2468 -0
@@ -0,0 +1,49 @@
1
+ name: Publish to PyPI
2
+
3
+ # Se dispara solo al empujar un tag de versión (ej. v0.2.0). El tag define la
4
+ # versión publicada (la lee hatch-vcs). Para publicar: git tag v0.2.0 && git push --tags
5
+ on:
6
+ push:
7
+ tags:
8
+ - "v*"
9
+
10
+ jobs:
11
+ build:
12
+ name: Construir distribución
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ with:
17
+ # hatch-vcs necesita el historial completo + tags para calcular la versión.
18
+ fetch-depth: 0
19
+
20
+ - uses: actions/setup-python@v5
21
+ with:
22
+ python-version: "3.12"
23
+
24
+ - name: Build (sdist + wheel)
25
+ run: |
26
+ python -m pip install --upgrade build
27
+ python -m build
28
+
29
+ - uses: actions/upload-artifact@v4
30
+ with:
31
+ name: dist
32
+ path: dist/
33
+
34
+ publish:
35
+ name: Publicar en PyPI
36
+ needs: build
37
+ runs-on: ubuntu-latest
38
+ # Debe coincidir con el "Environment name" del Trusted Publisher en PyPI.
39
+ environment: pypi
40
+ permissions:
41
+ # OIDC: token efímero para Trusted Publishing. SIN tokens ni passwords.
42
+ id-token: write
43
+ steps:
44
+ - uses: actions/download-artifact@v4
45
+ with:
46
+ name: dist
47
+ path: dist/
48
+
49
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,33 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .pytest_cache/
6
+ .venv/
7
+ venv*/
8
+ .env
9
+ .venv
10
+
11
+ # Build artifacts
12
+ build/
13
+ dist/
14
+
15
+ # IDE
16
+ .vscode/
17
+ .idea/
18
+
19
+ # Input videos (heavy binaries, keep the folder via .gitkeep)
20
+ data/*
21
+ !data/.gitkeep
22
+ !data/README.md
23
+
24
+ # Runtime outputs (regenerated by the pipeline)
25
+ outputs/
26
+ *.pkl
27
+ *.npy
28
+ *.ply
29
+ !pruebas1/*.ply
30
+
31
+ # OS noise
32
+ .DS_Store
33
+ Thumbs.db
@@ -0,0 +1 @@
1
+ 3.10
@@ -0,0 +1,203 @@
1
+ # CLAUDE.md — Contexto del proyecto para agentes de IA
2
+
3
+ > Este archivo orienta a Claude Code (y otros agentes) sobre **qué es** este
4
+ > proyecto, **cómo está organizado** y **cómo trabajar en él** sin romper nada.
5
+ > Léelo antes de proponer cambios.
6
+
7
+ ## Qué es (y qué NO es)
8
+
9
+ Pipeline **offline** de reconstrucción 3D a partir de un video monocular. El
10
+ nombre del repo dice "SLAM", pero técnicamente esto es **Structure-from-Motion
11
+ (SfM) incremental + densificación MVS-lite**, no SLAM:
12
+
13
+ - **NO** es tiempo real, **NO** hay loop-closure ni mapeo online.
14
+ - Procesa un video ya grabado por **etapas secuenciales** que escriben artefactos
15
+ a disco.
16
+ - El "tracking" de cámara se hace con **PnP** (`cv2.solvePnPRansac`), no con un
17
+ filtro recursivo.
18
+
19
+ El emparejamiento de features tiene **3 front-ends intercambiables** (no uno
20
+ solo): `sift` (default), `orb`, y `spglue` (SuperPoint + LightGlue, requiere
21
+ torch). LightGlue es **opcional**, no el camino por defecto.
22
+
23
+ ## Arquitectura: el pipeline de 6 etapas
24
+
25
+ Todas las etapas comparten un **directorio de salida** (`outputs/<frontend>/`)
26
+ donde leen/escriben artefactos. El orden importa: cada etapa consume lo que
27
+ produjo la anterior.
28
+
29
+ ```
30
+ video.mp4
31
+
32
+ ▼ [1] core.py ............ extrae features + matrices esenciales por pares
33
+ sfm_data.pkl
34
+
35
+ ▼ [2] init_sfm.py ........ triangula el "par semilla" (mejor baseline)
36
+ map_state.npy + init_cloud.ply
37
+
38
+ ▼ [3] track_sfm.py ....... registro incremental PnP + BA local + fusión
39
+ tracked_cloud.ply (nube rala/sparse)
40
+
41
+ ▼ [4] bundle_adjust.py ... BA GLOBAL opcional (refina todo el mapa)
42
+ map_state.npy (refinado)
43
+
44
+ ▼ [5] dense_mvs.py ....... densificación stereo SGBM + fusión multi-vista
45
+ dense_cloud.ply (nube densa)
46
+
47
+ ▼ [6] viewer.py .......... visor POV interactivo (proyecta puntos sobre video)
48
+ ```
49
+
50
+ ### Estructura de paquete (importante)
51
+
52
+ El código se reorganizó como **paquete instalable `reconstruct3d/`**. Los módulos
53
+ de la tabla viven ahora en `reconstruct3d/<módulo>.py` (no en la raíz), con
54
+ **imports absolutos** `from reconstruct3d.X import ...`. Piezas nuevas:
55
+
56
+ - `reconstruct3d/api.py` — **API de alto nivel** (clase `Pipeline` con callback
57
+ `on_event` para progreso). Es la forma recomendada de usar la librería desde
58
+ código; `reconstruct3d/__init__.py` exporta `Pipeline`, `run_all`, `CameraConfig`.
59
+ - `reconstruct3d/cli.py` — el orquestador CLI (antes `pipeline.py`). El `pipeline.py`
60
+ de la raíz es solo un **shim** `from reconstruct3d.cli import main`. Console script
61
+ instalado: `reconstruct3d`.
62
+ - `reconstruct3d/cgal_mesh/` — el programa C++ se movió aquí (mesh.py lo localiza
63
+ vía `dirname(__file__)`).
64
+ - `backend/` (FastAPI) y `frontend/` (Vite+React+Three.js) — **demo**, NO forman
65
+ parte del paquete publicado.
66
+ - `pyproject.toml` — paquete con hatchling. **torch/lightglue NO van en deps base**
67
+ (solo en el extra `spglue`), porque lightglue no está en PyPI y torch es pesado.
68
+ Para dev local, `[tool.uv.sources]` resuelve lightglue desde git.
69
+
70
+ ### Mapa de archivos
71
+
72
+ | Archivo | Rol | Entradas → Salidas |
73
+ |---|---|---|
74
+ | [pipeline.py](pipeline.py) | **Orquestador CLI**. Punto de entrada único; subcomandos `extract/init/track/ba/dense/view/all`. | — |
75
+ | [core.py](core.py) | Config de cámara, clase `Features`, front-ends (`SiftFrontEnd`, `OrbFrontEnd`, `SuperPointLightGlueFrontEnd`), `SfMDatabase`, `filter_triangulation`. **Módulo base que todos importan.** | video → `sfm_data.pkl` |
76
+ | [init_sfm.py](init_sfm.py) | Selección de par semilla + triangulación inicial. Define `export_ply`. | `sfm_data.pkl` → `map_state.npy`, `init_cloud.ply` |
77
+ | [track_sfm.py](track_sfm.py) | Registro incremental de cámaras (PnP) + inyección de puntos nuevos. | `map_state.npy` → `tracked_cloud.ply` |
78
+ | [bundle_adjust.py](bundle_adjust.py) | `run_local_ba` (ventana, usado por track), `run_bundle_adjustment` (global), `fuse_map_points`. | `map_state.npy` → `map_state.npy` |
79
+ | [dense_mvs.py](dense_mvs.py) | MVS de dos vistas (rectify + StereoSGBM) + fusión por votación de vóxel. **Paralelizado** (`jobs`). | `map_state.npy` + video → `dense_cloud.ply` |
80
+ | [viewer.py](viewer.py) | Visor interactivo OpenCV (sliders de frame y opacidad). | `map_state.npy` + video |
81
+ | [calibrate.py](calibrate.py) | Calibración de `K` desde un video de tablero (`cv2.calibrateCamera`). Modo automático e interactivo. Escribe/crea `camera.json`. | video tablero → `camera.json` |
82
+ | [chunked.py](chunked.py) | Reconstrucción por fragmentos solapados (procesos en paralelo) + alineación Sim(3) por Umeyama sobre centros de cámara compartidos + fusión. | video → `merged_cloud.ply`, `merged_state.npy` |
83
+ | [mesh.py](mesh.py) + [cgal_mesh/](cgal_mesh/) | Mallado de la nube densa con **CGAL** (C++). `mesh.py` compila el binario bajo demanda (CMake) y lo invoca; `mesh_reconstruct.cpp` hace denoise + Advancing Front / Poisson. | `dense_cloud.ply` → `mesh.ply` |
84
+
85
+ ### Estructuras de datos clave
86
+
87
+ - **`CameraConfig`** (core.py): intrínsecos `K`, `dist_coeffs`, `PROC_SIZE`.
88
+ Parametrizable y serializable (`to_dict`/`from_dict`/`from_json`). `from_dict`
89
+ acepta K explícita o el atajo `fx/fy/cx/cy` + `width/height`.
90
+ - **`Features`** (core.py): `pts (N,2)`, `descriptors`, `colors (N,3 BGR)`,
91
+ `scores`, `image_size`. Serializada en `sfm_data.pkl` vía pickle.
92
+ - **`SfMDatabase`** (core.py / `sfm_data.pkl`): `features`, `pairwise`,
93
+ `frontend_name`, **`camera`** (dict de `CameraConfig.to_dict()`). `get_camera()`
94
+ devuelve la `CameraConfig` persistida.
95
+ - **`map3d`** (dict en `map_state.npy`, cargado con `np.load(..., allow_pickle=True).item()`):
96
+ - `points (M,3)`, `colors (M,3)`
97
+ - `poses`: `{frame_id: matriz 4x4 world→cam}`
98
+ - `obs`: `{frame_id: {kp_idx: punto3d_idx}}` — la tabla de observaciones que
99
+ conecta keypoints 2D con puntos 3D. **Es el corazón del estado**; BA y
100
+ fusión la reindexan.
101
+ - `frontend`: nombre del front-end usado.
102
+ - `camera`: dict de intrínsecos (lo copia `init` desde la DB; lo leen las etapas
103
+ que solo cargan map_state: `dense`, `view`).
104
+
105
+ ## Comandos (con uv)
106
+
107
+ `uv` gestiona el entorno. **No uses `pip` ni venvs manuales.**
108
+
109
+ ```bash
110
+ uv sync # instala dependencias base (sift/orb)
111
+ uv sync --extra spglue # + torch/torchvision/lightglue (para --frontend spglue)
112
+
113
+ # Pipeline completo end-to-end:
114
+ uv run python pipeline.py all data/video.mp4
115
+ uv run python pipeline.py all data/video.mp4 --frontend spglue --no-dense
116
+
117
+ # Calibración / paralelización / videos largos:
118
+ uv run python pipeline.py calibrate data/calib.mp4 --board 9x6 # crea camera.json
119
+ uv run python pipeline.py all data/video.mp4 --jobs 8 # paraleliza
120
+ uv run python pipeline.py chunked data/video.mp4 --chunk 80 --overlap 20 --chunk-jobs 4
121
+ uv run python pipeline.py mesh --method afront # malla CGAL (requiere CGAL nativo)
122
+
123
+ # Etapa por etapa (mismo --out implícito = outputs/<frontend>/):
124
+ uv run python pipeline.py extract data/video.mp4
125
+ uv run python pipeline.py init
126
+ uv run python pipeline.py track
127
+ uv run python pipeline.py ba # opcional pero recomendado
128
+ uv run python pipeline.py dense data/video.mp4
129
+ uv run python pipeline.py view data/video.mp4
130
+ ```
131
+
132
+ Los scripts individuales (`core.py`, etc.) **siguen funcionando** vía
133
+ `uv run python core.py video.mp4 --out ...`; el orquestador solo los encadena.
134
+
135
+ ## Convenciones del código
136
+
137
+ - **Idioma:** comentarios, docstrings y mensajes de consola en **español**.
138
+ Los nombres de variables/funciones en inglés. Mantén esa mezcla.
139
+ - **Comentarios densos y explicativos:** el código existente comenta el *porqué*
140
+ de cada decisión geométrica/numérica (chiralidad, baseline, gauge del BA…).
141
+ Iguala ese nivel; no añadas comentarios triviales.
142
+ - **Convención de poses:** todas las matrices `4x4` son **world→cam**. El centro
143
+ de cámara es `-R.T @ t`. Respétalo en todo el código nuevo.
144
+ - **Front-ends:** para añadir uno, hereda de `FrontEnd` (core.py), implementa
145
+ `extract`/`match` y regístralo en `_FRONTEND_REGISTRY`. Los imports pesados
146
+ (torch) van **lazy** dentro de `__init__`.
147
+ - **Salida vía `export_ply`** (init_sfm.py): la nube se exporta **centrada** en
148
+ la mediana y los colores se guardan BGR→RGB. Las cámaras van en rojo.
149
+
150
+ ## Gotchas / cosas que romper sin querer
151
+
152
+ - **Pickles atados al layout.** `sfm_data.pkl` guarda objetos `Features`; los
153
+ scripts hacen `sys.modules['__main__'].Features = Features` para poder
154
+ deserializarlos. Si reorganizas `core.py` como paquete, los pickles viejos
155
+ dejan de cargar (`ModuleNotFoundError`). Los artefactos en `outputs/` del repo
156
+ son de un layout empaquetado anterior y **no cargan con los scripts planos
157
+ actuales** — regenéralos con `extract`.
158
+ - **Intrínsecos por dispositivo.** `K` depende de la cámara que grabó. Se
159
+ configura con `--camera tu_camara.json` en `extract`/`all` (ver
160
+ `camera.example.json`), o se genera con `calibrate`. Se persiste en la DB +
161
+ map_state, propagándose a todas las etapas. **No vuelvas a hardcodear K**. `K` y
162
+ `PROC_SIZE` deben corresponder a la MISMA resolución (el frame se redimensiona a
163
+ `PROC_SIZE` antes de extraer). La resolución de calibración (`proc_size`) debe
164
+ tener el punto principal ≈ centro: si `cx,cy` no caen cerca de `W/2,H/2`, la
165
+ resolución es incorrecta.
166
+ - **Paralelismo y thread-safety.** Extracción/matching (core) y densificación
167
+ (dense_mvs) usan `ThreadPoolExecutor` con `--jobs`. Los objetos de OpenCV
168
+ (SIFT/ORB/BFMatcher/StereoSGBM) **no son thread-safe compartidos** → cada hilo
169
+ crea el suyo vía `threading.local` (`tls_frontend`, `_tls_sgbm`). Si añades una
170
+ etapa paralela, replica ese patrón; no compartas el detector entre hilos. Para
171
+ `spglue` el paralelismo se fuerza a 1 (`resolve_workers`). Los resultados
172
+ paralelos son idénticos al modo secuencial (verificado).
173
+ - **Mallado nativo (CGAL).** El paso `mesh` NO es Python puro: usa un binario C++
174
+ (`cgal_mesh/mesh_reconstruct.cpp`) que enlaza **CGAL** (header-only) + boost/gmp/
175
+ mpfr/eigen, compilado con CMake. `mesh.py` lo compila bajo demanda en
176
+ `cgal_mesh/build/` la primera vez. Si CGAL no está, `all` **omite** el mallado
177
+ con un aviso (no falla). CGAL 6.x cambió APIs respecto a 5.x: `property_map`
178
+ devuelve `std::optional`, los parámetros usan `CGAL::parameters::`. Advancing
179
+ Front conserva el color (vértices = puntos de entrada); Poisson genera vértices
180
+ nuevos y transfiere color por KD-tree (vecino más cercano).
181
+ - **Chunking y alineación.** `chunked.py` reconstruye fragmentos en **procesos**
182
+ separados (`ProcessPoolExecutor`, start method `spawn` en macOS → el worker
183
+ `_run_chunk_worker` y su `cfg` deben ser top-level/picklables). La alineación
184
+ necesita **≥4 frames de solape no degenerados**: con <4 la Sim(3) de Umeyama
185
+ queda subdeterminada (ajusta el solape pero no recupera la rotación global).
186
+ Todos los chunks muestrean la MISMA rejilla global de `k_skip` para que los
187
+ frames del solape tengan índices absolutos idénticos.
188
+ - **Reset del estado.** Si `track` se estanca, vuelve a correr `init` antes de
189
+ reintentar (reinicia `map_state.npy`).
190
+ - **`req.txt` es legacy** (un `pip freeze` inflado con open3d/dash/sklearn que el
191
+ código NO usa). La fuente de verdad de dependencias es `pyproject.toml`.
192
+ - **Escenas degeneradas.** Paneos sobre superficies planas o arranques de
193
+ casi-rotación pura no producen seed viable (paralaje ~0). Usa `--start-seconds`
194
+ para saltar arranques malos.
195
+ - **macOS/Wayland + visor.** `viewer.py` abre ventanas OpenCV HighGUI; en
196
+ entornos headless o Wayland puede fallar. En Arch/Wayland: `env -u WAYLAND_DISPLAY ...`.
197
+
198
+ ## Carpetas
199
+
200
+ - `data/` — videos de entrada (ignorada por git salvo `.gitkeep`).
201
+ - `outputs/` — artefactos regenerables (ignorada por git).
202
+ - `pruebas1/` — **scripts experimentales antiguos** (SfM/VO previos). No forman
203
+ parte del pipeline actual; no los uses como referencia de la arquitectura viva.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 BlackMonkcr
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.