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.
Files changed (237) hide show
  1. {pyarchinit_photogrammetry-1.2.2/src/pyarchinit_photogrammetry.egg-info → pyarchinit_photogrammetry-1.3.0}/PKG-INFO +5 -2
  2. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/pyproject.toml +3 -2
  3. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/__init__.py +4 -1
  4. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/_env.py +7 -0
  5. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/_native.py +39 -0
  6. pyarchinit_photogrammetry-1.3.0/src/pyarchinit_photogrammetry/ai/learned_net.py +117 -0
  7. pyarchinit_photogrammetry-1.3.0/src/pyarchinit_photogrammetry/ai/learned_worker.py +67 -0
  8. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/ai/sam_env.py +19 -16
  9. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/cli.py +109 -11
  10. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/core/chunk.py +12 -7
  11. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/core/progress.py +14 -1
  12. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/core/versions.py +1 -1
  13. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/doctor.py +24 -2
  14. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/chunks.py +42 -18
  15. pyarchinit_photogrammetry-1.3.0/src/pyarchinit_photogrammetry/engine/colmap_log.py +206 -0
  16. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/dense.py +157 -4
  17. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/fieldcheck.py +1 -1
  18. pyarchinit_photogrammetry-1.3.0/src/pyarchinit_photogrammetry/engine/learned.py +436 -0
  19. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/multispectral.py +456 -33
  20. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/ortho.py +116 -14
  21. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/precision.py +1 -1
  22. pyarchinit_photogrammetry-1.3.0/src/pyarchinit_photogrammetry/engine/probe.py +207 -0
  23. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/sfm.py +433 -54
  24. pyarchinit_photogrammetry-1.3.0/src/pyarchinit_photogrammetry/engine/thermal.py +1588 -0
  25. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/dialogs.py +39 -0
  26. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/flow_layout.py +18 -1
  27. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/icons.py +2 -1
  28. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/image_views.py +51 -16
  29. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/main_window.py +473 -40
  30. pyarchinit_photogrammetry-1.3.0/src/pyarchinit_photogrammetry/gui/multispectral_pane.py +413 -0
  31. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/panels.py +26 -2
  32. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/translations/en.json +130 -1
  33. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/worker.py +117 -10
  34. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/io/qgis_link.py +5 -2
  35. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0/src/pyarchinit_photogrammetry.egg-info}/PKG-INFO +5 -2
  36. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry.egg-info/SOURCES.txt +11 -0
  37. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry.egg-info/requires.txt +5 -1
  38. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_alignment_modes.py +67 -0
  39. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_chunks.py +1 -1
  40. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_cleaning.py +13 -6
  41. pyarchinit_photogrammetry-1.3.0/tests/test_colmap_log.py +85 -0
  42. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_gui.py +35 -0
  43. pyarchinit_photogrammetry-1.3.0/tests/test_learned.py +184 -0
  44. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_multispectral.py +208 -5
  45. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_native.py +139 -0
  46. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_nodes.py +4 -1
  47. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_ortho.py +37 -0
  48. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_plugins.py +13 -0
  49. pyarchinit_photogrammetry-1.3.0/tests/test_probe.py +96 -0
  50. pyarchinit_photogrammetry-1.3.0/tests/test_progress_dialog.py +81 -0
  51. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_satellite.py +5 -2
  52. pyarchinit_photogrammetry-1.3.0/tests/test_thermal.py +772 -0
  53. pyarchinit_photogrammetry-1.2.2/src/pyarchinit_photogrammetry/gui/multispectral_pane.py +0 -195
  54. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/LICENSE +0 -0
  55. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/README.md +0 -0
  56. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/setup.cfg +0 -0
  57. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/ppg_launcher.py +0 -0
  58. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/__main__.py +0 -0
  59. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/ai/__init__.py +0 -0
  60. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/ai/classify.py +0 -0
  61. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/ai/learn.py +0 -0
  62. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/ai/sam.py +0 -0
  63. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/ai/sam_worker.py +0 -0
  64. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/app.py +0 -0
  65. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/archaeo/__init__.py +0 -0
  66. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/archaeo/measures.py +0 -0
  67. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/archaeo/munsell.py +0 -0
  68. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/archaeo/pottery.py +0 -0
  69. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/archaeo/proxies.py +0 -0
  70. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/archaeo/relief.py +0 -0
  71. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/archaeo/sections.py +0 -0
  72. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/archaeo/stones.py +0 -0
  73. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/archaeo/stratigraphy.py +0 -0
  74. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/archaeo/terrain.py +0 -0
  75. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/archaeo/timeline.py +0 -0
  76. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/archaeo/us_proposal.py +0 -0
  77. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/batch.py +0 -0
  78. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/bim/__init__.py +0 -0
  79. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/bim/ifc.py +0 -0
  80. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/core/__init__.py +0 -0
  81. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/core/camera.py +0 -0
  82. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/core/crs.py +0 -0
  83. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/core/document.py +0 -0
  84. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/core/geometry.py +0 -0
  85. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/core/marker.py +0 -0
  86. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/core/paradata.py +0 -0
  87. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/core/pointcloud.py +0 -0
  88. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/demo.py +0 -0
  89. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/__init__.py +0 -0
  90. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/bundle.py +0 -0
  91. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/cleaning.py +0 -0
  92. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/colour.py +0 -0
  93. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/confidence.py +0 -0
  94. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/crop.py +0 -0
  95. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/dem.py +0 -0
  96. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/dem_edit.py +0 -0
  97. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/diagnostics.py +0 -0
  98. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/elevation.py +0 -0
  99. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/isolate.py +0 -0
  100. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/masks.py +0 -0
  101. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/meshing.py +0 -0
  102. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/nodes.py +0 -0
  103. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/normalmap.py +0 -0
  104. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/overviews.py +0 -0
  105. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/panorama.py +0 -0
  106. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/patches.py +0 -0
  107. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/quality.py +0 -0
  108. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/region.py +0 -0
  109. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/satellite.py +0 -0
  110. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/texturing.py +0 -0
  111. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/tiepoints.py +0 -0
  112. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/tsdf.py +0 -0
  113. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/video.py +0 -0
  114. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/engine/visibility.py +0 -0
  115. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/__init__.py +0 -0
  116. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/__main__.py +0 -0
  117. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/batch_dialog.py +0 -0
  118. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/calibration_dialog.py +0 -0
  119. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/i18n.py +0 -0
  120. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/logging_setup.py +0 -0
  121. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/matches_dialog.py +0 -0
  122. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/model_view.py +0 -0
  123. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/nodes_dialog.py +0 -0
  124. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/plugins_dialog.py +0 -0
  125. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/pottery_dialog.py +0 -0
  126. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/resources/logo.svg +0 -0
  127. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/scene.py +0 -0
  128. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/start_page.py +0 -0
  129. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/theme.py +0 -0
  130. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/timeline_dialog.py +0 -0
  131. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/undo.py +0 -0
  132. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/update_dialog.py +0 -0
  133. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/gui/us_proposal_dialog.py +0 -0
  134. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/integrations/__init__.py +0 -0
  135. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/integrations/metashape.py +0 -0
  136. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/integrations/photo3d.py +0 -0
  137. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/integrations/webodm.py +0 -0
  138. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/io/__init__.py +0 -0
  139. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/io/copc.py +0 -0
  140. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/io/exif.py +0 -0
  141. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/io/exports.py +0 -0
  142. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/io/flythrough.py +0 -0
  143. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/io/gpkg.py +0 -0
  144. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/io/i3s.py +0 -0
  145. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/io/mesh_ply.py +0 -0
  146. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/io/qfield.py +0 -0
  147. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/io/report.py +0 -0
  148. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/io/scans.py +0 -0
  149. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/io/web.py +0 -0
  150. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/markers/__init__.py +0 -0
  151. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/markers/gcp.py +0 -0
  152. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/markers/refine.py +0 -0
  153. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/markers/targets.py +0 -0
  154. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/plugins/__init__.py +0 -0
  155. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/plugins/api.py +0 -0
  156. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/plugins/index.py +0 -0
  157. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/plugins/manager.py +0 -0
  158. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/plugins/scaffold.py +0 -0
  159. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/pyarchinit/__init__.py +0 -0
  160. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/pyarchinit/bridge.py +0 -0
  161. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/pyarchinit/config.py +0 -0
  162. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/pyarchinit/database.py +0 -0
  163. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/pyarchinit/demo_db.py +0 -0
  164. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/pyarchinit/media.py +0 -0
  165. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/pyarchinit/pyarchinit5_schema.sql +0 -0
  166. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/testing/__init__.py +0 -0
  167. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/testing/synthetic.py +0 -0
  168. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry/updates.py +0 -0
  169. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry.egg-info/dependency_links.txt +0 -0
  170. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry.egg-info/entry_points.txt +0 -0
  171. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/src/pyarchinit_photogrammetry.egg-info/top_level.txt +0 -0
  172. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_archaeo.py +0 -0
  173. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_batch.py +0 -0
  174. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_bim_ai.py +0 -0
  175. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_bundle.py +0 -0
  176. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_classify.py +0 -0
  177. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_colour.py +0 -0
  178. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_dem_edit.py +0 -0
  179. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_dem_fill.py +0 -0
  180. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_dem_spikes.py +0 -0
  181. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_depth_mesh.py +0 -0
  182. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_diagnostics.py +0 -0
  183. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_elevation.py +0 -0
  184. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_env.py +0 -0
  185. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_exports_web.py +0 -0
  186. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_fieldcheck.py +0 -0
  187. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_fisheye_panorama.py +0 -0
  188. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_geometry.py +0 -0
  189. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_gui_cleaning.py +0 -0
  190. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_gui_layers.py +0 -0
  191. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_gui_patches.py +0 -0
  192. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_gui_start.py +0 -0
  193. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_i18n.py +0 -0
  194. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_i3s.py +0 -0
  195. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_ifc_strat.py +0 -0
  196. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_integrations.py +0 -0
  197. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_isolate.py +0 -0
  198. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_learn.py +0 -0
  199. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_marker_refine.py +0 -0
  200. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_markers.py +0 -0
  201. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_masks.py +0 -0
  202. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_measures.py +0 -0
  203. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_mesh_ply.py +0 -0
  204. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_meshing_fallback.py +0 -0
  205. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_munsell.py +0 -0
  206. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_normalmap.py +0 -0
  207. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_overviews.py +0 -0
  208. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_packaging.py +0 -0
  209. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_paradata.py +0 -0
  210. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_patches.py +0 -0
  211. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_photos.py +0 -0
  212. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_pipeline.py +0 -0
  213. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_pottery_demo.py +0 -0
  214. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_pottery_search.py +0 -0
  215. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_precision.py +0 -0
  216. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_proxies.py +0 -0
  217. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_pyarchinit.py +0 -0
  218. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_pyarchinit_media.py +0 -0
  219. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_qfield.py +0 -0
  220. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_quality_video.py +0 -0
  221. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_raster_detail.py +0 -0
  222. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_reference_cameras.py +0 -0
  223. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_region.py +0 -0
  224. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_regressions.py +0 -0
  225. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_relief.py +0 -0
  226. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_scalebars.py +0 -0
  227. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_scans.py +0 -0
  228. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_sfm_global_check.py +0 -0
  229. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_stones.py +0 -0
  230. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_texture.py +0 -0
  231. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_tiepoints.py +0 -0
  232. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_timeline.py +0 -0
  233. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_tools_3d.py +0 -0
  234. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_undo.py +0 -0
  235. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_updates.py +0 -0
  236. {pyarchinit_photogrammetry-1.2.2 → pyarchinit_photogrammetry-1.3.0}/tests/test_us_proposal.py +0 -0
  237. {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.2.2
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.3; extra == "fast"
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.2.2"
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.3"] # native kernels in Rust (native/), optional
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.2.2"
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 importable in this interpreter (pip installs with the [sam] extra),
61
- with huggingface_hub that downloads the weights (sam2 does not require it)."""
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
- import huggingface_hub # noqa: F401
64
- import sam2 # noqa: F401
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
- """``"inprocess"``, ``"external"`` or None."""
77
+ """``"local"`` (the worker runs with this Python), ``"external"`` or None."""
74
78
  if in_process_available():
75
- return "inprocess"
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: in process, external environment, or ImportError."""
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 == "inprocess":
278
- from .sam import Sam2Segmenter
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) or global (all at "
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'])}, reference {info['reference']}")
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
- typer.echo(f"reflectance orthomosaic: {path}")
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) -> int:
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) -> PointCloud:
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":