texture3d 0.2.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.
- texture3d-0.2.0/PKG-INFO +362 -0
- texture3d-0.2.0/README.md +339 -0
- texture3d-0.2.0/pyproject.toml +56 -0
- texture3d-0.2.0/setup.cfg +4 -0
- texture3d-0.2.0/src/texture3d/__init__.py +31 -0
- texture3d-0.2.0/src/texture3d/bake.py +159 -0
- texture3d-0.2.0/src/texture3d/blur.py +54 -0
- texture3d-0.2.0/src/texture3d/camera.py +108 -0
- texture3d-0.2.0/src/texture3d/cli.py +74 -0
- texture3d-0.2.0/src/texture3d/config.py +57 -0
- texture3d-0.2.0/src/texture3d/io.py +52 -0
- texture3d-0.2.0/src/texture3d/keyframes.py +42 -0
- texture3d-0.2.0/src/texture3d/mapping/__init__.py +31 -0
- texture3d-0.2.0/src/texture3d/mapping/atlas.py +246 -0
- texture3d-0.2.0/src/texture3d/mapping/imageops.py +80 -0
- texture3d-0.2.0/src/texture3d/mapping/labeling.py +298 -0
- texture3d-0.2.0/src/texture3d/mapping/leveling.py +548 -0
- texture3d-0.2.0/src/texture3d/mapping/mapper.py +114 -0
- texture3d-0.2.0/src/texture3d/mapping/patches.py +287 -0
- texture3d-0.2.0/src/texture3d/mapping/raster.py +185 -0
- texture3d-0.2.0/src/texture3d/mapping/scoring.py +229 -0
- texture3d-0.2.0/src/texture3d/mapping/topology.py +99 -0
- texture3d-0.2.0/src/texture3d/mapping/types.py +199 -0
- texture3d-0.2.0/src/texture3d.egg-info/PKG-INFO +362 -0
- texture3d-0.2.0/src/texture3d.egg-info/SOURCES.txt +28 -0
- texture3d-0.2.0/src/texture3d.egg-info/dependency_links.txt +1 -0
- texture3d-0.2.0/src/texture3d.egg-info/entry_points.txt +2 -0
- texture3d-0.2.0/src/texture3d.egg-info/requires.txt +9 -0
- texture3d-0.2.0/src/texture3d.egg-info/top_level.txt +1 -0
- texture3d-0.2.0/tests/test_mapping.py +321 -0
texture3d-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: texture3d
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Texture a triangle mesh from posed RGB-D frames: one keyframe per face with seam leveling
|
|
5
|
+
Author: Sai Kiran
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: texturing,rgbd,mesh,texture-mapping,3d-reconstruction
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Topic :: Multimedia :: Graphics :: 3D Modeling
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: numpy>=1.24
|
|
16
|
+
Requires-Dist: scipy>=1.10
|
|
17
|
+
Requires-Dist: opencv-python>=4.5
|
|
18
|
+
Requires-Dist: trimesh>=4.0
|
|
19
|
+
Requires-Dist: pillow>=9.0
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
22
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
23
|
+
|
|
24
|
+
# texture3d
|
|
25
|
+
|
|
26
|
+
Texture an aligned triangle mesh from posed RGB-D frames — one photograph per face, seams levelled, one atlas out.
|
|
27
|
+
|
|
28
|
+
[](#installation)
|
|
29
|
+
[](#license)
|
|
30
|
+
[](https://github.com/saikiran321/texture3d)
|
|
31
|
+
|
|
32
|
+
Not on PyPI yet. Install from this repo. The texturing library is trimesh-based and does not depend on Open3D; reconstruction (optional, same repo) does.
|
|
33
|
+
|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
*A scanner gives you geometry; a camera gives you posed photos. `texture3d` puts each photo onto the faces it sees best, levels the seams, and packs one atlas. `apt0_seq0`: 977k faces, 1000 frames, 58 keyframes, ~2 min on a laptop.*
|
|
37
|
+
|
|
38
|
+
## Quick start
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
python3 -m venv .venv
|
|
42
|
+
.venv/bin/pip install -e ".[dev]"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import texture3d
|
|
47
|
+
|
|
48
|
+
frames = texture3d.load_dataset("path/to/dataset") # directory with dataset.json
|
|
49
|
+
result = texture3d.bake("mesh.ply", frames)
|
|
50
|
+
result.export("textured_mesh.obj") # or .glb
|
|
51
|
+
print(result.stats)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
.venv/bin/texture3d path/to/dataset --mesh mesh.ply --out textured/run
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
You need a triangle mesh in the same world frame as the camera poses, plus RGB-D frames. See [Dataset layout](#dataset-layout). Without a `dataset.json` folder, build `RGBDFrame` objects yourself ([Usage](#usage)).
|
|
59
|
+
|
|
60
|
+
## Table of contents
|
|
61
|
+
|
|
62
|
+
- [Why](#why)
|
|
63
|
+
- [Features](#features)
|
|
64
|
+
- [Installation](#installation)
|
|
65
|
+
- [Usage](#usage)
|
|
66
|
+
- [CLI](#cli)
|
|
67
|
+
- [Configuration](#configuration)
|
|
68
|
+
- [How it works](#how-it-works)
|
|
69
|
+
- [RGB-D reconstruction](#rgb-d-reconstruction)
|
|
70
|
+
- [Dataset layout](#dataset-layout)
|
|
71
|
+
- [Testing](#testing)
|
|
72
|
+
- [Limitations](#limitations)
|
|
73
|
+
- [License](#license)
|
|
74
|
+
|
|
75
|
+
## Why
|
|
76
|
+
|
|
77
|
+
Averaging every view onto a texel blurs photograph detail as soon as poses or geometry are off by a few millimetres. **texture3d** assigns each face one keyframe (the view that sees it best), cuts connected same-view faces out as patches, then hides the seams with a global colour-offset solve and a local Poisson blend. The result is a single-material textured mesh (OBJ or GLB). Input UVs are ignored; output UVs follow the labelling, with vertices split along patch borders.
|
|
78
|
+
|
|
79
|
+

|
|
80
|
+
|
|
81
|
+
*Left: a captured frame. Middle: the TSDF reconstruction's per-vertex colour, i.e. every frame averaged — the carpet weave and the shoes smear. Right: `texture3d`, one photograph per face. Bottom row is the boxed region at native resolution.*
|
|
82
|
+
|
|
83
|
+
This repo also ships an Open3D TSDF reconstruction pipeline for turning a `dataset.json` sequence into `mesh.ply`. You can texture that mesh, or an untextured STL/OBJ from another engine, as long as it is aligned to the same poses.
|
|
84
|
+
|
|
85
|
+
**Status:** 0.2.0 — end-to-end usable, public API may still change.
|
|
86
|
+
|
|
87
|
+
## Features
|
|
88
|
+
|
|
89
|
+
- **One keyframe per face** — sharpness-weighted view scoring, outlier rejection on mean face colour, MRF labelling with Potts smoothness
|
|
90
|
+
- **Seam leveling** — global least-squares offsets in YCbCr, then a Poisson blend along a configurable border strip
|
|
91
|
+
- **Single atlas** — MaxRects packing (shelf packer above 4,000 patches), optional unsharp mask
|
|
92
|
+
- **Unseen-face fill** — sub-pixel faces inherit a neighbour's view instead of speckling the empty colour
|
|
93
|
+
- **Dataset-agnostic inputs** — `RGBDFrame` + `PinholeCamera`, or `load_dataset()` for the `dataset.json` layout
|
|
94
|
+
- **OBJ and GLB export** via trimesh; `result.stats` reports coverage and per-stage timings
|
|
95
|
+
|
|
96
|
+
## Installation
|
|
97
|
+
|
|
98
|
+
**Prerequisites:** Python 3.9 or newer. Texturing needs NumPy, SciPy, OpenCV, trimesh, and Pillow (pulled in by the package). Reconstruction additionally needs Open3D 0.18 and NumPy 1.x (`requirements.txt`).
|
|
99
|
+
|
|
100
|
+
### Texturing library + CLI
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
git clone https://github.com/saikiran321/texture3d.git
|
|
104
|
+
cd texture3d
|
|
105
|
+
python3 -m venv .venv
|
|
106
|
+
.venv/bin/pip install -e .
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Dev extras (pytest, ruff):
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
.venv/bin/pip install -e ".[dev]"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Reconstruction extras
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
.venv/bin/pip install -r requirements.txt
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Open3D 0.18 is built against the NumPy 1.x ABI. Keep `numpy>=1.24,<2` in that environment; NumPy 2.x can segfault during TSDF integrate.
|
|
122
|
+
|
|
123
|
+
<!-- TODO: add a PyPI one-liner when the package is published -->
|
|
124
|
+
|
|
125
|
+
## Usage
|
|
126
|
+
|
|
127
|
+
### From a `dataset.json` directory
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
import texture3d
|
|
131
|
+
|
|
132
|
+
frames = texture3d.load_dataset("assets/datasets/apt0_seq0")
|
|
133
|
+
result = texture3d.bake(
|
|
134
|
+
"assets/reconstructions/apt0_seq0/mesh.ply",
|
|
135
|
+
frames,
|
|
136
|
+
)
|
|
137
|
+
result.export("textured/apt0_seq0/textured_mesh.obj")
|
|
138
|
+
print(result.stats["textured_fraction"], result.stats["stage_seconds"])
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`BakeResult` has `.mesh` (textured `trimesh.Trimesh`), `.texture` (uint8 RGB atlas), and `.stats`.
|
|
142
|
+
|
|
143
|
+
### From frames you already have in memory
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
import numpy as np
|
|
147
|
+
import texture3d
|
|
148
|
+
from texture3d import BakeConfig, PinholeCamera, RGBDFrame
|
|
149
|
+
|
|
150
|
+
camera = PinholeCamera(width=640, height=480, fx=582.871, fy=582.871, cx=320.0, cy=240.0)
|
|
151
|
+
pose_c2w = np.eye(4) # camera-to-world, OpenCV convention (+z forward, +y down)
|
|
152
|
+
rgb = ... # HxWx3 uint8, RGB
|
|
153
|
+
depth_m = ... # HxW float32 meters, 0 = invalid
|
|
154
|
+
|
|
155
|
+
frames = [
|
|
156
|
+
RGBDFrame(pose_c2w=pose_c2w, camera=camera, rgb=rgb, depth=depth_m, depth_scale=1.0),
|
|
157
|
+
]
|
|
158
|
+
cfg = BakeConfig()
|
|
159
|
+
cfg.keyframes.rgbd_frame_gap = 1
|
|
160
|
+
result = texture3d.bake("mesh.ply", frames, cfg)
|
|
161
|
+
result.export("textured_mesh.glb")
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Poses are 4×4 **camera-to-world**. Integer depth images (and integer files) are multiplied by `depth_scale` (default `1e-3` for millimetre PNGs). Float arrays are treated as metres already.
|
|
165
|
+
|
|
166
|
+
### Tuning
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
from texture3d import BakeConfig
|
|
170
|
+
|
|
171
|
+
cfg = BakeConfig()
|
|
172
|
+
cfg.keyframes.rgbd_frame_gap = 5 # 1 keyframe per N frames
|
|
173
|
+
cfg.mapping.data_smoothness_ratio = 0.05 # lower → larger patches
|
|
174
|
+
cfg.mapping.sharpness_weight = 0.0 # disable unsharp mask
|
|
175
|
+
cfg.mapping.local_leveling_strip_width = 30
|
|
176
|
+
cfg.mapping.fill_unseen = True
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## CLI
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
texture3d DATASET_DIR --mesh mesh.ply [--out DIR] [--format obj|glb]
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
| Flag | Default | Meaning |
|
|
186
|
+
| --- | --- | --- |
|
|
187
|
+
| `--mesh` | `reconstructions/<dataset-name>/mesh.ply` | Mesh to texture (`.ply` / `.obj` / `.stl`) |
|
|
188
|
+
| `--out` | `textured/<dataset-name>` | Output directory |
|
|
189
|
+
| `--format` | `obj` | `obj` or `glb` |
|
|
190
|
+
| `--gap` | `5` | One keyframe per N frames (`1` = all) |
|
|
191
|
+
| `--outlier` | `0.06` | Drop views below this colour likelihood (`0` = off) |
|
|
192
|
+
| `--data-ratio` | `0.1` | Data cost vs smoothness; lower = larger patches |
|
|
193
|
+
| `--no-global-leveling` | off | Skip the global colour-offset solve |
|
|
194
|
+
| `--no-local-leveling` | off | Skip the Poisson border blend |
|
|
195
|
+
| `--strip-width` | `20` | Local leveling strip width in pixels |
|
|
196
|
+
| `--sharpness` | `0.5` | Unsharp-mask weight (`0` = off) |
|
|
197
|
+
| `--max-texture` | `8192` | Largest atlas edge in pixels |
|
|
198
|
+
| `--no-fill-unseen` | off | Leave sub-pixel faces in the empty colour |
|
|
199
|
+
|
|
200
|
+
Writes `textured_mesh.<format>`, `config.json`, and `summary.json`. If the default mesh path does not exist, pass `--mesh` explicitly.
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
.venv/bin/texture3d assets/datasets/apt0_seq0 \
|
|
204
|
+
--mesh assets/reconstructions/apt0_seq0/mesh.ply \
|
|
205
|
+
--out textured/apt0_seq0
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
On a 977k-face mesh with 58 keyframes (640×480, 18-core Mac) this run was about **120 s** (scoring 8 s, labelling 9 s, global leveling 15 s, local leveling 82 s, atlas 1 s), **85%** of faces textured (the rest is the TSDF back shell no camera saw), 4096 atlas.
|
|
209
|
+
|
|
210
|
+
`run_texturing.py` is a thin shim around the same CLI.
|
|
211
|
+
|
|
212
|
+
## Configuration
|
|
213
|
+
|
|
214
|
+
`BakeConfig` / `KeyframeConfig` / `MappingConfig` (`texture3d.config`):
|
|
215
|
+
|
|
216
|
+
| Name | Type | Default | Description |
|
|
217
|
+
| --- | --- | --- | --- |
|
|
218
|
+
| `keyframes.rgbd_frame_gap` | int | `5` | Least-blurry frame in every window of this many frames |
|
|
219
|
+
| `keyframes.closest_pose_translation` | float | `0.05` | Skip a candidate within this many metres of the last keyframe |
|
|
220
|
+
| `keyframes.closest_pose_rotation_angle` | float | `0.09` | Same, for relative rotation (radians) |
|
|
221
|
+
| `mapping.outlier_threshold` | float | `0.06` | Gaussian colour likelihood; `0` disables |
|
|
222
|
+
| `mapping.data_smoothness_ratio` | float | `0.1` | Data cost scale vs Potts smoothness |
|
|
223
|
+
| `mapping.global_seam_leveling` | bool | `True` | Least-squares colour offsets per (vertex, patch) |
|
|
224
|
+
| `mapping.local_seam_leveling` | bool | `True` | Poisson blend along patch borders |
|
|
225
|
+
| `mapping.local_leveling_strip_width` | int | `20` | Blend strip width in pixels |
|
|
226
|
+
| `mapping.sharpness_weight` | float | `0.5` | Unsharp mask; `0` disables |
|
|
227
|
+
| `mapping.max_texture_size` | int | `8192` | Max atlas edge (power of two) |
|
|
228
|
+
| `mapping.empty_color` | RGB tuple | `(255, 255, 255)` | Texels no patch covers |
|
|
229
|
+
| `mapping.scoring_workers` | int | `4` | Views scored in parallel |
|
|
230
|
+
| `mapping.fill_unseen` | bool | `True` | Neighbour view for sub-pixel faces |
|
|
231
|
+
| `mapping.exact_packing_limit` | int | `4000` | MaxRects up to this many patches; shelf packer beyond. `0` = always MaxRects |
|
|
232
|
+
|
|
233
|
+
## How it works
|
|
234
|
+
|
|
235
|
+
```mermaid
|
|
236
|
+
flowchart LR
|
|
237
|
+
frames[RGB-D frames] --> kf[Keyframes]
|
|
238
|
+
mesh[Triangle mesh] --> topo[Topology]
|
|
239
|
+
kf --> score[Score views]
|
|
240
|
+
topo --> score
|
|
241
|
+
score --> label[Label faces]
|
|
242
|
+
label --> patches[Patches + seams]
|
|
243
|
+
patches --> level[Seam leveling]
|
|
244
|
+
level --> atlas[Pack atlas]
|
|
245
|
+
atlas --> out[Textured mesh]
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
1. **Keyframes** — least blurry frame in each `--gap` window (Crete metric); near-duplicate poses are skipped.
|
|
249
|
+
2. **Topology** — incidence, edge adjacency, face normals smoothed over neighbours within 25°.
|
|
250
|
+
3. **Scoring** — depth-tested face maps; quality is blurred Sobel magnitude times squared cosine to the normal. Colour outliers are dropped.
|
|
251
|
+
4. **Labelling** — MRF over the face graph (views + “none”), loopy belief propagation.
|
|
252
|
+
5. **Patches** — connected same-view faces, 2 px border in that view; seam vertices where patches meet.
|
|
253
|
+
6. **Leveling** — global Tikhonov-regularised offsets (λ = 0.1), then a 20 px Poisson strip.
|
|
254
|
+
7. **Atlas** — pack, copy levelled pixels, write per-corner UVs, optional sharpen.
|
|
255
|
+
|
|
256
|
+

|
|
257
|
+
|
|
258
|
+
*Stages 4–7 on one view of `apt0_seq0`. Column 4 is the atlas itself: patches are packed largest-first, so the top-left corner holds whole surfaces (stairs, picture wall, door) at full resolution; the long tail of small patches fills the rows below. 48 % of the 4096² atlas is used.*
|
|
259
|
+
|
|
260
|
+
Faces no camera sees stay the empty colour (typical TSDF back shell). `result.stats["stage_seconds"]` times each stage.
|
|
261
|
+
|
|
262
|
+
To check a bake visually, render the textured mesh back from dataset poses and compare with the photos:
|
|
263
|
+
|
|
264
|
+

|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
# input/output check above
|
|
268
|
+
.venv/bin/python tools/make_readme_figure.py assets/datasets/apt0_seq0 textured/apt0_seq0 \
|
|
269
|
+
--frames 100,350,600,850 --out docs/figures/pipeline.png
|
|
270
|
+
# purpose / why / how figures used in this README
|
|
271
|
+
.venv/bin/python tools/make_story_figures.py assets/datasets/apt0_seq0 \
|
|
272
|
+
assets/reconstructions/apt0_seq0/mesh.ply textured/apt0_seq0 --out docs/figures
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Package layout: `src/texture3d/` is the installable library (`bake`, `camera`, `io`, `mapping/`). `rgbd_recon/` is reconstruction only.
|
|
276
|
+
|
|
277
|
+
## RGB-D reconstruction
|
|
278
|
+
|
|
279
|
+
Optional. Fuses a `dataset.json` sequence with Open3D TSDF (OpenCV depth cleanup first).
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
.venv/bin/pip install -r requirements.txt
|
|
283
|
+
.venv/bin/python run_reconstruction.py assets/datasets/apt0_seq0
|
|
284
|
+
.venv/bin/python run_reconstruction.py assets/datasets/apt0_seq0 --stride 3 --voxel 0.02
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Writes to `reconstructions/<name>/` by default: `mesh.ply`, `point_cloud.ply`, `trajectory_tum.txt`, `config.json`, `summary.json`.
|
|
288
|
+
|
|
289
|
+
`create_rgbd_dataset.py` cuts contiguous windows from a BundleFusion-style capture into that layout. Three `apt0` windows used while developing:
|
|
290
|
+
|
|
291
|
+
| Subset | Source frames |
|
|
292
|
+
| --- | --- |
|
|
293
|
+
| `apt0_seq0` | 0–999 |
|
|
294
|
+
| `apt0_seq1` | 3780–4779 |
|
|
295
|
+
| `apt0_seq2` | 7560–8559 |
|
|
296
|
+
|
|
297
|
+
Those sequences are not in the git tree (`assets/` is gitignored). Point `--output-root` / dataset paths at your own copy.
|
|
298
|
+
|
|
299
|
+
## Dataset layout
|
|
300
|
+
|
|
301
|
+
`texture3d.load_dataset(dir)` expects `dir/dataset.json`:
|
|
302
|
+
|
|
303
|
+
```json
|
|
304
|
+
{
|
|
305
|
+
"image_size": {"width": 640, "height": 480},
|
|
306
|
+
"intrinsics": {
|
|
307
|
+
"color_matrix_3x3": [[fx, 0, cx], [0, fy, cy], [0, 0, 1]],
|
|
308
|
+
"depth_matrix_3x3": null
|
|
309
|
+
},
|
|
310
|
+
"depth": {"scale_to_meters": 0.001},
|
|
311
|
+
"frames": [
|
|
312
|
+
{"frame_id": "000000", "rgb": "000000/rgb.jpg", "depth": "000000/depth.png", "pose": "000000/pose.txt"}
|
|
313
|
+
]
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Paths are relative to the dataset directory. `pose.txt` is a 4×4 camera-to-world matrix. Depth PNGs are typically uint16 millimetres.
|
|
318
|
+
|
|
319
|
+
## Testing
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
.venv/bin/pip install -e ".[dev]"
|
|
323
|
+
.venv/bin/python -m pytest -q
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
15 tests: rasterisers, labelling, atlas packing, seam-leveling helpers, and a synthetic checker-plane bake. No GPU required.
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
.venv/bin/ruff check src tests
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## Limitations
|
|
333
|
+
|
|
334
|
+
- Mesh and cameras must already share a world frame. This library does not estimate poses or reconstruct geometry (use `rgbd_recon` or another engine).
|
|
335
|
+
- Input UVs are discarded.
|
|
336
|
+
- Unseen faces (back of a TSDF shell, or never projected) stay `empty_color` unless `fill_unseen` can borrow a neighbour.
|
|
337
|
+
- Not published to PyPI; install from git.
|
|
338
|
+
- Measured timings are from one Mac; local leveling dominates on dense meshes.
|
|
339
|
+
|
|
340
|
+
## Contributing
|
|
341
|
+
|
|
342
|
+
Issues and PRs are welcome.
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
git clone https://github.com/saikiran321/texture3d.git
|
|
346
|
+
cd texture3d
|
|
347
|
+
python3 -m venv .venv
|
|
348
|
+
.venv/bin/pip install -e ".[dev]"
|
|
349
|
+
.venv/bin/python -m pytest -q
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
<!-- TODO: add CONTRIBUTING.md / CODE_OF_CONDUCT.md if you want a formal process -->
|
|
353
|
+
|
|
354
|
+
## License
|
|
355
|
+
|
|
356
|
+
MIT — see `pyproject.toml`.
|
|
357
|
+
|
|
358
|
+
<!-- TODO: add a LICENSE file at the repo root so GitHub detects the license automatically -->
|
|
359
|
+
|
|
360
|
+
—
|
|
361
|
+
|
|
362
|
+
[Sai Kiran](https://github.com/saikiran321)
|