pyarchinit-photogrammetry 1.3.1__tar.gz → 1.3.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 (245) hide show
  1. {pyarchinit_photogrammetry-1.3.1/src/pyarchinit_photogrammetry.egg-info → pyarchinit_photogrammetry-1.3.2}/PKG-INFO +2 -1
  2. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/pyproject.toml +2 -1
  3. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/__init__.py +1 -1
  4. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/ortho.py +18 -2
  5. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/image_views.py +70 -4
  6. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/main_window.py +93 -1
  7. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/translations/en.json +4 -0
  8. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/io/gpkg.py +14 -3
  9. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/io/qfield.py +1 -0
  10. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/io/qgis_link.py +2 -0
  11. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/plugins/api.py +70 -0
  12. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/plugins/manager.py +4 -0
  13. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2/src/pyarchinit_photogrammetry.egg-info}/PKG-INFO +2 -1
  14. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry.egg-info/requires.txt +1 -0
  15. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_ortho.py +6 -1
  16. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_plugins.py +84 -0
  17. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_qfield.py +26 -0
  18. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/LICENSE +0 -0
  19. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/README.md +0 -0
  20. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/setup.cfg +0 -0
  21. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/ppg_launcher.py +0 -0
  22. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/__main__.py +0 -0
  23. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/_env.py +0 -0
  24. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/_native.py +0 -0
  25. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/ai/__init__.py +0 -0
  26. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/ai/classify.py +0 -0
  27. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/ai/learn.py +0 -0
  28. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/ai/learned_net.py +0 -0
  29. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/ai/learned_worker.py +0 -0
  30. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/ai/sam.py +0 -0
  31. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/ai/sam_env.py +0 -0
  32. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/ai/sam_worker.py +0 -0
  33. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/app.py +0 -0
  34. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/archaeo/__init__.py +0 -0
  35. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/archaeo/measures.py +0 -0
  36. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/archaeo/munsell.py +0 -0
  37. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/archaeo/pottery.py +0 -0
  38. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/archaeo/proxies.py +0 -0
  39. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/archaeo/relief.py +0 -0
  40. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/archaeo/sections.py +0 -0
  41. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/archaeo/stones.py +0 -0
  42. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/archaeo/stratigraphy.py +0 -0
  43. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/archaeo/terrain.py +0 -0
  44. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/archaeo/timeline.py +0 -0
  45. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/archaeo/us_proposal.py +0 -0
  46. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/batch.py +0 -0
  47. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/bim/__init__.py +0 -0
  48. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/bim/ifc.py +0 -0
  49. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/cli.py +0 -0
  50. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/core/__init__.py +0 -0
  51. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/core/camera.py +0 -0
  52. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/core/chunk.py +0 -0
  53. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/core/crs.py +0 -0
  54. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/core/document.py +0 -0
  55. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/core/geometry.py +0 -0
  56. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/core/marker.py +0 -0
  57. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/core/paradata.py +0 -0
  58. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/core/pointcloud.py +0 -0
  59. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/core/progress.py +0 -0
  60. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/core/versions.py +0 -0
  61. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/demo.py +0 -0
  62. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/doctor.py +0 -0
  63. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/__init__.py +0 -0
  64. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/bundle.py +0 -0
  65. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/chunks.py +0 -0
  66. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/cleaning.py +0 -0
  67. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/colmap_log.py +0 -0
  68. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/colour.py +0 -0
  69. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/confidence.py +0 -0
  70. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/crop.py +0 -0
  71. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/dem.py +0 -0
  72. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/dem_edit.py +0 -0
  73. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/dense.py +0 -0
  74. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/diagnostics.py +0 -0
  75. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/elevation.py +0 -0
  76. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/fieldcheck.py +0 -0
  77. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/isolate.py +0 -0
  78. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/learned.py +0 -0
  79. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/masks.py +0 -0
  80. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/mesh_cut.py +0 -0
  81. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/meshing.py +0 -0
  82. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/multispectral.py +0 -0
  83. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/nodes.py +0 -0
  84. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/normalmap.py +0 -0
  85. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/overviews.py +0 -0
  86. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/panorama.py +0 -0
  87. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/patches.py +0 -0
  88. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/precision.py +0 -0
  89. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/probe.py +0 -0
  90. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/quality.py +0 -0
  91. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/region.py +0 -0
  92. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/satellite.py +0 -0
  93. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/sfm.py +0 -0
  94. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/texturing.py +0 -0
  95. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/thermal.py +0 -0
  96. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/tiepoints.py +0 -0
  97. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/tsdf.py +0 -0
  98. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/video.py +0 -0
  99. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/engine/visibility.py +0 -0
  100. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/__init__.py +0 -0
  101. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/__main__.py +0 -0
  102. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/batch_dialog.py +0 -0
  103. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/calibration_dialog.py +0 -0
  104. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/dialogs.py +0 -0
  105. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/flow_layout.py +0 -0
  106. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/i18n.py +0 -0
  107. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/icons.py +0 -0
  108. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/logging_setup.py +0 -0
  109. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/matches_dialog.py +0 -0
  110. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/model_view.py +0 -0
  111. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/multispectral_pane.py +0 -0
  112. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/nodes_dialog.py +0 -0
  113. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/panels.py +0 -0
  114. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/plugins_dialog.py +0 -0
  115. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/pottery_dialog.py +0 -0
  116. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/resources/logo.svg +0 -0
  117. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/scene.py +0 -0
  118. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/start_page.py +0 -0
  119. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/table_export.py +0 -0
  120. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/theme.py +0 -0
  121. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/timeline_dialog.py +0 -0
  122. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/undo.py +0 -0
  123. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/update_dialog.py +0 -0
  124. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/us_proposal_dialog.py +0 -0
  125. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/gui/worker.py +0 -0
  126. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/integrations/__init__.py +0 -0
  127. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/integrations/metashape.py +0 -0
  128. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/integrations/photo3d.py +0 -0
  129. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/integrations/webodm.py +0 -0
  130. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/io/__init__.py +0 -0
  131. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/io/copc.py +0 -0
  132. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/io/exif.py +0 -0
  133. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/io/exports.py +0 -0
  134. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/io/flythrough.py +0 -0
  135. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/io/i3s.py +0 -0
  136. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/io/mesh_import.py +0 -0
  137. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/io/mesh_ply.py +0 -0
  138. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/io/pdf_report.py +0 -0
  139. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/io/report.py +0 -0
  140. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/io/report_content.py +0 -0
  141. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/io/scans.py +0 -0
  142. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/io/tables.py +0 -0
  143. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/io/web.py +0 -0
  144. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/markers/__init__.py +0 -0
  145. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/markers/gcp.py +0 -0
  146. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/markers/refine.py +0 -0
  147. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/markers/targets.py +0 -0
  148. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/plugins/__init__.py +0 -0
  149. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/plugins/index.py +0 -0
  150. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/plugins/scaffold.py +0 -0
  151. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/pyarchinit/__init__.py +0 -0
  152. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/pyarchinit/bridge.py +0 -0
  153. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/pyarchinit/config.py +0 -0
  154. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/pyarchinit/database.py +0 -0
  155. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/pyarchinit/demo_db.py +0 -0
  156. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/pyarchinit/media.py +0 -0
  157. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/pyarchinit/pyarchinit5_schema.sql +0 -0
  158. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/testing/__init__.py +0 -0
  159. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/testing/synthetic.py +0 -0
  160. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry/updates.py +0 -0
  161. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry.egg-info/SOURCES.txt +0 -0
  162. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry.egg-info/dependency_links.txt +0 -0
  163. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry.egg-info/entry_points.txt +0 -0
  164. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/src/pyarchinit_photogrammetry.egg-info/top_level.txt +0 -0
  165. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_alignment_modes.py +0 -0
  166. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_archaeo.py +0 -0
  167. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_batch.py +0 -0
  168. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_bim_ai.py +0 -0
  169. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_bundle.py +0 -0
  170. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_chunks.py +0 -0
  171. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_classify.py +0 -0
  172. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_cleaning.py +0 -0
  173. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_colmap_log.py +0 -0
  174. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_colour.py +0 -0
  175. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_dem_edit.py +0 -0
  176. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_dem_fill.py +0 -0
  177. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_dem_spikes.py +0 -0
  178. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_depth_mesh.py +0 -0
  179. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_diagnostics.py +0 -0
  180. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_elevation.py +0 -0
  181. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_env.py +0 -0
  182. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_exports_web.py +0 -0
  183. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_fieldcheck.py +0 -0
  184. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_fisheye_panorama.py +0 -0
  185. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_geometry.py +0 -0
  186. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_gui.py +0 -0
  187. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_gui_cleaning.py +0 -0
  188. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_gui_layers.py +0 -0
  189. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_gui_patches.py +0 -0
  190. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_gui_start.py +0 -0
  191. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_i18n.py +0 -0
  192. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_i3s.py +0 -0
  193. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_ifc_strat.py +0 -0
  194. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_integrations.py +0 -0
  195. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_isolate.py +0 -0
  196. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_learn.py +0 -0
  197. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_learned.py +0 -0
  198. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_marker_refine.py +0 -0
  199. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_markers.py +0 -0
  200. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_masks.py +0 -0
  201. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_measures.py +0 -0
  202. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_mesh_cut.py +0 -0
  203. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_mesh_ply.py +0 -0
  204. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_meshing_fallback.py +0 -0
  205. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_model_import.py +0 -0
  206. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_multispectral.py +0 -0
  207. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_munsell.py +0 -0
  208. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_native.py +0 -0
  209. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_nodes.py +0 -0
  210. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_normalmap.py +0 -0
  211. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_overviews.py +0 -0
  212. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_packaging.py +0 -0
  213. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_paradata.py +0 -0
  214. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_patches.py +0 -0
  215. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_photos.py +0 -0
  216. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_pipeline.py +0 -0
  217. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_pottery_demo.py +0 -0
  218. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_pottery_search.py +0 -0
  219. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_precision.py +0 -0
  220. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_probe.py +0 -0
  221. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_progress_dialog.py +0 -0
  222. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_proxies.py +0 -0
  223. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_pyarchinit.py +0 -0
  224. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_pyarchinit_media.py +0 -0
  225. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_quality_video.py +0 -0
  226. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_raster_detail.py +0 -0
  227. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_reference_cameras.py +0 -0
  228. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_region.py +0 -0
  229. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_regressions.py +0 -0
  230. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_relief.py +0 -0
  231. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_report.py +0 -0
  232. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_satellite.py +0 -0
  233. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_scalebars.py +0 -0
  234. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_scans.py +0 -0
  235. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_sfm_global_check.py +0 -0
  236. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_stones.py +0 -0
  237. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_texture.py +0 -0
  238. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_thermal.py +0 -0
  239. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_tiepoints.py +0 -0
  240. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_timeline.py +0 -0
  241. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_tools_3d.py +0 -0
  242. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_undo.py +0 -0
  243. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_updates.py +0 -0
  244. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.2}/tests/test_us_proposal.py +0 -0
  245. {pyarchinit_photogrammetry-1.3.1 → pyarchinit_photogrammetry-1.3.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.3.1
3
+ Version: 1.3.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
@@ -32,6 +32,7 @@ Requires-Dist: sqlalchemy>=2.0
32
32
  Requires-Dist: typer>=0.9
33
33
  Requires-Dist: fpdf2>=2.8
34
34
  Requires-Dist: openpyxl>=3.1
35
+ Requires-Dist: threadpoolctl>=3.1
35
36
  Provides-Extra: mesh
36
37
  Requires-Dist: open3d!=0.20.0,>=0.18; extra == "mesh"
37
38
  Provides-Extra: bim
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pyarchinit-photogrammetry"
7
- version = "1.3.1"
7
+ version = "1.3.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"
@@ -37,6 +37,7 @@ dependencies = [
37
37
  "typer>=0.9",
38
38
  "fpdf2>=2.8", # the processing report as PDF (io.pdf_report)
39
39
  "openpyxl>=3.1", # the tables as Excel workbooks (io.tables)
40
+ "threadpoolctl>=3.1", # BLAS on one thread where results must not depend on the threads (engine.ortho)
40
41
  ]
41
42
 
42
43
  [project.optional-dependencies]
@@ -35,7 +35,7 @@ from .core import (ARCHAEO_CLASSES, ASPRS_CLASSES, Camera, Chunk, Document, Mark
35
35
  from .core.progress import Cancelled, Progress, print_progress
36
36
  from . import demo # noqa: E402 (ppg.demo.create_demo)
37
37
 
38
- __version__ = "1.3.1"
38
+ __version__ = "1.3.2"
39
39
 
40
40
  __all__ = ["app", "Document", "Chunk", "Camera", "Sensor", "Marker", "Projection", "Reference", "ScaleBar",
41
41
  "PointCloud", "ASPRS_CLASSES", "ARCHAEO_CLASSES", "Progress", "Cancelled", "print_progress", "__version__"]
@@ -1203,11 +1203,27 @@ def build_orthomosaic(chunk: Chunk, resolution: float | None = None, surface: st
1203
1203
  return out_path
1204
1204
 
1205
1205
 
1206
+ def _one_blas_thread():
1207
+ """BLAS on one thread while the tiles are made: OpenBLAS (numpy on Linux and Windows) splits a
1208
+ product among its threads in a way that depends on how many calls run at once, and a row that
1209
+ lands in a remainder block is rounded by another kernel: one bit in a cell's coordinates was
1210
+ enough to pick another photo, so one worker and four gave different pixels. The tiles are
1211
+ already in parallel: one BLAS thread each is also the faster way."""
1212
+ try:
1213
+ from threadpoolctl import threadpool_limits
1214
+ except ImportError: # pragma: no cover - a dependency of the program
1215
+ import contextlib
1216
+
1217
+ return contextlib.nullcontext()
1218
+ return threadpool_limits(limits=1, user_api="blas")
1219
+
1220
+
1206
1221
  def _run_tiles(ctx, tiles, workers: int, progress: Progress, put, start: float = 0.03, span: float = 0.9) -> None:
1207
1222
  """Make the tiles in parallel (at most twice the workers waiting) and hand each one to
1208
- ``put(r0, c0, block)`` in this thread as soon as it is ready."""
1223
+ ``put(r0, c0, block)`` in this thread as soon as it is ready; the same pixels with any number
1224
+ of workers (:func:`_one_blas_thread`)."""
1209
1225
  done = 0
1210
- with ThreadPoolExecutor(workers) as pool:
1226
+ with _one_blas_thread(), ThreadPoolExecutor(workers) as pool:
1211
1227
  queue = iter(tiles)
1212
1228
  running = {}
1213
1229
  try:
@@ -391,8 +391,10 @@ class RasterView(_ZoomView):
391
391
  file) so that it opens at once; zooming in further, the visible part is read again from the
392
392
  file at the resolution of the screen, up to the full one (``detail``), in a thread."""
393
393
 
394
- DRAW_MODES = ("polygon", "line", "measure", "quota", "marker", "crop", "patch", "dem_edit", "probe")
395
- AREA_MODES = ("crop", "patch", "dem_edit", "area", "chart") # drag a box or click a polygon
394
+ DRAW_MODES = ("polygon", "line", "measure", "quota", "marker", "crop", "patch", "dem_edit", "probe",
395
+ "req_polygon", "req_line", "req_point")
396
+ AREA_MODES = ("crop", "patch", "dem_edit", "area", "chart", "req_polygon") # drag a box or click a polygon
397
+ REQUEST_MODES = ("req_polygon", "req_line", "req_point") # the drawing lent to a plugin (request_geometry)
396
398
  SAM_TOOLS = ("tap", "box", "area")
397
399
 
398
400
  mouse_world = Signal(float, float, object) # x, y, z or None
@@ -410,6 +412,9 @@ class RasterView(_ZoomView):
410
412
  quota_clicked = Signal(float, float) # spot height at world x, y
411
413
  marker_clicked = Signal(float, float) # a marker at world x, y (the height from the DEM)
412
414
  probe_clicked = Signal(float, float) # a measuring point at world x, y (elevation, temperature, bands)
415
+ request_finished = Signal(object) # (N,2) world XY drawn for a request: polygon, line or one point
416
+ request_cancelled = Signal() # Esc during a request
417
+ raster_loaded = Signal() # a raster, an empty frame or nothing is now shown: layers must be drawn again
413
418
  photos_at_point = Signal(float, float) # filter the photos that see world x, y
414
419
  photos_in_area_requested = Signal() # filter the photos that frame the selected polygon
415
420
  sam_click = Signal(float, float, int, bool) # x, y, label (1 add / 0 remove), True = a new object
@@ -521,6 +526,33 @@ class RasterView(_ZoomView):
521
526
  self.tr = None
522
527
  if text:
523
528
  self.scene().addText(text)
529
+ self.raster_loaded.emit()
530
+
531
+ def show_frame(self, bounds, resolution: float, text: str = "") -> None:
532
+ """An empty frame in world coordinates instead of a raster (``bounds`` = xmin, ymin, xmax,
533
+ ymax; ``resolution`` = metres per pixel of the view): layers and overlays of plugins can be
534
+ drawn on it with no orthophoto or DEM (a geophysical grid before the survey has one)."""
535
+ from rasterio.transform import from_origin
536
+
537
+ x0, y0, x1, y1 = (float(v) for v in bounds)
538
+ if not (x1 > x0 and y1 > y0) or not resolution > 0:
539
+ raise ValueError("an empty frame needs xmin < xmax, ymin < ymax and a positive resolution")
540
+ self._empty_scene()
541
+ w = max(1, int(np.ceil((x1 - x0) / resolution)))
542
+ h = max(1, int(np.ceil((y1 - y0) / resolution)))
543
+ self.path, self.kind, self.z = None, "frame", None
544
+ self.tr = from_origin(x0, y1, resolution, resolution)
545
+ self._src_size = self._base_size = (w, h)
546
+ self._dem_lookup = None
547
+ self.pixmap_item = None
548
+ frame = self.scene().addRect(QRectF(0, 0, w, h), self._pen("#9aa0a6", 1))
549
+ frame.setZValue(-2)
550
+ if text:
551
+ label = self.scene().addText(text)
552
+ label.setPos(4, 4)
553
+ self.setSceneRect(QRectF(0, 0, w, h))
554
+ self.fit()
555
+ self.raster_loaded.emit()
524
556
 
525
557
  def load(self, path: str | Path, kind: str, max_side: int = 6000, bands=None) -> None:
526
558
  import rasterio
@@ -589,6 +621,7 @@ class RasterView(_ZoomView):
589
621
  self.setSceneRect(QRectF(0, 0, w, h))
590
622
  self.fit()
591
623
  self._view_changed()
624
+ self.raster_loaded.emit()
592
625
 
593
626
  # ----------------------------------------------------------- full-resolution detail
594
627
  def _view_changed(self, *_args) -> None:
@@ -1201,6 +1234,23 @@ class RasterView(_ZoomView):
1201
1234
  self.clear_selection()
1202
1235
 
1203
1236
  def keyPressEvent(self, ev) -> None:
1237
+ if self.mode in self.REQUEST_MODES:
1238
+ if ev.key() == Qt.Key.Key_Escape:
1239
+ self.set_mode(self.mode)
1240
+ self.request_cancelled.emit()
1241
+ return
1242
+ if ev.key() in (Qt.Key.Key_Return, Qt.Key.Key_Enter):
1243
+ self._finish_request()
1244
+ return
1245
+ if ev.key() == Qt.Key.Key_Backspace and self._clicks: # the last vertex away
1246
+ self._clicks.pop()
1247
+ self._click_xy.pop()
1248
+ if self._clicks:
1249
+ self._update_rubber()
1250
+ elif self._rubber is not None:
1251
+ self.scene().removeItem(self._rubber)
1252
+ self._rubber = None
1253
+ return
1204
1254
  if self.mode == "sam":
1205
1255
  if ev.key() in (Qt.Key.Key_Return, Qt.Key.Key_Enter):
1206
1256
  self.sam_key.emit("accept")
@@ -1372,6 +1422,9 @@ class RasterView(_ZoomView):
1372
1422
  self._clicks.append(sp)
1373
1423
  self._click_xy.append(xy)
1374
1424
  self._update_rubber()
1425
+ if self.mode == "req_point":
1426
+ self._finish_request()
1427
+ return
1375
1428
  if self.mode in ("line", "measure") and len(self._clicks) == 2:
1376
1429
  a, b = (np.array(p) for p in self._click_xy)
1377
1430
  if self.mode == "line":
@@ -1388,6 +1441,9 @@ class RasterView(_ZoomView):
1388
1441
  def mouseDoubleClickEvent(self, ev) -> None:
1389
1442
  if self.mode == "sam": # a second press: nothing more (Enter accepts the mask)
1390
1443
  return
1444
+ if self.mode == "req_line":
1445
+ self._finish_request()
1446
+ return
1391
1447
  if self.mode in ("polygon", *self.AREA_MODES) and len(self._click_xy) >= 3:
1392
1448
  xy = np.array(self._click_xy)
1393
1449
  mode = self.mode
@@ -1397,8 +1453,18 @@ class RasterView(_ZoomView):
1397
1453
  super().mouseDoubleClickEvent(ev)
1398
1454
 
1399
1455
  def _area_signal(self, mode: str):
1400
- return {"patch": self.patch_finished, "dem_edit": self.dem_edit_finished,
1401
- "area": self.area_finished, "chart": self.chart_finished}.get(mode, self.crop_finished)
1456
+ return {"patch": self.patch_finished, "dem_edit": self.dem_edit_finished, "area": self.area_finished,
1457
+ "chart": self.chart_finished, "req_polygon": self.request_finished}.get(mode, self.crop_finished)
1458
+
1459
+ def _finish_request(self) -> None:
1460
+ """The geometry of a request, when it has enough vertices (polygon 3, line 2, point 1)."""
1461
+ need = {"req_polygon": 3, "req_line": 2, "req_point": 1}[self.mode]
1462
+ xy = [p for i, p in enumerate(self._click_xy) if i == 0 or not np.allclose(p, self._click_xy[i - 1])]
1463
+ if len(xy) < need:
1464
+ return
1465
+ mode = self.mode
1466
+ self.set_mode(mode)
1467
+ self.request_finished.emit(np.array(xy, float))
1402
1468
 
1403
1469
 
1404
1470
  class PhotoView(_ZoomView):
@@ -11,7 +11,7 @@ import traceback
11
11
  from pathlib import Path
12
12
 
13
13
  import numpy as np
14
- from PySide6.QtCore import QRectF, QSize, Qt, QTimer, QUrl
14
+ from PySide6.QtCore import QObject, QRectF, QSize, Qt, QTimer, QUrl, Signal
15
15
  from PySide6.QtGui import QAction, QActionGroup, QDesktopServices, QKeySequence
16
16
  from PySide6.QtWidgets import (QApplication, QDialog, QDialogButtonBox, QDockWidget, QFileDialog, QFrame,
17
17
  QInputDialog, QLabel, QMainWindow, QMenu, QMessageBox, QPlainTextEdit, QPushButton,
@@ -76,6 +76,24 @@ def _it(value: float, decimals: int = 0) -> str:
76
76
  return number(value, decimals)
77
77
 
78
78
 
79
+ class _GuiCalls(QObject):
80
+ """Runs functions in the interface thread: emitted from any thread, received there (queued)."""
81
+
82
+ call = Signal(object)
83
+
84
+ def __init__(self, parent=None):
85
+ super().__init__(parent)
86
+ self.call.connect(self._run, Qt.ConnectionType.QueuedConnection)
87
+
88
+ @staticmethod
89
+ def _run(job) -> None:
90
+ function, args, kwargs = job
91
+ try:
92
+ function(*args, **kwargs)
93
+ except Exception: # a plugin's function never stops the window
94
+ log.exception("function run in the interface thread failed")
95
+
96
+
79
97
  class _StatusBar(QStatusBar):
80
98
  """The status bar: its messages in the language of the interface (also those of the engine)."""
81
99
 
@@ -193,6 +211,10 @@ class MainWindow(QMainWindow):
193
211
  self.ortho_view.area_requested.connect(self._area_selected)
194
212
  self.ortho_view.patch_requested.connect(self.patch_with_selection)
195
213
  self.ortho_view.line_finished.connect(self._section_drawn)
214
+ self._request = None # the drawing lent by request_geometry: (on_finished, on_cancelled)
215
+ self._gui_calls = _GuiCalls(self)
216
+ self.ortho_view.request_finished.connect(self._request_drawn)
217
+ self.ortho_view.request_cancelled.connect(lambda: self._end_request(cancelled=True))
196
218
  self.ortho_view.measured.connect(lambda t: (self.statusBar().showMessage(t, 30000),
197
219
  self.log_pane.info(t)))
198
220
  self.ortho_view.quota_clicked.connect(self._quota_clicked)
@@ -567,6 +589,7 @@ class MainWindow(QMainWindow):
567
589
  self._act(km, tr("Rimuovi tutte le maschere"), lambda: self.masks_remove(selected=False))
568
590
  dm = m.addMenu(icon("polygon"), tr("Disegno su ortofoto"))
569
591
  self.draw_group = QActionGroup(self)
592
+ self.draw_group.triggered.connect(lambda _a: self._end_request(cancelled=True))
570
593
  self.draw_actions: dict[str, QAction] = {}
571
594
  for mode, label, ic in (("pan", "Sposta / interroga", "pan"), ("select", "Seleziona", "select"),
572
595
  ("polygon", "Disegna unita' stratigrafica", "polygon"),
@@ -1086,6 +1109,75 @@ class MainWindow(QMainWindow):
1086
1109
  if flag and self.history is not None and not self._task_done:
1087
1110
  self.history.record()
1088
1111
 
1112
+ def mark_modified(self) -> None:
1113
+ """The project has changes to save (a plugin changed ``chunk.meta`` or its files): the title
1114
+ shows it, *Salva* and the question at closing take it into account, Modifica > Annulla can
1115
+ take it back."""
1116
+ self._dirty(True)
1117
+
1118
+ def call_in_gui(self, function, *args, **kwargs) -> None:
1119
+ """``function(*args, **kwargs)`` in the interface thread, soon: from a background step or an
1120
+ exporter, for whatever touches the window."""
1121
+ self._gui_calls.call.emit((function, args, kwargs))
1122
+
1123
+ # ---------------------------------------------------------------- drawing lent to plugins
1124
+ def request_geometry(self, kind: str, on_finished, on_cancelled=None, hint: str = "") -> bool:
1125
+ """Lend the drawing of the 2D view: ``kind`` "polygon" (drag a box, or click the vertices and
1126
+ double click or Enter), "line" (click the vertices, double click or Enter to end) or "point"
1127
+ (one click). ``on_finished(xy)`` gets the vertices, ``(N, 2)`` in the coordinates of the chunk,
1128
+ in the interface thread; ``on_cancelled()`` when Esc, another tool of the drawing bar or another
1129
+ request ends it. Backspace drops the last vertex. Returns False when there is nothing to draw
1130
+ on (no orthophoto, DEM or frame)."""
1131
+ if kind not in ("polygon", "line", "point"):
1132
+ raise ValueError("kind: 'polygon', 'line' or 'point'")
1133
+ self._end_request(cancelled=True)
1134
+ view = self.ortho_view
1135
+ if view.tr is None and self.chunk is not None and (self.chunk.orthomosaic_path or self.chunk.dem_path):
1136
+ self._show_raster("ortho" if self.chunk.orthomosaic_path else "dem")
1137
+ if view.tr is None:
1138
+ self._warn("Costruisci prima l'ortofoto o il DEM.")
1139
+ if on_cancelled is not None:
1140
+ on_cancelled()
1141
+ return False
1142
+ self._request = (on_finished, on_cancelled)
1143
+ for action in self.draw_group.actions(): # no tool of the bar is the one drawing now
1144
+ action.setChecked(False)
1145
+ view.set_mode("req_" + kind)
1146
+ self.show_view(view)
1147
+ default = {"polygon": "Trascina un riquadro, oppure clic per i vertici e doppio clic o Invio per chiudere; "
1148
+ "Esc annulla",
1149
+ "line": "Clic per i vertici, doppio clic o Invio per finire; Backspace toglie l'ultimo, Esc annulla",
1150
+ "point": "Clic sul punto; Esc annulla"}[kind]
1151
+ self.statusBar().showMessage(hint or tr(default), 0)
1152
+ return True
1153
+
1154
+ def _request_drawn(self, xy) -> None:
1155
+ request, self._request = self._request, None
1156
+ self.ortho_view.set_mode("pan")
1157
+ self.draw_actions["pan"].setChecked(True)
1158
+ self.statusBar().clearMessage()
1159
+ if request is not None:
1160
+ try:
1161
+ request[0](np.asarray(xy, float))
1162
+ except Exception as exc:
1163
+ log.exception("the function of a drawing request failed")
1164
+ self._warn(tr("Il disegno non e' stato accettato: {0}", str(exc)))
1165
+
1166
+ def _end_request(self, cancelled: bool = False) -> None:
1167
+ """A pending request ends (another tool, another request, Esc): its ``on_cancelled``."""
1168
+ request, self._request = self._request, None
1169
+ if request is None:
1170
+ return
1171
+ if self.ortho_view.mode in self.ortho_view.REQUEST_MODES:
1172
+ self.ortho_view.set_mode("pan")
1173
+ self.draw_actions["pan"].setChecked(True)
1174
+ self.statusBar().clearMessage()
1175
+ if cancelled and request[1] is not None:
1176
+ try:
1177
+ request[1]()
1178
+ except Exception:
1179
+ log.exception("the cancel function of a drawing request failed")
1180
+
1089
1181
  def _update_title(self) -> None:
1090
1182
  name = self.doc.path.name if self.doc.path else tr("senza nome")
1091
1183
  site = self.project_site()
@@ -364,6 +364,7 @@
364
364
  "Classificazione automatica (regole)": "Automatic classification (rules)",
365
365
  "Classificazione con il classificatore": "Classification with the classifier",
366
366
  "Classificazione della nuvola": "Classification of the cloud",
367
+ "Clic per i vertici, doppio clic o Invio per finire; Backspace toglie l'ultimo, Esc annulla": "Click the vertices, double click or Enter to end; Backspace drops the last one, Esc cancels",
367
368
  "Clic per i vertici, doppio clic per chiudere, tasto destro per annullare": "Click for the vertices, double click to close, right click to cancel",
368
369
  "Clic per selezionare, Maiusc/Cmd+clic per aggiungere o togliere, trascina per un riquadro; Canc elimina, Unisci fonde le unita', Esc deseleziona": "Click to select, Shift/Cmd+click to add or remove, drag for a box; Delete deletes, Merge merges the units, Esc deselects",
369
370
  "Clic su un punto del modello o della nuvola: le foto che lo vedono, come Filter Photos by Point di Metashape": "Click a point of the model or the cloud: the photos that see it, as Metashape's Filter Photos by Point",
@@ -372,6 +373,7 @@
372
373
  "Clic su un punto: un marker con la quota del DEM, proiettato sulle foto che lo vedono (come Aggiungi marker in Metashape); con due marker si fa una barra di scala": "Click a point: a marker with the height of the DEM, projected on the photos that see it (as Add marker in Metashape); two markers make a scale bar",
373
374
  "Clic su un punto: un punto di misura con coordinate, quota del DEM, temperatura, bande e indici letti lì a piena risoluzione (layer Punti di misura, esportabile e in QGIS)": "Click on a point: a measuring point with coordinates, DEM elevation, temperature, bands and indices read there at full resolution (layer Measuring points, exportable and in QGIS)",
374
375
  "Clic sui vertici dell'area, doppio clic (o Invio) chiude, Esc annulla": "Click the vertices of the area, double click (or Enter) closes, Esc cancels",
376
+ "Clic sul punto; Esc annulla": "Click the point; Esc cancels",
375
377
  "Clic sull'ortofoto, termica o multispettrale: un punto con coordinate, quota del DEM, temperatura, bande e indici letti lì a piena risoluzione": "Click on the orthomosaic, thermal or multispectral: a point with coordinates, DEM elevation, temperature, bands and indices read there at full resolution",
376
378
  "Codici": "Codes",
377
379
  "Collega alle US (selezione multipla):": "Link to the US (multiple selection):",
@@ -1840,6 +1842,7 @@
1840
1842
  "Traccia una sezione: Disegno > Traccia sezione, poi due clic sull'ortofoto.": "Trace a section: Drawing > Trace section, then two clicks on the orthophoto.",
1841
1843
  "Trascina per spostare, rotella per lo zoom, clic su un elemento per i suoi attributi": "Drag to pan, wheel to zoom, click on a feature for its attributes",
1842
1844
  "Trascina un riquadro attorno alla tabella colori (ColorChecker da 24 colori), oppure clic per i vertici e doppio clic per chiudere": "Drag a box around the colour chart (24-patch ColorChecker), or click the vertices and double click to close",
1845
+ "Trascina un riquadro, oppure clic per i vertici e doppio clic o Invio per chiudere; Esc annulla": "Drag a box, or click the vertices and double click or Enter to close; Esc cancels",
1843
1846
  "Trascina un riquadro, oppure clic per i vertici e doppio clic per chiudere il poligono: poi scegli i prodotti da esportare ritagliati o da ritagliare nel progetto": "Drag a box, or click for the vertices and double click to close the polygon: then choose the products to export cropped or to crop in the project",
1844
1847
  "Trascina un riquadro, oppure clic per i vertici e doppio clic per chiudere: area in pianta e superficie 3D (dal DEM e dal modello), pendenza media, quote": "Drag a box, or click for the vertices and double click to close: plan area and 3D surface (from the DEM and from the model), mean slope, heights",
1845
1848
  "Trascina un riquadro, oppure clic per i vertici e doppio clic per chiudere: poi scegli da quali foto prendere i colori dell'area (alberi, veicoli, persone, cuciture)": "Drag a box, or click for the vertices and double click to close: then choose from which photos to take the colours of the area (trees, vehicles, people, seams)",
@@ -2325,6 +2328,7 @@
2325
2328
  "Il chunk {0} e' stato prima messo in bolla (come per il DEM).": "The chunk {0} was levelled first (as for the DEM).",
2326
2329
  "Il classificatore impara dai punti di cui conosci la classe: con il lazo (vista 3D) assegna la classe giusta ad alcuni gruppi di punti di ogni tipo (almeno due classi), poi addestralo. Usa la geometria attorno a ogni punto a tre scale, l'altezza sul terreno e il colore.\n\nClassi corrette a mano: {0}.\nClassi presenti: {1}.": "The classifier learns from the points whose class you know: with the lasso (3D view) assign the right class to a few groups of points of every kind (at least two classes), then train it. It uses the geometry around every point at three scales, the height above the ground and the colour.\n\nClasses corrected by hand: {0}.\nClasses present: {1}.",
2327
2330
  "Il database contiene {0} siti (scavi).": "The database holds {0} sites (excavations).",
2331
+ "Il disegno non e' stato accettato: {0}": "The drawing was not accepted: {0}",
2328
2332
  "Il modello importato e' a {0} m dai dati del chunk: controlla il sistema di riferimento, lo spostamento, la scala e l'asse verticale del file. Il modello di prima e' tra le versioni (tasto destro sul modello nell'area di lavoro).": "The imported model lies {0} m from the data of the chunk: check the coordinate system, the shift, the scale and the vertical axis of the file. The previous model is among the versions (right click on the model in the workspace).",
2329
2333
  "Il piano a quota {0} m non taglia il modello: nessuna pianta": "The plane at height {0} m does not cut the model: no plan",
2330
2334
  "Il prospetto «{0}» non ha piu' i suoi file.": "The elevation «{0}» no longer has its files.",
@@ -68,6 +68,14 @@ def is_geopackage(path: str | Path) -> bool:
68
68
  return False
69
69
 
70
70
 
71
+ def refuse_foreign(path: str | Path) -> None:
72
+ """:class:`FileExistsError` when ``path`` is a file with content that is not a GeoPackage: the
73
+ program never deletes or rewrites a file it did not make."""
74
+ path = Path(path)
75
+ if path.exists() and (path.is_dir() or path.stat().st_size) and not is_geopackage(path):
76
+ raise FileExistsError(f"{path} exists and is not a GeoPackage: it was left as it is, choose another file")
77
+
78
+
71
79
  def connect(path: str | Path) -> sqlite3.Connection:
72
80
  """Connection in autocommit mode (transactions are explicit), waiting for other writers."""
73
81
  Path(path).parent.mkdir(parents=True, exist_ok=True)
@@ -136,12 +144,15 @@ def replace_rows(con: sqlite3.Connection, table: str, columns: list[str], featur
136
144
 
137
145
  def write_table(path: str | Path, table: str, geometry_type: str, columns: dict[str, str], features,
138
146
  crs: str | None, identifier: str | None = None) -> Path:
139
- """Write one feature table into a GeoPackage (created when missing; other tables kept)."""
147
+ """Write one feature table into a GeoPackage (created when missing; other tables kept). A file
148
+ that exists and is not a GeoPackage - another database, a document chosen by mistake - is never
149
+ touched: :class:`FileExistsError`. An empty file (left by a save dialog) is used."""
140
150
  path = Path(path)
141
151
  srs = srs_of(crs)
142
152
  new = not is_geopackage(path)
143
153
  if new and path.exists():
144
- path.unlink() # not a GeoPackage (an empty or foreign file chosen as output)
154
+ refuse_foreign(path)
155
+ path.unlink() # empty: nothing to lose
145
156
  con = connect(path)
146
157
  try:
147
158
  con.execute("BEGIN IMMEDIATE")
@@ -188,5 +199,5 @@ def read_table(path: str | Path, table: str) -> list[tuple[object, dict]]:
188
199
  con.close()
189
200
 
190
201
 
191
- __all__ = ["write_table", "is_geopackage", "srs_of", "blob", "create", "ensure_table", "replace_rows", "connect",
202
+ __all__ = ["write_table", "is_geopackage", "refuse_foreign", "srs_of", "blob", "create", "ensure_table", "replace_rows", "connect",
192
203
  "quote", "geometry", "read_table"]
@@ -175,6 +175,7 @@ def export_package(chunk, folder: str | os.PathLike, resolution: float | None =
175
175
  crs = None if crs_mod.is_local(chunk.crs) else chunk.crs
176
176
  gp = folder / GPKG
177
177
  if gp.exists():
178
+ gpkg.refuse_foreign(gp) # an earlier package is written again, a file of something else stays
178
179
  gp.unlink()
179
180
  progress(0.05, "QField: layers")
180
181
  units = [(wkt.loads(u["wkt"]), _unit_row(u)) for u in d["units"]]
@@ -181,6 +181,8 @@ class LiveLink:
181
181
 
182
182
  groups = self._group(layers)
183
183
  new = not gpkg.is_geopackage(self.path)
184
+ if new:
185
+ gpkg.refuse_foreign(self.path) # never write the drawings into another file of that name
184
186
  if new:
185
187
  self._signatures = {}
186
188
  todo = {t: f for t, f in groups.items() if force or new or self._signatures.get(t) != self._signature(f)}
@@ -77,6 +77,7 @@ class Contributions:
77
77
  actions: list = field(default_factory=list)
78
78
  commands: dict = field(default_factory=dict)
79
79
  exporters: list = field(default_factory=list)
80
+ connections: list = field(default_factory=list) # (signal, slot) disconnected when the plugin goes away
80
81
 
81
82
 
82
83
  class Context:
@@ -154,6 +155,75 @@ class Context:
154
155
  if method is not None:
155
156
  method()
156
157
 
158
+ # ------------------------------------------------------------------ the window (since 1.3.2)
159
+ def request_polygon(self, on_finished: Callable, on_cancelled: Callable | None = None, hint: str = "") -> bool:
160
+ """Let the user draw a polygon on the 2D view with the program's own tool (drag a box, or click
161
+ the vertices and double click or Enter; Backspace drops the last vertex, Esc cancels):
162
+ ``on_finished(xy)`` gets the vertices, an ``(N, 2)`` array in the coordinates of the chunk, in the
163
+ interface thread; ``on_cancelled()`` when the user picks another tool or another request comes.
164
+ ``hint``: the text of the status bar. False (and ``on_cancelled``) when there is nothing to draw
165
+ on; in the command line there is no drawing (``RuntimeError``)."""
166
+ return self._request("polygon", on_finished, on_cancelled, hint)
167
+
168
+ def request_line(self, on_finished: Callable, on_cancelled: Callable | None = None, hint: str = "") -> bool:
169
+ """As :meth:`request_polygon` for a line of two or more vertices (double click or Enter ends)."""
170
+ return self._request("line", on_finished, on_cancelled, hint)
171
+
172
+ def request_point(self, on_finished: Callable, on_cancelled: Callable | None = None, hint: str = "") -> bool:
173
+ """As :meth:`request_polygon` for one point: ``on_finished`` gets a ``(1, 2)`` array."""
174
+ return self._request("point", on_finished, on_cancelled, hint)
175
+
176
+ def _request(self, kind, on_finished, on_cancelled, hint) -> bool:
177
+ if self.window is None:
178
+ raise RuntimeError("drawing on the view needs the window: not in the command line")
179
+ return self.window.request_geometry(kind, on_finished, on_cancelled, hint)
180
+
181
+ def on_raster_loaded(self, callback: Callable[[], object]) -> None:
182
+ """``callback()`` each time the 2D view shows another raster, an empty frame or nothing: the
183
+ layers and overlays of a plugin are gone with the old one and are drawn again there.
184
+ Disconnected by itself when the plugin is disabled. Nothing in the command line."""
185
+ view = getattr(self.window, "ortho_view", None)
186
+ if view is None:
187
+ return
188
+ view.raster_loaded.connect(callback)
189
+ self.contributions.connections.append((view.raster_loaded, callback))
190
+
191
+ def show_frame(self, bounds, resolution: float, text: str = "") -> None:
192
+ """An empty frame on the 2D view (``bounds`` = xmin, ymin, xmax, ymax in the coordinates of the
193
+ chunk, ``resolution`` metres per pixel of the view) when the chunk has no orthophoto or DEM to
194
+ draw on: then layers and overlays can be added as usual."""
195
+ view = getattr(self.window, "ortho_view", None)
196
+ if view is None:
197
+ raise RuntimeError("the 2D view needs the window: not in the command line")
198
+ view.show_frame(bounds, resolution, text)
199
+
200
+ def mark_modified(self) -> None:
201
+ """The project has changes to save (``chunk.meta`` or files of the plugin changed): the window
202
+ shows it and asks to save at closing. Nothing in the command line, where the plugin saves."""
203
+ method = getattr(self.window, "mark_modified", None)
204
+ if method is not None:
205
+ method()
206
+
207
+ def call_in_gui(self, function: Callable, *args, **kwargs) -> None:
208
+ """``function(*args, **kwargs)`` in the interface thread, soon: from the function given to
209
+ :meth:`run` or from an exporter, for anything that touches the window. At once in the command
210
+ line."""
211
+ method = getattr(self.window, "call_in_gui", None)
212
+ if method is None:
213
+ function(*args, **kwargs)
214
+ else:
215
+ method(function, *args, **kwargs)
216
+
217
+ def release(self) -> None:
218
+ """What the plugin connected through the context, disconnected (the manager calls it after
219
+ :meth:`Plugin.deactivate`)."""
220
+ for signal, slot in self.contributions.connections:
221
+ try:
222
+ signal.disconnect(slot)
223
+ except (RuntimeError, TypeError):
224
+ pass
225
+ self.contributions.connections.clear()
226
+
157
227
  def run(self, title: str, function: Callable, done: Callable | None = None) -> object:
158
228
  """``function(progress)`` with a progress bar: in the background in the window (``done(result)``
159
229
  afterwards, in the interface thread), at once in the command line (``done`` too)."""
@@ -187,6 +187,8 @@ class Manager:
187
187
  info.plugin.deactivate()
188
188
  except Exception as exc:
189
189
  log.warning("plugin %s: deactivate failed: %s", info.name, exc)
190
+ if info.context is not None:
191
+ info.context.release()
190
192
  info.active, info.plugin, info.context = False, None, None
191
193
 
192
194
  def remove(self, name: str) -> None:
@@ -199,6 +201,8 @@ class Manager:
199
201
  info.plugin.deactivate()
200
202
  except Exception:
201
203
  pass
204
+ if info.context is not None:
205
+ info.context.release()
202
206
  shutil.rmtree(info.location)
203
207
  self.plugins = [i for i in self.plugins if i.name != name]
204
208
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyarchinit-photogrammetry
3
- Version: 1.3.1
3
+ Version: 1.3.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
@@ -32,6 +32,7 @@ Requires-Dist: sqlalchemy>=2.0
32
32
  Requires-Dist: typer>=0.9
33
33
  Requires-Dist: fpdf2>=2.8
34
34
  Requires-Dist: openpyxl>=3.1
35
+ Requires-Dist: threadpoolctl>=3.1
35
36
  Provides-Extra: mesh
36
37
  Requires-Dist: open3d!=0.20.0,>=0.18; extra == "mesh"
37
38
  Provides-Extra: bim
@@ -15,6 +15,7 @@ sqlalchemy>=2.0
15
15
  typer>=0.9
16
16
  fpdf2>=2.8
17
17
  openpyxl>=3.1
18
+ threadpoolctl>=3.1
18
19
 
19
20
  [all]
20
21
  open3d!=0.20.0,>=0.18
@@ -44,7 +44,12 @@ def test_surfaces_and_workers(processed, scene, tmp_path):
44
44
  assert src.overviews(1) # pyramids for a quick display
45
45
  many = ch.build_orthomosaic(resolution=0.004, workers=4)
46
46
  with rasterio.open(many) as src:
47
- assert np.array_equal(src.read(), a) # the same pixels, tile by tile in any order
47
+ b = src.read()
48
+ differ = np.any(a != b, axis=0)
49
+ if differ.any(): # say where, if it ever happens again (it did with OpenBLAS splitting products)
50
+ rows, cols = np.nonzero(differ)
51
+ raise AssertionError(f"1 and 4 workers: {int(differ.sum())} pixels differ, rows {rows.min()}-{rows.max()}, "
52
+ f"columns {cols.min()}-{cols.max()}, up to {int(np.abs(a.astype(int) - b).max())}")
48
53
  worst = _targets_error(many, scene)
49
54
  assert worst < 0.005, f"targets in the orthomosaic off by up to {worst * 1000:.2f} mm"
50
55
  # a true orthophoto on the mesh (vertical rays), and from the dense cloud alone