pyarchinit-photogrammetry 1.2.2__tar.gz → 1.3.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.
- {pyarchinit_photogrammetry-1.2.2/src/pyarchinit_photogrammetry.egg-info → pyarchinit_photogrammetry-1.3.0}/PKG-INFO +5 -2
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/pyproject.toml +3 -2
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/__init__.py +4 -1
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/_env.py +7 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/_native.py +39 -0
- pyarchinit_photogrammetry-1.3.0/src/pyarchinit_photogrammetry/ai/learned_net.py +117 -0
- pyarchinit_photogrammetry-1.3.0/src/pyarchinit_photogrammetry/ai/learned_worker.py +67 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/ai/sam_env.py +19 -16
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/cli.py +109 -11
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/core/chunk.py +12 -7
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/core/progress.py +14 -1
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/core/versions.py +1 -1
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/doctor.py +24 -2
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/chunks.py +42 -18
- pyarchinit_photogrammetry-1.3.0/src/pyarchinit_photogrammetry/engine/colmap_log.py +206 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/dense.py +157 -4
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/fieldcheck.py +1 -1
- pyarchinit_photogrammetry-1.3.0/src/pyarchinit_photogrammetry/engine/learned.py +436 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/multispectral.py +456 -33
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/ortho.py +116 -14
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/precision.py +1 -1
- pyarchinit_photogrammetry-1.3.0/src/pyarchinit_photogrammetry/engine/probe.py +207 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/sfm.py +433 -54
- pyarchinit_photogrammetry-1.3.0/src/pyarchinit_photogrammetry/engine/thermal.py +1588 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/dialogs.py +39 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/flow_layout.py +18 -1
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/icons.py +2 -1
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/image_views.py +51 -16
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/main_window.py +473 -40
- pyarchinit_photogrammetry-1.3.0/src/pyarchinit_photogrammetry/gui/multispectral_pane.py +413 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/panels.py +26 -2
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/translations/en.json +130 -1
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/worker.py +117 -10
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/io/qgis_link.py +5 -2
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0/src/pyarchinit_photogrammetry.egg-info}/PKG-INFO +5 -2
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry.egg-info/SOURCES.txt +11 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry.egg-info/requires.txt +5 -1
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_alignment_modes.py +67 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_chunks.py +1 -1
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_cleaning.py +13 -6
- pyarchinit_photogrammetry-1.3.0/tests/test_colmap_log.py +85 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_gui.py +35 -0
- pyarchinit_photogrammetry-1.3.0/tests/test_learned.py +184 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_multispectral.py +208 -5
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_native.py +139 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_nodes.py +4 -1
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_ortho.py +37 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_plugins.py +13 -0
- pyarchinit_photogrammetry-1.3.0/tests/test_probe.py +96 -0
- pyarchinit_photogrammetry-1.3.0/tests/test_progress_dialog.py +81 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_satellite.py +5 -2
- pyarchinit_photogrammetry-1.3.0/tests/test_thermal.py +772 -0
- pyarchinit_photogrammetry-1.2.2/src/pyarchinit_photogrammetry/gui/multispectral_pane.py +0 -195
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/LICENSE +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/README.md +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/setup.cfg +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/ppg_launcher.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/__main__.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/ai/__init__.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/ai/classify.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/ai/learn.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/ai/sam.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/ai/sam_worker.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/app.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/archaeo/__init__.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/archaeo/measures.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/archaeo/munsell.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/archaeo/pottery.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/archaeo/proxies.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/archaeo/relief.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/archaeo/sections.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/archaeo/stones.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/archaeo/stratigraphy.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/archaeo/terrain.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/archaeo/timeline.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/archaeo/us_proposal.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/batch.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/bim/__init__.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/bim/ifc.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/core/__init__.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/core/camera.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/core/crs.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/core/document.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/core/geometry.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/core/marker.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/core/paradata.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/core/pointcloud.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/demo.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/__init__.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/bundle.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/cleaning.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/colour.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/confidence.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/crop.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/dem.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/dem_edit.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/diagnostics.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/elevation.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/isolate.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/masks.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/meshing.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/nodes.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/normalmap.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/overviews.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/panorama.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/patches.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/quality.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/region.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/satellite.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/texturing.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/tiepoints.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/tsdf.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/video.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/visibility.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/__init__.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/__main__.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/batch_dialog.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/calibration_dialog.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/i18n.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/logging_setup.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/matches_dialog.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/model_view.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/nodes_dialog.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/plugins_dialog.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/pottery_dialog.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/resources/logo.svg +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/scene.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/start_page.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/theme.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/timeline_dialog.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/undo.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/update_dialog.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/us_proposal_dialog.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/integrations/__init__.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/integrations/metashape.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/integrations/photo3d.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/integrations/webodm.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/io/__init__.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/io/copc.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/io/exif.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/io/exports.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/io/flythrough.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/io/gpkg.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/io/i3s.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/io/mesh_ply.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/io/qfield.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/io/report.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/io/scans.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/io/web.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/markers/__init__.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/markers/gcp.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/markers/refine.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/markers/targets.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/plugins/__init__.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/plugins/api.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/plugins/index.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/plugins/manager.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/plugins/scaffold.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/pyarchinit/__init__.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/pyarchinit/bridge.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/pyarchinit/config.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/pyarchinit/database.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/pyarchinit/demo_db.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/pyarchinit/media.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/pyarchinit/pyarchinit5_schema.sql +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/testing/__init__.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/testing/synthetic.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/updates.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry.egg-info/dependency_links.txt +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry.egg-info/entry_points.txt +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry.egg-info/top_level.txt +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_archaeo.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_batch.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_bim_ai.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_bundle.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_classify.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_colour.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_dem_edit.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_dem_fill.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_dem_spikes.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_depth_mesh.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_diagnostics.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_elevation.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_env.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_exports_web.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_fieldcheck.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_fisheye_panorama.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_geometry.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_gui_cleaning.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_gui_layers.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_gui_patches.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_gui_start.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_i18n.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_i3s.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_ifc_strat.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_integrations.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_isolate.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_learn.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_marker_refine.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_markers.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_masks.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_measures.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_mesh_ply.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_meshing_fallback.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_munsell.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_normalmap.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_overviews.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_packaging.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_paradata.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_patches.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_photos.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_pipeline.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_pottery_demo.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_pottery_search.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_precision.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_proxies.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_pyarchinit.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_pyarchinit_media.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_qfield.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_quality_video.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_raster_detail.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_reference_cameras.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_region.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_regressions.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_relief.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_scalebars.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_scans.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_sfm_global_check.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_stones.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_texture.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_tiepoints.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_timeline.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_tools_3d.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_undo.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_updates.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_us_proposal.py +0 -0
- {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_versions.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyarchinit-photogrammetry
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Open-source photogrammetry engine for archaeology and BIM, integrated with PyArchInit
|
|
5
5
|
Author-email: Enzo Cocca <enzo.ccc@gmail.com>
|
|
6
6
|
License-Expression: GPL-2.0-or-later
|
|
@@ -40,10 +40,13 @@ Provides-Extra: sam
|
|
|
40
40
|
Requires-Dist: torch>=2.3; extra == "sam"
|
|
41
41
|
Requires-Dist: sam2>=1.1; extra == "sam"
|
|
42
42
|
Requires-Dist: huggingface_hub>=0.20; extra == "sam"
|
|
43
|
+
Provides-Extra: learned
|
|
44
|
+
Requires-Dist: torch>=2.3; extra == "learned"
|
|
45
|
+
Requires-Dist: kornia>=0.8; extra == "learned"
|
|
43
46
|
Provides-Extra: learn
|
|
44
47
|
Requires-Dist: scikit-learn>=1.3; extra == "learn"
|
|
45
48
|
Provides-Extra: fast
|
|
46
|
-
Requires-Dist: pyarchinit-photogrammetry-core>=0.
|
|
49
|
+
Requires-Dist: pyarchinit-photogrammetry-core>=0.4.1; extra == "fast"
|
|
47
50
|
Provides-Extra: scans
|
|
48
51
|
Requires-Dist: pye57>=0.4; extra == "scans"
|
|
49
52
|
Provides-Extra: munsell
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pyarchinit-photogrammetry"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.3.0"
|
|
8
8
|
description = "Open-source photogrammetry engine for archaeology and BIM, integrated with PyArchInit"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "GPL-2.0-or-later"
|
|
@@ -42,8 +42,9 @@ bim = ["ifcopenshell>=0.8"]
|
|
|
42
42
|
postgis = ["psycopg2-binary>=2.9; sys_platform != 'win32' or platform_machine != 'ARM64'",
|
|
43
43
|
"psycopg>=3.1; sys_platform == 'win32' and platform_machine == 'ARM64'"] # psycopg 3 over libpq on Windows ARM64
|
|
44
44
|
sam = ["torch>=2.3", "sam2>=1.1", "huggingface_hub>=0.20"] # the weights come from the Hugging Face hub
|
|
45
|
+
learned = ["torch>=2.3", "kornia>=0.8"] # learned key points + LightGlue (engine.learned), run in a worker process
|
|
45
46
|
learn = ["scikit-learn>=1.3"] # trainable point classification (ai.learn)
|
|
46
|
-
fast = ["pyarchinit-photogrammetry-core>=0.
|
|
47
|
+
fast = ["pyarchinit-photogrammetry-core>=0.4.1"] # native kernels in Rust (native/), optional
|
|
47
48
|
scans = ["pye57>=0.4"] # laser scans in E57 (io.scans; PTX needs nothing)
|
|
48
49
|
munsell = ["colour-science>=0.4.4"] # Munsell notation of the colours (archaeo.munsell)
|
|
49
50
|
gui = ["PySide6>=6.6", "pyvista>=0.43", "pyvistaqt>=0.13", "qtawesome>=1.3", "scikit-learn>=1.3",
|
|
@@ -19,9 +19,12 @@ Quick start::
|
|
|
19
19
|
chunk.build_orthomosaic()
|
|
20
20
|
doc.save("scavo.ppg")
|
|
21
21
|
"""
|
|
22
|
+
from ._env import glog_flushes_each_line as _glog_flushes_each_line
|
|
22
23
|
from ._env import load_open3d_first as _load_open3d_first
|
|
23
24
|
from ._env import use_bundled_proj_data as _use_bundled_proj_data
|
|
24
25
|
|
|
26
|
+
_glog_flushes_each_line() # before pycolmap loads: its log is tailed for the progress (see _env.py)
|
|
27
|
+
|
|
25
28
|
_load_open3d_first() # Open3D's OpenMP runtime must be bound before rasterio/scipy (see _env.py)
|
|
26
29
|
|
|
27
30
|
_use_bundled_proj_data() # before anything opens a CRS (see _env.py)
|
|
@@ -32,7 +35,7 @@ from .core import (ARCHAEO_CLASSES, ASPRS_CLASSES, Camera, Chunk, Document, Mark
|
|
|
32
35
|
from .core.progress import Cancelled, Progress, print_progress
|
|
33
36
|
from . import demo # noqa: E402 (ppg.demo.create_demo)
|
|
34
37
|
|
|
35
|
-
__version__ = "1.
|
|
38
|
+
__version__ = "1.3.0"
|
|
36
39
|
|
|
37
40
|
__all__ = ["app", "Document", "Chunk", "Camera", "Sensor", "Marker", "Projection", "Reference", "ScaleBar",
|
|
38
41
|
"PointCloud", "ASPRS_CLASSES", "ARCHAEO_CLASSES", "Progress", "Cancelled", "print_progress", "__version__"]
|
|
@@ -82,3 +82,10 @@ def skip_open3d_extras() -> None:
|
|
|
82
82
|
|
|
83
83
|
plotly.draw_plotly = plotly.draw_plotly_server = unavailable
|
|
84
84
|
sys.modules[plotly.__name__] = plotly
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def glog_flushes_each_line() -> None:
|
|
88
|
+
"""COLMAP's log (glog) written to a file line by line: by default glog keeps 30 seconds of
|
|
89
|
+
lines in a buffer, and :mod:`engine.colmap_log` tails that file to show the progress of a
|
|
90
|
+
step while COLMAP runs it. Read when the library loads: call before importing pycolmap."""
|
|
91
|
+
os.environ.setdefault("GLOG_logbufsecs", "0")
|
|
@@ -116,3 +116,42 @@ def gpu_forget() -> None:
|
|
|
116
116
|
fn = getattr(core, "gpu_forget", None) if core is not None else None
|
|
117
117
|
if fn is not None:
|
|
118
118
|
fn()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def gpu_fuse(reference, neighbours, min_pixels: int = 3, max_reproj: float = 2.0, max_depth_rel: float = 0.01,
|
|
122
|
+
max_normal_deg: float = 20.0, support_tol: float = 0.01, device: int = -1):
|
|
123
|
+
"""The dense points of one depth map fused with its covisible maps on the GPU (API 7): every
|
|
124
|
+
map ``(key, depth (h x w float32) or None when the GPU holds it, (fx, fy, cx, cy), rotation
|
|
125
|
+
3x3, translation, order)``; returns ``(xyz, normals, pixel, pixels, support)``."""
|
|
126
|
+
fn = kernel("gpu_fuse")
|
|
127
|
+
if fn is None:
|
|
128
|
+
raise RuntimeError("the GPU fusion needs pyarchinit-photogrammetry-core 0.4 (API 7)")
|
|
129
|
+
return fn(reference, neighbours, int(min_pixels), float(max_reproj), float(max_depth_rel), float(max_normal_deg),
|
|
130
|
+
float(support_tol), int(device))
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def gpu_fuse_cached(device: int = -1) -> list[int]:
|
|
134
|
+
"""The keys of the depth maps the GPU keeps for the fusion."""
|
|
135
|
+
fn = kernel("gpu_fuse_cached")
|
|
136
|
+
return list(fn(int(device))) if fn is not None else []
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def gpu_fuse_forget(device: int = -1) -> None:
|
|
140
|
+
"""Drop the depth maps the GPU keeps for the fusion: their keys are image ids, unique within
|
|
141
|
+
one workspace only."""
|
|
142
|
+
fn = kernel("gpu_fuse_forget")
|
|
143
|
+
if fn is not None:
|
|
144
|
+
fn(int(device))
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def gpu_sift(image, max_features: int = 8192, first_octave: int = -1, num_octaves: int = 4, octave_resolution: int = 3,
|
|
148
|
+
peak_threshold: float = 0.02 / 3.0, edge_threshold: float = 10.0, max_orientations: int = 2,
|
|
149
|
+
upright: bool = False, device: int = -1):
|
|
150
|
+
"""SIFT key points ``(n, 4)`` float32 (x, y, scale, orientation; the centre of the first pixel
|
|
151
|
+
at (0, 0), as VLFeat) and descriptors ``(n, 128)`` uint8 of a grey float image in [0, 1] on
|
|
152
|
+
the GPU, as COLMAP's CPU extractor computes them (API 6 of the extension)."""
|
|
153
|
+
fn = kernel("gpu_sift")
|
|
154
|
+
if fn is None:
|
|
155
|
+
raise RuntimeError("the GPU SIFT needs pyarchinit-photogrammetry-core 0.4 (API 6)")
|
|
156
|
+
return fn(image, int(max_features), int(first_octave), int(num_octaves), int(octave_resolution),
|
|
157
|
+
float(peak_threshold), float(edge_threshold), int(max_orientations), bool(upright), int(device))
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Learned key points and LightGlue with kornia: the part that needs PyTorch, on its own.
|
|
2
|
+
|
|
3
|
+
The program imports it through :mod:`pyarchinit_photogrammetry.engine.learned` when PyTorch and
|
|
4
|
+
kornia are installed beside it. The packaged application has no PyTorch: there it runs in the
|
|
5
|
+
separate environment of SAM 2.1 through ``learned_worker.py``. So it imports nothing but numpy,
|
|
6
|
+
torch and kornia.
|
|
7
|
+
|
|
8
|
+
ALIKED (Zhao et al. 2023, BSD-3-Clause) or DISK (Tyszkiewicz et al. 2020, Apache-2.0) find the key points and
|
|
9
|
+
describe them with a small network; LightGlue (Lindenberger et al. 2023, Apache-2.0) matches two
|
|
10
|
+
sets of them with attention and leaves out the pairs it is not sure of. The key points come back
|
|
11
|
+
with the centre of the first pixel at 0, as OpenCV.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from collections import OrderedDict
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
|
|
19
|
+
KINDS = {"disk": "DISK + LightGlue", "aliked": "ALIKED + LightGlue"}
|
|
20
|
+
MAX_KEYPOINTS = 4096
|
|
21
|
+
CACHED_FILES = 96 # descriptor files kept in memory while the pairs are matched
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def pick_device(name: str | None = None):
|
|
25
|
+
"""CUDA, else Apple's GPU (Metal), else the CPU; ``name`` forces one."""
|
|
26
|
+
import torch
|
|
27
|
+
|
|
28
|
+
if name:
|
|
29
|
+
return torch.device(name)
|
|
30
|
+
if torch.cuda.is_available():
|
|
31
|
+
return torch.device("cuda")
|
|
32
|
+
mps = getattr(torch.backends, "mps", None)
|
|
33
|
+
if mps is not None and mps.is_available():
|
|
34
|
+
return torch.device("mps")
|
|
35
|
+
return torch.device("cpu")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Features:
|
|
39
|
+
"""The extractor of one kind and LightGlue for it, on the device."""
|
|
40
|
+
|
|
41
|
+
def __init__(self, kind: str = "disk", max_keypoints: int = MAX_KEYPOINTS, device: str | None = None):
|
|
42
|
+
import kornia.feature as KF
|
|
43
|
+
import torch
|
|
44
|
+
|
|
45
|
+
if kind not in KINDS:
|
|
46
|
+
raise ValueError(f"unknown learned features {kind!r}: {', '.join(KINDS)}")
|
|
47
|
+
self.kind = kind
|
|
48
|
+
self.max_keypoints = int(max_keypoints)
|
|
49
|
+
self.dev = pick_device(device)
|
|
50
|
+
self._torch = torch
|
|
51
|
+
self._KF = KF
|
|
52
|
+
if kind == "aliked":
|
|
53
|
+
# threshold 0: the strongest points up to the limit, whatever their score (thermal frames have weak ones)
|
|
54
|
+
self.net = KF.ALIKED.from_pretrained("aliked-n16", max_num_keypoints=self.max_keypoints,
|
|
55
|
+
detection_threshold=0.0, device=self.dev).eval()
|
|
56
|
+
else:
|
|
57
|
+
self.net = KF.DISK.from_pretrained("depth", device=self.dev).eval()
|
|
58
|
+
self.matcher = KF.LightGlueMatcher(kind).to(self.dev).eval()
|
|
59
|
+
self._files: OrderedDict[str, tuple] = OrderedDict()
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def device(self) -> str:
|
|
63
|
+
return self.dev.type
|
|
64
|
+
|
|
65
|
+
def extract(self, grey: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
|
|
66
|
+
"""Key points ``(N, 2)`` float32 (x, y) and descriptors ``(N, D)`` float16 of a grey image
|
|
67
|
+
(uint8, or float in 0..1)."""
|
|
68
|
+
torch = self._torch
|
|
69
|
+
img = np.asarray(grey)
|
|
70
|
+
scale = 255.0 if img.dtype == np.uint8 else 1.0
|
|
71
|
+
t = torch.from_numpy(np.ascontiguousarray(img, np.float32) / scale)[None, None].repeat(1, 3, 1, 1).to(self.dev)
|
|
72
|
+
with torch.inference_mode():
|
|
73
|
+
if self.kind == "disk":
|
|
74
|
+
out = self.net(t, n=self.max_keypoints, pad_if_not_divisible=True)
|
|
75
|
+
else:
|
|
76
|
+
out = self.net(t)
|
|
77
|
+
kp = out[0].keypoints.float().cpu().numpy().astype(np.float32)
|
|
78
|
+
desc = out[0].descriptors.float().cpu().numpy().astype(np.float16)
|
|
79
|
+
h, w = img.shape[:2]
|
|
80
|
+
inside = (kp[:, 0] <= w - 1) & (kp[:, 1] <= h - 1) # DISK pads to a multiple of 16
|
|
81
|
+
return np.ascontiguousarray(kp[inside]), np.ascontiguousarray(desc[inside])
|
|
82
|
+
|
|
83
|
+
def match(self, kp1, desc1, size1, kp2, desc2, size2) -> np.ndarray:
|
|
84
|
+
"""LightGlue's matches ``(M, 2)`` uint32 (indices into the two sets); ``size`` = (height, width)
|
|
85
|
+
of the image the points were found on."""
|
|
86
|
+
torch, KF = self._torch, self._KF
|
|
87
|
+
if len(kp1) < 2 or len(kp2) < 2:
|
|
88
|
+
return np.zeros((0, 2), np.uint32)
|
|
89
|
+
dev = self.dev
|
|
90
|
+
k1 = torch.from_numpy(np.ascontiguousarray(kp1, np.float32)).to(dev)
|
|
91
|
+
k2 = torch.from_numpy(np.ascontiguousarray(kp2, np.float32)).to(dev)
|
|
92
|
+
d1 = torch.from_numpy(np.ascontiguousarray(desc1, np.float32)).to(dev)
|
|
93
|
+
d2 = torch.from_numpy(np.ascontiguousarray(desc2, np.float32)).to(dev)
|
|
94
|
+
with torch.inference_mode():
|
|
95
|
+
laf1 = KF.laf_from_center_scale_ori(k1[None], torch.ones(1, len(k1), 1, 1, device=dev))
|
|
96
|
+
laf2 = KF.laf_from_center_scale_ori(k2[None], torch.ones(1, len(k2), 1, 1, device=dev))
|
|
97
|
+
_dist, idx = self.matcher(d1, d2, laf1, laf2, hw1=tuple(int(v) for v in size1),
|
|
98
|
+
hw2=tuple(int(v) for v in size2))
|
|
99
|
+
return np.ascontiguousarray(idx.cpu().numpy().astype(np.uint32).reshape(-1, 2))
|
|
100
|
+
|
|
101
|
+
def _load(self, path: str) -> tuple:
|
|
102
|
+
got = self._files.get(path)
|
|
103
|
+
if got is None:
|
|
104
|
+
with np.load(path) as z:
|
|
105
|
+
got = (z["kp"], z["desc"], tuple(int(v) for v in z["size"]))
|
|
106
|
+
self._files[path] = got
|
|
107
|
+
if len(self._files) > CACHED_FILES:
|
|
108
|
+
self._files.popitem(last=False)
|
|
109
|
+
else:
|
|
110
|
+
self._files.move_to_end(path)
|
|
111
|
+
return got
|
|
112
|
+
|
|
113
|
+
def match_files(self, a: str, b: str) -> np.ndarray:
|
|
114
|
+
""":meth:`match` on two files written by the extraction (``kp``, ``desc``, ``size``)."""
|
|
115
|
+
ka, da, sa = self._load(str(a))
|
|
116
|
+
kb, db, sb = self._load(str(b))
|
|
117
|
+
return self.match(ka, da, sa, kb, db, sb)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Worker process of the learned key points (ALIKED or DISK) and LightGlue.
|
|
2
|
+
|
|
3
|
+
Run by the separate Python environment of the packaged application (the one of SAM 2.1, with
|
|
4
|
+
PyTorch and kornia), so it imports nothing from pyarchinit_photogrammetry, only
|
|
5
|
+
``learned_net.py`` beside it. Same protocol as ``sam_worker.py``: on stdin/stdout each message is
|
|
6
|
+
an 8-byte big-endian length followed by a pickle. Requests are dicts with ``op``:
|
|
7
|
+
|
|
8
|
+
* ``init`` ``{"kind", "max_keypoints", "device"?}`` -> ``{"device"}``
|
|
9
|
+
* ``extract`` ``{"image"}`` (grey uint8) -> ``{"kp", "desc"}``
|
|
10
|
+
* ``match`` ``{"a", "b"}`` (descriptor files) -> ``{"matches"}``
|
|
11
|
+
* ``quit``
|
|
12
|
+
|
|
13
|
+
Replies are ``{"ok": True, ...}`` or ``{"ok": False, "error": traceback}``.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import pickle
|
|
19
|
+
import struct
|
|
20
|
+
import sys
|
|
21
|
+
import traceback
|
|
22
|
+
|
|
23
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _read(stream):
|
|
27
|
+
head = stream.read(8)
|
|
28
|
+
if len(head) < 8:
|
|
29
|
+
return None
|
|
30
|
+
(n,) = struct.unpack(">Q", head)
|
|
31
|
+
return pickle.loads(stream.read(n))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _write(stream, obj) -> None:
|
|
35
|
+
data = pickle.dumps(obj, protocol=4)
|
|
36
|
+
stream.write(struct.pack(">Q", len(data)) + data)
|
|
37
|
+
stream.flush()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def main() -> None:
|
|
41
|
+
stdin, stdout = sys.stdin.buffer, sys.stdout.buffer
|
|
42
|
+
sys.stdout = sys.stderr # anything the libraries print must not break the protocol
|
|
43
|
+
feats = None
|
|
44
|
+
while True:
|
|
45
|
+
req = _read(stdin)
|
|
46
|
+
if req is None or req.get("op") == "quit":
|
|
47
|
+
break
|
|
48
|
+
try:
|
|
49
|
+
op = req["op"]
|
|
50
|
+
if op == "init":
|
|
51
|
+
from learned_net import Features
|
|
52
|
+
|
|
53
|
+
feats = Features(req.get("kind", "disk"), int(req.get("max_keypoints", 4096)), req.get("device"))
|
|
54
|
+
_write(stdout, {"ok": True, "device": feats.device})
|
|
55
|
+
elif op == "extract":
|
|
56
|
+
kp, desc = feats.extract(req["image"])
|
|
57
|
+
_write(stdout, {"ok": True, "kp": kp, "desc": desc})
|
|
58
|
+
elif op == "match":
|
|
59
|
+
_write(stdout, {"ok": True, "matches": feats.match_files(req["a"], req["b"])})
|
|
60
|
+
else:
|
|
61
|
+
_write(stdout, {"ok": False, "error": f"unknown op {op!r}"})
|
|
62
|
+
except Exception: # noqa: BLE001 - every error goes back to the program
|
|
63
|
+
_write(stdout, {"ok": False, "error": traceback.format_exc()})
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
main()
|
|
@@ -9,9 +9,14 @@ its official GitHub release when it is not on the system):
|
|
|
9
9
|
The model runs in a worker process of that environment (``sam_worker.py``) and
|
|
10
10
|
:class:`ExternalSam2Segmenter` talks to it; it has the same methods as
|
|
11
11
|
:class:`~pyarchinit_photogrammetry.ai.sam.Sam2Segmenter`, so everything else is unchanged.
|
|
12
|
+
|
|
13
|
+
When PyTorch and sam2 are installed beside the program (the ``[sam]`` extra) the same worker runs
|
|
14
|
+
with this Python: never PyTorch in the program's process, where pycolmap is loaded. Each brings
|
|
15
|
+
its own OpenMP runtime, and the second to start aborts the process (``OMP: Error #15``).
|
|
12
16
|
"""
|
|
13
17
|
from __future__ import annotations
|
|
14
18
|
|
|
19
|
+
import importlib.util
|
|
15
20
|
import io
|
|
16
21
|
import json
|
|
17
22
|
import os
|
|
@@ -28,7 +33,7 @@ from pathlib import Path
|
|
|
28
33
|
|
|
29
34
|
import numpy as np
|
|
30
35
|
|
|
31
|
-
PACKAGES = ["torch", "sam2", "huggingface_hub", "numpy", "pillow"]
|
|
36
|
+
PACKAGES = ["torch", "sam2", "huggingface_hub", "numpy", "pillow", "kornia"] # kornia: learned key points (engine.learned)
|
|
32
37
|
UV_URL = "https://github.com/astral-sh/uv/releases/latest/download/uv-{target}.{ext}"
|
|
33
38
|
|
|
34
39
|
|
|
@@ -57,22 +62,21 @@ def is_installed(env: Path | None = None) -> bool:
|
|
|
57
62
|
|
|
58
63
|
|
|
59
64
|
def in_process_available() -> bool:
|
|
60
|
-
"""PyTorch and sam2
|
|
61
|
-
|
|
65
|
+
"""PyTorch and sam2 installed for this Python (pip installs with the [sam] extra), with
|
|
66
|
+
huggingface_hub that downloads the weights (sam2 does not require it). Looked up, not
|
|
67
|
+
imported: importing PyTorch here would start its OpenMP runtime beside pycolmap's."""
|
|
68
|
+
if getattr(sys, "frozen", False): # the packaged application: its executable is not a Python
|
|
69
|
+
return False
|
|
62
70
|
try:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
import torch # noqa: F401
|
|
66
|
-
|
|
67
|
-
return True
|
|
68
|
-
except Exception:
|
|
71
|
+
return all(importlib.util.find_spec(name) is not None for name in ("torch", "sam2", "huggingface_hub"))
|
|
72
|
+
except (ImportError, ValueError):
|
|
69
73
|
return False
|
|
70
74
|
|
|
71
75
|
|
|
72
76
|
def available() -> str | None:
|
|
73
|
-
"""``"
|
|
77
|
+
"""``"local"`` (the worker runs with this Python), ``"external"`` or None."""
|
|
74
78
|
if in_process_available():
|
|
75
|
-
return "
|
|
79
|
+
return "local"
|
|
76
80
|
if is_installed():
|
|
77
81
|
return "external"
|
|
78
82
|
return None
|
|
@@ -272,12 +276,11 @@ class ExternalSam2Segmenter:
|
|
|
272
276
|
|
|
273
277
|
|
|
274
278
|
def make_segmenter(model: str = "small"):
|
|
275
|
-
"""The SAM 2.1 segmenter available here
|
|
279
|
+
"""The SAM 2.1 segmenter available here, always in a worker process (see the module): with
|
|
280
|
+
this Python, with the external environment, or ImportError."""
|
|
276
281
|
mode = available()
|
|
277
|
-
if mode == "
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
return Sam2Segmenter(model)
|
|
282
|
+
if mode == "local":
|
|
283
|
+
return ExternalSam2Segmenter(model, python=sys.executable)
|
|
281
284
|
if mode == "external":
|
|
282
285
|
return ExternalSam2Segmenter(model)
|
|
283
286
|
raise ImportError("SAM 2.1 non e' installato")
|
|
@@ -473,19 +473,28 @@ def align(project: Path, quality: str = typer.Option("high", help="the accuracy:
|
|
|
473
473
|
help="the GPS positions of the photos hold the block during the "
|
|
474
474
|
"incremental alignment (default: when they exist)"),
|
|
475
475
|
keep: bool = typer.Option(False, help="keep the current alignment and add the photos not yet in it"),
|
|
476
|
-
mapper: str = typer.Option("auto", help="auto, incremental (one photo after the other)
|
|
477
|
-
"once, much faster with many photos)"
|
|
476
|
+
mapper: str = typer.Option("auto", help="auto, incremental (one photo after the other), global (all at "
|
|
477
|
+
"once, much faster with many photos) or hierarchical (clusters of "
|
|
478
|
+
"photos on the cores, then merged)"),
|
|
479
|
+
ba_ratio: Optional[float] = typer.Option(None, help="the incremental mapper adjusts the whole block when it "
|
|
480
|
+
"grows by this ratio (default 1.1; 1.3 adjusts a third "
|
|
481
|
+
"as often)"),
|
|
482
|
+
ba_refinements: Optional[int] = typer.Option(None, help="refinements of every global adjustment (default 5)"),
|
|
478
483
|
gpu: Optional[str] = typer.Option(None, help="the GPU for the matching, shared with the CPU: auto (the "
|
|
479
484
|
"best one), off, an adapter index or part of its name "
|
|
480
485
|
"(default: PPG_GPU, else auto; ppg doctor lists them)"),
|
|
486
|
+
matcher: str = typer.Option("sift", help="the points: sift (COLMAP), or learned key points matched by "
|
|
487
|
+
"LightGlue, disk or aliked (thermal frames, surfaces with little "
|
|
488
|
+
"texture; need PyTorch and kornia, the [learned] extra)"),
|
|
481
489
|
chunk: Optional[str] = None):
|
|
482
490
|
"""Match photos and align cameras, with the choices of Metashape's Align Photos."""
|
|
483
491
|
doc, ch = _open(project, chunk)
|
|
484
492
|
ch.match_photos(quality=quality, pair_selection=pairs, keypoint_limit=keypoints, tiepoint_limit=tiepoints,
|
|
485
493
|
generic=generic, reference=None if reference == "none" else reference, neighbours=neighbours,
|
|
486
494
|
apply_masks=masks, guided_matching=guided, exclude_stationary=stationary, reset=not keep,
|
|
487
|
-
gpu=gpu, progress=print_progress)
|
|
488
|
-
n = ch.align_cameras(progress=print_progress, mapper=mapper, reset=not keep, use_gps=gps, adaptive_fitting=adaptive
|
|
495
|
+
gpu=gpu, matcher=matcher, progress=print_progress)
|
|
496
|
+
n = ch.align_cameras(progress=print_progress, mapper=mapper, reset=not keep, use_gps=gps, adaptive_fitting=adaptive,
|
|
497
|
+
ba_global_ratio=ba_ratio, ba_global_refinements=ba_refinements)
|
|
489
498
|
doc.save()
|
|
490
499
|
typer.echo(f"aligned {n}/{len(ch.cameras)} photos")
|
|
491
500
|
|
|
@@ -659,7 +668,7 @@ def dense(project: Path, quality: str = "medium", filter_mode: str = "moderate",
|
|
|
659
668
|
doc, ch = _open(project, chunk)
|
|
660
669
|
ch.build_depth_maps(quality=quality, filter_mode=filter_mode, backend=backend, progress=print_progress,
|
|
661
670
|
skip_unreadable=skip_unreadable, gpu=gpu)
|
|
662
|
-
pc = ch.build_point_cloud(progress=print_progress)
|
|
671
|
+
pc = ch.build_point_cloud(progress=print_progress, gpu=gpu)
|
|
663
672
|
doc.save()
|
|
664
673
|
typer.echo(f"{len(pc):,} points")
|
|
665
674
|
|
|
@@ -1684,9 +1693,13 @@ def plugins_disable(name: str):
|
|
|
1684
1693
|
typer.echo(f"{name} disabled")
|
|
1685
1694
|
|
|
1686
1695
|
|
|
1687
|
-
@app.command("plugin", context_settings={"allow_extra_args": True, "ignore_unknown_options": True
|
|
1696
|
+
@app.command("plugin", context_settings={"allow_extra_args": True, "ignore_unknown_options": True,
|
|
1697
|
+
"help_option_names": []})
|
|
1688
1698
|
def plugin_command(ctx: typer.Context, name: Optional[str] = typer.Argument(None, help="command of a plugin")):
|
|
1689
|
-
"""Run a command of a plugin: ``ppg plugin NAME [ARGS]...``; without NAME, the commands available.
|
|
1699
|
+
"""Run a command of a plugin: ``ppg plugin NAME [ARGS]...``; without NAME, the commands available.
|
|
1700
|
+
``--help`` goes to the plugin (``ppg plugin NAME --help`` is the plugin's own help)."""
|
|
1701
|
+
if name in ("--help", "-h"):
|
|
1702
|
+
name = None
|
|
1690
1703
|
from .plugins import Manager
|
|
1691
1704
|
|
|
1692
1705
|
m = Manager()
|
|
@@ -1714,6 +1727,8 @@ def multispectral(project: Path, what: str = typer.Argument(..., help="add, pane
|
|
|
1714
1727
|
"photo of the panel (a reference-band file or "
|
|
1715
1728
|
"its capture name)"),
|
|
1716
1729
|
reference: Optional[str] = typer.Option(None, help="add: the band to align (Red by default)"),
|
|
1730
|
+
thermal: bool = typer.Option(False, help="add: the frames of a thermal camera (every radiometric image "
|
|
1731
|
+
"without a band: FLIR JPEGs, TIFFs of temperatures or integers)"),
|
|
1717
1732
|
region: Optional[str] = typer.Option(None, help="panel: x0,y0,x1,y1 of the panel in the images"),
|
|
1718
1733
|
reflectance: float = typer.Option(0.5, help="panel: its known reflectance (0-1)"),
|
|
1719
1734
|
index: str = typer.Option("NDVI", help="index/zoning: one of the indices, or a formula over the band names"),
|
|
@@ -1722,6 +1737,8 @@ def multispectral(project: Path, what: str = typer.Argument(..., help="add, pane
|
|
|
1722
1737
|
method: str = typer.Option("quantiles", help="zoning: quantiles, equal or kmeans"),
|
|
1723
1738
|
out: Optional[Path] = typer.Option(None, help="zoning: the GeoPackage to write (default beside the project)"),
|
|
1724
1739
|
resolution: Optional[float] = typer.Option(None, help="reflectance: metres per pixel (the orthomosaic's)"),
|
|
1740
|
+
balance: bool = typer.Option(True, help="reflectance: bring the photos to the same reflectance where "
|
|
1741
|
+
"they overlap (against the strips between flight lines)"),
|
|
1725
1742
|
chunk: Optional[str] = None):
|
|
1726
1743
|
"""Multispectral captures: add (one file per band, side by side), the calibration panel, the
|
|
1727
1744
|
reflectance orthomosaic (a band per band), an index raster, the zoning polygons."""
|
|
@@ -1731,10 +1748,11 @@ def multispectral(project: Path, what: str = typer.Argument(..., help="add, pane
|
|
|
1731
1748
|
if what == "add":
|
|
1732
1749
|
if source is None:
|
|
1733
1750
|
raise typer.BadParameter("add needs the folder of the captures")
|
|
1734
|
-
cams = ms.add_captures(ch, [source], reference=reference)
|
|
1751
|
+
cams = ms.add_captures(ch, [source], reference=reference, recursive=True, thermal=thermal)
|
|
1735
1752
|
doc.save()
|
|
1736
1753
|
info = ch.meta["multispectral"]
|
|
1737
|
-
typer.echo(f"{len(cams)} captures, bands {', '.join(info['bands'])},
|
|
1754
|
+
typer.echo(f"{len(cams)} captures added ({info['captures']} in the chunk), bands {', '.join(info['bands'])}, "
|
|
1755
|
+
f"reference {info['reference']}")
|
|
1738
1756
|
elif what == "bands":
|
|
1739
1757
|
info = ch.meta.get("multispectral") or {}
|
|
1740
1758
|
typer.echo(json.dumps({k: v for k, v in info.items() if k != "registration"}, indent=1))
|
|
@@ -1750,9 +1768,11 @@ def multispectral(project: Path, what: str = typer.Argument(..., help="add, pane
|
|
|
1750
1768
|
doc.save()
|
|
1751
1769
|
typer.echo("panel factors: " + ", ".join(f"{b} {f:.4g}" for b, f in factors.items()))
|
|
1752
1770
|
elif what == "reflectance":
|
|
1753
|
-
path = ms.build_reflectance(ch, resolution=resolution, progress=print_progress)
|
|
1771
|
+
path = ms.build_reflectance(ch, resolution=resolution, balance=balance, progress=print_progress)
|
|
1754
1772
|
doc.save()
|
|
1755
|
-
|
|
1773
|
+
spread = (ch.meta["multispectral"].get("balance") or {}).get("spread") or {}
|
|
1774
|
+
typer.echo(f"reflectance orthomosaic: {path}" + (" (photos balanced: " + ", ".join(
|
|
1775
|
+
f"{b} {v * 100:.1f} %" for b, v in spread.items()) + ")" if spread else ""))
|
|
1756
1776
|
elif what == "index":
|
|
1757
1777
|
if formula is None and index not in ms.INDICES:
|
|
1758
1778
|
raise typer.BadParameter(f"{index}: one of {', '.join(ms.INDICES)} or --formula")
|
|
@@ -1768,6 +1788,84 @@ def multispectral(project: Path, what: str = typer.Argument(..., help="add, pane
|
|
|
1768
1788
|
raise typer.BadParameter("what: add, bands, panel, reflectance, index or zoning")
|
|
1769
1789
|
|
|
1770
1790
|
|
|
1791
|
+
@app.command("thermal-sdk")
|
|
1792
|
+
def thermal_sdk(install: bool = typer.Option(False, help="download DJI's Thermal SDK from DJI into the program's data"),
|
|
1793
|
+
accept_license: bool = typer.Option(False, "--accept-license",
|
|
1794
|
+
help="you accept DJI's terms: the MIT licence for the samples and "
|
|
1795
|
+
"utilities such as dji_irp, DJI's SDK EULA for the rest"),
|
|
1796
|
+
folder: Optional[Path] = typer.Option(None, help="install: another folder (default: <app data>/dji_tsdk)")):
|
|
1797
|
+
"""DJI's Thermal SDK, which reads the temperatures of DJI's R-JPEGs: how the frames would be decoded
|
|
1798
|
+
here, or (``--install --accept-license``) the SDK downloaded from DJI. It is not shipped with the
|
|
1799
|
+
program; on macOS it runs in Docker Desktop."""
|
|
1800
|
+
from .engine import thermal as th
|
|
1801
|
+
|
|
1802
|
+
if install:
|
|
1803
|
+
if not accept_license:
|
|
1804
|
+
typer.echo(f"DJI's Thermal SDK is DJI's: its samples and utilities (dji_irp) are under the MIT licence, the "
|
|
1805
|
+
f"rest under DJI's SDK EULA ({th.SDK_EULA_URL}). Run again with --accept-license to download it "
|
|
1806
|
+
f"from DJI ({th.SDK_URL}, about 74 MB).")
|
|
1807
|
+
raise typer.Exit(1)
|
|
1808
|
+
path = th.install_sdk(progress=print_progress, folder=folder)
|
|
1809
|
+
typer.echo(f"DJI's Thermal SDK in {path}")
|
|
1810
|
+
typer.echo(json.dumps(th.decoder_status(), indent=1))
|
|
1811
|
+
|
|
1812
|
+
|
|
1813
|
+
@app.command()
|
|
1814
|
+
def thermal(project: Path, what: str = typer.Argument(..., help="status, params, build, anomalies or export"),
|
|
1815
|
+
target: Optional[Path] = typer.Argument(None, help="export: the file to write (.tif, .csv, .jpg)"),
|
|
1816
|
+
emissivity: Optional[float] = typer.Option(None, help="params: emissivity (factory 0.95)"),
|
|
1817
|
+
reflected: Optional[float] = typer.Option(None, help="params: reflected temperature, degrees (factory 22)"),
|
|
1818
|
+
ambient: Optional[float] = typer.Option(None, help="params: ambient temperature, degrees (factory 22)"),
|
|
1819
|
+
humidity: Optional[float] = typer.Option(None, help="params: relative humidity, percent (70)"),
|
|
1820
|
+
distance: Optional[float] = typer.Option(None, help="params: distance to the ground, metres (default: the flight height, 1-25)"),
|
|
1821
|
+
scale: Optional[float] = typer.Option(None, help="params: TIFFs of integers (WIRIS and others): degrees = value x scale + offset"),
|
|
1822
|
+
offset: Optional[float] = typer.Option(None, help="params: the offset of that conversion, degrees (centikelvin: "
|
|
1823
|
+
"--scale 0.01 --offset -273.15)"),
|
|
1824
|
+
resolution: Optional[float] = typer.Option(None, help="build: metres per pixel (default: a thermal pixel on the ground)"),
|
|
1825
|
+
balance: bool = typer.Option(True, help="build: take away the offsets between frames"),
|
|
1826
|
+
radius: float = typer.Option(2.0, help="anomalies: the radius of the surroundings, metres"),
|
|
1827
|
+
fmt: Optional[str] = typer.Option(None, "--format", help="export: tif (degrees, float32), tif16 (centikelvin), "
|
|
1828
|
+
"csv, or flir (FLIR radiometric JPEGs in tiles with world files)"),
|
|
1829
|
+
tile: int = typer.Option(2048, help="export flir: the side of the tiles, cells (at most 2800)"),
|
|
1830
|
+
palette: str = typer.Option("iron", help="export flir: the colours of the picture (iron, inferno, rainbow, grey)"),
|
|
1831
|
+
chunk: Optional[str] = None):
|
|
1832
|
+
"""Thermal frames (a DJI R-JPEG camera, MicaSense LWIR, temperature TIFFs) added with
|
|
1833
|
+
``ppg multispectral PROJECT add``: the measure's parameters, the temperature orthomosaic in
|
|
1834
|
+
degrees Celsius (DJI's Thermal SDK decodes the R-JPEGs; on macOS it runs in Docker), its
|
|
1835
|
+
anomalies, the exports that keep degrees."""
|
|
1836
|
+
from .engine import thermal as th
|
|
1837
|
+
|
|
1838
|
+
doc, ch = _open(project, chunk)
|
|
1839
|
+
if what == "status":
|
|
1840
|
+
st = th.decoder_status()
|
|
1841
|
+
typer.echo(json.dumps({"decoder": st, "params": th.params_of(ch),
|
|
1842
|
+
"thermal": {k: v for k, v in (ch.meta.get("thermal") or {}).items() if k != "registration"}},
|
|
1843
|
+
indent=1, default=str))
|
|
1844
|
+
elif what == "params":
|
|
1845
|
+
given = {k: v for k, v in (("emissivity", emissivity), ("reflected", reflected), ("ambient", ambient),
|
|
1846
|
+
("humidity", humidity), ("distance", distance), ("scale", scale), ("offset", offset))
|
|
1847
|
+
if v is not None}
|
|
1848
|
+
typer.echo(json.dumps(th.set_params(ch, **given), indent=1))
|
|
1849
|
+
doc.save()
|
|
1850
|
+
elif what == "build":
|
|
1851
|
+
path = th.build_thermal(ch, resolution=resolution, balance=balance, progress=print_progress)
|
|
1852
|
+
doc.save()
|
|
1853
|
+
info = ch.meta["thermal"]
|
|
1854
|
+
typer.echo(f"temperature orthomosaic: {path} ({info['resolution'] * 100:.1f} cm/px"
|
|
1855
|
+
+ (f", {info['stats']['p2']:.1f} - {info['stats']['p98']:.1f} degrees" if info.get("stats") else "")
|
|
1856
|
+
+ (f", offsets between frames {info['offsets']['spread']:.2f} degrees" if info.get("offsets") else "") + ")")
|
|
1857
|
+
elif what == "anomalies":
|
|
1858
|
+
path = th.anomalies(ch, radius_m=radius)
|
|
1859
|
+
doc.save()
|
|
1860
|
+
typer.echo(f"thermal anomalies: {path}")
|
|
1861
|
+
elif what == "export":
|
|
1862
|
+
if target is None:
|
|
1863
|
+
raise typer.BadParameter("export needs the file to write")
|
|
1864
|
+
typer.echo(str(th.export(ch, target, fmt, tile=tile, palette=palette)))
|
|
1865
|
+
else:
|
|
1866
|
+
raise typer.BadParameter("what: status, params, build, anomalies or export")
|
|
1867
|
+
|
|
1868
|
+
|
|
1771
1869
|
@app.command()
|
|
1772
1870
|
def satellite(project: Path, what: str = typer.Argument(..., help="info, ortho or dsm"),
|
|
1773
1871
|
scene: Path = typer.Argument(..., help="the scene (GeoTIFF with its RPC, or a sidecar beside it)"),
|
|
@@ -242,7 +242,7 @@ class Chunk:
|
|
|
242
242
|
pair_selection: str = "auto", progress: ProgressCallback | None = None,
|
|
243
243
|
exclude_stationary: bool = False, generic: bool | None = None, reference: str | None = None,
|
|
244
244
|
neighbours: int = 20, apply_masks: str = "keypoints", guided_matching: bool = False,
|
|
245
|
-
reset: bool = True, gpu: str | None = None) -> None:
|
|
245
|
+
reset: bool = True, gpu: str | None = None, matcher: str = "sift") -> None:
|
|
246
246
|
"""Detect and match features (SIFT) between photos, with the choices of Metashape's
|
|
247
247
|
*Align Photos* (see :func:`engine.sfm.match_photos`): ``quality`` (the accuracy), the
|
|
248
248
|
``generic`` preselection (None: from 40 photos up), the ``reference`` preselection
|
|
@@ -254,17 +254,20 @@ class Chunk:
|
|
|
254
254
|
there are and only matches the photos added since. The older ``pair_selection`` word
|
|
255
255
|
stands in when ``generic`` and ``reference`` are not given. ``gpu``: ``auto`` (the best
|
|
256
256
|
GPU shares the pairs with the CPU), ``off``, an adapter index or part of its name
|
|
257
|
-
(None: the ``PPG_GPU`` environment variable, ``auto`` by default).
|
|
257
|
+
(None: the ``PPG_GPU`` environment variable, ``auto`` by default). ``matcher``: ``sift``,
|
|
258
|
+
or learned key points matched by LightGlue, ``disk`` or ``aliked`` (:mod:`engine.learned`:
|
|
259
|
+
more matches on thermal frames and on surfaces with little texture)."""
|
|
258
260
|
from ..engine import sfm
|
|
259
261
|
|
|
260
262
|
sfm.match_photos(self, quality=quality, keypoint_limit=keypoint_limit, tiepoint_limit=tiepoint_limit,
|
|
261
263
|
pair_selection=pair_selection, progress=as_progress(progress),
|
|
262
264
|
exclude_stationary=exclude_stationary, generic=generic, reference=reference,
|
|
263
265
|
neighbours=neighbours, apply_masks=apply_masks, guided_matching=guided_matching,
|
|
264
|
-
reset=reset, gpu=gpu)
|
|
266
|
+
reset=reset, gpu=gpu, matcher=matcher)
|
|
265
267
|
|
|
266
268
|
def align_cameras(self, progress: ProgressCallback | None = None, mapper: str = "auto", reset: bool = True,
|
|
267
|
-
use_gps: bool | None = None, adaptive_fitting: bool = False
|
|
269
|
+
use_gps: bool | None = None, adaptive_fitting: bool = False, tiepoint_limit: int | None = None,
|
|
270
|
+
ba_global_ratio: float | None = None, ba_global_refinements: int | None = None) -> int:
|
|
268
271
|
"""Estimate camera poses and sparse tie points. Returns the number of aligned photos.
|
|
269
272
|
``mapper``: ``incremental``, ``global`` or ``auto``; ``use_gps`` (None: when the photos
|
|
270
273
|
have them): the GPS positions of the photos hold the block during the incremental
|
|
@@ -279,7 +282,8 @@ class Chunk:
|
|
|
279
282
|
from ..engine import sfm
|
|
280
283
|
|
|
281
284
|
n = sfm.align_cameras(self, progress=as_progress(progress), mapper=mapper, reset=reset, use_gps=use_gps,
|
|
282
|
-
adaptive_fitting=adaptive_fitting
|
|
285
|
+
adaptive_fitting=adaptive_fitting, tiepoint_limit=tiepoint_limit,
|
|
286
|
+
ba_global_ratio=ba_global_ratio, ba_global_refinements=ba_global_refinements)
|
|
283
287
|
self.transform = geometry.identity()
|
|
284
288
|
self.meta.pop("levelled", None)
|
|
285
289
|
self.forget_precision()
|
|
@@ -540,10 +544,11 @@ class Chunk:
|
|
|
540
544
|
dense.build_depth_maps(self, quality=quality, filter_mode=filter_mode, backend=backend,
|
|
541
545
|
progress=as_progress(progress), skip_unreadable=skip_unreadable, gpu=gpu)
|
|
542
546
|
|
|
543
|
-
def build_point_cloud(self, point_colors: bool = True, progress: ProgressCallback | None = None
|
|
547
|
+
def build_point_cloud(self, point_colors: bool = True, progress: ProgressCallback | None = None,
|
|
548
|
+
gpu: str | None = None) -> PointCloud:
|
|
544
549
|
from ..engine import dense
|
|
545
550
|
|
|
546
|
-
return dense.build_point_cloud(self, point_colors=point_colors, progress=as_progress(progress))
|
|
551
|
+
return dense.build_point_cloud(self, point_colors=point_colors, progress=as_progress(progress), gpu=gpu)
|
|
547
552
|
|
|
548
553
|
def crop_point_cloud(self, zmin: float | None = None, zmax: float | None = None, radius: float | None = None,
|
|
549
554
|
center=(0.0, 0.0), bounds=None, region=None) -> "PointCloud":
|