pyarchinit-photogrammetry 1.2.0__tar.gz → 1.2.2__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 (225) hide show
  1. {pyarchinit_photogrammetry-1.2.0/src/pyarchinit_photogrammetry.egg-info → pyarchinit_photogrammetry-1.2.2}/PKG-INFO +1 -1
  2. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/pyproject.toml +1 -1
  3. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/__init__.py +1 -1
  4. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/multispectral.py +33 -2
  5. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/gui/icons.py +3 -0
  6. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/gui/image_views.py +105 -8
  7. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/gui/main_window.py +199 -4
  8. pyarchinit_photogrammetry-1.2.2/src/pyarchinit_photogrammetry/gui/multispectral_pane.py +195 -0
  9. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/gui/panels.py +31 -0
  10. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/gui/translations/en.json +57 -0
  11. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/io/qgis_link.py +70 -5
  12. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2/src/pyarchinit_photogrammetry.egg-info}/PKG-INFO +1 -1
  13. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry.egg-info/SOURCES.txt +1 -0
  14. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_multispectral.py +94 -0
  15. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_ortho.py +4 -2
  16. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/LICENSE +0 -0
  17. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/README.md +0 -0
  18. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/setup.cfg +0 -0
  19. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/ppg_launcher.py +0 -0
  20. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/__main__.py +0 -0
  21. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/_env.py +0 -0
  22. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/_native.py +0 -0
  23. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/ai/__init__.py +0 -0
  24. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/ai/classify.py +0 -0
  25. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/ai/learn.py +0 -0
  26. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/ai/sam.py +0 -0
  27. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/ai/sam_env.py +0 -0
  28. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/ai/sam_worker.py +0 -0
  29. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/app.py +0 -0
  30. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/archaeo/__init__.py +0 -0
  31. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/archaeo/measures.py +0 -0
  32. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/archaeo/munsell.py +0 -0
  33. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/archaeo/pottery.py +0 -0
  34. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/archaeo/proxies.py +0 -0
  35. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/archaeo/relief.py +0 -0
  36. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/archaeo/sections.py +0 -0
  37. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/archaeo/stones.py +0 -0
  38. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/archaeo/stratigraphy.py +0 -0
  39. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/archaeo/terrain.py +0 -0
  40. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/archaeo/timeline.py +0 -0
  41. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/archaeo/us_proposal.py +0 -0
  42. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/batch.py +0 -0
  43. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/bim/__init__.py +0 -0
  44. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/bim/ifc.py +0 -0
  45. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/cli.py +0 -0
  46. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/core/__init__.py +0 -0
  47. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/core/camera.py +0 -0
  48. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/core/chunk.py +0 -0
  49. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/core/crs.py +0 -0
  50. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/core/document.py +0 -0
  51. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/core/geometry.py +0 -0
  52. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/core/marker.py +0 -0
  53. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/core/paradata.py +0 -0
  54. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/core/pointcloud.py +0 -0
  55. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/core/progress.py +0 -0
  56. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/core/versions.py +0 -0
  57. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/demo.py +0 -0
  58. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/doctor.py +0 -0
  59. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/__init__.py +0 -0
  60. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/bundle.py +0 -0
  61. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/chunks.py +0 -0
  62. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/cleaning.py +0 -0
  63. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/colour.py +0 -0
  64. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/confidence.py +0 -0
  65. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/crop.py +0 -0
  66. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/dem.py +0 -0
  67. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/dem_edit.py +0 -0
  68. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/dense.py +0 -0
  69. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/diagnostics.py +0 -0
  70. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/elevation.py +0 -0
  71. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/fieldcheck.py +0 -0
  72. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/isolate.py +0 -0
  73. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/masks.py +0 -0
  74. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/meshing.py +0 -0
  75. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/nodes.py +0 -0
  76. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/normalmap.py +0 -0
  77. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/ortho.py +0 -0
  78. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/overviews.py +0 -0
  79. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/panorama.py +0 -0
  80. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/patches.py +0 -0
  81. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/precision.py +0 -0
  82. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/quality.py +0 -0
  83. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/region.py +0 -0
  84. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/satellite.py +0 -0
  85. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/sfm.py +0 -0
  86. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/texturing.py +0 -0
  87. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/tiepoints.py +0 -0
  88. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/tsdf.py +0 -0
  89. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/video.py +0 -0
  90. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/engine/visibility.py +0 -0
  91. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/gui/__init__.py +0 -0
  92. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/gui/__main__.py +0 -0
  93. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/gui/batch_dialog.py +0 -0
  94. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/gui/calibration_dialog.py +0 -0
  95. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/gui/dialogs.py +0 -0
  96. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/gui/flow_layout.py +0 -0
  97. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/gui/i18n.py +0 -0
  98. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/gui/logging_setup.py +0 -0
  99. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/gui/matches_dialog.py +0 -0
  100. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/gui/model_view.py +0 -0
  101. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/gui/nodes_dialog.py +0 -0
  102. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/gui/plugins_dialog.py +0 -0
  103. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/gui/pottery_dialog.py +0 -0
  104. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/gui/resources/logo.svg +0 -0
  105. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/gui/scene.py +0 -0
  106. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/gui/start_page.py +0 -0
  107. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/gui/theme.py +0 -0
  108. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/gui/timeline_dialog.py +0 -0
  109. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/gui/undo.py +0 -0
  110. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/gui/update_dialog.py +0 -0
  111. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/gui/us_proposal_dialog.py +0 -0
  112. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/gui/worker.py +0 -0
  113. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/integrations/__init__.py +0 -0
  114. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/integrations/metashape.py +0 -0
  115. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/integrations/photo3d.py +0 -0
  116. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/integrations/webodm.py +0 -0
  117. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/io/__init__.py +0 -0
  118. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/io/copc.py +0 -0
  119. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/io/exif.py +0 -0
  120. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/io/exports.py +0 -0
  121. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/io/flythrough.py +0 -0
  122. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/io/gpkg.py +0 -0
  123. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/io/i3s.py +0 -0
  124. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/io/mesh_ply.py +0 -0
  125. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/io/qfield.py +0 -0
  126. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/io/report.py +0 -0
  127. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/io/scans.py +0 -0
  128. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/io/web.py +0 -0
  129. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/markers/__init__.py +0 -0
  130. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/markers/gcp.py +0 -0
  131. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/markers/refine.py +0 -0
  132. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/markers/targets.py +0 -0
  133. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/plugins/__init__.py +0 -0
  134. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/plugins/api.py +0 -0
  135. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/plugins/index.py +0 -0
  136. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/plugins/manager.py +0 -0
  137. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/plugins/scaffold.py +0 -0
  138. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/pyarchinit/__init__.py +0 -0
  139. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/pyarchinit/bridge.py +0 -0
  140. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/pyarchinit/config.py +0 -0
  141. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/pyarchinit/database.py +0 -0
  142. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/pyarchinit/demo_db.py +0 -0
  143. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/pyarchinit/media.py +0 -0
  144. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/pyarchinit/pyarchinit5_schema.sql +0 -0
  145. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/testing/__init__.py +0 -0
  146. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/testing/synthetic.py +0 -0
  147. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry/updates.py +0 -0
  148. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry.egg-info/dependency_links.txt +0 -0
  149. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry.egg-info/entry_points.txt +0 -0
  150. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry.egg-info/requires.txt +0 -0
  151. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/src/pyarchinit_photogrammetry.egg-info/top_level.txt +0 -0
  152. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_alignment_modes.py +0 -0
  153. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_archaeo.py +0 -0
  154. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_batch.py +0 -0
  155. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_bim_ai.py +0 -0
  156. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_bundle.py +0 -0
  157. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_chunks.py +0 -0
  158. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_classify.py +0 -0
  159. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_cleaning.py +0 -0
  160. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_colour.py +0 -0
  161. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_dem_edit.py +0 -0
  162. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_dem_fill.py +0 -0
  163. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_dem_spikes.py +0 -0
  164. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_depth_mesh.py +0 -0
  165. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_diagnostics.py +0 -0
  166. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_elevation.py +0 -0
  167. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_env.py +0 -0
  168. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_exports_web.py +0 -0
  169. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_fieldcheck.py +0 -0
  170. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_fisheye_panorama.py +0 -0
  171. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_geometry.py +0 -0
  172. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_gui.py +0 -0
  173. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_gui_cleaning.py +0 -0
  174. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_gui_layers.py +0 -0
  175. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_gui_patches.py +0 -0
  176. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_gui_start.py +0 -0
  177. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_i18n.py +0 -0
  178. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_i3s.py +0 -0
  179. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_ifc_strat.py +0 -0
  180. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_integrations.py +0 -0
  181. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_isolate.py +0 -0
  182. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_learn.py +0 -0
  183. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_marker_refine.py +0 -0
  184. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_markers.py +0 -0
  185. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_masks.py +0 -0
  186. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_measures.py +0 -0
  187. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_mesh_ply.py +0 -0
  188. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_meshing_fallback.py +0 -0
  189. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_munsell.py +0 -0
  190. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_native.py +0 -0
  191. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_nodes.py +0 -0
  192. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_normalmap.py +0 -0
  193. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_overviews.py +0 -0
  194. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_packaging.py +0 -0
  195. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_paradata.py +0 -0
  196. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_patches.py +0 -0
  197. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_photos.py +0 -0
  198. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_pipeline.py +0 -0
  199. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_plugins.py +0 -0
  200. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_pottery_demo.py +0 -0
  201. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_pottery_search.py +0 -0
  202. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_precision.py +0 -0
  203. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_proxies.py +0 -0
  204. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_pyarchinit.py +0 -0
  205. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_pyarchinit_media.py +0 -0
  206. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_qfield.py +0 -0
  207. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_quality_video.py +0 -0
  208. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_raster_detail.py +0 -0
  209. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_reference_cameras.py +0 -0
  210. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_region.py +0 -0
  211. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_regressions.py +0 -0
  212. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_relief.py +0 -0
  213. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_satellite.py +0 -0
  214. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_scalebars.py +0 -0
  215. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_scans.py +0 -0
  216. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_sfm_global_check.py +0 -0
  217. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_stones.py +0 -0
  218. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_texture.py +0 -0
  219. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_tiepoints.py +0 -0
  220. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_timeline.py +0 -0
  221. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_tools_3d.py +0 -0
  222. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_undo.py +0 -0
  223. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_updates.py +0 -0
  224. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/tests/test_us_proposal.py +0 -0
  225. {pyarchinit_photogrammetry-1.2.0 → pyarchinit_photogrammetry-1.2.2}/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.0
3
+ Version: 1.2.2
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pyarchinit-photogrammetry"
7
- version = "1.2.0"
7
+ version = "1.2.2"
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"
@@ -32,7 +32,7 @@ from .core import (ARCHAEO_CLASSES, ASPRS_CLASSES, Camera, Chunk, Document, Mark
32
32
  from .core.progress import Cancelled, Progress, print_progress
33
33
  from . import demo # noqa: E402 (ppg.demo.create_demo)
34
34
 
35
- __version__ = "1.2.0"
35
+ __version__ = "1.2.2"
36
36
 
37
37
  __all__ = ["app", "Document", "Chunk", "Camera", "Sensor", "Marker", "Projection", "Reference", "ScaleBar",
38
38
  "PointCloud", "ASPRS_CLASSES", "ARCHAEO_CLASSES", "Progress", "Cancelled", "print_progress", "__version__"]
@@ -607,8 +607,35 @@ def build_reflectance(chunk: Chunk, bands: list[str] | None = None, resolution:
607
607
 
608
608
 
609
609
  # ------------------------------------------------------------------------------ indices and zoning
610
+ def formula_bands(formula: str) -> set[str]:
611
+ """The band names a formula uses (``np`` and the numbers left out)."""
612
+ import ast
613
+
614
+ try:
615
+ tree = ast.parse(formula, mode="eval")
616
+ except SyntaxError as exc:
617
+ raise ValueError(f"formula {formula!r}: {exc.msg}") from None
618
+ return {n.id for n in ast.walk(tree) if isinstance(n, ast.Name) and n.id != "np"}
619
+
620
+
621
+ def missing_bands(formula: str, available) -> list[str]:
622
+ """The bands a formula needs that ``available`` has not got (sorted)."""
623
+ return sorted(formula_bands(formula) - set(available))
624
+
625
+
626
+ def available_indices(bands) -> list[str]:
627
+ """The indices of :data:`INDICES` whose bands are all among ``bands``."""
628
+ return [name for name, (formula, _) in INDICES.items() if not missing_bands(formula, bands)]
629
+
630
+
610
631
  def index_values(formula: str, bands: dict[str, np.ndarray]) -> np.ndarray:
611
- """``formula`` over the band arrays (names as in :data:`INDICES`); NaN where undefined."""
632
+ """``formula`` over the band arrays (names as in :data:`INDICES`); NaN where undefined. A
633
+ band the captures have not got (the thermal LWIR of a camera without it) is an error that
634
+ names it, not a crash."""
635
+ missing = missing_bands(formula, bands)
636
+ if missing:
637
+ raise ValueError(f"the formula needs the band(s) {', '.join(missing)}, which these captures have not got "
638
+ f"(bands: {', '.join(bands)})")
612
639
  names = {k: np.asarray(v, np.float32) for k, v in bands.items()}
613
640
  with np.errstate(divide="ignore", invalid="ignore"):
614
641
  out = eval(formula, {"__builtins__": {}, "np": np}, names) # noqa: S307 - our own formulas
@@ -632,6 +659,9 @@ def index_raster(chunk: Chunk, name: str, formula: str | None = None) -> Path:
632
659
  profile = src.profile.copy()
633
660
  out = index_values(formula, bands)
634
661
  profile.update(count=1, dtype="float32", nodata=float("nan"))
662
+ if not profile.get("tiled"):
663
+ profile.pop("blockxsize", None)
664
+ profile.pop("blockysize", None)
635
665
  target = chunk.data_dir / "multispectral" / f"{name}.tif"
636
666
  with rasterio.open(target, "w", **profile) as dst:
637
667
  dst.write(out, 1)
@@ -736,7 +766,8 @@ def export_zoning(chunk: Chunk, name: str, path, classes: int = 3, method: str =
736
766
  dst.write(cls, 1)
737
767
  areas = {k: round(sum(a["area_m2"] for _, a in polys if a["classe"] == k), 2) for k in range(1, classes + 1)}
738
768
  summary = {"index": name, "classes": classes, "method": method, "breaks": [round(b, 4) for b in breaks],
739
- "polygons": len(polys), "area_m2": areas, "file": str(path)}
769
+ "polygons": len(polys), "area_m2": areas, "file": str(path),
770
+ "raster": str((chunk.data_dir / "multispectral" / f"{name}_zone.tif").relative_to(chunk.data_dir))}
740
771
  ms.setdefault("zoning", {})[name] = summary
741
772
  json.dumps(summary) # serialisable
742
773
  return summary
@@ -49,6 +49,9 @@ ICONS = {
49
49
  "ifc": ("mdi6.office-building-outline", "slate"), "script": ("mdi6.language-python", "blue"),
50
50
  "plugin": ("mdi6.puzzle-outline", "violet"),
51
51
  "network": ("mdi6.lan-connect", "violet"),
52
+ "multispectral": ("mdi6.leaf", "green"), "run": ("mdi6.play-circle-outline", "green"),
53
+ "index": ("mdi6.sprout-outline", "lime"), "zones": ("mdi6.view-dashboard-outline", "amber"),
54
+ "qgis": ("mdi6.map-search-outline", "lime"),
52
55
  # PyArchInit
53
56
  "database": ("mdi6.database-outline", "olive"), "publish": ("mdi6.database-arrow-up-outline", "olive"),
54
57
  "load": ("mdi6.database-arrow-down-outline", "olive"), "refresh": ("mdi6.refresh", "grey"),
@@ -15,7 +15,9 @@ from PySide6.QtWidgets import (QGraphicsEllipseItem, QGraphicsPathItem, QGraphic
15
15
  from .i18n import tr
16
16
 
17
17
  RELIEF_KINDS = ("svf", "openness_pos", "openness_neg", "slrm", "msrm", "multi_hillshade") # archaeo.relief
18
- SCALAR_KINDS = ("dem", "diff", "sigma", "coverage", "slope", "aspect") + RELIEF_KINDS # one band of values, coloured
18
+ SCALAR_KINDS = ("dem", "diff", "sigma", "coverage", "slope", "aspect", "index", "zones") + RELIEF_KINDS # one band of values, coloured
19
+ ZONE_COLORS = [(214, 96, 77), (247, 203, 120), (178, 223, 138), (26, 152, 80), (0, 109, 44), (69, 117, 180),
20
+ (152, 78, 163), (255, 127, 0), (166, 86, 40), (120, 120, 120)] # the classes of a zoning, 1 = lowest
19
21
  US_COLORS = ["#e6194b", "#3cb44b", "#ffe119", "#4363d8", "#f58231", "#911eb4", "#46f0f0", "#f032e6", "#bcf60c",
20
22
  "#fabebe", "#008080", "#e6beff", "#9a6324", "#800000", "#aaffc3", "#808000"]
21
23
 
@@ -70,6 +72,76 @@ def dem_colors(z: np.ndarray, res: float, limits: tuple[float, float] | None = N
70
72
  return rgba
71
73
 
72
74
 
75
+ def index_limits(z: np.ndarray) -> tuple[float, float]:
76
+ """The ends of the colour ramp of a vegetation index: the 2nd and 98th percentiles, within
77
+ -1 and 1 for the normalised indices."""
78
+ valid = np.isfinite(z)
79
+ if not valid.any():
80
+ return -1.0, 1.0
81
+ lo, hi = np.percentile(z[valid], [2, 98])
82
+ if lo >= -1.0 and hi <= 1.0:
83
+ lo, hi = max(lo, -1.0), min(hi, 1.0)
84
+ return float(lo), float(hi) if hi > lo else float(lo) + 1e-6
85
+
86
+
87
+ def index_colors(z: np.ndarray, limits: tuple[float, float] | None = None) -> np.ndarray:
88
+ """A vegetation index: brown-red where low, pale yellow in the middle, dark green where
89
+ high (the ramp of the agronomic maps); transparent where no data."""
90
+ valid = np.isfinite(z)
91
+ rgba = np.zeros(z.shape + (4,), np.uint8)
92
+ if not valid.any():
93
+ return rgba
94
+ lo, hi = limits if limits is not None else index_limits(z)
95
+ t = np.clip((np.where(valid, z, lo) - lo) / max(hi - lo, 1e-9), 0, 1)
96
+ stops = np.array([[0.0, 165, 0, 38], [0.25, 244, 109, 67], [0.5, 254, 224, 139], [0.75, 102, 189, 99],
97
+ [1.0, 0, 104, 55]])
98
+ rgba[..., :3] = np.stack([np.interp(t, stops[:, 0], stops[:, k]) for k in (1, 2, 3)], -1).astype(np.uint8)
99
+ rgba[..., 3] = np.where(valid, 255, 0)
100
+ return rgba
101
+
102
+
103
+ def zone_colors(c: np.ndarray) -> np.ndarray:
104
+ """The classes of a zoning (1, 2, ...), one colour each; transparent where no class."""
105
+ valid = np.isfinite(c) & (c > 0)
106
+ rgba = np.zeros(c.shape + (4,), np.uint8)
107
+ if not valid.any():
108
+ return rgba
109
+ k = (np.where(valid, c, 1).astype(int) - 1) % len(ZONE_COLORS)
110
+ rgba[..., :3] = np.asarray(ZONE_COLORS, np.uint8)[k]
111
+ rgba[..., 3] = np.where(valid, 255, 0)
112
+ return rgba
113
+
114
+
115
+ def band_limits(path, bands: tuple[int, int, int]) -> list[tuple[float, float]]:
116
+ """The 2nd-98th percentile stretch of three bands of a multi-band raster, read at a reduced
117
+ size: the same for the whole raster and for the details."""
118
+ import rasterio
119
+ from rasterio.enums import Resampling
120
+
121
+ out = []
122
+ with rasterio.open(path) as src:
123
+ f = max(1, int(np.ceil(max(src.width, src.height) / 2048)))
124
+ for b in bands:
125
+ a = src.read(b, out_shape=(max(src.height // f, 1), max(src.width // f, 1)), resampling=Resampling.nearest,
126
+ masked=True).astype(np.float64).filled(np.nan)
127
+ v = a[np.isfinite(a)]
128
+ lo, hi = (np.percentile(v, [2, 98]) if v.size > 16 else (0.0, 1.0))
129
+ out.append((float(lo), float(hi) if hi > lo else float(lo) + 1e-6))
130
+ return out
131
+
132
+
133
+ def stretched_rgba(arr: np.ndarray, limits: list[tuple[float, float]]) -> np.ndarray:
134
+ """Three float bands ``(h, w, 3)`` stretched between their limits into RGBA, transparent
135
+ where a band has no value."""
136
+ h, w = arr.shape[:2]
137
+ rgba = np.zeros((h, w, 4), np.uint8)
138
+ valid = np.isfinite(arr).all(axis=-1)
139
+ for k, (lo, hi) in enumerate(limits[:3]):
140
+ rgba[..., k] = np.clip((np.nan_to_num(arr[..., k], nan=lo) - lo) / max(hi - lo, 1e-9) * 255, 0, 255).astype(np.uint8)
141
+ rgba[..., 3] = np.where(valid, 255, 0)
142
+ return rgba
143
+
144
+
73
145
  def diff_colors(d: np.ndarray, limit: float | None = None) -> tuple[np.ndarray, float]:
74
146
  """Differences between two DEMs: blue below zero, white at zero, red above (symmetric,
75
147
  saturated at ``limit``, default the 98th percentile of the absolute differences). Returns
@@ -191,10 +263,13 @@ def relief_colors(z: np.ndarray, kind: str, limits: tuple[float, float] | None =
191
263
 
192
264
 
193
265
  def read_window_rgba(path, kind: str, window: tuple[int, int, int, int], out_shape: tuple[int, int],
194
- dem_range: tuple[float, float] | None = None, diff_limit: float | None = None) -> np.ndarray:
266
+ dem_range: tuple[float, float] | None = None, diff_limit: float | None = None,
267
+ bands: tuple[int, int, int] | None = None, limits: list | None = None) -> np.ndarray:
195
268
  """RGBA ``(h, w, 4)`` of a window ``(col, row, width, height)`` of a raster read at
196
269
  ``out_shape`` (the overviews of the file are used when smaller), coloured like the whole
197
- raster: orthomosaic bands as they are, DEM tinted and shaded, difference of DEMs diverging."""
270
+ raster: orthomosaic bands as they are, DEM tinted and shaded, difference of DEMs diverging,
271
+ a vegetation index on its ramp, a zoning by class, a reflectance orthomosaic by three of its
272
+ bands (1-based ``bands``) stretched between ``limits``."""
198
273
  import rasterio
199
274
  from rasterio.enums import Resampling
200
275
  from rasterio.windows import Window
@@ -205,7 +280,7 @@ def read_window_rgba(path, kind: str, window: tuple[int, int, int, int], out_sha
205
280
  win = Window(col, row, width, height)
206
281
  if kind in SCALAR_KINDS:
207
282
  z = src.read(1, window=win, out_shape=(h, w), masked=True,
208
- resampling=Resampling.nearest if kind in ("coverage", "aspect") else Resampling.average
283
+ resampling=Resampling.nearest if kind in ("coverage", "aspect", "zones") else Resampling.average
209
284
  ).astype(np.float64).filled(np.nan)
210
285
  if kind == "slope":
211
286
  return slope_colors(z, diff_limit)[0]
@@ -217,10 +292,19 @@ def read_window_rgba(path, kind: str, window: tuple[int, int, int, int], out_sha
217
292
  return sigma_colors(z, diff_limit)[0]
218
293
  if kind == "coverage":
219
294
  return coverage_colors(z)
295
+ if kind == "index":
296
+ return index_colors(z, dem_range)
297
+ if kind == "zones":
298
+ return zone_colors(z)
220
299
  if kind in RELIEF_KINDS:
221
300
  return relief_colors(z, kind, dem_range)
222
301
  res = abs(src.transform.a) * width / max(w, 1)
223
302
  return dem_colors(z, res, dem_range)
303
+ if kind == "reflectance":
304
+ bands = bands or (1, 2, 3)
305
+ arr = src.read(list(bands), window=win, out_shape=(3, h, w), resampling=Resampling.average,
306
+ masked=True).astype(np.float64).filled(np.nan)
307
+ return stretched_rgba(np.moveaxis(arr, 0, -1), limits or band_limits(path, bands))
224
308
  n = min(src.count, 4)
225
309
  arr = np.moveaxis(src.read(list(range(1, n + 1)), window=win, out_shape=(n, h, w),
226
310
  resampling=Resampling.average), 0, -1)
@@ -388,7 +472,7 @@ class RasterView(_ZoomView):
388
472
  self.layers_changed.emit()
389
473
 
390
474
  # ----------------------------------------------------------- data
391
- def load(self, path: str | Path, kind: str, max_side: int = 6000) -> None:
475
+ def load(self, path: str | Path, kind: str, max_side: int = 6000, bands=None) -> None:
392
476
  import rasterio
393
477
  from rasterio.enums import Resampling
394
478
 
@@ -430,9 +514,21 @@ class RasterView(_ZoomView):
430
514
  elif kind in RELIEF_KINDS: # the stretch of the whole raster, also for the detail
431
515
  self._dem_range = relief_limits(self.z, kind)
432
516
  rgba = relief_colors(self.z, kind, self._dem_range)
517
+ elif kind == "index": # a vegetation index: its ramp between the percentiles of the whole raster
518
+ self._dem_range = index_limits(self.z)
519
+ rgba = index_colors(self.z, self._dem_range)
520
+ elif kind == "zones": # the classes of a zoning
521
+ rgba = zone_colors(self.z)
433
522
  else:
434
523
  self._dem_range = dem_limits(self.z)
435
524
  rgba = dem_colors(self.z, abs(self.tr.a), self._dem_range)
525
+ elif kind == "reflectance": # three bands of a float multi-band raster, stretched
526
+ self.z = None
527
+ self._bands = tuple(int(b) for b in (bands or (1, 2, 3)))[:3]
528
+ self._band_limits = band_limits(path, self._bands)
529
+ arr = src.read(list(self._bands), out_shape=(3, h, w), resampling=Resampling.average,
530
+ masked=True).astype(np.float64).filled(np.nan)
531
+ rgba = stretched_rgba(np.moveaxis(arr, 0, -1), self._band_limits)
436
532
  else:
437
533
  self.z = None
438
534
  n = min(src.count, 4)
@@ -502,7 +598,7 @@ class RasterView(_ZoomView):
502
598
  self._detail_key = (window, shape)
503
599
  self._detail_gen += 1
504
600
  job = (self._detail_gen, str(self.path), self.kind, window, shape, scene, self._dem_range,
505
- self.diff_limit)
601
+ self.diff_limit, getattr(self, "_bands", None), getattr(self, "_band_limits", None))
506
602
  if wait:
507
603
  self._show_detail(*self._read_detail(job))
508
604
  else:
@@ -510,8 +606,9 @@ class RasterView(_ZoomView):
510
606
 
511
607
  @staticmethod
512
608
  def _read_detail(job):
513
- gen, path, kind, window, shape, scene, dem_range, diff_limit = job
514
- rgba = read_window_rgba(path, kind if kind in SCALAR_KINDS else "ortho", window, shape, dem_range, diff_limit)
609
+ gen, path, kind, window, shape, scene, dem_range, diff_limit, bands, limits = job
610
+ kind = kind if kind in SCALAR_KINDS or kind == "reflectance" else "ortho"
611
+ rgba = read_window_rgba(path, kind, window, shape, dem_range, diff_limit, bands=bands, limits=limits)
515
612
  return gen, to_qimage(rgba), scene
516
613
 
517
614
  def _read_detail_async(self, job) -> None:
@@ -279,6 +279,19 @@ class MainWindow(QMainWindow):
279
279
  self.proxies_pane.quote_requested.connect(self.quote_proxies)
280
280
  self.proxies_pane.us_requested.connect(self._us_form_from_proxy)
281
281
  self.proxies_pane.delete_requested.connect(self.delete_proxies)
282
+ from .multispectral_pane import MultispectralPane
283
+
284
+ self.ms_pane = MultispectralPane()
285
+ self.left_tabs.addTab(self.ms_pane, icon("multispectral"), tr("Multispettrale"))
286
+ self.ms_pane.add_requested.connect(self.ms_add)
287
+ self.ms_pane.align_requested.connect(self.align_photos)
288
+ self.ms_pane.surface_requested.connect(self.ms_build_surface)
289
+ self.ms_pane.panel_requested.connect(self.ms_panel)
290
+ self.ms_pane.reflectance_requested.connect(self.ms_reflectance)
291
+ self.ms_pane.show_requested.connect(self.ms_show)
292
+ self.ms_pane.index_requested.connect(self.ms_compute_index)
293
+ self.ms_pane.zoning_requested.connect(self.ms_zoning)
294
+ self.ms_pane.qgis_requested.connect(self.open_in_qgis)
282
295
  self.sections_pane = SectionsPane()
283
296
  self.sections_pane.zoom_requested.connect(self._zoom_section)
284
297
  self.sections_pane.publish_requested.connect(self._publish_section)
@@ -933,6 +946,7 @@ class MainWindow(QMainWindow):
933
946
  step(0.95, tr("Sezioni e disegni"))
934
947
  self._load_sections()
935
948
  self.proxies_pane.set_proxies(self.chunk.meta.get("proxies", []) if self.chunk else [])
949
+ self.ms_pane.set_state(self.chunk)
936
950
  if self.history is None:
937
951
  self.history = UndoHistory(self._undo_state, self._undo_apply, lambda: self.chunk,
938
952
  on_change=self._undo_actions)
@@ -963,10 +977,12 @@ class MainWindow(QMainWindow):
963
977
  step(0.6, tr("Ortofoto e DEM"))
964
978
  self._load_raster()
965
979
  self._update_stack()
980
+ self.ms_pane.set_state(self.chunk)
966
981
 
967
- def _load_raster(self, kind: str | None = None, path=None) -> None:
982
+ def _load_raster(self, kind: str | None = None, path=None, bands=None) -> None:
968
983
  """The orthomosaic or the DEM of the chunk in the raster view (``path``: another file of
969
- that kind, e.g. a kept version or a difference of DEMs) with the drawings on top."""
984
+ that kind, e.g. a kept version or a difference of DEMs; ``bands``: the three to show of a
985
+ reflectance orthomosaic) with the drawings on top."""
970
986
  ch = self.chunk
971
987
  kind = kind or ("ortho" if ch is not None and ch.orthomosaic_path else "dem")
972
988
  if path is None:
@@ -976,7 +992,7 @@ class MainWindow(QMainWindow):
976
992
  self.ortho_view.tr = None
977
993
  self.ortho_view.scene().addText(tr("Costruisci il DEM o l'ortofoto per vederli qui"))
978
994
  return
979
- self.ortho_view.load(path, kind)
995
+ self.ortho_view.load(path, kind, bands=bands)
980
996
  if kind == "ortho" and ch.dem_path:
981
997
  self.ortho_view.set_dem(ch.dem_path)
982
998
  self._raster_version = None
@@ -1202,6 +1218,153 @@ class MainWindow(QMainWindow):
1202
1218
  self.run_task("Ortofoto di riflettanza", lambda p: ms.build_reflectance(self.chunk, resolution=v["resolution"], progress=p),
1203
1219
  lambda path: self._info(tr("Ortofoto di riflettanza"), str(path)))
1204
1220
 
1221
+ def ms_build_surface(self) -> None:
1222
+ """Depth maps, dense cloud and DEM in one step with the default choices: what the
1223
+ reflectance orthomosaic needs, for the multispectral pane."""
1224
+ if not self._need_chunk(aligned=True):
1225
+ return
1226
+ ch = self.chunk
1227
+
1228
+ def job(p):
1229
+ if not (ch.data_dir / "dense" / "stereo" / "depth_maps").exists():
1230
+ ch.build_depth_maps(quality="medium", progress=p.sub(0.0, 0.6))
1231
+ if not ch.product_path("point_cloud"):
1232
+ ch.build_point_cloud(progress=p.sub(0.6, 0.25))
1233
+ ch.build_dem(progress=p.sub(0.85, 0.15))
1234
+ return ch.dem_path
1235
+
1236
+ self.run_task("Superficie per il multispettrale", job,
1237
+ lambda path: self._info(tr("Superficie"), tr("DEM pronto: {0}", path)))
1238
+
1239
+ def _ms_meta(self) -> dict:
1240
+ return (self.chunk.meta.get("multispectral") if self.chunk is not None else None) or {}
1241
+
1242
+ def ms_show(self, kind: str, payload=None) -> None:
1243
+ """A multispectral product in the raster view: the reflectance orthomosaic by three of
1244
+ its bands (names, false colour by default), an index on its ramp, the zones by class
1245
+ (and their polygons as a layer)."""
1246
+ ms = self._ms_meta()
1247
+ ch = self.chunk
1248
+ if kind == "reflectance":
1249
+ rel = ms.get("reflectance")
1250
+ if not rel:
1251
+ self._warn("Costruisci prima l'ortofoto di riflettanza.")
1252
+ return
1253
+ names = list(ms.get("reflectance_bands") or [])
1254
+ wanted = list(payload) if payload else ["NIR", "Red", "Green"]
1255
+ idx = [names.index(b) + 1 for b in wanted if b in names]
1256
+ if len(idx) < 3:
1257
+ idx = (idx + [1, 2, 3])[:3]
1258
+ self._load_raster("reflectance", ch.data_dir / rel, bands=tuple(idx))
1259
+ self.statusBar().showMessage(tr("Riflettanza: bande {0}", ", ".join(names[i - 1] for i in idx)))
1260
+ elif kind == "index":
1261
+ info = (ms.get("indices") or {}).get(payload or "")
1262
+ if not info:
1263
+ self._warn("Calcola prima l'indice.")
1264
+ return
1265
+ self._load_raster("index", ch.data_dir / info["file"])
1266
+ lo, hi = self.ortho_view._dem_range or (-1.0, 1.0)
1267
+ self.statusBar().showMessage(tr("{0}: colori da {1} (rosso) a {2} (verde)", payload, f"{lo:.2f}", f"{hi:.2f}"))
1268
+ elif kind == "zones":
1269
+ z = (ms.get("zoning") or {}).get(payload or "")
1270
+ if not z or not z.get("raster"):
1271
+ self._warn("Fai prima la zonizzazione.")
1272
+ return
1273
+ self._load_raster("zones", ch.data_dir / z["raster"])
1274
+ self._ms_zone_layer(payload, z)
1275
+ else:
1276
+ return
1277
+ self.show_view(self.ortho_view)
1278
+
1279
+ def _ms_zone_layer(self, name: str, z: dict, limit: int = 3000) -> None:
1280
+ """The polygons of a zoning as a layer of the raster view (up to ``limit``, the largest)."""
1281
+ from ..io import gpkg
1282
+
1283
+ layer = tr("Zone {0}", name)
1284
+ if layer in self.ortho_view.layers:
1285
+ return
1286
+ path = Path(z.get("file", ""))
1287
+ if not path.exists():
1288
+ return
1289
+ try:
1290
+ rows = gpkg.read_table(path, "zone")
1291
+ except Exception as exc: # noqa: BLE001
1292
+ log.warning("zone %s: %s", path, exc)
1293
+ return
1294
+ rows = sorted(rows, key=lambda r: -(r[1].get("area_m2") or 0))[:limit]
1295
+ from .image_views import ZONE_COLORS
1296
+
1297
+ with self.ortho_view.batch():
1298
+ for geom, attrs in rows:
1299
+ polys = list(geom.geoms) if geom.geom_type == "MultiPolygon" else [geom]
1300
+ for poly in polys:
1301
+ rgb = ZONE_COLORS[(int(attrs.get("classe") or 1) - 1) % len(ZONE_COLORS)]
1302
+ self.ortho_view.add_polygon(list(poly.exterior.coords), label="", color="#%02x%02x%02x" % rgb,
1303
+ fill_alpha=0, layer=layer, attrs=dict(attrs))
1304
+ if len(rows) == limit:
1305
+ self.log_pane.info(tr("Zone {0}: nel layer i {1} poligoni più grandi; tutti sono nel GeoPackage {2}",
1306
+ name, limit, path))
1307
+
1308
+ def ms_compute_index(self, name: str, formula: str = "") -> None:
1309
+ """An index from the reflectance orthomosaic (a known one, or a formula asked for), then shown."""
1310
+ if not self._need_chunk():
1311
+ return
1312
+ from ..engine import multispectral as ms_mod
1313
+
1314
+ if not self._ms_meta().get("reflectance"):
1315
+ self._warn("Costruisci prima l'ortofoto di riflettanza.")
1316
+ return
1317
+ if name == "formula":
1318
+ text, ok = QInputDialog.getText(self, tr("Formula dell'indice"),
1319
+ tr("Formula sui nomi delle bande ({0}):", ", ".join(self._ms_meta().get("reflectance_bands") or [])),
1320
+ text="(NIR - Red) / (NIR + Red)")
1321
+ if not ok or not text.strip():
1322
+ return
1323
+ formula = text.strip()
1324
+ label, ok = QInputDialog.getText(self, tr("Nome dell'indice"), tr("Nome del raster (senza spazi):"), text="Indice")
1325
+ if not ok or not label.strip():
1326
+ return
1327
+ name = "".join(c for c in label.strip() if c.isalnum() or c in "_-") or "Indice"
1328
+ formula = formula or None
1329
+ used = formula or ms_mod.INDICES.get(name, ("", ""))[0]
1330
+ bands = self._ms_meta().get("reflectance_bands") or self._ms_meta().get("bands") or []
1331
+ try:
1332
+ missing = ms_mod.missing_bands(used, bands) if used else []
1333
+ except ValueError as exc:
1334
+ self._warn(tr("Formula non valida: {0}", exc))
1335
+ return
1336
+ if missing:
1337
+ self._warn(tr("L'indice {0} usa le bande {1}, che queste riprese non hanno (bande: {2}).", name,
1338
+ ", ".join(missing), ", ".join(bands)))
1339
+ return
1340
+ self.run_task(tr("Indice {0}", name), lambda p: ms_mod.index_raster(self.chunk, name, formula),
1341
+ lambda path: self.ms_show("index", name))
1342
+
1343
+ def ms_zoning(self, name: str, classes: int = 3, method: str = "quantiles") -> None:
1344
+ """The zoning of an index: polygons in a GeoPackage beside the project, the class raster
1345
+ shown with the polygons as a layer."""
1346
+ if not self._need_chunk():
1347
+ return
1348
+ from ..engine import multispectral as ms_mod
1349
+
1350
+ if name == "formula" or name not in (self._ms_meta().get("indices") or {}):
1351
+ self._warn("Calcola prima l'indice scelto.")
1352
+ return
1353
+ base = Path(self.doc.path).with_name(f"{Path(self.doc.path).stem}_{name}_zone.gpkg") if self.doc.path \
1354
+ else self.chunk.data_dir / "multispectral" / f"{name}_zone.gpkg"
1355
+ path, _f = QFileDialog.getSaveFileName(self, tr("GeoPackage delle zone"), str(base), "GeoPackage (*.gpkg)")
1356
+ if not path:
1357
+ return
1358
+
1359
+ def done(summary):
1360
+ self.ortho_view.remove_layer(tr("Zone {0}", name)) if tr("Zone {0}", name) in self.ortho_view.layers else None
1361
+ self.ms_show("zones", name)
1362
+ self._info(tr("Zonizzazione"), tr("{0}: {1} poligoni, soglie {2}, in {3}", name, summary["polygons"],
1363
+ ", ".join(f"{b:.3g}" for b in summary["breaks"]), path))
1364
+
1365
+ self.run_task(tr("Zonizzazione {0}", name),
1366
+ lambda p: ms_mod.export_zoning(self.chunk, name, path, classes=int(classes), method=method), done)
1367
+
1205
1368
  def ms_index(self) -> None:
1206
1369
  if not self._need_chunk():
1207
1370
  return
@@ -4571,8 +4734,13 @@ class MainWindow(QMainWindow):
4571
4734
  self.show_coverage_map()
4572
4735
  elif payload in ("dem", "orthomosaic"):
4573
4736
  self._show_raster("dem" if payload == "dem" else "ortho")
4737
+ elif isinstance(payload, str) and payload.startswith("ms:"):
4738
+ parts = payload.split(":", 2)
4739
+ self.ms_show(parts[1], parts[2] if len(parts) > 2 else None)
4574
4740
  else:
4575
4741
  self._show_model(payload)
4742
+ elif kind == "multispectral":
4743
+ self._show_pane(self.left_tabs, self.ms_pane)
4576
4744
  elif kind == "version":
4577
4745
  self.show_version(*payload)
4578
4746
  elif kind == "elevation":
@@ -6229,10 +6397,37 @@ class MainWindow(QMainWindow):
6229
6397
  rasters.append({"name": "DEM (ombreggiato)", "path": str(ch.dem_path), "hillshade": True,
6230
6398
  "opacity": 0.6, "hidden": True})
6231
6399
  rasters.append({"name": "DEM", "path": str(ch.dem_path), "hidden": True})
6400
+ extra_vectors = []
6401
+ ms = self._ms_meta()
6402
+ if ch is not None and ms.get("reflectance") and (ch.data_dir / ms["reflectance"]).exists():
6403
+ names = list(ms.get("reflectance_bands") or [])
6404
+ idx = [names.index(b) + 1 for b in ("NIR", "Red", "Green") if b in names]
6405
+ rasters.append({"name": "Riflettanza (falso colore)", "path": str(ch.data_dir / ms["reflectance"]),
6406
+ "bands": idx if len(idx) == 3 else [1, 2, 3], "hidden": True})
6407
+ for iname, info in (ms.get("indices") or {}).items():
6408
+ p_i = ch.data_dir / info.get("file", "")
6409
+ if p_i.exists():
6410
+ rasters.append({"name": f"Indice {iname}", "path": str(p_i), "ramp": "index", "hidden": True})
6411
+ for zname, z in (ms.get("zoning") or {}).items():
6412
+ if z.get("raster") and (ch.data_dir / z["raster"]).exists():
6413
+ rasters.append({"name": f"Zone {zname} (raster)", "path": str(ch.data_dir / z["raster"]),
6414
+ "ramp": "zones", "classes": z.get("classes", 3), "hidden": True})
6415
+ if z.get("file") and Path(z["file"]).exists():
6416
+ extra_vectors.append({"name": f"Zone {zname}", "path": z["file"], "table": "zone",
6417
+ "geometry": "POLYGON", "color": "#1a9850", "label": "classe"})
6418
+
6419
+ for provider in getattr(self, "qgis_providers", []): # plugins: their rasters and GeoPackage layers
6420
+ try:
6421
+ more_rasters, more_vectors = provider()
6422
+ rasters += list(more_rasters or [])
6423
+ extra_vectors += list(more_vectors or [])
6424
+ except Exception as exc: # noqa: BLE001 - a plugin must not stop the export
6425
+ log.warning("QGIS: layer di un plugin saltati: %s", exc)
6232
6426
 
6233
6427
  def job(p):
6234
6428
  p(0.2, "Creazione del progetto QGIS")
6235
- rep = qgis_link.build_project(project, ch.crs if ch else "", files, rasters, title=name, qgis=q)
6429
+ rep = qgis_link.build_project(project, ch.crs if ch else "", files, rasters, title=name, qgis=q,
6430
+ extra_vectors=extra_vectors)
6236
6431
  qgis_link.open_in_qgis(project, q)
6237
6432
  return rep
6238
6433