From 11129684fc8768ffb0dd57ffa29663714c4ea582 Mon Sep 17 00:00:00 2001 From: Ashley Walker Date: Thu, 1 Aug 2019 09:58:04 +1000 Subject: [PATCH 1/4] replaced - with _ to be able to import into other scripts --- .gitignore | 1 + ...create-project.py => 1a_create_project.py} | 0 ...mera-config.py => 1b_set_camera_config.py} | 0 scripts/{2a-set-poses.py => 2a_set_poses.py} | 0 ...n-direct-ac3d.py => 2e_gen_direct_ac3d.py} | 0 ...rped-images.py => 2f_gen_warped_images.py} | 0 ...tect-features.py => 3a_detect_features.py} | 0 ...b-show-features.py => 3b_show_features.py} | 0 scripts/{4a-matching.py => 4a_matching.py} | 0 ...hes.py => 4b_clean_and_combine_matches.py} | 0 ...ngulation.py => 4c_match_triangulation.py} | 0 ...{4d-image-groups.py => 4d_image_groups.py} | 0 ...review-matches.py => 4e_review_matches.py} | 0 ...mage-groups.py => 4e_show_image_groups.py} | 0 ...-match-pairs.py => 4e_show_match_pairs.py} | 0 scripts/{5a-optimize.py => 5a_optimize.py} | 0 ...located-feats.py => 5b_colocated_feats.py} | 0 ...{5b-mre-by-image.py => 5b_mre_by_image.py} | 0 ...matches.py => 5b_remove_camera_matches.py} | 0 scripts/{5c-by-depth.py => 5c_by_depth.py} | 0 ...colocated-cams.py => 5c_colocated_cams.py} | 0 scripts/{5c-movers.py => 5c_movers.py} | 0 ...e-outliers1.py => 5c_surface_outliers1.py} | 0 ...e-outliers2.py => 5c_surface_outliers2.py} | 0 ...e-outliers3.py => 5c_surface_outliers3.py} | 0 scripts/{5d-diff-sba.py => 5d_diff_sba.py} | 0 ...{5d-plot-results.py => 5d_plot_results.py} | 0 ...{5e-plot-matches.py => 5e_plot_matches.py} | 0 ...a-render-model2.py => 6a_render_model2.py} | 0 ...a-render-model3.py => 6a_render_model3.py} | 0 scripts/{6b-delaunay5.py => 6b_delaunay5.py} | 0 scripts/{7a-explore.py => 7a_explore.py} | 0 scripts/{7a-explore.spec => 7a_explore.spec} | 96 +++++++++---------- ...-project.py => 99_create_group_project.py} | 0 ...m-transform.py => 99_est_cam_transform.py} | 0 .../{99-make-pix4d.py => 99_make_pix4d.py} | 0 .../{99-new-camera.py => 99_new_camera.py} | 0 scripts/{99-trim-far.py => 99_trim_far.py} | 0 scripts/{99-vignette.py => 99_vignette.py} | 0 .../{99-zip-project.py => 99_zip_project.py} | 0 scripts/__init__.py | 0 ...c-import-images.py => 1c_import_images.py} | 0 .../{1d-show-images.py => 1d_show_images.py} | 0 ...camera-poses.py => 2b_set_camera_poses.py} | 0 ...gen-direct-kml.py => 2d_gen_direct_kml.py} | 0 ...features.py => 3c_cull_fringe_features.py} | 0 ...c-partition-set.py => 3c_partition_set.py} | 0 ...es-reset.py => 4b_simple_matches_reset.py} | 0 ...match-grouping.py => 4d_match_grouping.py} | 0 ...tches-ned1.py => 4d_reset_matches_ned1.py} | 0 ...tches-ned2.py => 4d_reset_matches_ned2.py} | 0 ...g-match-culling.py => 4g_match_culling.py} | 0 ...apse-matches.py => 4z_collapse_matches.py} | 0 scripts/archive/{5a-sba1.py => 5a_sba1.py} | 0 scripts/archive/{5a-sba3.py => 5a_sba3.py} | 0 scripts/archive/{5a-sba4.py => 5a_sba4.py} | 0 .../archive/{5b-solver1.py => 5b_solver1.py} | 0 .../archive/{5b-solver2.py => 5b_solver2.py} | 0 .../archive/{5b-solver3.py => 5b_solver3.py} | 0 .../archive/{5b-solver4.py => 5b_solver4.py} | 0 .../archive/{5b-solver5.py => 5b_solver5.py} | 0 ...mre-by-feature.py => 5c_mre_by_feature.py} | 0 ...e-by-feature1.py => 5c_mre_by_feature1.py} | 0 ...e-by-feature2.py => 5c_mre_by_feature2.py} | 0 ...e-by-feature3.py => 5c_mre_by_feature3.py} | 0 ...a-render-model1.py => 6a_render_model1.py} | 0 .../{6b-delaunay1.py => 6b_delaunay1.py} | 0 .../{6b-delaunay2.py => 6b_delaunay2.py} | 0 .../{6b-delaunay3.py => 6b_delaunay3.py} | 0 .../{6b-delaunay4.py => 6b_delaunay4.py} | 0 ...amera-poses.py => 2c_show_camera_poses.py} | 0 ...disparity-test.py => 99_disparity_test.py} | 0 ...-channel.py => 99_sentera_five_channel.py} | 0 .../{0-distort-test.py => 0_distort_test.py} | 0 ...-pickle-matches.py => 0_pickle_matches.py} | 0 ...rojection-test.py => 0_projection_test.py} | 0 tests/{0-quat-avg.py => 0_quat_avg.py} | 0 tests/{0-srtm-test-1.py => 0_srtm_test_1.py} | 0 tests/{0-srtm-test-2.py => 0_srtm_test_2.py} | 0 ...-transform-test.py => 0_transform_test.py} | 0 ...eighted-affine.py => 0_weighted_affine.py} | 0 ...r-test.py => illumintation_sensor_test.py} | 0 ...bittersweet.py => oriental_bittersweet.py} | 0 ...est-gyro-rates.py => 1a_est_gyro_rates.py} | 0 ...c-aruco-tracker.py => 1c_aruco_tracker.py} | 0 ...en-hud-overlay.py => 2_gen_hud_overlay.py} | 0 ...ames.py => 3_extract_and_geotag_frames.py} | 0 ...est-gyro-rates.py => 1a_est_gyro_rates.py} | 0 ...est-gyro-rates.py => 1b_est_gyro_rates.py} | 0 ...n-hud-overlay.py => 2a_gen_hud_overlay.py} | 0 90 files changed, 49 insertions(+), 48 deletions(-) rename scripts/{1a-create-project.py => 1a_create_project.py} (100%) mode change 100755 => 100644 rename scripts/{1b-set-camera-config.py => 1b_set_camera_config.py} (100%) mode change 100755 => 100644 rename scripts/{2a-set-poses.py => 2a_set_poses.py} (100%) mode change 100755 => 100644 rename scripts/{2e-gen-direct-ac3d.py => 2e_gen_direct_ac3d.py} (100%) mode change 100755 => 100644 rename scripts/{2f-gen-warped-images.py => 2f_gen_warped_images.py} (100%) mode change 100755 => 100644 rename scripts/{3a-detect-features.py => 3a_detect_features.py} (100%) mode change 100755 => 100644 rename scripts/{3b-show-features.py => 3b_show_features.py} (100%) mode change 100755 => 100644 rename scripts/{4a-matching.py => 4a_matching.py} (100%) mode change 100755 => 100644 rename scripts/{4b-clean-and-combine-matches.py => 4b_clean_and_combine_matches.py} (100%) mode change 100755 => 100644 rename scripts/{4c-match-triangulation.py => 4c_match_triangulation.py} (100%) mode change 100755 => 100644 rename scripts/{4d-image-groups.py => 4d_image_groups.py} (100%) mode change 100755 => 100644 rename scripts/{4e-review-matches.py => 4e_review_matches.py} (100%) mode change 100755 => 100644 rename scripts/{4e-show-image-groups.py => 4e_show_image_groups.py} (100%) mode change 100755 => 100644 rename scripts/{4e-show-match-pairs.py => 4e_show_match_pairs.py} (100%) mode change 100755 => 100644 rename scripts/{5a-optimize.py => 5a_optimize.py} (100%) mode change 100755 => 100644 rename scripts/{5b-colocated-feats.py => 5b_colocated_feats.py} (100%) mode change 100755 => 100644 rename scripts/{5b-mre-by-image.py => 5b_mre_by_image.py} (100%) mode change 100755 => 100644 rename scripts/{5b-remove-camera-matches.py => 5b_remove_camera_matches.py} (100%) mode change 100755 => 100644 rename scripts/{5c-by-depth.py => 5c_by_depth.py} (100%) mode change 100755 => 100644 rename scripts/{5c-colocated-cams.py => 5c_colocated_cams.py} (100%) mode change 100755 => 100644 rename scripts/{5c-movers.py => 5c_movers.py} (100%) mode change 100755 => 100644 rename scripts/{5c-surface-outliers1.py => 5c_surface_outliers1.py} (100%) mode change 100755 => 100644 rename scripts/{5c-surface-outliers2.py => 5c_surface_outliers2.py} (100%) mode change 100755 => 100644 rename scripts/{5c-surface-outliers3.py => 5c_surface_outliers3.py} (100%) mode change 100755 => 100644 rename scripts/{5d-diff-sba.py => 5d_diff_sba.py} (100%) mode change 100755 => 100644 rename scripts/{5d-plot-results.py => 5d_plot_results.py} (100%) mode change 100755 => 100644 rename scripts/{5e-plot-matches.py => 5e_plot_matches.py} (100%) mode change 100755 => 100644 rename scripts/{6a-render-model2.py => 6a_render_model2.py} (100%) mode change 100755 => 100644 rename scripts/{6a-render-model3.py => 6a_render_model3.py} (100%) mode change 100755 => 100644 rename scripts/{6b-delaunay5.py => 6b_delaunay5.py} (100%) mode change 100755 => 100644 rename scripts/{7a-explore.py => 7a_explore.py} (100%) mode change 100755 => 100644 rename scripts/{7a-explore.spec => 7a_explore.spec} (97%) rename scripts/{99-create-group-project.py => 99_create_group_project.py} (100%) mode change 100755 => 100644 rename scripts/{99-est-cam-transform.py => 99_est_cam_transform.py} (100%) mode change 100755 => 100644 rename scripts/{99-make-pix4d.py => 99_make_pix4d.py} (100%) mode change 100755 => 100644 rename scripts/{99-new-camera.py => 99_new_camera.py} (100%) mode change 100755 => 100644 rename scripts/{99-trim-far.py => 99_trim_far.py} (100%) mode change 100755 => 100644 rename scripts/{99-vignette.py => 99_vignette.py} (100%) mode change 100755 => 100644 rename scripts/{99-zip-project.py => 99_zip_project.py} (100%) mode change 100755 => 100644 create mode 100644 scripts/__init__.py rename scripts/archive/{1c-import-images.py => 1c_import_images.py} (100%) mode change 100755 => 100644 rename scripts/archive/{1d-show-images.py => 1d_show_images.py} (100%) mode change 100755 => 100644 rename scripts/archive/{2b-set-camera-poses.py => 2b_set_camera_poses.py} (100%) mode change 100755 => 100644 rename scripts/archive/{2d-gen-direct-kml.py => 2d_gen_direct_kml.py} (100%) mode change 100755 => 100644 rename scripts/archive/{3c-cull-fringe-features.py => 3c_cull_fringe_features.py} (100%) mode change 100755 => 100644 rename scripts/archive/{3c-partition-set.py => 3c_partition_set.py} (100%) mode change 100755 => 100644 rename scripts/archive/{4b-simple-matches-reset.py => 4b_simple_matches_reset.py} (100%) mode change 100755 => 100644 rename scripts/archive/{4d-match-grouping.py => 4d_match_grouping.py} (100%) mode change 100755 => 100644 rename scripts/archive/{4d-reset-matches-ned1.py => 4d_reset_matches_ned1.py} (100%) mode change 100755 => 100644 rename scripts/archive/{4d-reset-matches-ned2.py => 4d_reset_matches_ned2.py} (100%) mode change 100755 => 100644 rename scripts/archive/{4g-match-culling.py => 4g_match_culling.py} (100%) mode change 100755 => 100644 rename scripts/archive/{4z-collapse-matches.py => 4z_collapse_matches.py} (100%) mode change 100755 => 100644 rename scripts/archive/{5a-sba1.py => 5a_sba1.py} (100%) mode change 100755 => 100644 rename scripts/archive/{5a-sba3.py => 5a_sba3.py} (100%) mode change 100755 => 100644 rename scripts/archive/{5a-sba4.py => 5a_sba4.py} (100%) mode change 100755 => 100644 rename scripts/archive/{5b-solver1.py => 5b_solver1.py} (100%) mode change 100755 => 100644 rename scripts/archive/{5b-solver2.py => 5b_solver2.py} (100%) mode change 100755 => 100644 rename scripts/archive/{5b-solver3.py => 5b_solver3.py} (100%) mode change 100755 => 100644 rename scripts/archive/{5b-solver4.py => 5b_solver4.py} (100%) mode change 100755 => 100644 rename scripts/archive/{5b-solver5.py => 5b_solver5.py} (100%) mode change 100755 => 100644 rename scripts/archive/{5c-mre-by-feature.py => 5c_mre_by_feature.py} (100%) mode change 100755 => 100644 rename scripts/archive/{5c-mre-by-feature1.py => 5c_mre_by_feature1.py} (100%) mode change 100755 => 100644 rename scripts/archive/{5c-mre-by-feature2.py => 5c_mre_by_feature2.py} (100%) mode change 100755 => 100644 rename scripts/archive/{5c-mre-by-feature3.py => 5c_mre_by_feature3.py} (100%) mode change 100755 => 100644 rename scripts/archive/{6a-render-model1.py => 6a_render_model1.py} (100%) mode change 100755 => 100644 rename scripts/archive/{6b-delaunay1.py => 6b_delaunay1.py} (100%) mode change 100755 => 100644 rename scripts/archive/{6b-delaunay2.py => 6b_delaunay2.py} (100%) mode change 100755 => 100644 rename scripts/archive/{6b-delaunay3.py => 6b_delaunay3.py} (100%) mode change 100755 => 100644 rename scripts/archive/{6b-delaunay4.py => 6b_delaunay4.py} (100%) mode change 100755 => 100644 rename scripts/sandbox/{2c-show-camera-poses.py => 2c_show_camera_poses.py} (100%) mode change 100755 => 100644 rename scripts/sandbox/{99-disparity-test.py => 99_disparity_test.py} (100%) mode change 100755 => 100644 rename scripts/sandbox/{99-sentera-five-channel.py => 99_sentera_five_channel.py} (100%) mode change 100755 => 100644 rename tests/{0-distort-test.py => 0_distort_test.py} (100%) mode change 100755 => 100644 rename tests/{0-pickle-matches.py => 0_pickle_matches.py} (100%) mode change 100755 => 100644 rename tests/{0-projection-test.py => 0_projection_test.py} (100%) mode change 100755 => 100644 rename tests/{0-quat-avg.py => 0_quat_avg.py} (100%) mode change 100755 => 100644 rename tests/{0-srtm-test-1.py => 0_srtm_test_1.py} (100%) mode change 100755 => 100644 rename tests/{0-srtm-test-2.py => 0_srtm_test_2.py} (100%) mode change 100755 => 100644 rename tests/{0-transform-test.py => 0_transform_test.py} (100%) mode change 100755 => 100644 rename tests/{0-weighted-affine.py => 0_weighted_affine.py} (100%) mode change 100755 => 100644 rename tests/{illumintation-sensor-test.py => illumintation_sensor_test.py} (100%) mode change 100755 => 100644 rename tests/{oriental-bittersweet.py => oriental_bittersweet.py} (100%) mode change 100755 => 100644 rename video/{1a-est-gyro-rates.py => 1a_est_gyro_rates.py} (100%) mode change 100755 => 100644 rename video/{1c-aruco-tracker.py => 1c_aruco_tracker.py} (100%) mode change 100755 => 100644 rename video/{2-gen-hud-overlay.py => 2_gen_hud_overlay.py} (100%) mode change 100755 => 100644 rename video/{3-extract-and-geotag-frames.py => 3_extract_and_geotag_frames.py} (100%) mode change 100755 => 100644 rename video/archive/{1a-est-gyro-rates.py => 1a_est_gyro_rates.py} (100%) mode change 100755 => 100644 rename video/archive/{1b-est-gyro-rates.py => 1b_est_gyro_rates.py} (100%) mode change 100755 => 100644 rename video/archive/{2a-gen-hud-overlay.py => 2a_gen_hud_overlay.py} (100%) mode change 100755 => 100644 diff --git a/.gitignore b/.gitignore index 2f836aac..fc4795bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *~ *.pyc +*.vscode \ No newline at end of file diff --git a/scripts/1a-create-project.py b/scripts/1a_create_project.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/1a-create-project.py rename to scripts/1a_create_project.py diff --git a/scripts/1b-set-camera-config.py b/scripts/1b_set_camera_config.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/1b-set-camera-config.py rename to scripts/1b_set_camera_config.py diff --git a/scripts/2a-set-poses.py b/scripts/2a_set_poses.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/2a-set-poses.py rename to scripts/2a_set_poses.py diff --git a/scripts/2e-gen-direct-ac3d.py b/scripts/2e_gen_direct_ac3d.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/2e-gen-direct-ac3d.py rename to scripts/2e_gen_direct_ac3d.py diff --git a/scripts/2f-gen-warped-images.py b/scripts/2f_gen_warped_images.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/2f-gen-warped-images.py rename to scripts/2f_gen_warped_images.py diff --git a/scripts/3a-detect-features.py b/scripts/3a_detect_features.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/3a-detect-features.py rename to scripts/3a_detect_features.py diff --git a/scripts/3b-show-features.py b/scripts/3b_show_features.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/3b-show-features.py rename to scripts/3b_show_features.py diff --git a/scripts/4a-matching.py b/scripts/4a_matching.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/4a-matching.py rename to scripts/4a_matching.py diff --git a/scripts/4b-clean-and-combine-matches.py b/scripts/4b_clean_and_combine_matches.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/4b-clean-and-combine-matches.py rename to scripts/4b_clean_and_combine_matches.py diff --git a/scripts/4c-match-triangulation.py b/scripts/4c_match_triangulation.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/4c-match-triangulation.py rename to scripts/4c_match_triangulation.py diff --git a/scripts/4d-image-groups.py b/scripts/4d_image_groups.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/4d-image-groups.py rename to scripts/4d_image_groups.py diff --git a/scripts/4e-review-matches.py b/scripts/4e_review_matches.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/4e-review-matches.py rename to scripts/4e_review_matches.py diff --git a/scripts/4e-show-image-groups.py b/scripts/4e_show_image_groups.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/4e-show-image-groups.py rename to scripts/4e_show_image_groups.py diff --git a/scripts/4e-show-match-pairs.py b/scripts/4e_show_match_pairs.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/4e-show-match-pairs.py rename to scripts/4e_show_match_pairs.py diff --git a/scripts/5a-optimize.py b/scripts/5a_optimize.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/5a-optimize.py rename to scripts/5a_optimize.py diff --git a/scripts/5b-colocated-feats.py b/scripts/5b_colocated_feats.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/5b-colocated-feats.py rename to scripts/5b_colocated_feats.py diff --git a/scripts/5b-mre-by-image.py b/scripts/5b_mre_by_image.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/5b-mre-by-image.py rename to scripts/5b_mre_by_image.py diff --git a/scripts/5b-remove-camera-matches.py b/scripts/5b_remove_camera_matches.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/5b-remove-camera-matches.py rename to scripts/5b_remove_camera_matches.py diff --git a/scripts/5c-by-depth.py b/scripts/5c_by_depth.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/5c-by-depth.py rename to scripts/5c_by_depth.py diff --git a/scripts/5c-colocated-cams.py b/scripts/5c_colocated_cams.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/5c-colocated-cams.py rename to scripts/5c_colocated_cams.py diff --git a/scripts/5c-movers.py b/scripts/5c_movers.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/5c-movers.py rename to scripts/5c_movers.py diff --git a/scripts/5c-surface-outliers1.py b/scripts/5c_surface_outliers1.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/5c-surface-outliers1.py rename to scripts/5c_surface_outliers1.py diff --git a/scripts/5c-surface-outliers2.py b/scripts/5c_surface_outliers2.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/5c-surface-outliers2.py rename to scripts/5c_surface_outliers2.py diff --git a/scripts/5c-surface-outliers3.py b/scripts/5c_surface_outliers3.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/5c-surface-outliers3.py rename to scripts/5c_surface_outliers3.py diff --git a/scripts/5d-diff-sba.py b/scripts/5d_diff_sba.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/5d-diff-sba.py rename to scripts/5d_diff_sba.py diff --git a/scripts/5d-plot-results.py b/scripts/5d_plot_results.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/5d-plot-results.py rename to scripts/5d_plot_results.py diff --git a/scripts/5e-plot-matches.py b/scripts/5e_plot_matches.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/5e-plot-matches.py rename to scripts/5e_plot_matches.py diff --git a/scripts/6a-render-model2.py b/scripts/6a_render_model2.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/6a-render-model2.py rename to scripts/6a_render_model2.py diff --git a/scripts/6a-render-model3.py b/scripts/6a_render_model3.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/6a-render-model3.py rename to scripts/6a_render_model3.py diff --git a/scripts/6b-delaunay5.py b/scripts/6b_delaunay5.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/6b-delaunay5.py rename to scripts/6b_delaunay5.py diff --git a/scripts/7a-explore.py b/scripts/7a_explore.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/7a-explore.py rename to scripts/7a_explore.py diff --git a/scripts/7a-explore.spec b/scripts/7a_explore.spec similarity index 97% rename from scripts/7a-explore.spec rename to scripts/7a_explore.spec index 34055f71..32b2e329 100644 --- a/scripts/7a-explore.spec +++ b/scripts/7a_explore.spec @@ -1,48 +1,48 @@ -# -*- mode: python ; coding: utf-8 -*- - -block_cipher = None - - -a = Analysis(['7a-explore.py'], - pathex=['H:\\Projects\\ImageAnalysis\\scripts'], - binaries=[ - ('C:\\Users\curt\Anaconda3\Lib\site-packages\panda3d\cgGL.dll', '.'), - ('C:\\Users\curt\Anaconda3\Lib\site-packages\panda3d\libpandagl.dll', '.'), - ('C:\\Users\curt\Anaconda3\Lib\site-packages\panda3d\libpandaegg.dll', '.'), - ('C:\\Users\curt\Anaconda3\Lib\site-packages\panda3d\libp3assimp.dll', '.'), - ('C:\\Users\curt\Anaconda3\Lib\site-packages\panda3d\libp3ptloader.dll', '.'), - ('C:\\Users\curt\Anaconda3\Lib\site-packages\panda3d\libp3windisplay.dll', '.') - ], - datas=[ - ('C:\\Users\curt\Anaconda3\Lib\site-packages\panda3d\etc\*.prc', 'etc'), - ('explore/*.png', 'explore'), - ('explore/*.frag', 'explore'), - ('explore/*.vert', 'explore') - ], - hiddenimports=[], - hookspath=[], - runtime_hooks=[], - excludes=[], - win_no_prefer_redirects=False, - win_private_assemblies=False, - cipher=block_cipher, - noarchive=False) -pyz = PYZ(a.pure, a.zipped_data, - cipher=block_cipher) -exe = EXE(pyz, - a.scripts, - [], - exclude_binaries=True, - name='7a-explore', - debug=False, - bootloader_ignore_signals=False, - strip=False, - upx=True, - console=True ) -coll = COLLECT(exe, - a.binaries, - a.zipfiles, - a.datas, - strip=False, - upx=True, - name='7a-explore') +# -*- mode: python ; coding: utf-8 -*- + +block_cipher = None + + +a = Analysis(['7a-explore.py'], + pathex=['H:\\Projects\\ImageAnalysis\\scripts'], + binaries=[ + ('C:\\Users\curt\Anaconda3\Lib\site-packages\panda3d\cgGL.dll', '.'), + ('C:\\Users\curt\Anaconda3\Lib\site-packages\panda3d\libpandagl.dll', '.'), + ('C:\\Users\curt\Anaconda3\Lib\site-packages\panda3d\libpandaegg.dll', '.'), + ('C:\\Users\curt\Anaconda3\Lib\site-packages\panda3d\libp3assimp.dll', '.'), + ('C:\\Users\curt\Anaconda3\Lib\site-packages\panda3d\libp3ptloader.dll', '.'), + ('C:\\Users\curt\Anaconda3\Lib\site-packages\panda3d\libp3windisplay.dll', '.') + ], + datas=[ + ('C:\\Users\curt\Anaconda3\Lib\site-packages\panda3d\etc\*.prc', 'etc'), + ('explore/*.png', 'explore'), + ('explore/*.frag', 'explore'), + ('explore/*.vert', 'explore') + ], + hiddenimports=[], + hookspath=[], + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False) +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) +exe = EXE(pyz, + a.scripts, + [], + exclude_binaries=True, + name='7a-explore', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=True ) +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=True, + name='7a-explore') diff --git a/scripts/99-create-group-project.py b/scripts/99_create_group_project.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/99-create-group-project.py rename to scripts/99_create_group_project.py diff --git a/scripts/99-est-cam-transform.py b/scripts/99_est_cam_transform.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/99-est-cam-transform.py rename to scripts/99_est_cam_transform.py diff --git a/scripts/99-make-pix4d.py b/scripts/99_make_pix4d.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/99-make-pix4d.py rename to scripts/99_make_pix4d.py diff --git a/scripts/99-new-camera.py b/scripts/99_new_camera.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/99-new-camera.py rename to scripts/99_new_camera.py diff --git a/scripts/99-trim-far.py b/scripts/99_trim_far.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/99-trim-far.py rename to scripts/99_trim_far.py diff --git a/scripts/99-vignette.py b/scripts/99_vignette.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/99-vignette.py rename to scripts/99_vignette.py diff --git a/scripts/99-zip-project.py b/scripts/99_zip_project.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/99-zip-project.py rename to scripts/99_zip_project.py diff --git a/scripts/__init__.py b/scripts/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/scripts/archive/1c-import-images.py b/scripts/archive/1c_import_images.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/1c-import-images.py rename to scripts/archive/1c_import_images.py diff --git a/scripts/archive/1d-show-images.py b/scripts/archive/1d_show_images.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/1d-show-images.py rename to scripts/archive/1d_show_images.py diff --git a/scripts/archive/2b-set-camera-poses.py b/scripts/archive/2b_set_camera_poses.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/2b-set-camera-poses.py rename to scripts/archive/2b_set_camera_poses.py diff --git a/scripts/archive/2d-gen-direct-kml.py b/scripts/archive/2d_gen_direct_kml.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/2d-gen-direct-kml.py rename to scripts/archive/2d_gen_direct_kml.py diff --git a/scripts/archive/3c-cull-fringe-features.py b/scripts/archive/3c_cull_fringe_features.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/3c-cull-fringe-features.py rename to scripts/archive/3c_cull_fringe_features.py diff --git a/scripts/archive/3c-partition-set.py b/scripts/archive/3c_partition_set.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/3c-partition-set.py rename to scripts/archive/3c_partition_set.py diff --git a/scripts/archive/4b-simple-matches-reset.py b/scripts/archive/4b_simple_matches_reset.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/4b-simple-matches-reset.py rename to scripts/archive/4b_simple_matches_reset.py diff --git a/scripts/archive/4d-match-grouping.py b/scripts/archive/4d_match_grouping.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/4d-match-grouping.py rename to scripts/archive/4d_match_grouping.py diff --git a/scripts/archive/4d-reset-matches-ned1.py b/scripts/archive/4d_reset_matches_ned1.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/4d-reset-matches-ned1.py rename to scripts/archive/4d_reset_matches_ned1.py diff --git a/scripts/archive/4d-reset-matches-ned2.py b/scripts/archive/4d_reset_matches_ned2.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/4d-reset-matches-ned2.py rename to scripts/archive/4d_reset_matches_ned2.py diff --git a/scripts/archive/4g-match-culling.py b/scripts/archive/4g_match_culling.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/4g-match-culling.py rename to scripts/archive/4g_match_culling.py diff --git a/scripts/archive/4z-collapse-matches.py b/scripts/archive/4z_collapse_matches.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/4z-collapse-matches.py rename to scripts/archive/4z_collapse_matches.py diff --git a/scripts/archive/5a-sba1.py b/scripts/archive/5a_sba1.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/5a-sba1.py rename to scripts/archive/5a_sba1.py diff --git a/scripts/archive/5a-sba3.py b/scripts/archive/5a_sba3.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/5a-sba3.py rename to scripts/archive/5a_sba3.py diff --git a/scripts/archive/5a-sba4.py b/scripts/archive/5a_sba4.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/5a-sba4.py rename to scripts/archive/5a_sba4.py diff --git a/scripts/archive/5b-solver1.py b/scripts/archive/5b_solver1.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/5b-solver1.py rename to scripts/archive/5b_solver1.py diff --git a/scripts/archive/5b-solver2.py b/scripts/archive/5b_solver2.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/5b-solver2.py rename to scripts/archive/5b_solver2.py diff --git a/scripts/archive/5b-solver3.py b/scripts/archive/5b_solver3.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/5b-solver3.py rename to scripts/archive/5b_solver3.py diff --git a/scripts/archive/5b-solver4.py b/scripts/archive/5b_solver4.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/5b-solver4.py rename to scripts/archive/5b_solver4.py diff --git a/scripts/archive/5b-solver5.py b/scripts/archive/5b_solver5.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/5b-solver5.py rename to scripts/archive/5b_solver5.py diff --git a/scripts/archive/5c-mre-by-feature.py b/scripts/archive/5c_mre_by_feature.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/5c-mre-by-feature.py rename to scripts/archive/5c_mre_by_feature.py diff --git a/scripts/archive/5c-mre-by-feature1.py b/scripts/archive/5c_mre_by_feature1.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/5c-mre-by-feature1.py rename to scripts/archive/5c_mre_by_feature1.py diff --git a/scripts/archive/5c-mre-by-feature2.py b/scripts/archive/5c_mre_by_feature2.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/5c-mre-by-feature2.py rename to scripts/archive/5c_mre_by_feature2.py diff --git a/scripts/archive/5c-mre-by-feature3.py b/scripts/archive/5c_mre_by_feature3.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/5c-mre-by-feature3.py rename to scripts/archive/5c_mre_by_feature3.py diff --git a/scripts/archive/6a-render-model1.py b/scripts/archive/6a_render_model1.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/6a-render-model1.py rename to scripts/archive/6a_render_model1.py diff --git a/scripts/archive/6b-delaunay1.py b/scripts/archive/6b_delaunay1.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/6b-delaunay1.py rename to scripts/archive/6b_delaunay1.py diff --git a/scripts/archive/6b-delaunay2.py b/scripts/archive/6b_delaunay2.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/6b-delaunay2.py rename to scripts/archive/6b_delaunay2.py diff --git a/scripts/archive/6b-delaunay3.py b/scripts/archive/6b_delaunay3.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/6b-delaunay3.py rename to scripts/archive/6b_delaunay3.py diff --git a/scripts/archive/6b-delaunay4.py b/scripts/archive/6b_delaunay4.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/archive/6b-delaunay4.py rename to scripts/archive/6b_delaunay4.py diff --git a/scripts/sandbox/2c-show-camera-poses.py b/scripts/sandbox/2c_show_camera_poses.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/sandbox/2c-show-camera-poses.py rename to scripts/sandbox/2c_show_camera_poses.py diff --git a/scripts/sandbox/99-disparity-test.py b/scripts/sandbox/99_disparity_test.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/sandbox/99-disparity-test.py rename to scripts/sandbox/99_disparity_test.py diff --git a/scripts/sandbox/99-sentera-five-channel.py b/scripts/sandbox/99_sentera_five_channel.py old mode 100755 new mode 100644 similarity index 100% rename from scripts/sandbox/99-sentera-five-channel.py rename to scripts/sandbox/99_sentera_five_channel.py diff --git a/tests/0-distort-test.py b/tests/0_distort_test.py old mode 100755 new mode 100644 similarity index 100% rename from tests/0-distort-test.py rename to tests/0_distort_test.py diff --git a/tests/0-pickle-matches.py b/tests/0_pickle_matches.py old mode 100755 new mode 100644 similarity index 100% rename from tests/0-pickle-matches.py rename to tests/0_pickle_matches.py diff --git a/tests/0-projection-test.py b/tests/0_projection_test.py old mode 100755 new mode 100644 similarity index 100% rename from tests/0-projection-test.py rename to tests/0_projection_test.py diff --git a/tests/0-quat-avg.py b/tests/0_quat_avg.py old mode 100755 new mode 100644 similarity index 100% rename from tests/0-quat-avg.py rename to tests/0_quat_avg.py diff --git a/tests/0-srtm-test-1.py b/tests/0_srtm_test_1.py old mode 100755 new mode 100644 similarity index 100% rename from tests/0-srtm-test-1.py rename to tests/0_srtm_test_1.py diff --git a/tests/0-srtm-test-2.py b/tests/0_srtm_test_2.py old mode 100755 new mode 100644 similarity index 100% rename from tests/0-srtm-test-2.py rename to tests/0_srtm_test_2.py diff --git a/tests/0-transform-test.py b/tests/0_transform_test.py old mode 100755 new mode 100644 similarity index 100% rename from tests/0-transform-test.py rename to tests/0_transform_test.py diff --git a/tests/0-weighted-affine.py b/tests/0_weighted_affine.py old mode 100755 new mode 100644 similarity index 100% rename from tests/0-weighted-affine.py rename to tests/0_weighted_affine.py diff --git a/tests/illumintation-sensor-test.py b/tests/illumintation_sensor_test.py old mode 100755 new mode 100644 similarity index 100% rename from tests/illumintation-sensor-test.py rename to tests/illumintation_sensor_test.py diff --git a/tests/oriental-bittersweet.py b/tests/oriental_bittersweet.py old mode 100755 new mode 100644 similarity index 100% rename from tests/oriental-bittersweet.py rename to tests/oriental_bittersweet.py diff --git a/video/1a-est-gyro-rates.py b/video/1a_est_gyro_rates.py old mode 100755 new mode 100644 similarity index 100% rename from video/1a-est-gyro-rates.py rename to video/1a_est_gyro_rates.py diff --git a/video/1c-aruco-tracker.py b/video/1c_aruco_tracker.py old mode 100755 new mode 100644 similarity index 100% rename from video/1c-aruco-tracker.py rename to video/1c_aruco_tracker.py diff --git a/video/2-gen-hud-overlay.py b/video/2_gen_hud_overlay.py old mode 100755 new mode 100644 similarity index 100% rename from video/2-gen-hud-overlay.py rename to video/2_gen_hud_overlay.py diff --git a/video/3-extract-and-geotag-frames.py b/video/3_extract_and_geotag_frames.py old mode 100755 new mode 100644 similarity index 100% rename from video/3-extract-and-geotag-frames.py rename to video/3_extract_and_geotag_frames.py diff --git a/video/archive/1a-est-gyro-rates.py b/video/archive/1a_est_gyro_rates.py old mode 100755 new mode 100644 similarity index 100% rename from video/archive/1a-est-gyro-rates.py rename to video/archive/1a_est_gyro_rates.py diff --git a/video/archive/1b-est-gyro-rates.py b/video/archive/1b_est_gyro_rates.py old mode 100755 new mode 100644 similarity index 100% rename from video/archive/1b-est-gyro-rates.py rename to video/archive/1b_est_gyro_rates.py diff --git a/video/archive/2a-gen-hud-overlay.py b/video/archive/2a_gen_hud_overlay.py old mode 100755 new mode 100644 similarity index 100% rename from video/archive/2a-gen-hud-overlay.py rename to video/archive/2a_gen_hud_overlay.py From c397f623b0e1c4e87445d86128aae628f7a62fd0 Mon Sep 17 00:00:00 2001 From: Ashley Walker Date: Fri, 2 Aug 2019 14:30:23 +1000 Subject: [PATCH 2/4] A standard project runs --- .gitignore | 4 +- cameras/DJI_FC330.json | 28 + scripts/1a_create_project.py | 30 +- scripts/1b_set_camera_config.py | 106 +- scripts/2a_set_poses.py | 89 +- scripts/3a_detect_features.py | 184 ++-- scripts/4a_matching.py | 250 ++--- scripts/4b_clean_and_combine_matches.py | 563 +++++----- scripts/4c_match_triangulation.py | 257 ++--- scripts/4d_image_groups.py | 109 +- scripts/4e_show_match_pairs.py | 216 ++-- scripts/5a_optimize.py | 469 ++++----- scripts/5b_colocated_feats.py | 189 ++-- scripts/5b_mre_by_image.py | 356 +++---- scripts/6a_render_model2.py | 421 ++++---- scripts/6b_delaunay5.py | 159 +-- scripts/7a_explore.py | 1250 ++++++++++++----------- scripts/import_proxy.py | 43 + scripts/lib/Optimizer.py | 17 +- scripts/lib/ProjectMgr.py | 3 + scripts/lib/SRTM.py | 11 +- scripts/project_runner.py | 161 +++ 22 files changed, 2645 insertions(+), 2270 deletions(-) create mode 100644 cameras/DJI_FC330.json create mode 100644 scripts/import_proxy.py create mode 100644 scripts/project_runner.py diff --git a/.gitignore b/.gitignore index fc4795bf..9cf68468 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ *~ *.pyc -*.vscode \ No newline at end of file +**.vscode +**/aura-props +**/tmp \ No newline at end of file diff --git a/cameras/DJI_FC330.json b/cameras/DJI_FC330.json new file mode 100644 index 00000000..e0703748 --- /dev/null +++ b/cameras/DJI_FC330.json @@ -0,0 +1,28 @@ +{ + "K": [ + 3666.666504, + 0.0, + 2432.0, + 0.0, + 3666.666504, + 1824.0, + 0.0, + 0.0, + 1.0 + ], + "ccd_height_mm": 4.72, + "ccd_width_mm": 6.30, + "dist_coeffs": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "focal_len_mm": 4, + "height_px": 3000, + "lens_model": "unknown", + "make": "DJI", + "model": "FC330", + "width_px": 4000 +} \ No newline at end of file diff --git a/scripts/1a_create_project.py b/scripts/1a_create_project.py index e9aacf79..92414f3a 100644 --- a/scripts/1a_create_project.py +++ b/scripts/1a_create_project.py @@ -1,25 +1,25 @@ #!/usr/bin/python3 -import argparse -import os +import os, sys, argparse from lib import ProjectMgr # initialize a new project workspace +def new_project(project_dir): + # test if images directory exists + if not os.path.isdir(project_dir): + print("Images directory doesn't exist:", args.project) + quit() -parser = argparse.ArgumentParser(description='Create an empty project.') -parser.add_argument('--project', required=True, help='Directory with a set of aerial images.') + # create an empty project + proj = ProjectMgr.ProjectMgr(project_dir, create=True) -args = parser.parse_args() + # and save what we have so far ... + proj.save() -# test if images directory exists -if not os.path.isdir(args.project): - print("Images directory doesn't exist:", args.project) - quit() - -# create an empty project -proj = ProjectMgr.ProjectMgr(args.project, create=True) - -# and save what we have so far ... -proj.save() +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Create an empty project.') + parser.add_argument('--project', required=True, help='Directory with a set of aerial images.') + args = parser.parse_args() + new_project(args.project) \ No newline at end of file diff --git a/scripts/1b_set_camera_config.py b/scripts/1b_set_camera_config.py index 0ae7fd55..6a6b204c 100644 --- a/scripts/1b_set_camera_config.py +++ b/scripts/1b_set_camera_config.py @@ -10,63 +10,65 @@ from lib import ProjectMgr -# set all the various camera configuration parameters -parser = argparse.ArgumentParser(description='Set camera configuration.') -parser.add_argument('--project', required=True, help='project directory') -parser.add_argument('--camera', help='camera config file') -parser.add_argument('--yaw-deg', type=float, default=0.0, - help='camera yaw mounting offset from aircraft') -parser.add_argument('--pitch-deg', type=float, default=-90.0, - help='camera pitch mounting offset from aircraft') -parser.add_argument('--roll-deg', type=float, default=0.0, - help='camera roll mounting offset from aircraft') -args = parser.parse_args() +def set_camera(project_dir, camera, yaw_deg = 0.0, pitch_deg = -90.0, roll_deg = 0.0): + proj = ProjectMgr.ProjectMgr(project_dir) -proj = ProjectMgr.ProjectMgr(args.project) - -if args.camera: - # specified on command line - camera_file = args.camera -else: - # auto detect camera from image meta data - camera, make, model, lens_model = proj.detect_camera() - camera_file = os.path.join("..", "cameras", camera + ".json") -print("Camera:", camera_file) + if camera: + # specified on command line + camera_file = camera + else: + # auto detect camera from image meta data + camera, make, model, lens_model = proj.detect_camera() + camera_file = os.path.join("..", "cameras", camera + ".json") + print("Camera:", camera_file) -# copy/overlay/update the specified camera config into the existing -# project configuration -cam_node = getNode('/config/camera', True) -tmp_node = PropertyNode() -if props_json.load(camera_file, tmp_node): - for child in tmp_node.getChildren(expand=False): - if tmp_node.isEnum(child): - # print(child, tmp_node.getLen(child)) - for i in range(tmp_node.getLen(child)): - cam_node.setFloatEnum(child, i, tmp_node.getFloatEnum(child, i)) - else: - # print(child, type(tmp_node.__dict__[child])) - child_type = type(tmp_node.__dict__[child]) - if child_type is float: - cam_node.setFloat(child, tmp_node.getFloat(child)) - elif child_type is int: - cam_node.setInt(child, tmp_node.getInt(child)) - elif child_type is str: - cam_node.setString(child, tmp_node.getString(child)) + # copy/overlay/update the specified camera config into the existing + # project configuration + cam_node = getNode('/config/camera', True) + tmp_node = PropertyNode() + if props_json.load(camera_file, tmp_node): + for child in tmp_node.getChildren(expand=False): + if tmp_node.isEnum(child): + # print(child, tmp_node.getLen(child)) + for i in range(tmp_node.getLen(child)): + cam_node.setFloatEnum(child, i, tmp_node.getFloatEnum(child, i)) else: - print('Unknown child type:', child, child_type) + # print(child, type(tmp_node.__dict__[child])) + child_type = type(tmp_node.__dict__[child]) + if child_type is float: + cam_node.setFloat(child, tmp_node.getFloat(child)) + elif child_type is int: + cam_node.setInt(child, tmp_node.getInt(child)) + elif child_type is str: + cam_node.setString(child, tmp_node.getString(child)) + else: + print('Unknown child type:', child, child_type) - proj.cam.set_mount_params(args.yaw_deg, args.pitch_deg, args.roll_deg) + proj.cam.set_mount_params(yaw_deg, pitch_deg, roll_deg) - # note: dist_coeffs = array[5] = k1, k2, p1, p2, k3 + # note: dist_coeffs = array[5] = k1, k2, p1, p2, k3 - # ... and save - proj.save() -else: - # failed to load camera config file - if not args.camera: - print("Camera autodetection failed.") - print("Consider running the new camera script to create a camera config") - print("and then try running this script again.") + # ... and save + proj.save() else: - print("Provided camera config not found:", args.camera) + # failed to load camera config file + if not camera: + print("Camera autodetection failed.") + print("Consider running the new camera script to create a camera config") + print("and then try running this script again.") + else: + print("Provided camera config not found:", camera) + +if __name__ == "__main__": + # set all the various camera configuration parameters + parser = argparse.ArgumentParser(description='Set camera configuration.') + parser.add_argument('--project', required=True, help='project directory') + parser.add_argument('--camera', help='camera config file') + parser.add_argument('--yaw-deg', type=float, default=0.0, + help='camera yaw mounting offset from aircraft') + parser.add_argument('--pitch-deg', type=float, default=-90.0, + help='camera pitch mounting offset from aircraft') + parser.add_argument('--roll-deg', type=float, default=0.0, + help='camera roll mounting offset from aircraft') + args = parser.parse_args() \ No newline at end of file diff --git a/scripts/2a_set_poses.py b/scripts/2a_set_poses.py index a5715683..4d5a8991 100644 --- a/scripts/2a_set_poses.py +++ b/scripts/2a_set_poses.py @@ -11,48 +11,49 @@ # for all the images in the project image_dir, detect features using the # specified method and parameters -parser = argparse.ArgumentParser(description='Set the aircraft poses from flight data.') -parser.add_argument('--project', required=True, help='project directory') -#parser.add_argument('--meta', help='use the specified image-metadata.txt file (lat,lon,alt,yaw,pitch,roll)') -#parser.add_argument('--pix4d', help='use the specified pix4d csv file (lat,lon,alt,roll,pitch,yaw)') -parser.add_argument('--max-angle', type=float, default=25.0, help='max pitch or roll angle for image inclusion') - -args = parser.parse_args() - -proj = ProjectMgr.ProjectMgr(args.project) -print("Loading image info...") -proj.load_images_info() - -# simplifying assumption -image_dir = args.project - -pix4d_file = os.path.join(image_dir, 'pix4d.csv') -meta_file = os.path.join(image_dir, 'image-metadata.txt') -if os.path.exists(pix4d_file): - Pose.setAircraftPoses(proj, pix4d_file, order='rpy', - max_angle=args.max_angle) -elif os.path.exists(meta_file): - Pose.setAircraftPoses(proj, meta_file, order='ypr', - max_angle=args.max_angle) -else: - print("Error: no pose file found in image directory:", image_dir) - quit() - -# compute the project's NED reference location (based on average of -# aircraft poses) -proj.compute_ned_reference_lla() -ned_node = getNode('/config/ned_reference', True) -print("NED reference location:") -ned_node.pretty_print(" ") - -# set the camera poses (fixed offset from aircraft pose) Camera pose -# location is specfied in ned, so do this after computing the ned -# reference point for this project. -Pose.compute_camera_poses(proj) - -# save the poses -proj.save_images_info() - -# save change to ned reference -proj.save() +def set_pose(project_dir, max_angle = 25.0 ): + proj = ProjectMgr.ProjectMgr(project_dir) + print("Loading image info...") + proj.load_images_info() + + # simplifying assumption + image_dir = project_dir + + pix4d_file = os.path.join(image_dir, 'pix4d.csv') + meta_file = os.path.join(image_dir, 'image-metadata.txt') + if os.path.exists(pix4d_file): + Pose.setAircraftPoses(proj, pix4d_file, order='rpy', + max_angle=max_angle) + elif os.path.exists(meta_file): + Pose.setAircraftPoses(proj, meta_file, order='ypr', + max_angle=max_angle) + else: + print("Error: no pose file found in image directory:", image_dir) + quit() + + # compute the project's NED reference location (based on average of + # aircraft poses) + proj.compute_ned_reference_lla() + ned_node = getNode('/config/ned_reference', True) + print("NED reference location:") + ned_node.pretty_print(" ") + + # set the camera poses (fixed offset from aircraft pose) Camera pose + # location is specfied in ned, so do this after computing the ned + # reference point for this project. + Pose.compute_camera_poses(proj) + + # save the poses + proj.save_images_info() + + # save change to ned reference + proj.save() +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Set the aircraft poses from flight data.') + parser.add_argument('--project', required=True, help='project directory') + #parser.add_argument('--meta', help='use the specified image-metadata.txt file (lat,lon,alt,yaw,pitch,roll)') + #parser.add_argument('--pix4d', help='use the specified pix4d csv file (lat,lon,alt,roll,pitch,yaw)') + parser.add_argument('--max-angle', type=float, default=25.0, help='max pitch or roll angle for image inclusion') + + args = parser.parse_args() \ No newline at end of file diff --git a/scripts/3a_detect_features.py b/scripts/3a_detect_features.py index 226f00ca..85690139 100644 --- a/scripts/3a_detect_features.py +++ b/scripts/3a_detect_features.py @@ -9,84 +9,106 @@ from lib import ProjectMgr -# for all the images in the project image_dir, detect features using the -# specified method and parameters -# -# Suggests censure/star has good stability between images (highest -# likelihood of finding a match in the target features set: -# http://computer-vision-talks.com/articles/2011-01-04-comparison-of-the-opencv-feature-detection-algorithms/ -# -# Suggests censure/star works better than sift in outdoor natural -# environments: http://www.ai.sri.com/~agrawal/isrr.pdf -# -# Basic description of censure/star algorithm: http://www.researchgate.net/publication/221304099_CenSurE_Center_Surround_Extremas_for_Realtime_Feature_Detection_and_Matching - -parser = argparse.ArgumentParser(description='Detect features in the project images.') -parser.add_argument('--project', required=True, help='project directory') -parser.add_argument('--scale', type=float, default=0.5, help='scale images before detecting features, this acts much like a noise filter') -parser.add_argument('--detector', default='SIFT', - choices=['SIFT', 'SURF', 'ORB', 'Star']) -parser.add_argument('--sift-max-features', default=30000, - help='maximum SIFT features') -parser.add_argument('--surf-hessian-threshold', default=600, - help='hessian threshold for surf method') -parser.add_argument('--surf-noctaves', default=4, - help='use a bigger number to detect bigger features') -parser.add_argument('--orb-max-features', default=2000, - help='maximum ORB features') -parser.add_argument('--grid-detect', default=1, - help='run detect on gridded squares for (maybe) better feature distribution, 4 is a good starting value, only affects ORB method') -parser.add_argument('--star-max-size', default=16, - help='4, 6, 8, 11, 12, 16, 22, 23, 32, 45, 46, 64, 90, 128') -parser.add_argument('--star-response-threshold', default=30) -parser.add_argument('--star-line-threshold-projected', default=10) -parser.add_argument('--star-line-threshold-binarized', default=8) -parser.add_argument('--star-suppress-nonmax-size', default=5) -parser.add_argument('--reject-margin', default=0, help='reject features within this distance of the image outer edge margin') - -parser.add_argument('--show', action='store_true', - help='show features as we detect them') - -args = parser.parse_args() - -proj = ProjectMgr.ProjectMgr(args.project) - -# load existing images info which could include things like camera pose -proj.load_images_info() - -# setup project detector params -detector_node = getNode('/config/detector', True) -detector_node.setString('detector', args.detector) -detector_node.setString('scale', args.scale) -if args.detector == 'SIFT': - detector_node.setInt('sift_max_features', args.sift_max_features) -elif args.detector == 'SURF': - detector_node.setInt('surf_hessian_threshold', args.surf_hessian_threshold) - detector_node.setInt('surf_noctaves', args.surf_noctaves) -elif args.detector == 'ORB': - detector_node.setInt('grid_detect', args.grid_detect) - detector_node.setInt('orb_max_features', args.orb_max_features) -elif args.detector == 'Star': - detector_node.setInt('star_max_size', args.star_max_size) - detector_node.setInt('star_response_threshold', - args.star_response_threshold) - detector_node.setInt('star_line_threshold_projected', - args.star_response_threshold) - detector_node.setInt('star_line_threshold_binarized', - args.star_line_threshold_binarized) - detector_node.setInt('star_suppress_nonmax_size', - args.star_suppress_nonmax_size) - -# find features in the full image set -proj.detect_features(scale=args.scale, show=args.show) - -feature_count = 0 -image_count = 0 -for image in proj.image_list: - feature_count += len(image.kp_list) - image_count += 1 - -print("Average # of features per image found = %.0f" % (feature_count / image_count)) - -print("Saving project configuration") -proj.save() +def detect(project_dir, detection_options): + detector = detection_options[0] + scale = detection_options[1] + sift_max_features = detection_options[2] + surf_hessian_threshold = detection_options[3] + surf_noctaves = detection_options[4] + grid_detect = detection_options[5] + orb_max_features = detection_options[6] + star_max_size = detection_options[7] + star_response_threshold = detection_options[8] + star_line_threshold_binarized = detection_options[9] + star_suppress_nonmax_size = detection_options[10] + show = detection_options[11] + + proj = ProjectMgr.ProjectMgr(project_dir) + + # load existing images info which could include things like camera pose + proj.load_images_info() + + # setup project detector params + detector_node = getNode('/config/detector', True) + detector_node.setString('detector', detector) + detector_node.setString('scale', scale) + if detector == 'SIFT': + detector_node.setInt('sift_max_features', sift_max_features) + elif detector == 'SURF': + detector_node.setInt('surf_hessian_threshold', surf_hessian_threshold) + detector_node.setInt('surf_noctaves', surf_noctaves) + elif detector == 'ORB': + detector_node.setInt('grid_detect', grid_detect) + detector_node.setInt('orb_max_features',orb_max_features) + elif detector == 'Star': + detector_node.setInt('star_max_size', star_max_size) + detector_node.setInt('star_response_threshold', + star_response_threshold) + detector_node.setInt('star_line_threshold_projected', + star_response_threshold) + detector_node.setInt('star_line_threshold_binarized', + star_line_threshold_binarized) + detector_node.setInt('star_suppress_nonmax_size', + star_suppress_nonmax_size) + + # find features in the full image set + proj.detect_features(scale=scale, show=show) + + feature_count = 0 + image_count = 0 + for image in proj.image_list: + feature_count += len(image.kp_list) + image_count += 1 + + print("Average # of features per image found = %.0f" % (feature_count / image_count)) + + print("Saving project configuration") + proj.save() + +if __name__ == "__main__": + # for all the images in the project image_dir, detect features using the + # specified method and parameters + # + # Suggests censure/star has good stability between images (highest + # likelihood of finding a match in the target features set: + # http://computer-vision-talks.com/articles/2011-01-04-comparison-of-the-opencv-feature-detection-algorithms/ + # + # Suggests censure/star works better than sift in outdoor natural + # environments: http://www.ai.sri.com/~agrawal/isrr.pdf + # + # Basic description of censure/star algorithm: http://www.researchgate.net/publication/221304099_CenSurE_Center_Surround_Extremas_for_Realtime_Feature_Detection_and_Matching + + parser = argparse.ArgumentParser(description='Detect features in the project images.') + parser.add_argument('--project', required=True, help='project directory') + parser.add_argument('--scale', type=float, default=0.5, help='scale images before detecting features, this acts much like a noise filter') + parser.add_argument('--detector', default='SIFT', + choices=['SIFT', 'SURF', 'ORB', 'Star']) + parser.add_argument('--sift-max-features', default=30000, + help='maximum SIFT features') + parser.add_argument('--surf-hessian-threshold', default=600, + help='hessian threshold for surf method') + parser.add_argument('--surf-noctaves', default=4, + help='use a bigger number to detect bigger features') + parser.add_argument('--orb-max-features', default=2000, + help='maximum ORB features') + parser.add_argument('--grid-detect', default=1, + help='run detect on gridded squares for (maybe) better feature distribution, 4 is a good starting value, only affects ORB method') + parser.add_argument('--star-max-size', default=16, + help='4, 6, 8, 11, 12, 16, 22, 23, 32, 45, 46, 64, 90, 128') + parser.add_argument('--star-response-threshold', default=30) + parser.add_argument('--star-line-threshold-projected', default=10) + parser.add_argument('--star-line-threshold-binarized', default=8) + parser.add_argument('--star-suppress-nonmax-size', default=5) + parser.add_argument('--reject-margin', default=0, help='reject features within this distance of the image outer edge margin') + parser.add_argument('--show', action='store_true', + help='show features as we detect them') + + args = parser.parse_args() + + detection_options = [args.detector, args.scale, args.sift_max_features, + args.surf_hessian_threshold, args.surf_noctaves, args.grid_detect, + args.orb_max_features, args.star_max_size, args.star_response_threshold, + args.star_line_threshold_binarized, args.star_suppress_nonmax_size, + args.show] + + detect(args.project, detection_options) \ No newline at end of file diff --git a/scripts/4a_matching.py b/scripts/4a_matching.py index 76c3bdcd..3b79acdd 100644 --- a/scripts/4a_matching.py +++ b/scripts/4a_matching.py @@ -13,122 +13,138 @@ from lib import SRTM # working on matching features ... - -parser = argparse.ArgumentParser(description='Keypoint projection.') -parser.add_argument('--project', required=True, help='project directory') -parser.add_argument('--matcher', default='FLANN', - choices=['FLANN', 'BF']) -parser.add_argument('--match-ratio', default=0.75, type=float, - help='match ratio') -parser.add_argument('--min-pairs', default=25, type=int, - help='minimum matches between image pairs to keep') -parser.add_argument('--min-dist', default=0, type=float, - help='minimum 2d camera distance for pair comparison') -parser.add_argument('--max-dist', default=75, type=float, - help='maximum 2d camera distance for pair comparison') -parser.add_argument('--filter', default='gms', - choices=['gms', 'homography', 'fundamental', 'essential', 'none']) -parser.add_argument('--min-chain-length', type=int, default=3, help='minimum match chain length (3 recommended)') -#parser.add_argument('--ground', type=float, help='ground elevation in meters') - -args = parser.parse_args() - -proj = ProjectMgr.ProjectMgr(args.project) -proj.load_images_info() -proj.load_features(descriptors=False) # descriptors cached on the fly later -proj.undistort_keypoints() -proj.load_match_pairs() - -matcher_node = getNode('/config/matcher', True) -matcher_node.setString('matcher', args.matcher) -matcher_node.setFloat('match_ratio', args.match_ratio) -matcher_node.setString('filter', args.filter) -matcher_node.setInt('min_pairs', args.min_pairs) -matcher_node.setFloat('min_dist', args.min_dist) -matcher_node.setFloat('max_dist', args.max_dist) -matcher_node.setInt('min_chain_len', args.min_chain_length) - -# save any config changes -proj.save() - -# camera calibration -K = proj.cam.get_K() -print("K:", K) - -# fire up the matcher -m = Matcher.Matcher() -m.configure() -m.robustGroupMatches(proj.image_list, K, - filter=args.filter, review=False) - -# The following code is deprecated ... -do_old_match_consolodation = False -if do_old_match_consolodation: - # build a list of all 'unique' keypoints. Include an index to each - # containing image and feature. - matches_dict = {} - for i, i1 in enumerate(proj.image_list): - for j, matches in enumerate(i1.match_list): - if j > i: - for pair in matches: - key = "%d-%d" % (i, pair[0]) - m1 = [i, pair[0]] - m2 = [j, pair[1]] - if key in matches_dict: - feature_dict = matches_dict[key] - feature_dict['pts'].append(m2) - else: - feature_dict = {} - feature_dict['pts'] = [m1, m2] - matches_dict[key] = feature_dict - #print match_dict - count = 0.0 - sum = 0.0 - for key in matches_dict: - sum += len(matches_dict[key]['pts']) - count += 1 - if count > 0.1: - print("total unique features in image set = %d" % count) - print("kp average instances = %.4f" % (sum / count)) - - # compute an initial guess at the 3d location of each unique feature - # by averaging the locations of each projection - for key in matches_dict: - feature_dict = matches_dict[key] +def match(project_dir, matching_options): + + + matcher = matching_options[0] + match_ratio = matching_options[1] + min_pairs = matching_options[2] + min_dist = matching_options[3] + max_dist = matching_options[4] + filters = matching_options[5] + min_chain_length = matching_options[6] + + proj = ProjectMgr.ProjectMgr(project_dir) + proj.load_images_info() + proj.load_features(descriptors=False) # descriptors cached on the fly later + proj.undistort_keypoints() + proj.load_match_pairs() + + matcher_node = getNode('/config/matcher', True) + matcher_node.setString('matcher', matcher) + matcher_node.setFloat('match_ratio', match_ratio) + matcher_node.setString('filter', filters) + matcher_node.setInt('min_pairs', min_pairs) + matcher_node.setFloat('min_dist', min_dist) + matcher_node.setFloat('max_dist', max_dist) + matcher_node.setInt('min_chain_len', min_chain_length) + + # save any config changes + proj.save() + + # camera calibration + K = proj.cam.get_K() + print("K:", K) + + # fire up the matcher + m = Matcher.Matcher() + m.configure() + m.robustGroupMatches(proj.image_list, K, + filter=filters, review=False) + + # The following code is deprecated ... + do_old_match_consolodation = False + if do_old_match_consolodation: + # build a list of all 'unique' keypoints. Include an index to each + # containing image and feature. + matches_dict = {} + for i, i1 in enumerate(proj.image_list): + for j, matches in enumerate(i1.match_list): + if j > i: + for pair in matches: + key = "%d-%d" % (i, pair[0]) + m1 = [i, pair[0]] + m2 = [j, pair[1]] + if key in matches_dict: + feature_dict = matches_dict[key] + feature_dict['pts'].append(m2) + else: + feature_dict = {} + feature_dict['pts'] = [m1, m2] + matches_dict[key] = feature_dict + #print match_dict + count = 0.0 + sum = 0.0 + for key in matches_dict: + sum += len(matches_dict[key]['pts']) + count += 1 + if count > 0.1: + print("total unique features in image set = %d" % count) + print("kp average instances = %.4f" % (sum / count)) + + # compute an initial guess at the 3d location of each unique feature + # by averaging the locations of each projection + for key in matches_dict: + feature_dict = matches_dict[key] + sum = np.array( [0.0, 0.0, 0.0] ) + for p in feature_dict['pts']: + sum += proj.image_list[ p[0] ].coord_list[ p[1] ] + ned = sum / len(feature_dict['pts']) + feature_dict['ned'] = ned.tolist() + + def update_match_location(match): sum = np.array( [0.0, 0.0, 0.0] ) - for p in feature_dict['pts']: + for p in match[1:]: + # print proj.image_list[ p[0] ].coord_list[ p[1] ] sum += proj.image_list[ p[0] ].coord_list[ p[1] ] - ned = sum / len(feature_dict['pts']) - feature_dict['ned'] = ned.tolist() - -def update_match_location(match): - sum = np.array( [0.0, 0.0, 0.0] ) - for p in match[1:]: - # print proj.image_list[ p[0] ].coord_list[ p[1] ] - sum += proj.image_list[ p[0] ].coord_list[ p[1] ] - ned = sum / len(match[1:]) - # print "avg =", ned - match[0] = ned.tolist() - return match - -if False: - print("Constructing unified match structure...") - print("This probably will fail because we didn't do the ground intersection at the start...") - matches_direct = [] - for i, image in enumerate(proj.image_list): - # print image.name - for j, matches in enumerate(image.match_list): - # print proj.image_list[j].name - if j > i: - for pair in matches: - match = [] - # ned place holder - match.append([0.0, 0.0, 0.0]) - match.append([i, pair[0]]) - match.append([j, pair[1]]) - update_match_location(match) - matches_direct.append(match) - # print pair, match - - print("Writing match file ...") - pickle.dump(matches_direct, open(args.project + "/matches_direct", "wb")) + ned = sum / len(match[1:]) + # print "avg =", ned + match[0] = ned.tolist() + return match + + if False: + print("Constructing unified match structure...") + print("This probably will fail because we didn't do the ground intersection at the start...") + matches_direct = [] + for i, image in enumerate(proj.image_list): + # print image.name + for j, matches in enumerate(image.match_list): + # print proj.image_list[j].name + if j > i: + for pair in matches: + match = [] + # ned place holder + match.append([0.0, 0.0, 0.0]) + match.append([i, pair[0]]) + match.append([j, pair[1]]) + update_match_location(match) + matches_direct.append(match) + # print pair, match + + print("Writing match file ...") + pickle.dump(matches_direct, open(project_dir + "/matches_direct", "wb")) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Keypoint projection.') + parser.add_argument('--project', required=True, help='project directory') + parser.add_argument('--matcher', default='FLANN', + choices=['FLANN', 'BF']) + parser.add_argument('--match-ratio', default=0.75, type=float, + help='match ratio') + parser.add_argument('--min-pairs', default=25, type=int, + help='minimum matches between image pairs to keep') + parser.add_argument('--min-dist', default=0, type=float, + help='minimum 2d camera distance for pair comparison') + parser.add_argument('--max-dist', default=75, type=float, + help='maximum 2d camera distance for pair comparison') + parser.add_argument('--filter', default='gms', + choices=['gms', 'homography', 'fundamental', 'essential', 'none']) + parser.add_argument('--min-chain-length', type=int, default=3, help='minimum match chain length (3 recommended)') + #parser.add_argument('--ground', type=float, help='ground elevation in meters') + + args = parser.parse_args() + + matching_options = [args.matcher, args.match_ratio, args.min_pairs, args.min_distance, + args.filter, args.min_chain_length] + + match(args.prject, matching_options) diff --git a/scripts/4b_clean_and_combine_matches.py b/scripts/4b_clean_and_combine_matches.py index 07ff8732..23d5f2dc 100644 --- a/scripts/4b_clean_and_combine_matches.py +++ b/scripts/4b_clean_and_combine_matches.py @@ -11,304 +11,307 @@ from lib import Matcher from lib import ProjectMgr -# Reset all match point locations to their original direct -# georeferenced locations based on estimated camera pose and -# projection onto DEM earth surface +def clean(project_dir): + m = Matcher.Matcher() -parser = argparse.ArgumentParser(description='Keypoint projection.') -parser.add_argument('--project', required=True, help='project directory') -args = parser.parse_args() + proj = ProjectMgr.ProjectMgr(project_dir) + proj.load_images_info() + proj.load_features(descriptors=False) + #proj.undistort_keypoints() + proj.load_match_pairs() -m = Matcher.Matcher() + # compute keypoint usage map + proj.compute_kp_usage() -proj = ProjectMgr.ProjectMgr(args.project) -proj.load_images_info() -proj.load_features(descriptors=False) -#proj.undistort_keypoints() -proj.load_match_pairs() + # For some features detection algorithms we expect duplicated feature + # uv coordinates. These duplicates may have different scaling or + # other attributes important during feature matching, yet ultimately + # resolve to the same uv coordinate in an image. + print("Indexing features by unique uv coordinates:") + for image in tqdm(proj.image_list): + # pass one, build a tmp structure of unique keypoints (by uv) and + # the index of the first instance. + image.kp_remap = {} + used = 0 + for i, kp in enumerate(image.kp_list): + if image.kp_used[i]: + used += 1 + key = "%.2f-%.2f" % (kp.pt[0], kp.pt[1]) + if not key in image.kp_remap: + image.kp_remap[key] = i + else: + #print("%d -> %d" % (i, image.kp_remap[key])) + #print(" ", image.coord_list[i], image.coord_list[image.kp_remap[key]]) + pass -# compute keypoint usage map -proj.compute_kp_usage() + #print(" features used:", used) + #print(" unique by uv and used:", len(image.kp_remap)) -# For some features detection algorithms we expect duplicated feature -# uv coordinates. These duplicates may have different scaling or -# other attributes important during feature matching, yet ultimately -# resolve to the same uv coordinate in an image. -print("Indexing features by unique uv coordinates:") -for image in tqdm(proj.image_list): - # pass one, build a tmp structure of unique keypoints (by uv) and - # the index of the first instance. - image.kp_remap = {} - used = 0 - for i, kp in enumerate(image.kp_list): - if image.kp_used[i]: - used += 1 - key = "%.2f-%.2f" % (kp.pt[0], kp.pt[1]) - if not key in image.kp_remap: - image.kp_remap[key] = i - else: - #print("%d -> %d" % (i, image.kp_remap[key])) - #print(" ", image.coord_list[i], image.coord_list[image.kp_remap[key]]) - pass - - #print(" features used:", used) - #print(" unique by uv and used:", len(image.kp_remap)) - -# after feature matching we don't care about other attributes, just -# the uv coordinate. -# -# notes: we do a first pass duplicate removal during the original -# matching process. This removes 1->many relationships, or duplicate -# matches at different scales within a match pair. However, different -# pairs could reference the same keypoint at different scales, so -# duplicates could still exist. This finds all the duplicates within -# the entire match set and collapses them down to eliminate any -# redundancy. -print("Merging keypoints with duplicate uv coordinates:") -for i, i1 in enumerate(tqdm(proj.image_list)): - for key in i1.match_list: - matches = i1.match_list[key] - count = 0 - i2 = proj.findImageByName(key) - if i2 is None: - # ignore pairs outside our area set - continue - for k, pair in enumerate(matches): - # print pair - idx1 = pair[0] - idx2 = pair[1] - kp1 = i1.kp_list[idx1] - kp2 = i2.kp_list[idx2] - key1 = "%.2f-%.2f" % (kp1.pt[0], kp1.pt[1]) - key2 = "%.2f-%.2f" % (kp2.pt[0], kp2.pt[1]) - # print key1, key2 - new_idx1 = i1.kp_remap[key1] - new_idx2 = i2.kp_remap[key2] - # count the number of match rewrites - if idx1 != new_idx1 or idx2 != new_idx2: - count += 1 - if idx1 != new_idx1: - # sanity check - uv1 = list(i1.kp_list[idx1].pt) - new_uv1 = list(i1.kp_list[new_idx1].pt) - if not np.allclose(uv1, new_uv1): - print("OOPS!!!") - print(" index 1: %d -> %d" % (idx1, new_idx1)) - print(" [%.2f, %.2f] -> [%.2f, %.2f]" % (uv1[0], uv1[1], - new_uv1[0], - new_uv1[1])) - if idx2 != new_idx2: - # sanity check - uv2 = list(i2.kp_list[idx2].pt) - new_uv2 = list(i2.kp_list[new_idx2].pt) - if not np.allclose(uv2, new_uv2): - print("OOPS!") - print(" index 2: %d -> %d" % (idx2, new_idx2)) - print(" [%.2f, %.2f] -> [%.2f, %.2f]" % (uv2[0], uv2[1], - new_uv2[0], - new_uv2[1])) - # rewrite matches - matches[k] = [new_idx1, new_idx2] - #if count > 0: - # print('Match:', i1.name, 'vs', i2.name, '%d/%d' % ( count, len(matches) ), 'rewrites') - -# enable the following code to visualize the matches after collapsing -# identical uv coordinates -if False: - for i, i1 in enumerate(proj.image_list): - for j, i2 in enumerate(proj.image_list): - if i >= j: - # don't repeat reciprocal matches + # after feature matching we don't care about other attributes, just + # the uv coordinate. + # + # notes: we do a first pass duplicate removal during the original + # matching process. This removes 1->many relationships, or duplicate + # matches at different scales within a match pair. However, different + # pairs could reference the same keypoint at different scales, so + # duplicates could still exist. This finds all the duplicates within + # the entire match set and collapses them down to eliminate any + # redundancy. + print("Merging keypoints with duplicate uv coordinates:") + for i, i1 in enumerate(tqdm(proj.image_list)): + for key in i1.match_list: + matches = i1.match_list[key] + count = 0 + i2 = proj.findImageByName(key) + if i2 is None: + # ignore pairs outside our area set continue - if len(i1.match_list[j]): - print("Showing %s vs %s" % (i1.name, i2.name)) - status = m.showMatchOrient(i1, i2, i1.match_list[j]) - -# after collapsing by uv coordinate, we could be left with duplicate -# matches (matched at different scales or other attributes, but same -# exact point.) -# -# notes: this really shouldn't (!) (by my best current understanding) -# be able to find any dups. These should all get caught in the -# original pair matching step. -print("Checking for pair duplicates (there never should be any):") -for i, i1 in enumerate(tqdm(proj.image_list)): - for key in i1.match_list: - matches = i1.match_list[key] - i2 = proj.findImageByName(key) - if i2 is None: - # ignore pairs not in our area set - continue - count = 0 - pair_dict = {} - new_matches = [] - for k, pair in enumerate(matches): - pair_key = "%d-%d" % (pair[0], pair[1]) - if not pair_key in pair_dict: - pair_dict[pair_key] = True - new_matches.append(pair) - else: - count += 1 - if count > 0: - print('Match:', i, 'vs', j, 'matches:', len(matches), 'dups:', count) - i1.match_list[key] = new_matches + for k, pair in enumerate(matches): + # print pair + idx1 = pair[0] + idx2 = pair[1] + kp1 = i1.kp_list[idx1] + kp2 = i2.kp_list[idx2] + key1 = "%.2f-%.2f" % (kp1.pt[0], kp1.pt[1]) + key2 = "%.2f-%.2f" % (kp2.pt[0], kp2.pt[1]) + # print key1, key2 + new_idx1 = i1.kp_remap[key1] + new_idx2 = i2.kp_remap[key2] + # count the number of match rewrites + if idx1 != new_idx1 or idx2 != new_idx2: + count += 1 + if idx1 != new_idx1: + # sanity check + uv1 = list(i1.kp_list[idx1].pt) + new_uv1 = list(i1.kp_list[new_idx1].pt) + if not np.allclose(uv1, new_uv1): + print("OOPS!!!") + print(" index 1: %d -> %d" % (idx1, new_idx1)) + print(" [%.2f, %.2f] -> [%.2f, %.2f]" % (uv1[0], uv1[1], + new_uv1[0], + new_uv1[1])) + if idx2 != new_idx2: + # sanity check + uv2 = list(i2.kp_list[idx2].pt) + new_uv2 = list(i2.kp_list[new_idx2].pt) + if not np.allclose(uv2, new_uv2): + print("OOPS!") + print(" index 2: %d -> %d" % (idx2, new_idx2)) + print(" [%.2f, %.2f] -> [%.2f, %.2f]" % (uv2[0], uv2[1], + new_uv2[0], + new_uv2[1])) + # rewrite matches + matches[k] = [new_idx1, new_idx2] + #if count > 0: + # print('Match:', i1.name, 'vs', i2.name, '%d/%d' % ( count, len(matches) ), 'rewrites') + + # enable the following code to visualize the matches after collapsing + # identical uv coordinates + if False: + for i, i1 in enumerate(proj.image_list): + for j, i2 in enumerate(proj.image_list): + if i >= j: + # don't repeat reciprocal matches + continue + if len(i1.match_list[j]): + print("Showing %s vs %s" % (i1.name, i2.name)) + status = m.showMatchOrient(i1, i2, i1.match_list[j]) -# enable the following code to visualize the matches after eliminating -# duplicates (duplicates can happen after collapsing uv coordinates.) -if False: - for i, i1 in enumerate(proj.image_list): - for j, i2 in enumerate(proj.image_list): - if i >= j: - # don't repeat reciprocal matches + # after collapsing by uv coordinate, we could be left with duplicate + # matches (matched at different scales or other attributes, but same + # exact point.) + # + # notes: this really shouldn't (!) (by my best current understanding) + # be able to find any dups. These should all get caught in the + # original pair matching step. + print("Checking for pair duplicates (there never should be any):") + for i, i1 in enumerate(tqdm(proj.image_list)): + for key in i1.match_list: + matches = i1.match_list[key] + i2 = proj.findImageByName(key) + if i2 is None: + # ignore pairs not in our area set continue - if len(i1.match_list[j]): - print("Showing %s vs %s" % (i1.name, i2.name)) - status = m.showMatchOrient(i1, i2, i1.match_list[j]) + count = 0 + pair_dict = {} + new_matches = [] + for k, pair in enumerate(matches): + pair_key = "%d-%d" % (pair[0], pair[1]) + if not pair_key in pair_dict: + pair_dict[pair_key] = True + new_matches.append(pair) + else: + count += 1 + if count > 0: + print('Match:', i, 'vs', j, 'matches:', len(matches), 'dups:', count) + i1.match_list[key] = new_matches + + # enable the following code to visualize the matches after eliminating + # duplicates (duplicates can happen after collapsing uv coordinates.) + if False: + for i, i1 in enumerate(proj.image_list): + for j, i2 in enumerate(proj.image_list): + if i >= j: + # don't repeat reciprocal matches + continue + if len(i1.match_list[j]): + print("Showing %s vs %s" % (i1.name, i2.name)) + status = m.showMatchOrient(i1, i2, i1.match_list[j]) -# Do we have a keypoint in i1 matching multiple keypoints in i2? -# -# Notes: again these shouldn't exist here, but let's check anyway. If -# we start finding these here, I should hunt for the reason earlier in -# the code that lets some through, or try to understand what larger -# logic principle allows somne of these to still exist here. -print("Testing for 1 vs. n keypoint duplicates (there never should be any):") -for i, i1 in enumerate(tqdm(proj.image_list)): - for key in i1.match_list: - matches = i1.match_list[key] - i2 = proj.findImageByName(key) - if i2 is None: - # skip pairs outside our area set - continue - count = 0 - kp_dict = {} - for k, pair in enumerate(matches): - if not pair[0] in kp_dict: - kp_dict[pair[0]] = pair[1] - else: - print("Warning keypoint idx", pair[0], "already used in another match.") - uv2a = list(i2.kp_list[ kp_dict[pair[0]] ].pt) - uv2b = list(i2.kp_list[ pair[1] ].pt) - if not np.allclose(uv2, new_uv2): - print(" [%.2f, %.2f] -> [%.2f, %.2f]" % (uv2a[0], uv2a[1], - uv2b[0], uv2b[1])) - count += 1 - if count > 0: - print('Match:', i, 'vs', j, 'matches:', len(matches), 'dups:', count) + # Do we have a keypoint in i1 matching multiple keypoints in i2? + # + # Notes: again these shouldn't exist here, but let's check anyway. If + # we start finding these here, I should hunt for the reason earlier in + # the code that lets some through, or try to understand what larger + # logic principle allows somne of these to still exist here. + print("Testing for 1 vs. n keypoint duplicates (there never should be any):") + for i, i1 in enumerate(tqdm(proj.image_list)): + for key in i1.match_list: + matches = i1.match_list[key] + i2 = proj.findImageByName(key) + if i2 is None: + # skip pairs outside our area set + continue + count = 0 + kp_dict = {} + for k, pair in enumerate(matches): + if not pair[0] in kp_dict: + kp_dict[pair[0]] = pair[1] + else: + print("Warning keypoint idx", pair[0], "already used in another match.") + uv2a = list(i2.kp_list[ kp_dict[pair[0]] ].pt) + uv2b = list(i2.kp_list[ pair[1] ].pt) + if not np.allclose(uv2, new_uv2): + print(" [%.2f, %.2f] -> [%.2f, %.2f]" % (uv2a[0], uv2a[1], + uv2b[0], uv2b[1])) + count += 1 + if count > 0: + print('Match:', i, 'vs', j, 'matches:', len(matches), 'dups:', count) -print("Constructing unified match structure:") -# create an initial pair-wise match list -matches_direct = [] -for i, img in enumerate(tqdm(proj.image_list)): - # print img.name - for key in img.match_list: - j = proj.findIndexByName(key) - if j is None: - continue - matches = img.match_list[key] - # print proj.image_list[j].name - if j > i: - for pair in matches: - # ned place holder, in use flag - match = [None, -1] - # camera/feature references - match.append([i, pair[0]]) - match.append([j, pair[1]]) - matches_direct.append(match) - # print pair, match + print("Constructing unified match structure:") + # create an initial pair-wise match list + matches_direct = [] + for i, img in enumerate(tqdm(proj.image_list)): + # print img.name + for key in img.match_list: + j = proj.findIndexByName(key) + if j is None: + continue + matches = img.match_list[key] + # print proj.image_list[j].name + if j > i: + for pair in matches: + # ned place holder, in use flag + match = [None, -1] + # camera/feature references + match.append([i, pair[0]]) + match.append([j, pair[1]]) + matches_direct.append(match) + # print pair, match -sum = 0.0 -for match in matches_direct: - sum += len(match[2:]) - -if len(matches_direct): - print("Total image pairs in image set:", len(matches_direct)) - print("Keypoint average instances = %.1f (should be 2.0 here)" % (sum / len(matches_direct))) + sum = 0.0 + for match in matches_direct: + sum += len(match[2:]) + + if len(matches_direct): + print("Total image pairs in image set:", len(matches_direct)) + print("Keypoint average instances = %.1f (should be 2.0 here)" % (sum / len(matches_direct))) -# Note to self: I don't think we need the matches_direct file any more -# (except for debugging possibly in the future.) -# -#print("Writing matches_direct file ...") -#direct_file = os.path.join(proj.analysis_dir, "matches_direct") -#pickle.dump(matches_direct, open(direct_file, "wb")) + # Note to self: I don't think we need the matches_direct file any more + # (except for debugging possibly in the future.) + # + #print("Writing matches_direct file ...") + #direct_file = os.path.join(proj.analysis_dir, "matches_direct") + #pickle.dump(matches_direct, open(direct_file, "wb")) -# collect/group match chains that refer to the same keypoint + # collect/group match chains that refer to the same keypoint -print("Linking common matches together into chains:") -count = 0 -done = False -while not done: - print("Iteration %d:" % count) - count += 1 - matches_new = [] - matches_lookup = {} - for i, match in enumerate(tqdm(matches_direct)): - # scan if any of these match points have been previously seen - # and record the match index - index = -1 - for p in match[2:]: - key = "%d-%d" % (p[0], p[1]) - if key in matches_lookup: - index = matches_lookup[key] - break - if index < 0: - # not found, append to the new list + print("Linking common matches together into chains:") + count = 0 + done = False + while not done: + print("Iteration %d:" % count) + count += 1 + matches_new = [] + matches_lookup = {} + for i, match in enumerate(tqdm(matches_direct)): + # scan if any of these match points have been previously seen + # and record the match index + index = -1 for p in match[2:]: key = "%d-%d" % (p[0], p[1]) - matches_lookup[key] = len(matches_new) - matches_new.append(list(match)) # shallow copy + if key in matches_lookup: + index = matches_lookup[key] + break + if index < 0: + # not found, append to the new list + for p in match[2:]: + key = "%d-%d" % (p[0], p[1]) + matches_lookup[key] = len(matches_new) + matches_new.append(list(match)) # shallow copy + else: + # found a previous reference, append these match items + existing = matches_new[index] + for p in match[2:]: + key = "%d-%d" % (p[0], p[1]) + found = False + for e in existing[2:]: + if p[0] == e[0]: + found = True + break + if not found: + # add + existing.append(list(p)) # shallow copy + matches_lookup[key] = index + # no 3d location estimation yet + # # attempt to combine location equitably + # size1 = len(match[2:]) + # size2 = len(existing[2:]) + # ned1 = np.array(match[0]) + # ned2 = np.array(existing[0]) + # avg = (ned1 * size1 + ned2 * size2) / (size1 + size2) + # existing[0] = avg.tolist() + # # print(ned1, ned2, existing[0]) + # # print "new:", existing + # # print + if len(matches_new) == len(matches_direct): + done = True else: - # found a previous reference, append these match items - existing = matches_new[index] - for p in match[2:]: - key = "%d-%d" % (p[0], p[1]) - found = False - for e in existing[2:]: - if p[0] == e[0]: - found = True - break - if not found: - # add - existing.append(list(p)) # shallow copy - matches_lookup[key] = index - # no 3d location estimation yet - # # attempt to combine location equitably - # size1 = len(match[2:]) - # size2 = len(existing[2:]) - # ned1 = np.array(match[0]) - # ned2 = np.array(existing[0]) - # avg = (ned1 * size1 + ned2 * size2) / (size1 + size2) - # existing[0] = avg.tolist() - # # print(ned1, ned2, existing[0]) - # # print "new:", existing - # # print - if len(matches_new) == len(matches_direct): - done = True - else: - matches_direct = list(matches_new) # shallow copy + matches_direct = list(matches_new) # shallow copy -# replace the keypoint index in the matches file with the actual kp -# values. This will save time later and avoid needing to load the -# full original feature files which are quite large. This also will -# reduce the in-memory footprint for many steps. -print('Replacing keypoint indices with uv coordinates:') -for match in tqdm(matches_direct): - for m in match[2:]: - kp = proj.image_list[m[0]].kp_list[m[1]].pt - m[1] = list(kp) - # print(match) + # replace the keypoint index in the matches file with the actual kp + # values. This will save time later and avoid needing to load the + # full original feature files which are quite large. This also will + # reduce the in-memory footprint for many steps. + print('Replacing keypoint indices with uv coordinates:') + for match in tqdm(matches_direct): + for m in match[2:]: + kp = proj.image_list[m[0]].kp_list[m[1]].pt + m[1] = list(kp) + # print(match) -# sort by longest match chains first -print("Sorting matches by longest chain first.") -matches_direct.sort(key=len, reverse=True) + # sort by longest match chains first + print("Sorting matches by longest chain first.") + matches_direct.sort(key=len, reverse=True) -sum = 0.0 -for i, match in enumerate(matches_direct): - refs = len(match[2:]) - sum += refs - -if count >= 1: - print("Total unique features in image set:", len(matches_direct)) - print("Keypoint average instances:", "%.2f" % (sum / len(matches_direct))) + sum = 0.0 + for i, match in enumerate(matches_direct): + refs = len(match[2:]) + sum += refs + + if count >= 1: + print("Total unique features in image set:", len(matches_direct)) + print("Keypoint average instances:", "%.2f" % (sum / len(matches_direct))) + + print("Writing full group chain matches_grouped file ...") + pickle.dump(matches_direct, open(os.path.join(proj.analysis_dir, "matches_grouped"), "wb")) + +if __name__ == "__main__": + # Reset all match point locations to their original direct + # georeferenced locations based on estimated camera pose and + # projection onto DEM earth surface -print("Writing full group chain matches_grouped file ...") -pickle.dump(matches_direct, open(os.path.join(proj.analysis_dir, "matches_grouped"), "wb")) + parser = argparse.ArgumentParser(description='Keypoint projection.') + parser.add_argument('--project', required=True, help='project directory') + args = parser.parse_args() + clean(args.project) \ No newline at end of file diff --git a/scripts/4c_match_triangulation.py b/scripts/4c_match_triangulation.py index 95388776..b2c1d718 100644 --- a/scripts/4c_match_triangulation.py +++ b/scripts/4c_match_triangulation.py @@ -14,139 +14,148 @@ from lib import ProjectMgr from lib import SRTM -parser = argparse.ArgumentParser(description='Keypoint projection.') -parser.add_argument('--project', required=True, help='project directory') -parser.add_argument('--group', type=int, default=0, help='group number') -parser.add_argument('--method', default='srtm', choices=['srtm', 'triangulate']) -args = parser.parse_args() +def match_trig(project_dir, match_trig_options): -proj = ProjectMgr.ProjectMgr(args.project) -proj.load_images_info() + group = match_trig_options[0] + method = match_trig_options[1] + proj = ProjectMgr.ProjectMgr(project_dir) + proj.load_images_info() -source = 'matches_grouped' -print("Loading source matches:", source) -matches = pickle.load( open( os.path.join(proj.analysis_dir, source), 'rb' ) ) + source = 'matches_grouped' + print("Loading source matches:", source) + matches = pickle.load( open( os.path.join(proj.analysis_dir, source), 'rb' ) ) -# load the group connections within the image set -groups = Groups.load(proj.analysis_dir) -print('Group sizes:', end=" ") -for group in groups: - print(len(group), end=" ") -print() + # load the group connections within the image set + groups = Groups.load(proj.analysis_dir) + print('Group sizes:', end=" ") + for group in groups: + print(len(group), end=" ") + print() -if args.method == 'triangulate': - K = proj.cam.get_K(optimized=True) - dist_coeffs = np.array(proj.cam.get_dist_coeffs(optimized=True)) -else: - K = proj.cam.get_K(optimized=False) -IK = np.linalg.inv(K) + if method == 'triangulate': + K = proj.cam.get_K(optimized=True) + dist_coeffs = np.array(proj.cam.get_dist_coeffs(optimized=True)) + else: + K = proj.cam.get_K(optimized=False) + IK = np.linalg.inv(K) -do_sanity_check = False + do_sanity_check = False -# assume global K and distcoeff set earlier -def undistort(uv_orig): - # convert the point into the proper format for opencv - uv_raw = np.zeros((1,1,2), dtype=np.float32) - uv_raw[0][0] = (uv_orig[0], uv_orig[1]) - # do the actual undistort - uv_new = cv2.undistortPoints(uv_raw, K, dist_coeffs, P=K) - # print(uv_orig, type(uv_new), uv_new) - return uv_new[0][0] - -if args.method == 'srtm': - # lookup ned reference - ref_node = getNode("/config/ned_reference", True) - ref = [ ref_node.getFloat('lat_deg'), - ref_node.getFloat('lon_deg'), - ref_node.getFloat('alt_m') ] - - # setup SRTM ground interpolator - sss = SRTM.NEDGround( ref, 3000, 3000, 30 ) + # assume global K and distcoeff set earlier + def undistort(uv_orig): + # convert the point into the proper format for opencv + uv_raw = np.zeros((1,1,2), dtype=np.float32) + uv_raw[0][0] = (uv_orig[0], uv_orig[1]) + # do the actual undistort + uv_new = cv2.undistortPoints(uv_raw, K, dist_coeffs, P=K) + # print(uv_orig, type(uv_new), uv_new) + return uv_new[0][0] + + if method == 'srtm': + # lookup ned reference + ref_node = getNode("/config/ned_reference", True) + ref = [ ref_node.getFloat('lat_deg'), + ref_node.getFloat('lon_deg'), + ref_node.getFloat('alt_m') ] - # for each image lookup the SRTM elevation under the camera - print("Looking up SRTM base elevation for each image location...") - for image in proj.image_list: - ned, ypr, quat = image.get_camera_pose() - image.base_elev = sss.interp([ned[0], ned[1]])[0] - # print(image.name, image.base_elev) + # setup SRTM ground interpolator + sss = SRTM.NEDGround( ref, 3000, 3000, 30 ) - print("Estimating initial projection for each feature...") - bad_count = 0 - bad_indices = [] - for i, match in enumerate(tqdm(matches)): - sum = np.zeros(3) - array = [] # fixme: temp/debug - for m in match[2:]: - image = proj.image_list[m[0]] - cam2body = image.get_cam2body() - body2ned = image.get_body2ned() + # for each image lookup the SRTM elevation under the camera + print("Looking up SRTM base elevation for each image location...") + for image in proj.image_list: ned, ypr, quat = image.get_camera_pose() - uv_list = [ m[1] ] # just one uv element - vec_list = proj.projectVectors(IK, body2ned, cam2body, uv_list) - v = vec_list[0] - if v[2] > 0.0: - d_proj = -(ned[2] + image.base_elev) - factor = d_proj / v[2] - n_proj = v[0] * factor - e_proj = v[1] * factor - p = [ ned[0] + n_proj, ned[1] + e_proj, ned[2] + d_proj ] - # print(' ', p) - sum += np.array(p) - array.append(p) - else: - print('vector projected above horizon.') - match[0] = (sum/len(match[2:])).tolist() - # print(match[0]) - if do_sanity_check: - # crude sanity check - ok = True - for p in array: - dist = np.linalg.norm(np.array(match[0]) - np.array(p)) - if dist > 100: - ok = False - if not ok: - bad_count += 1 - bad_indices.append(i) - print('match:', i, match[0]) + image.base_elev = sss.interp([ned[0], ned[1]])[0] + # print(image.name, image.base_elev) + + print("Estimating initial projection for each feature...") + bad_count = 0 + bad_indices = [] + for i, match in enumerate(tqdm(matches)): + sum = np.zeros(3) + array = [] # fixme: temp/debug + for m in match[2:]: + image = proj.image_list[m[0]] + cam2body = image.get_cam2body() + body2ned = image.get_body2ned() + ned, ypr, quat = image.get_camera_pose() + uv_list = [ m[1] ] # just one uv element + vec_list = proj.projectVectors(IK, body2ned, cam2body, uv_list) + v = vec_list[0] + if v[2] > 0.0: + d_proj = -(ned[2] + image.base_elev) + factor = d_proj / v[2] + n_proj = v[0] * factor + e_proj = v[1] * factor + p = [ ned[0] + n_proj, ned[1] + e_proj, ned[2] + d_proj ] + # print(' ', p) + sum += np.array(p) + array.append(p) + else: + print('vector projected above horizon.') + match[0] = (sum/len(match[2:])).tolist() + # print(match[0]) + if do_sanity_check: + # crude sanity check + ok = True for p in array: dist = np.linalg.norm(np.array(match[0]) - np.array(p)) - print(' ', dist, p) - if do_sanity_check: - print('bad count:', bad_count) - print('deleting bad matches...') - bad_indices.reverse() - for i in bad_indices: - del matches[i] -elif args.method == 'triangulate': - for i, match in enumerate(matches): - if match[1] == args.group: # used in current group - # print(match) - points = [] - vectors = [] - for m in match[2:]: - if proj.image_list[m[0]].name in groups[args.group]: - # print(m) - image = proj.image_list[m[0]] - cam2body = image.get_cam2body() - body2ned = image.get_body2ned() - ned, ypr, quat = image.get_camera_pose(opt=True) - uv_list = [ undistort(m[1]) ] # just one uv element - vec_list = proj.projectVectors(IK, body2ned, cam2body, uv_list) - points.append( ned ) - vectors.append( vec_list[0] ) - # print(' ', image.name) - # print(' ', uv_list) - # print(' ', vec_list) - if len(points) >= 2: - # print('points:', points) - # print('vectors:', vectors) - p = LineSolver.ls_lines_intersection(points, vectors, transpose=True).tolist() - # print('result:', p, p[0]) - print(i, match[0], '>>>', end=" ") - match[0] = [ p[0][0], p[1][0], p[2][0] ] - if p[2][0] > 0: - print("WHOA!") - print(match[0]) + if dist > 100: + ok = False + if not ok: + bad_count += 1 + bad_indices.append(i) + print('match:', i, match[0]) + for p in array: + dist = np.linalg.norm(np.array(match[0]) - np.array(p)) + print(' ', dist, p) + if do_sanity_check: + print('bad count:', bad_count) + print('deleting bad matches...') + bad_indices.reverse() + for i in bad_indices: + del matches[i] + elif method == 'triangulate': + for i, match in enumerate(matches): + if match[1] == group: # used in current group + # print(match) + points = [] + vectors = [] + for m in match[2:]: + if proj.image_list[m[0]].name in groups[group]: + # print(m) + image = proj.image_list[m[0]] + cam2body = image.get_cam2body() + body2ned = image.get_body2ned() + ned, ypr, quat = image.get_camera_pose(opt=True) + uv_list = [ undistort(m[1]) ] # just one uv element + vec_list = proj.projectVectors(IK, body2ned, cam2body, uv_list) + points.append( ned ) + vectors.append( vec_list[0] ) + # print(' ', image.name) + # print(' ', uv_list) + # print(' ', vec_list) + if len(points) >= 2: + # print('points:', points) + # print('vectors:', vectors) + p = LineSolver.ls_lines_intersection(points, vectors, transpose=True).tolist() + # print('result:', p, p[0]) + print(i, match[0], '>>>', end=" ") + match[0] = [ p[0][0], p[1][0], p[2][0] ] + if p[2][0] > 0: + print("WHOA!") + print(match[0]) + + print("Writing:", source) + pickle.dump(matches, open(os.path.join(proj.analysis_dir, source), "wb")) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Keypoint projection.') + parser.add_argument('--project', required=True, help='project directory') + parser.add_argument('--group', type=int, default=0, help='group number') + parser.add_argument('--method', default='srtm', choices=['srtm', 'triangulate']) + args = parser.parse_args() + + match_trig_options = [args.group, args.method] -print("Writing:", source) -pickle.dump(matches, open(os.path.join(proj.analysis_dir, source), "wb")) + match_trig(args.project, match_trig_options) \ No newline at end of file diff --git a/scripts/4d_image_groups.py b/scripts/4d_image_groups.py index 1d74bbfe..4dad1207 100644 --- a/scripts/4d_image_groups.py +++ b/scripts/4d_image_groups.py @@ -10,63 +10,68 @@ from lib import Groups from lib import ProjectMgr -parser = argparse.ArgumentParser(description='Keypoint projection.') -parser.add_argument('--project', required=True, help='project directory') -args = parser.parse_args() -proj = ProjectMgr.ProjectMgr(args.project) -proj.load_images_info() +def group(project_dir): + + proj = ProjectMgr.ProjectMgr(project_dir) + proj.load_images_info() -source = 'matches_grouped' -print("Loading source matches:", source) -matches = pickle.load( open( os.path.join(proj.analysis_dir, source), 'rb' ) ) + source = 'matches_grouped' + print("Loading source matches:", source) + matches = pickle.load( open( os.path.join(proj.analysis_dir, source), 'rb' ) ) -print("features:", len(matches)) + print("features:", len(matches)) -# compute the group connections within the image set. -groups = Groups.compute(proj.image_list, matches) -Groups.save(proj.analysis_dir, groups) + # compute the group connections within the image set. + groups = Groups.compute(proj.image_list, matches) + Groups.save(proj.analysis_dir, groups) -print('Total images:', len(proj.image_list)) -print('Group sizes:', end=" ") -for g in groups: - print(len(g), end=" ") -print() + print('Total images:', len(proj.image_list)) + print('Group sizes:', end=" ") + for g in groups: + print(len(g), end=" ") + print() -# debug -print("Counting allocated features...") -count = 0 -for i, match in enumerate(matches): - if match[1] >= 0: - count += 1 + # debug + print("Counting allocated features...") + count = 0 + for i, match in enumerate(matches): + if match[1] >= 0: + count += 1 -print("Writing:", source, "...") -print("Features: %d/%d" % (count, len(matches))) -pickle.dump(matches, open(os.path.join(proj.analysis_dir, source), "wb")) + print("Writing:", source, "...") + print("Features: %d/%d" % (count, len(matches))) + pickle.dump(matches, open(os.path.join(proj.analysis_dir, source), "wb")) -# this is extra (and I'll put it here for now for lack of a better -# place), but for visualization's sake, create a gnuplot data file -# that will show all the match connectivity in the set. -# file = os.path.join(proj.analysis_dir, 'connections.gnuplot') -# f = open(file, 'w') -# pair_dict = {} -# for match in matches: -# for m1 in match[1:]: -# for m2 in match[1:]: -# if m1[0] == m2[0]: -# # skip selfies -# continue -# key = '%d %d' % (m1[0], m2[0]) -# pair_dict[key] = [m1[0], m2[0]] -# for pair in pair_dict: -# #print 'pair:', pair, pair_dict[pair] -# image1 = proj.image_list[pair_dict[pair][0]] -# image2 = proj.image_list[pair_dict[pair][1]] -# (ned1, ypr1, quat1) = image1.get_camera_pose() -# (ned2, ypr2, quat2) = image2.get_camera_pose() -# f.write("%.2f %.2f\n" % (ned1[1], ned1[0])) -# f.write("%.2f %.2f\n" % (ned2[1], ned2[0])) -# f.write("\n") -# f.close() - - + # this is extra (and I'll put it here for now for lack of a better + # place), but for visualization's sake, create a gnuplot data file + # that will show all the match connectivity in the set. + # file = os.path.join(proj.analysis_dir, 'connections.gnuplot') + # f = open(file, 'w') + # pair_dict = {} + # for match in matches: + # for m1 in match[1:]: + # for m2 in match[1:]: + # if m1[0] == m2[0]: + # # skip selfies + # continue + # key = '%d %d' % (m1[0], m2[0]) + # pair_dict[key] = [m1[0], m2[0]] + # for pair in pair_dict: + # #print 'pair:', pair, pair_dict[pair] + # image1 = proj.image_list[pair_dict[pair][0]] + # image2 = proj.image_list[pair_dict[pair][1]] + # (ned1, ypr1, quat1) = image1.get_camera_pose() + # (ned2, ypr2, quat2) = image2.get_camera_pose() + # f.write("%.2f %.2f\n" % (ned1[1], ned1[0])) + # f.write("%.2f %.2f\n" % (ned2[1], ned2[0])) + # f.write("\n") + # f.close() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Keypoint projection.') + parser.add_argument('--project', required=True, help='project directory') + args = parser.parse_args() + + group(args.project) \ No newline at end of file diff --git a/scripts/4e_show_match_pairs.py b/scripts/4e_show_match_pairs.py index 06d2ba79..4993671d 100644 --- a/scripts/4e_show_match_pairs.py +++ b/scripts/4e_show_match_pairs.py @@ -12,110 +12,124 @@ # working on matching features ... -parser = argparse.ArgumentParser(description='Keypoint projection.') -parser.add_argument('--project', required=True, help='project directory') -parser.add_argument('--order', default='sequential', - choices=['sequential', 'fewest-matches'], - help='sort order') -parser.add_argument('--orient', default='relative', - choices=['relative', 'aircraft', 'camera', 'sba'], - help='yaw orientation reference') -parser.add_argument('--image', default="", help='show specific image matches') -parser.add_argument('--index', type=int, help='show specific image by index') -parser.add_argument('--direct', action='store_true', help='show matches_direct') -parser.add_argument('--sba', action='store_true', help='show matches_sba') -args = parser.parse_args() +def show_matches(project_dir, show_matches_option): -proj = ProjectMgr.ProjectMgr(args.project) -proj.load_images_info() -proj.load_features() -if args.direct: - # recreate the pair-wise match structure - matches_list = pickle.load( open( os.path.join(args.project, "matches_direct"), "rb" ) ) - for i1 in proj.image_list: - i1.match_list = [] - for i2 in proj.image_list: - i1.match_list.append([]) - for match in matches_list: - for p1 in match[1:]: - for p2 in match[1:]: - if p1 == p2: - pass - else: - i = p1[0] - j = p2[0] - image = proj.image_list[i] - image.match_list[j].append( [p1[1], p2[1]] ) - # for i in range(len(proj.image_list)): - # print(len(proj.image_list[i].match_list)) - # print(proj.image_list[i].match_list) - # for j in range(len(proj.image_list)): - # print(i, j, len(proj.image_list[i].match_list[j]), - # proj.image_list[i].match_list[j]) -else: - proj.load_match_pairs() + orders = show_matches_option[0] + orient = show_matches_option[1] + image = show_matches_option[2] + index = show_matches_option[3] + direct = show_matches_option[4] + sba = show_matches_option[5] -# lookup ned reference -ref_node = getNode("/config/ned_reference", True) -ref = [ ref_node.getFloat('lat_deg'), - ref_node.getFloat('lon_deg'), - ref_node.getFloat('alt_m') ] + proj = ProjectMgr.ProjectMgr(project_dir) + proj.load_images_info() + proj.load_features() + if args.direct: + # recreate the pair-wise match structure + matches_list = pickle.load( open( os.path.join(project_dir, "matches_direct"), "rb" ) ) + for i1 in proj.image_list: + i1.match_list = [] + for i2 in proj.image_list: + i1.match_list.append([]) + for match in matches_list: + for p1 in match[1:]: + for p2 in match[1:]: + if p1 == p2: + pass + else: + i = p1[0] + j = p2[0] + image = proj.image_list[i] + image.match_list[j].append( [p1[1], p2[1]] ) + # for i in range(len(proj.image_list)): + # print(len(proj.image_list[i].match_list)) + # print(proj.image_list[i].match_list) + # for j in range(len(proj.image_list)): + # print(i, j, len(proj.image_list[i].match_list[j]), + # proj.image_list[i].match_list[j]) + else: + proj.load_match_pairs() -m = Matcher.Matcher() + # lookup ned reference + ref_node = getNode("/config/ned_reference", True) + ref = [ ref_node.getFloat('lat_deg'), + ref_node.getFloat('lon_deg'), + ref_node.getFloat('alt_m') ] -order = 'fewest-matches' + m = Matcher.Matcher() -if args.image: - i1 = proj.findImageByName(args.image) - if i1 != None: - for key in i1.match_list: - print(key, len(i1.match_list[key])) - if len(i1.match_list[key]): - i2 = proj.findImageByName(key) - print("Showing %s vs %s (%d matches)" % (i1.name, i2.name, len(i1.match_list[key]))) - status = m.showMatchOrient(i1, i2, i1.match_list[key], - orient=args.orient) - else: - print("Cannot locate:", args.image) -elif args.index: - i1 = proj.image_list[args.index] - if i1 != None: - for j, i2 in enumerate(proj.image_list): - if len(i1.match_list[j]): - print("Showing %s vs %s" % (i1.name, i2.name)) - status = m.showMatchOrient(i1, i2, i1.match_list[j], - orient=args.orient) - else: - print("Cannot locate:", args.index) -elif args.order == 'sequential': - for i, i1 in enumerate(proj.image_list): - for j, i2 in enumerate(proj.image_list): - if i >= j: - # don't repeat reciprocal matches - continue - if i2.name in i1.match_list: - if len(i1.match_list[i2.name]): + order = 'fewest-matches' + + if image: + i1 = proj.findImageByName(image) + if i1 != None: + for key in i1.match_list: + print(key, len(i1.match_list[key])) + if len(i1.match_list[key]): + i2 = proj.findImageByName(key) + print("Showing %s vs %s (%d matches)" % (i1.name, i2.name, len(i1.match_list[key]))) + status = m.showMatchOrient(i1, i2, i1.match_list[key], + orient=orient) + else: + print("Cannot locate:", image) + elif index: + i1 = proj.image_list[index] + if i1 != None: + for j, i2 in enumerate(proj.image_list): + if len(i1.match_list[j]): print("Showing %s vs %s" % (i1.name, i2.name)) - status = m.showMatchOrient(i1, i2, i1.match_list[i2.name], - orient=args.orient) -elif args.order == 'fewest-matches': - match_list = [] - for i, i1 in enumerate(proj.image_list): - for j, i2 in enumerate(proj.image_list): - if i >= j: - # don't repeat reciprocal matches - continue - if len(i1.match_list[j]): - match_list.append( ( len(i1.match_list[j]), i, j ) ) - match_list = sorted(match_list, - key=lambda fields: fields[0], - reverse=False) - for match in match_list: - count = match[0] - i = match[1] - j = match[2] - i1 = proj.image_list[i] - i2 = proj.image_list[j] - print("Showing %s vs %s (matches=%d)" % (i1.name, i2.name, count)) - status = m.showMatchOrient(i1, i2, i1.match_list[j], - orient=args.orient) + status = m.showMatchOrient(i1, i2, i1.match_list[j], + orient=orient) + else: + print("Cannot locate:", index) + elif order == 'sequential': + for i, i1 in enumerate(proj.image_list): + for j, i2 in enumerate(proj.image_list): + if i >= j: + # don't repeat reciprocal matches + continue + if i2.name in i1.match_list: + if len(i1.match_list[i2.name]): + print("Showing %s vs %s" % (i1.name, i2.name)) + status = m.showMatchOrient(i1, i2, i1.match_list[i2.name], + orient=orient) + elif order == 'fewest-matches': + match_list = [] + for i, i1 in enumerate(proj.image_list): + for j, i2 in enumerate(proj.image_list): + if i >= j: + # don't repeat reciprocal matches + continue + if len(i1.match_list[j]): + match_list.append( ( len(i1.match_list[j]), i, j ) ) + match_list = sorted(match_list, + key=lambda fields: fields[0], + reverse=False) + for match in match_list: + count = match[0] + i = match[1] + j = match[2] + i1 = proj.image_list[i] + i2 = proj.image_list[j] + print("Showing %s vs %s (matches=%d)" % (i1.name, i2.name, count)) + status = m.showMatchOrient(i1, i2, i1.match_list[j], + orient=orient) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Keypoint projection.') + parser.add_argument('--project', required=True, help='project directory') + parser.add_argument('--order', default='sequential', + choices=['sequential', 'fewest-matches'], + help='sort order') + parser.add_argument('--orient', default='relative', + choices=['relative', 'aircraft', 'camera', 'sba'], + help='yaw orientation reference') + parser.add_argument('--image', default="", help='show specific image matches') + parser.add_argument('--index', type=int, help='show specific image by index') + parser.add_argument('--direct', action='store_true', help='show matches_direct') + parser.add_argument('--sba', action='store_true', help='show matches_sba') + args = parser.parse_args() + + show_matches_option = [args.order, args.orient, args.image, args.index, args.direct, args.sba] + + show_matches(args.project, show_matches_option) diff --git a/scripts/5a_optimize.py b/scripts/5a_optimize.py index eec7b16c..ce529eca 100644 --- a/scripts/5a_optimize.py +++ b/scripts/5a_optimize.py @@ -16,243 +16,256 @@ from lib import ProjectMgr from lib import transformations -d2r = math.pi / 180.0 -r2d = 180.0 / math.pi - -parser = argparse.ArgumentParser(description='Keypoint projection.') -parser.add_argument('--project', required=True, help='project directory') -parser.add_argument('--group', type=int, default=0, help='group number') -parser.add_argument('--refine', action='store_true', help='refine a previous optimization.') -parser.add_argument('--cam-calibration', action='store_true', help='include camera calibration in the optimization.') - -args = parser.parse_args() - -# return a 3d affine tranformation between current camera locations -# and original camera locations. -def get_recenter_affine(src_list, dst_list): - print('get_recenter_affine():') - src = [[], [], [], []] # current camera locations - dst = [[], [], [], []] # original camera locations - for i in range(len(src_list)): - src_ned = src_list[i] - src[0].append(src_ned[0]) - src[1].append(src_ned[1]) - src[2].append(src_ned[2]) - src[3].append(1.0) - dst_ned = dst_list[i] - dst[0].append(dst_ned[0]) - dst[1].append(dst_ned[1]) - dst[2].append(dst_ned[2]) - dst[3].append(1.0) - # print("{} <-- {}".format(dst_ned, src_ned)) - A = transformations.superimposition_matrix(src, dst, scale=True) - print("A:\n", A) - return A - -# transform a point list given an affine transform matrix -def transform_points( A, pts_list ): - src = [[], [], [], []] - for p in pts_list: - src[0].append(p[0]) - src[1].append(p[1]) - src[2].append(p[2]) - src[3].append(1.0) - dst = A.dot( np.array(src) ) - result = [] - for i in range(len(pts_list)): - result.append( [ float(dst[0][i]), - float(dst[1][i]), - float(dst[2][i]) ] ) - return result - -proj = ProjectMgr.ProjectMgr(args.project) -proj.load_images_info() - -source_file = os.path.join(proj.analysis_dir, 'matches_grouped' ) -print('Match file:', source_file) -matches = pickle.load( open(source_file, "rb") ) -print('Match features:', len(matches)) - -# load the group connections within the image set -groups = Groups.load(proj.analysis_dir) -# sort from smallest to largest: groups.sort(key=len) - -opt = Optimizer.Optimizer(args.project) -opt.setup( proj, groups, args.group, matches, optimized=args.refine, - cam_calib=args.cam_calibration) -cameras, features, cam_index_map, feat_index_map, fx_opt, fy_opt, cu_opt, cv_opt, distCoeffs_opt = opt.run() - -# mark all the optimized poses as invalid -for image in proj.image_list: - opt_cam_node = image.node.getChild('camera_pose_opt', True) - opt_cam_node.setBool('valid', False) - -for i, cam in enumerate(cameras): - image_index = cam_index_map[i] - image = proj.image_list[image_index] - ned_orig, ypr_orig, quat_orig = image.get_camera_pose() - print('optimized cam:', cam) - rvec = cam[0:3] - tvec = cam[3:6] - Rned2cam, jac = cv2.Rodrigues(rvec) - cam2body = image.get_cam2body() - Rned2body = cam2body.dot(Rned2cam) - Rbody2ned = np.matrix(Rned2body).T - (yaw, pitch, roll) = transformations.euler_from_matrix(Rbody2ned, 'rzyx') - #print "orig ypr =", image.camera_pose['ypr'] - #print "new ypr =", [yaw/d2r, pitch/d2r, roll/d2r] - pos = -np.matrix(Rned2cam).T * np.matrix(tvec).T - newned = pos.T[0].tolist()[0] - print(image.name, ned_orig, '->', newned, 'dist:', np.linalg.norm(np.array(ned_orig) - np.array(newned))) - image.set_camera_pose( newned, yaw*r2d, pitch*r2d, roll*r2d, opt=True ) - image.placed = True -proj.save_images_info() -print('Updated the optimized camera poses.') - -# update and save the optimized camera calibration -proj.cam.set_K(fx_opt, fy_opt, cu_opt, cv_opt, optimized=True) -proj.cam.set_dist_coeffs(distCoeffs_opt.tolist(), optimized=True) -proj.save() - -# compare original camera locations with optimized camera locations and -# derive a transform matrix to 'best fit' the new camera locations -# over the original ... trusting the original group gps solution as -# our best absolute truth for positioning the system in world -# coordinates. -# -# each optimized group needs a separate/unique fit - -matches_opt = list(matches) # shallow copy -refit_group_orientations = True -if refit_group_orientations: - group = groups[args.group] - print('refitting group size:', len(group)) - src_list = [] - dst_list = [] - # only consider images that are in the current group - for name in group: - image = proj.findImageByName(name) - ned, ypr, quat = image.get_camera_pose(opt=True) - src_list.append(ned) - ned, ypr, quat = image.get_camera_pose() - dst_list.append(ned) - A = get_recenter_affine(src_list, dst_list) - - # extract the rotation matrix (R) from the affine transform - scale, shear, angles, trans, persp = transformations.decompose_matrix(A) - print(' scale:', scale) - print(' shear:', shear) - print(' angles:', angles) - print(' translate:', trans) - print(' perspective:', persp) - R = transformations.euler_matrix(*angles) - print("R:\n{}".format(R)) - - # fixme (just group): - - # update the optimized camera locations based on best fit - camera_list = [] - # load optimized poses +def optmizer(project_dir, optmize_options): + + group_id = optmize_options[0] + refine = optmize_options[1] + cam_calibration = optmize_options[2] + + d2r = math.pi / 180.0 + r2d = 180.0 / math.pi + + # return a 3d affine tranformation between current camera locations + # and original camera locations. + def get_recenter_affine(src_list, dst_list): + print('get_recenter_affine():') + src = [[], [], [], []] # current camera locations + dst = [[], [], [], []] # original camera locations + for i in range(len(src_list)): + src_ned = src_list[i] + src[0].append(src_ned[0]) + src[1].append(src_ned[1]) + src[2].append(src_ned[2]) + src[3].append(1.0) + dst_ned = dst_list[i] + dst[0].append(dst_ned[0]) + dst[1].append(dst_ned[1]) + dst[2].append(dst_ned[2]) + dst[3].append(1.0) + # print("{} <-- {}".format(dst_ned, src_ned)) + A = transformations.superimposition_matrix(src, dst, scale=True) + print("A:\n", A) + return A + + # transform a point list given an affine transform matrix + def transform_points( A, pts_list ): + src = [[], [], [], []] + for p in pts_list: + src[0].append(p[0]) + src[1].append(p[1]) + src[2].append(p[2]) + src[3].append(1.0) + dst = A.dot( np.array(src) ) + result = [] + for i in range(len(pts_list)): + result.append( [ float(dst[0][i]), + float(dst[1][i]), + float(dst[2][i]) ] ) + return result + + proj = ProjectMgr.ProjectMgr(project_dir) + proj.load_images_info() + + source_file = os.path.join(proj.analysis_dir, 'matches_grouped' ) + print('Match file:', source_file) + matches = pickle.load( open(source_file, "rb") ) + print('Match features:', len(matches)) + + # load the group connections within the image set + groups = Groups.load(proj.analysis_dir) + # sort from smallest to largest: groups.sort(key=len) + + opt = Optimizer.Optimizer(project_dir) + opt.setup( proj, groups, group_id, matches, optimized=refine, + cam_calib=cam_calibration) + cameras, features, cam_index_map, feat_index_map, fx_opt, fy_opt, cu_opt, cv_opt, distCoeffs_opt = opt.run() + + # mark all the optimized poses as invalid for image in proj.image_list: - if image.name in group: + opt_cam_node = image.node.getChild('camera_pose_opt', True) + opt_cam_node.setBool('valid', False) + + for i, cam in enumerate(cameras): + image_index = cam_index_map[i] + image = proj.image_list[image_index] + ned_orig, ypr_orig, quat_orig = image.get_camera_pose() + print('optimized cam:', cam) + rvec = cam[0:3] + tvec = cam[3:6] + Rned2cam, jac = cv2.Rodrigues(rvec) + cam2body = image.get_cam2body() + Rned2body = cam2body.dot(Rned2cam) + Rbody2ned = np.matrix(Rned2body).T + (yaw, pitch, roll) = transformations.euler_from_matrix(Rbody2ned, 'rzyx') + #print "orig ypr =", image.camera_pose['ypr'] + #print "new ypr =", [yaw/d2r, pitch/d2r, roll/d2r] + pos = -np.matrix(Rned2cam).T * np.matrix(tvec).T + newned = pos.T[0].tolist()[0] + print(image.name, ned_orig, '->', newned, 'dist:', np.linalg.norm(np.array(ned_orig) - np.array(newned))) + image.set_camera_pose( newned, yaw*r2d, pitch*r2d, roll*r2d, opt=True ) + image.placed = True + proj.save_images_info() + print('Updated the optimized camera poses.') + + # update and save the optimized camera calibration + proj.cam.set_K(fx_opt, fy_opt, cu_opt, cv_opt, optimized=True) + proj.cam.set_dist_coeffs(distCoeffs_opt.tolist(), optimized=True) + proj.save() + + # compare original camera locations with optimized camera locations and + # derive a transform matrix to 'best fit' the new camera locations + # over the original ... trusting the original group gps solution as + # our best absolute truth for positioning the system in world + # coordinates. + # + # each optimized group needs a separate/unique fit + + matches_opt = list(matches) # shallow copy + refit_group_orientations = True + if refit_group_orientations: + group = groups[group_id] + print('refitting group size:', len(group)) + src_list = [] + dst_list = [] + # only consider images that are in the current group + for name in group: + image = proj.findImageByName(name) ned, ypr, quat = image.get_camera_pose(opt=True) - else: - # this is just fodder to match size/index of the lists + src_list.append(ned) ned, ypr, quat = image.get_camera_pose() - camera_list.append( ned ) + dst_list.append(ned) + A = get_recenter_affine(src_list, dst_list) - # refit - new_cams = transform_points(A, camera_list) + # extract the rotation matrix (R) from the affine transform + scale, shear, angles, trans, persp = transformations.decompose_matrix(A) + print(' scale:', scale) + print(' shear:', shear) + print(' angles:', angles) + print(' translate:', trans) + print(' perspective:', persp) + R = transformations.euler_matrix(*angles) + print("R:\n{}".format(R)) - # update position - for i, image in enumerate(proj.image_list): - if not image.name in group: - continue - ned, [y, p, r], quat = image.get_camera_pose(opt=True) - image.set_camera_pose(new_cams[i], y, p, r, opt=True) - proj.save_images_info() + # fixme (just group): - if True: - # update optimized pose orientation. - dist_report = [] + # update the optimized camera locations based on best fit + camera_list = [] + # load optimized poses + for image in proj.image_list: + if image.name in group: + ned, ypr, quat = image.get_camera_pose(opt=True) + else: + # this is just fodder to match size/index of the lists + ned, ypr, quat = image.get_camera_pose() + camera_list.append( ned ) + + # refit + new_cams = transform_points(A, camera_list) + + # update position for i, image in enumerate(proj.image_list): if not image.name in group: continue - ned_orig, ypr_orig, quat_orig = image.get_camera_pose() - ned, ypr, quat = image.get_camera_pose(opt=True) - Rbody2ned = image.get_body2ned(opt=True) - # update the orientation with the same transform to keep - # everything in proper consistent alignment - - newRbody2ned = R[:3,:3].dot(Rbody2ned) - (yaw, pitch, roll) = transformations.euler_from_matrix(newRbody2ned, 'rzyx') - image.set_camera_pose(new_cams[i], yaw*r2d, pitch*r2d, roll*r2d, - opt=True) - dist = np.linalg.norm( np.array(ned_orig) - np.array(new_cams[i])) - print('image: {}'.format(image.name)) - print(' orig pos: {}'.format(ned_orig)) - print(' fit pos: {}'.format(new_cams[i])) - print(' dist moved: {}'.format(dist)) - dist_report.append( (dist, image.name) ) + ned, [y, p, r], quat = image.get_camera_pose(opt=True) + image.set_camera_pose(new_cams[i], y, p, r, opt=True) proj.save_images_info() - dist_report = sorted(dist_report, - key=lambda fields: fields[0], - reverse=False) - print('Image movement sorted lowest to highest:') - for report in dist_report: - print('{} dist: {}'.format(report[1], report[0])) - - # tranform the optimized point locations using the same best - # fit transform for the camera locations. - new_feats = transform_points(A, features) - - # update any of the transformed feature locations that have - # membership in the currently processing group back to the - # master match structure. Note we process groups in order of - # little to big so if a match is in more than one group it - # follows the larger group. - for i, feat in enumerate(new_feats): - match_index = feat_index_map[i] - match = matches_opt[match_index] - in_group = False - for m in match[2:]: - if proj.image_list[m[0]].name in group: - in_group = True - break - if in_group: - #print(' before:', match) + if True: + # update optimized pose orientation. + dist_report = [] + for i, image in enumerate(proj.image_list): + if not image.name in group: + continue + ned_orig, ypr_orig, quat_orig = image.get_camera_pose() + ned, ypr, quat = image.get_camera_pose(opt=True) + Rbody2ned = image.get_body2ned(opt=True) + # update the orientation with the same transform to keep + # everything in proper consistent alignment + + newRbody2ned = R[:3,:3].dot(Rbody2ned) + (yaw, pitch, roll) = transformations.euler_from_matrix(newRbody2ned, 'rzyx') + image.set_camera_pose(new_cams[i], yaw*r2d, pitch*r2d, roll*r2d, + opt=True) + dist = np.linalg.norm( np.array(ned_orig) - np.array(new_cams[i])) + print('image: {}'.format(image.name)) + print(' orig pos: {}'.format(ned_orig)) + print(' fit pos: {}'.format(new_cams[i])) + print(' dist moved: {}'.format(dist)) + dist_report.append( (dist, image.name) ) + proj.save_images_info() + + dist_report = sorted(dist_report, + key=lambda fields: fields[0], + reverse=False) + print('Image movement sorted lowest to highest:') + for report in dist_report: + print('{} dist: {}'.format(report[1], report[0])) + + # tranform the optimized point locations using the same best + # fit transform for the camera locations. + new_feats = transform_points(A, features) + + # update any of the transformed feature locations that have + # membership in the currently processing group back to the + # master match structure. Note we process groups in order of + # little to big so if a match is in more than one group it + # follows the larger group. + for i, feat in enumerate(new_feats): + match_index = feat_index_map[i] + match = matches_opt[match_index] + in_group = False + for m in match[2:]: + if proj.image_list[m[0]].name in group: + in_group = True + break + if in_group: + #print(' before:', match) + match[0] = feat + #print(' after:', match) + else: + # not refitting group orientations, just copy over optimized + # coordinates + for i, feat in enumerate(features): + match_index = feat_index_map[i] + match = matches_opt[match_index] match[0] = feat - #print(' after:', match) -else: - # not refitting group orientations, just copy over optimized - # coordinates - for i, feat in enumerate(features): - match_index = feat_index_map[i] - match = matches_opt[match_index] - match[0] = feat - -# write out the updated match_dict -print('Updating matches file:', len(matches_opt), 'features') -pickle.dump(matches_opt, open(source_file, 'wb')) - -#proj.cam.set_K(fx_opt/scale[0], fy_opt/scale[0], cu_opt/scale[0], cv_opt/scale[0], optimized=True) -#proj.save() - -# temp write out just the points so we can plot them with gnuplot -f = open(os.path.join(proj.analysis_dir, 'opt-plot.txt'), 'w') -for m in matches_opt: - f.write('%.2f %.2f %.2f\n' % (m[0][0], m[0][1], m[0][2])) -f.close() - -# temp write out direct and optimized camera positions -f1 = open(os.path.join(proj.analysis_dir, 'cams-direct.txt'), 'w') -f2 = open(os.path.join(proj.analysis_dir, 'cams-opt.txt'), 'w') -for name in groups[args.group]: - image = proj.findImageByName(name) - ned1, ypr1, quat1 = image.get_camera_pose() - ned2, ypr2, quat2 = image.get_camera_pose(opt=True) - f1.write('%.2f %.2f %.2f\n' % (ned1[1], ned1[0], -ned1[2])) - f2.write('%.2f %.2f %.2f\n' % (ned2[1], ned2[0], -ned2[2])) -f1.close() -f2.close() + + # write out the updated match_dict + print('Updating matches file:', len(matches_opt), 'features') + pickle.dump(matches_opt, open(source_file, 'wb')) + + #proj.cam.set_K(fx_opt/scale[0], fy_opt/scale[0], cu_opt/scale[0], cv_opt/scale[0], optimized=True) + #proj.save() + + # temp write out just the points so we can plot them with gnuplot + f = open(os.path.join(proj.analysis_dir, 'opt-plot.txt'), 'w') + for m in matches_opt: + try: + f.write('%.2f %.2f %.2f\n' % (m[0][0], m[0][1], m[0][2])) + except: + pass + f.close() + + # temp write out direct and optimized camera positions + f1 = open(os.path.join(proj.analysis_dir, 'cams-direct.txt'), 'w') + f2 = open(os.path.join(proj.analysis_dir, 'cams-opt.txt'), 'w') + for name in groups[group_id]: + image = proj.findImageByName(name) + ned1, ypr1, quat1 = image.get_camera_pose() + ned2, ypr2, quat2 = image.get_camera_pose(opt=True) + f1.write('%.2f %.2f %.2f\n' % (ned1[1], ned1[0], -ned1[2])) + f2.write('%.2f %.2f %.2f\n' % (ned2[1], ned2[0], -ned2[2])) + f1.close() + f2.close() + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Keypoint projection.') + parser.add_argument('--project', required=True, help='project directory') + parser.add_argument('--group', type=int, default=0, help='group number') + parser.add_argument('--refine', action='store_true', help='refine a previous optimization.') + parser.add_argument('--cam-calibration', action='store_true', help='include camera calibration in the optimization.') + + args = parser.parse_args() + + optmize_options = [args.group, args.refine, args.cam_calibration] + optmizer(args.project, optmize_options) \ No newline at end of file diff --git a/scripts/5b_colocated_feats.py b/scripts/5b_colocated_feats.py index e6b1bde9..4931d994 100644 --- a/scripts/5b_colocated_feats.py +++ b/scripts/5b_colocated_feats.py @@ -13,95 +13,104 @@ from lib import ProjectMgr from lib import match_culling as cull -r2d = 180.0 / math.pi - -parser = argparse.ArgumentParser(description='Keypoint projection.') -parser.add_argument('--project', required=True, help='project directory') -parser.add_argument('--group', type=int, default=0, help='group index') -parser.add_argument('--min-angle', type=float, default=1.0, help='max feature angle') -args = parser.parse_args() - -proj = ProjectMgr.ProjectMgr(args.project) -proj.load_images_info() - -# a value of 2 let's pairs exist which can be trouble ... -matcher_node = getNode('/config/matcher', True) -min_chain_len = matcher_node.getInt("min_chain_len") -if min_chain_len == 0: - min_chain_len = 3 -print("Notice: min_chain_len is:", min_chain_len) - -#source = 'matches_direct' -source = 'matches_grouped' -print("Loading matches:", source) -matches = pickle.load( open( os.path.join(proj.analysis_dir, source), "rb" ) ) -print('Number of original features:', len(matches)) - -# load the group connections within the image set -groups = Groups.load(proj.analysis_dir) -print('Group sizes:', end=" ") -for group in groups: - print(len(group), end=" ") -print() - -def compute_angle(ned1, ned2, ned3): - vec1 = np.array(ned3) - np.array(ned1) - vec2 = np.array(ned3) - np.array(ned2) - n1 = np.linalg.norm(vec1) - n2 = np.linalg.norm(vec2) - denom = n1*n2 - if abs(denom - 0.000001) > 0: - try: - tmp = np.dot(vec1, vec2) / denom - if tmp > 1.0: tmp = 1.0 - return math.acos(tmp) - except: - print('vec1:', vec1, 'vec2', vec2, 'dot:', np.dot(vec1, vec2)) - print('denom:', denom) +def colocated(project_dir, colocated_options): + + group_id = colocated_options[0] + min_angle = colocated_options[1] + + r2d = 180.0 / math.pi + + proj = ProjectMgr.ProjectMgr(project_dir) + proj.load_images_info() + + # a value of 2 let's pairs exist which can be trouble ... + matcher_node = getNode('/config/matcher', True) + min_chain_len = matcher_node.getInt("min_chain_len") + if min_chain_len == 0: + min_chain_len = 3 + print("Notice: min_chain_len is:", min_chain_len) + + #source = 'matches_direct' + source = 'matches_grouped' + print("Loading matches:", source) + matches = pickle.load( open( os.path.join(proj.analysis_dir, source), "rb" ) ) + print('Number of original features:', len(matches)) + + # load the group connections within the image set + groups = Groups.load(proj.analysis_dir) + print('Group sizes:', end=" ") + for group in groups: + print(len(group), end=" ") + print() + + def compute_angle(ned1, ned2, ned3): + vec1 = np.array(ned3) - np.array(ned1) + vec2 = np.array(ned3) - np.array(ned2) + n1 = np.linalg.norm(vec1) + n2 = np.linalg.norm(vec2) + denom = n1*n2 + if abs(denom - 0.000001) > 0: + try: + tmp = np.dot(vec1, vec2) / denom + if tmp > 1.0: tmp = 1.0 + return math.acos(tmp) + except: + print('vec1:', vec1, 'vec2', vec2, 'dot:', np.dot(vec1, vec2)) + print('denom:', denom) + return 0 + else: return 0 - else: - return 0 - -print("Scanning match pair angles:") -mark_list = [] -for k, match in enumerate(tqdm(matches)): - if match[1] == args.group: # used by current group - for i, m1 in enumerate(match[2:]): - for j, m2 in enumerate(match[2:]): - if i < j: - i1 = proj.image_list[m1[0]] - i2 = proj.image_list[m2[0]] - if i1.name in groups[args.group] and i2.name in groups[args.group]: - ned1, ypr1, q1 = i1.get_camera_pose(opt=True) - ned2, ypr2, q2 = i2.get_camera_pose(opt=True) - quick_approx = False - if quick_approx: - # quick hack angle approximation - avg = (np.array(ned1) + np.array(ned2)) * 0.5 - y = np.linalg.norm(np.array(ned2) - np.array(ned1)) - x = np.linalg.norm(avg - np.array(match[0])) - angle_deg = math.atan2(y, x) * r2d - else: - angle_deg = compute_angle(ned1, ned2, match[0]) * r2d - if angle_deg < args.min_angle: - mark_list.append( [k, i] ) - -# Pairs with very small average angles between each feature and camera -# location indicate closely located camera poses and these cause -# problems because very small changes in camera pose lead to very -# large changes in feature location. - -# mark selection -cull.mark_using_list(mark_list, matches) -mark_sum = len(mark_list) - -mark_sum = len(mark_list) -if mark_sum > 0: - print('Outliers to remove from match lists:', mark_sum) - result = input('Save these changes? (y/n):') - if result == 'y' or result == 'Y': - cull.delete_marked_features(matches, min_chain_len) - # write out the updated match dictionaries - print("Writing original matches:", source) - pickle.dump(matches, open(os.path.join(proj.analysis_dir, source), "wb")) + print("Scanning match pair angles:") + mark_list = [] + for k, match in enumerate(tqdm(matches)): + if match[1] == group_id: # used by current group + for i, m1 in enumerate(match[2:]): + for j, m2 in enumerate(match[2:]): + if i < j: + i1 = proj.image_list[m1[0]] + i2 = proj.image_list[m2[0]] + if i1.name in groups[group_id] and i2.name in groups[group_id]: + ned1, ypr1, q1 = i1.get_camera_pose(opt=True) + ned2, ypr2, q2 = i2.get_camera_pose(opt=True) + quick_approx = False + if quick_approx: + # quick hack angle approximation + avg = (np.array(ned1) + np.array(ned2)) * 0.5 + y = np.linalg.norm(np.array(ned2) - np.array(ned1)) + x = np.linalg.norm(avg - np.array(match[0])) + angle_deg = math.atan2(y, x) * r2d + else: + angle_deg = compute_angle(ned1, ned2, match[0]) * r2d + if angle_deg < min_angle: + mark_list.append( [k, i] ) + + # Pairs with very small average angles between each feature and camera + # location indicate closely located camera poses and these cause + # problems because very small changes in camera pose lead to very + # large changes in feature location. + + # mark selection + cull.mark_using_list(mark_list, matches) + mark_sum = len(mark_list) + + mark_sum = len(mark_list) + if mark_sum > 0: + print('Outliers to remove from match lists:', mark_sum) + result = input('Save these changes? (y/n):') + if result == 'y' or result == 'Y': + cull.delete_marked_features(matches, min_chain_len) + # write out the updated match dictionaries + print("Writing original matches:", source) + pickle.dump(matches, open(os.path.join(proj.analysis_dir, source), "wb")) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Keypoint projection.') + parser.add_argument('--project', required=True, help='project directory') + parser.add_argument('--group', type=int, default=0, help='group index') + parser.add_argument('--min-angle', type=float, default=1.0, help='max feature angle') + args = parser.parse_args() + + colocated_options = [args.group, args.min_angle] + + colocated(args.project, colocated_options) \ No newline at end of file diff --git a/scripts/5b_mre_by_image.py b/scripts/5b_mre_by_image.py index d915114e..a5c408c7 100644 --- a/scripts/5b_mre_by_image.py +++ b/scripts/5b_mre_by_image.py @@ -16,180 +16,192 @@ from lib import ProjectMgr from lib import match_culling as cull -parser = argparse.ArgumentParser(description='Keypoint projection.') -parser.add_argument('--project', required=True, help='project directory') -parser.add_argument('--group', type=int, default=0, help='group number') -parser.add_argument('--stddev', type=float, default=5, help='how many stddevs above the mean for auto discarding features') -parser.add_argument('--initial-pose', action='store_true', help='work on initial pose, not optimized pose') -parser.add_argument('--strong', action='store_true', help='remove entire match chain, not just the worst offending element.') -parser.add_argument('--interactive', action='store_true', help='interactively review reprojection errors from worst to best and select for deletion or keep.') - -args = parser.parse_args() - -proj = ProjectMgr.ProjectMgr(args.project) -proj.load_images_info() - -# a value of 2 let's pairs exist which can be trouble ... -matcher_node = getNode('/config/matcher', True) -min_chain_len = matcher_node.getInt("min_chain_len") -if min_chain_len == 0: - min_chain_len = 3 -print("Notice: min_chain_len is:", min_chain_len) - -source = 'matches_grouped' -print("Loading matches:", source) -matches = pickle.load( open( os.path.join(proj.analysis_dir, source), "rb" ) ) -print('Number of original features:', len(matches)) - -# load the group connections within the image set -groups = Groups.load(proj.analysis_dir) -print('Group sizes:', end=" ") -for group in groups: - print(len(group), end=" ") -print() - -opt = Optimizer.Optimizer(args.project) -if args.initial_pose: - opt.setup( proj, groups, args.group, matches, optimized=False ) -else: - opt.setup( proj, groups, args.group, matches, optimized=True ) -x0 = np.hstack((opt.camera_params.ravel(), opt.points_3d.ravel(), - opt.K[0,0], opt.K[0,2], opt.K[1,2], - opt.distCoeffs)) -error = opt.fun(x0, opt.n_cameras, opt.n_points, opt.by_camera_point_indices, opt.by_camera_points_2d) - -print('cameras:', opt.n_cameras) - -print(len(error)) -mre = np.mean(np.abs(error)) -std = np.std(error) -max = np.amax(np.abs(error)) -print('mre: %.3f std: %.3f max: %.2f' % (mre, std, max) ) - -print('Tabulating results...') -results = [] -results_by_cam = [] -count = 0 -for i, cam in enumerate(opt.camera_params.reshape((opt.n_cameras, opt.ncp))): - # print(i, opt.camera_map_fwd[i]) - orig_cam_index = opt.camera_map_fwd[i] - cam_errors = [] - # print(count, opt.by_camera_point_indices[i]) - for j in opt.by_camera_point_indices[i]: - match = matches[opt.feat_map_rev[j]] - match_index = 0 - #print(orig_cam_index, match) - for k, p in enumerate(match[2:]): - if p[0] == orig_cam_index: - match_index = k - # print(match[0], opt.points_3d[j*3:j*3+3]) - e = error[count*2:count*2+2] - #print(count, e, np.linalg.norm(e)) - #if abs(e[0]) > 5*std or abs(e[1]) > 5*std: - # print("big") - cam_errors.append( np.linalg.norm(e) ) - results.append( [np.linalg.norm(e), opt.feat_map_rev[j], match_index] ) - count += 1 - if len(cam_errors): - results_by_cam.append( [np.mean(np.abs(np.array(cam_errors))), - np.amax(np.abs(np.array(cam_errors))), - proj.image_list[orig_cam_index].name ] ) - else: - results_by_cam.append( [9999.0, 9999.0, - proj.image_list[orig_cam_index].name ] ) - - #print(proj.image_list[orig_cam_index].name, ':', - # np.mean(np.abs(np.array(cam_errors)))) - -print("Report of images that aren't fitting well:") -results_by_cam = sorted(results_by_cam, key=lambda fields: fields[0], reverse=True) -for line in results_by_cam: - if line[0] > mre + 3*std: - print("%s - mean: %.3f max: %.3f" % (line[2], line[0], line[1])) -for line in results_by_cam: - if line[0] > mre + 3*std: - print(line[2], end=" ") -print() +def mre(project_dir, mre_options): -error_list = sorted(results, key=lambda fields: fields[0], reverse=True) - -def mark_outliers(error_list, trim_stddev): - print("Marking outliers...") - sum = 0.0 - count = len(error_list) - - # numerically it is better to sum up a list of floatting point - # numbers from smallest to biggest (error_list is sorted from - # biggest to smallest) - for line in reversed(error_list): - sum += line[0] + group_id = mre_options[0] + stddev = mre_options[1] + initial_pose = mre_options[2] + strong = mre_options[3] + interactive = mre_options[4] + + proj = ProjectMgr.ProjectMgr(project_dir) + proj.load_images_info() + + # a value of 2 let's pairs exist which can be trouble ... + matcher_node = getNode('/config/matcher', True) + min_chain_len = matcher_node.getInt("min_chain_len") + if min_chain_len == 0: + min_chain_len = 3 + print("Notice: min_chain_len is:", min_chain_len) + + source = 'matches_grouped' + print("Loading matches:", source) + matches = pickle.load( open( os.path.join(proj.analysis_dir, source), "rb" ) ) + print('Number of original features:', len(matches)) + + # load the group connections within the image set + groups = Groups.load(proj.analysis_dir) + print('Group sizes:', end=" ") + for group in groups: + print(len(group), end=" ") + print() + + opt = Optimizer.Optimizer(project_dir) + if initial_pose: + opt.setup( proj, groups, group_id, matches, optimized=False ) + else: + opt.setup( proj, groups, group_id, matches, optimized=True ) + x0 = np.hstack((opt.camera_params.ravel(), opt.points_3d.ravel(), + opt.K[0,0], opt.K[0,2], opt.K[1,2], + opt.distCoeffs)) + error = opt.fun(x0, opt.n_cameras, opt.n_points, opt.by_camera_point_indices, opt.by_camera_points_2d) + + print('cameras:', opt.n_cameras) + + print(len(error)) + mre = np.mean(np.abs(error)) + std = np.std(error) + max = np.amax(np.abs(error)) + print('mre: %.3f std: %.3f max: %.2f' % (mre, std, max) ) + + print('Tabulating results...') + results = [] + results_by_cam = [] + count = 0 + for i, cam in enumerate(opt.camera_params.reshape((opt.n_cameras, opt.ncp))): + # print(i, opt.camera_map_fwd[i]) + orig_cam_index = opt.camera_map_fwd[i] + cam_errors = [] + # print(count, opt.by_camera_point_indices[i]) + for j in opt.by_camera_point_indices[i]: + match = matches[opt.feat_map_rev[j]] + match_index = 0 + #print(orig_cam_index, match) + for k, p in enumerate(match[2:]): + if p[0] == orig_cam_index: + match_index = k + # print(match[0], opt.points_3d[j*3:j*3+3]) + e = error[count*2:count*2+2] + #print(count, e, np.linalg.norm(e)) + #if abs(e[0]) > 5*std or abs(e[1]) > 5*std: + # print("big") + cam_errors.append( np.linalg.norm(e) ) + results.append( [np.linalg.norm(e), opt.feat_map_rev[j], match_index] ) + count += 1 + if len(cam_errors): + results_by_cam.append( [np.mean(np.abs(np.array(cam_errors))), + np.amax(np.abs(np.array(cam_errors))), + proj.image_list[orig_cam_index].name ] ) + else: + results_by_cam.append( [9999.0, 9999.0, + proj.image_list[orig_cam_index].name ] ) + + #print(proj.image_list[orig_cam_index].name, ':', + # np.mean(np.abs(np.array(cam_errors)))) + + print("Report of images that aren't fitting well:") + results_by_cam = sorted(results_by_cam, key=lambda fields: fields[0], reverse=True) + for line in results_by_cam: + if line[0] > mre + 3*std: + print("%s - mean: %.3f max: %.3f" % (line[2], line[0], line[1])) + for line in results_by_cam: + if line[0] > mre + 3*std: + print(line[2], end=" ") + print() - # stats on error values - print(" computing stats...") - mre = sum / count - stddev_sum = 0.0 - for line in error_list: - error = line[0] - stddev_sum += (mre-error)*(mre-error) - stddev = math.sqrt(stddev_sum / count) - print("mre = %.4f stddev = %.4f" % (mre, stddev)) - - # mark match items to delete - print(" marking outliers...") - mark_count = 0 - for line in error_list: - # print "line:", line - if line[0] > mre + stddev * trim_stddev: - cull.mark_feature(matches, line[1], line[2], line[0]) - mark_count += 1 + error_list = sorted(results, key=lambda fields: fields[0], reverse=True) + + def mark_outliers(error_list, trim_stddev): + print("Marking outliers...") + sum = 0.0 + count = len(error_list) + + # numerically it is better to sum up a list of floatting point + # numbers from smallest to biggest (error_list is sorted from + # biggest to smallest) + for line in reversed(error_list): + sum += line[0] - return mark_count - -if args.interactive: - # interactively pick outliers - mark_list = cull.show_outliers(error_list, matches, proj.image_list) - - # mark selection - cull.mark_using_list(mark_list, matches) - mark_sum = len(mark_list) -else: - # trim outliers by some # of standard deviations high - mark_sum = mark_outliers(error_list, args.stddev) - -# after marking the bad matches, now count how many remaining features -# show up in each image -for i in proj.image_list: - i.feature_count = 0 -for i, match in enumerate(matches): - for j, p in enumerate(match[2:]): - if p[1] != [-1, -1]: - image = proj.image_list[ p[0] ] - image.feature_count += 1 - -purge_weak_images = False -if purge_weak_images: - # make a dict of all images with less than 25 feature matches - weak_dict = {} - for i, img in enumerate(proj.image_list): - # print img.name, img.feature_count - if img.feature_count > 0 and img.feature_count < 25: - weak_dict[i] = True - print('weak images:', weak_dict) - - # mark any features in the weak images list + # stats on error values + print(" computing stats...") + mre = sum / count + stddev_sum = 0.0 + for line in error_list: + error = line[0] + stddev_sum += (mre-error)*(mre-error) + stddev = math.sqrt(stddev_sum / count) + print("mre = %.4f stddev = %.4f" % (mre, stddev)) + + # mark match items to delete + print(" marking outliers...") + mark_count = 0 + for line in error_list: + # print "line:", line + if line[0] > mre + stddev * trim_stddev: + cull.mark_feature(matches, line[1], line[2], line[0]) + mark_count += 1 + + return mark_count + + if interactive: + # interactively pick outliers + mark_list = cull.show_outliers(error_list, matches, proj.image_list) + + # mark selection + cull.mark_using_list(mark_list, matches) + mark_sum = len(mark_list) + else: + # trim outliers by some # of standard deviations high + mark_sum = mark_outliers(error_list, stddev) + + # after marking the bad matches, now count how many remaining features + # show up in each image + for i in proj.image_list: + i.feature_count = 0 for i, match in enumerate(matches): - #print 'before:', match for j, p in enumerate(match[2:]): - if p[0] in weak_dict: - match[j+1] = [-1, -1] - mark_sum += 1 - -if mark_sum > 0: - print('Outliers removed from match lists:', mark_sum) - result = input('Save these changes? (y/n):') - if result == 'y' or result == 'Y': - cull.delete_marked_features(matches, min_chain_len, strong=args.strong) - # write out the updated match dictionaries - print("Writing:", source) - pickle.dump(matches, open(os.path.join(proj.analysis_dir, source), "wb")) - + if p[1] != [-1, -1]: + image = proj.image_list[ p[0] ] + image.feature_count += 1 + + purge_weak_images = False + if purge_weak_images: + # make a dict of all images with less than 25 feature matches + weak_dict = {} + for i, img in enumerate(proj.image_list): + # print img.name, img.feature_count + if img.feature_count > 0 and img.feature_count < 25: + weak_dict[i] = True + print('weak images:', weak_dict) + + # mark any features in the weak images list + for i, match in enumerate(matches): + #print 'before:', match + for j, p in enumerate(match[2:]): + if p[0] in weak_dict: + match[j+1] = [-1, -1] + mark_sum += 1 + + if mark_sum > 0: + print('Outliers removed from match lists:', mark_sum) + result = input('Save these changes? (y/n):') + if result == 'y' or result == 'Y': + cull.delete_marked_features(matches, min_chain_len, strong=strong) + # write out the updated match dictionaries + print("Writing:", source) + pickle.dump(matches, open(os.path.join(proj.analysis_dir, source), "wb")) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Keypoint projection.') + parser.add_argument('--project', required=True, help='project directory') + parser.add_argument('--group', type=int, default=0, help='group number') + parser.add_argument('--stddev', type=float, default=5, help='how many stddevs above the mean for auto discarding features') + parser.add_argument('--initial-pose', action='store_true', help='work on initial pose, not optimized pose') + parser.add_argument('--strong', action='store_true', help='remove entire match chain, not just the worst offending element.') + parser.add_argument('--interactive', action='store_true', help='interactively review reprojection errors from worst to best and select for deletion or keep.') + + args = parser.parse_args() + + mre_options = [args.group, args.stddev, args.initial_pose, args.strong, args.interactive] + + mre(args.project, mre_options) \ No newline at end of file diff --git a/scripts/6a_render_model2.py b/scripts/6a_render_model2.py index 16bf0cb6..989616c8 100644 --- a/scripts/6a_render_model2.py +++ b/scripts/6a_render_model2.py @@ -30,232 +30,245 @@ ac3d_steps = 8 r2d = 180 / math.pi -parser = argparse.ArgumentParser(description='Set the initial camera poses.') -parser.add_argument('--project', required=True, help='project directory') -parser.add_argument('--group', type=int, default=0, help='group index') -parser.add_argument('--texture-resolution', type=int, default=512, help='texture resolution (should be 2**n, so numbers like 256, 512, 1024, etc.') -parser.add_argument('--srtm', action='store_true', help='use srtm elevation') -parser.add_argument('--ground', type=float, help='force ground elevation in meters') -parser.add_argument('--direct', action='store_true', help='use direct pose') +def render(project_dir, render_options): -args = parser.parse_args() + group_id = render_options[0] + texture_resolution = render_options[1] + srtm = render_options[2] + ground = render_options[3] + direct = render_options[4] -proj = ProjectMgr.ProjectMgr(args.project) -proj.load_images_info() + proj = ProjectMgr.ProjectMgr(project_dir) + proj.load_images_info() -# lookup ned reference -ref_node = getNode("/config/ned_reference", True) -ref = [ ref_node.getFloat('lat_deg'), - ref_node.getFloat('lon_deg'), - ref_node.getFloat('alt_m') ] - -# setup SRTM ground interpolator -sss = SRTM.NEDGround( ref, 6000, 6000, 30 ) + # lookup ned reference + ref_node = getNode("/config/ned_reference", True) + ref = [ ref_node.getFloat('lat_deg'), + ref_node.getFloat('lon_deg'), + ref_node.getFloat('alt_m') ] + + # setup SRTM ground interpolator + sss = SRTM.NEDGround( ref, 6000, 6000, 30 ) -print("Loading optimized match points ...") -matches = pickle.load( open( os.path.join(proj.analysis_dir, "matches_grouped"), "rb" ) ) + print("Loading optimized match points ...") + matches = pickle.load( open( os.path.join(proj.analysis_dir, "matches_grouped"), "rb" ) ) -# load the group connections within the image set -groups = Groups.load(proj.analysis_dir) + # load the group connections within the image set + groups = Groups.load(proj.analysis_dir) -# initialize temporary structures for vanity stats -for image in proj.image_list: - image.sum_values = 0.0 - image.sum_count = 0.0 - image.max_z = -9999.0 - image.min_z = 9999.0 + # initialize temporary structures for vanity stats + for image in proj.image_list: + image.sum_values = 0.0 + image.sum_count = 0.0 + image.max_z = -9999.0 + image.min_z = 9999.0 -# elevation stats -print("Computing stats...") -ned_list = [] -for match in matches: - if match[1] == args.group: # used by current group - ned_list.append(match[0]) -avg = -np.mean(np.array(ned_list)[:,2]) -std = np.std(np.array(ned_list)[:,2]) -print("Average elevation: %.2f" % avg) -print("Standard deviation: %.2f" % std) + # elevation stats + print("Computing stats...") + ned_list = [] + for match in matches: + if match[1] == group_id: # used by current group + ned_list.append(match[0]) + avg = -np.mean(np.array(ned_list)[:,2]) + std = np.std(np.array(ned_list)[:,2]) + print("Average elevation: %.2f" % avg) + print("Standard deviation: %.2f" % std) -# sort through points -print('Reading feature locations from optimized match points ...') -raw_points = [] -raw_values = [] -for match in matches: - if match[1] == args.group: # used by current group - ned = match[0] - diff = abs(-ned[2] - avg) - if diff < 10*std: - raw_points.append( [ned[1], ned[0]] ) - raw_values.append( ned[2] ) - for m in match[2:]: - if proj.image_list[m[0]].name in groups[args.group]: - image = proj.image_list[ m[0] ] - z = -ned[2] - image.sum_values += z - image.sum_count += 1 - if z < image.min_z: - image.min_z = z - #print(min_z, match) - if z > image.max_z: - image.max_z = z - #print(max_z, match) - else: - print("Discarding match with excessive altitude:", match) + # sort through points + print('Reading feature locations from optimized match points ...') + raw_points = [] + raw_values = [] + for match in matches: + if match[1] == group_id: # used by current group + ned = match[0] + diff = abs(-ned[2] - avg) + if diff < 10*std: + raw_points.append( [ned[1], ned[0]] ) + raw_values.append( ned[2] ) + for m in match[2:]: + if proj.image_list[m[0]].name in groups[group_id]: + image = proj.image_list[ m[0] ] + z = -ned[2] + image.sum_values += z + image.sum_count += 1 + if z < image.min_z: + image.min_z = z + #print(min_z, match) + if z > image.max_z: + image.max_z = z + #print(max_z, match) + else: + print("Discarding match with excessive altitude:", match) -# save the surface definition as a separate file -models_dir = os.path.join(proj.analysis_dir, 'models') -if not os.path.exists(models_dir): - print("Notice: creating models directory =", models_dir) - os.makedirs(models_dir) -surface = { 'points': raw_points, - 'values': raw_values } -pickle.dump(surface, open(os.path.join(proj.analysis_dir, 'models', 'surface.bin'), "wb")) + # save the surface definition as a separate file + models_dir = os.path.join(proj.analysis_dir, 'models') + if not os.path.exists(models_dir): + print("Notice: creating models directory =", models_dir) + os.makedirs(models_dir) + surface = { 'points': raw_points, + 'values': raw_values } + pickle.dump(surface, open(os.path.join(proj.analysis_dir, 'models', 'surface.bin'), "wb")) -print('Generating Delaunay mesh and interpolator ...') -global_tri_list = scipy.spatial.Delaunay(np.array(raw_points)) -interp = scipy.interpolate.LinearNDInterpolator(global_tri_list, raw_values) + print('Generating Delaunay mesh and interpolator ...') + global_tri_list = scipy.spatial.Delaunay(np.array(raw_points)) + interp = scipy.interpolate.LinearNDInterpolator(global_tri_list, raw_values) -no_extrapolate = True -def intersect2d(ned, v, avg_ground): - p = ned[:] # copy + no_extrapolate = True + def intersect2d(ned, v, avg_ground): + p = ned[:] # copy - # sanity check (always assume camera pose is above ground!) - if v[2] <= 0.0: - return p + # sanity check (always assume camera pose is above ground!) + if v[2] <= 0.0: + return p - eps = 0.01 - count = 0 - #print("start:", p) - #print("vec:", v) - #print("ned:", ned) - tmp = interp([p[1], p[0]])[0] - if no_extrapolate or not np.isnan(tmp): - surface = tmp - else: - surface = avg_ground - error = abs(p[2] - surface) - #print("p=%s surface=%s error=%s" % (p, surface, error)) - while error > eps and count < 25: - d_proj = -(ned[2] - surface) - factor = d_proj / v[2] - n_proj = v[0] * factor - e_proj = v[1] * factor - #print(" proj = %s %s" % (n_proj, e_proj)) - p = [ ned[0] + n_proj, ned[1] + e_proj, ned[2] + d_proj ] - #print(" new p:", p) + eps = 0.01 + count = 0 + #print("start:", p) + #print("vec:", v) + #print("ned:", ned) tmp = interp([p[1], p[0]])[0] if no_extrapolate or not np.isnan(tmp): surface = tmp + else: + surface = avg_ground error = abs(p[2] - surface) - #print(" p=%s surface=%.2f error = %.3f" % (p, surface, error)) - count += 1 - #print("surface:", surface) - #if np.isnan(surface): - # #print(" returning nans") - # return [np.nan, np.nan, np.nan] - dy = ned[0] - p[0] - dx = ned[1] - p[1] - dz = ned[2] - p[2] - dist = math.sqrt(dx*dx+dy*dy) - angle = math.atan2(-dz, dist) * r2d # relative to horizon - if angle < 30: - print(" returning high angle nans:", angle) - return [np.nan, np.nan, np.nan] - else: - return p - -def intersect_vectors(ned, v_list, avg_ground): - pt_list = [] - for v in v_list: - p = intersect2d(ned, v.flatten(), avg_ground) - pt_list.append(p) - return pt_list - -for image in proj.image_list: - if image.sum_count > 0: - image.z_avg = image.sum_values / float(image.sum_count) - print(image.name, 'avg elev:', image.z_avg) - else: - image.z_avg = 0 - -# compute the uv grid for each image and project each point out into -# ned space, then intersect each vector with the srtm / ground / -# delauney surface. - -#for group in groups: -if True: - group = groups[args.group] - #if len(group) < 3: - # continue - for name in group: - image = proj.findImageByName(name) - print(image.name, image.z_avg) - width, height = proj.cam.get_image_params() - # scale the K matrix if we have scaled the images - K = proj.cam.get_K(optimized=True) - IK = np.linalg.inv(K) - - grid_list = [] - u_list = np.linspace(0, width, ac3d_steps + 1) - v_list = np.linspace(0, height, ac3d_steps + 1) - #print "u_list:", u_list - #print "v_list:", v_list + #print("p=%s surface=%s error=%s" % (p, surface, error)) + while error > eps and count < 25: + d_proj = -(ned[2] - surface) + factor = d_proj / v[2] + n_proj = v[0] * factor + e_proj = v[1] * factor + #print(" proj = %s %s" % (n_proj, e_proj)) + p = [ ned[0] + n_proj, ned[1] + e_proj, ned[2] + d_proj ] + #print(" new p:", p) + tmp = interp([p[1], p[0]])[0] + if no_extrapolate or not np.isnan(tmp): + surface = tmp + error = abs(p[2] - surface) + #print(" p=%s surface=%.2f error = %.3f" % (p, surface, error)) + count += 1 + #print("surface:", surface) + #if np.isnan(surface): + # #print(" returning nans") + # return [np.nan, np.nan, np.nan] + dy = ned[0] - p[0] + dx = ned[1] - p[1] + dz = ned[2] - p[2] + dist = math.sqrt(dx*dx+dy*dy) + angle = math.atan2(-dz, dist) * r2d # relative to horizon + if angle < 30: + print(" returning high angle nans:", angle) + return [np.nan, np.nan, np.nan] + else: + return p + + def intersect_vectors(ned, v_list, avg_ground): + pt_list = [] for v in v_list: - for u in u_list: - grid_list.append( [u, v] ) - #print 'grid_list:', grid_list - image.distorted_uv = proj.redistort(grid_list, optimized=True) + p = intersect2d(ned, v.flatten(), avg_ground) + pt_list.append(p) + return pt_list - if args.direct: - proj_list = proj.projectVectors( IK, image.get_body2ned(), - image.get_cam2body(), grid_list ) + for image in proj.image_list: + if image.sum_count > 0: + image.z_avg = image.sum_values / float(image.sum_count) + print(image.name, 'avg elev:', image.z_avg) else: - #print(image.get_body2ned(opt=True)) - proj_list = proj.projectVectors( IK, image.get_body2ned(opt=True), - image.get_cam2body(), grid_list ) - #print 'proj_list:', proj_list + image.z_avg = 0 + + # compute the uv grid for each image and project each point out into + # ned space, then intersect each vector with the srtm / ground / + # delauney surface. - if args.direct: - ned, ypr, quat = image.get_camera_pose() - else: - ned, ypr, quat = image.get_camera_pose(opt=True) - #print('cam orig:', image.camera_pose['ned'], 'optimized:', ned) - if args.ground: - pts_ned = proj.intersectVectorsWithGroundPlane(ned, - args.ground, proj_list) - elif args.srtm: - pts_ned = sss.interpolate_vectors(ned, proj_list) - elif False: - # this never seemed that productive + #for group in groups: + if True: + group = groups[group_id] + #if len(group) < 3: + # continue + for name in group: + image = proj.findImageByName(name) print(image.name, image.z_avg) - pts_ned = proj.intersectVectorsWithGroundPlane(ned, - image.z_avg, - proj_list) - elif True: - # intersect with our polygon surface approximation - pts_ned = intersect_vectors(ned, proj_list, -image.z_avg) - elif False: - # (moving away from the binned surface approach in this - # script towards the above delauney interpolation - # approach) - # intersect with 2d binned surface approximation - pts_ned = bin2d.intersect_vectors(ned, proj_list, -image.z_avg) - - #print(image.name, "pts_3d (ned):\n", pts_ned) + width, height = proj.cam.get_image_params() + # scale the K matrix if we have scaled the images + K = proj.cam.get_K(optimized=True) + IK = np.linalg.inv(K) + + grid_list = [] + u_list = np.linspace(0, width, ac3d_steps + 1) + v_list = np.linspace(0, height, ac3d_steps + 1) + #print "u_list:", u_list + #print "v_list:", v_list + for v in v_list: + for u in u_list: + grid_list.append( [u, v] ) + #print 'grid_list:', grid_list + image.distorted_uv = proj.redistort(grid_list, optimized=True) + + if direct: + proj_list = proj.projectVectors( IK, image.get_body2ned(), + image.get_cam2body(), grid_list ) + else: + #print(image.get_body2ned(opt=True)) + proj_list = proj.projectVectors( IK, image.get_body2ned(opt=True), + image.get_cam2body(), grid_list ) + #print 'proj_list:', proj_list + + if direct: + ned, ypr, quat = image.get_camera_pose() + else: + ned, ypr, quat = image.get_camera_pose(opt=True) + #print('cam orig:', image.camera_pose['ned'], 'optimized:', ned) + if ground: + pts_ned = proj.intersectVectorsWithGroundPlane(ned, + ground, proj_list) + elif srtm: + pts_ned = sss.interpolate_vectors(ned, proj_list) + elif False: + # this never seemed that productive + print(image.name, image.z_avg) + pts_ned = proj.intersectVectorsWithGroundPlane(ned, + image.z_avg, + proj_list) + elif True: + # intersect with our polygon surface approximation + pts_ned = intersect_vectors(ned, proj_list, -image.z_avg) + elif False: + # (moving away from the binned surface approach in this + # script towards the above delauney interpolation + # approach) + # intersect with 2d binned surface approximation + pts_ned = bin2d.intersect_vectors(ned, proj_list, -image.z_avg) + + #print(image.name, "pts_3d (ned):\n", pts_ned) + + # convert ned to xyz and stash the result for each image + image.grid_list = [] + for p in pts_ned: + image.grid_list.append( [p[1], p[0], -p[2]] ) + + # generate the panda3d egg models + dir_node = getNode('/config/directories', True) + img_src_dir = dir_node.getString('images_source') + Panda3d.generate_from_grid(proj, groups[group_id], src_dir=img_src_dir, + analysis_dir=proj.analysis_dir, + resolution=texture_resolution) + + # call the ac3d generator + # AC3D.generate(proj.image_list, groups[0], src_dir=img_src_dir, + # analysis_dir=proj.analysis_dir, base_name='direct', + # version=1.0, trans=0.1, resolution=texture_resolution) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Set the initial camera poses.') + parser.add_argument('--project', required=True, help='project directory') + parser.add_argument('--render-group', type=int, default=0, help='group index') + parser.add_argument('--texture-resolution', type=int, default=512, help='texture resolution (should be 2**n, so numbers like 256, 512, 1024, etc.') + parser.add_argument('--srtm', action='store_true', help='use srtm elevation') + parser.add_argument('--ground', type=float, help='force ground elevation in meters') + parser.add_argument('--direct', action='store_true', help='use direct pose') - # convert ned to xyz and stash the result for each image - image.grid_list = [] - for p in pts_ned: - image.grid_list.append( [p[1], p[0], -p[2]] ) + args = parser.parse_args() -# generate the panda3d egg models -dir_node = getNode('/config/directories', True) -img_src_dir = dir_node.getString('images_source') -Panda3d.generate_from_grid(proj, groups[args.group], src_dir=img_src_dir, - analysis_dir=proj.analysis_dir, - resolution=args.texture_resolution) + render_options = [args.render_group, args.texture_resolution, args.srtm, args.ground, args.direct] -# call the ac3d generator -# AC3D.generate(proj.image_list, groups[0], src_dir=img_src_dir, -# analysis_dir=proj.analysis_dir, base_name='direct', -# version=1.0, trans=0.1, resolution=args.texture_resolution) + render(args.project, render_options) \ No newline at end of file diff --git a/scripts/6b_delaunay5.py b/scripts/6b_delaunay5.py index 6f026ce4..5cde9ca7 100644 --- a/scripts/6b_delaunay5.py +++ b/scripts/6b_delaunay5.py @@ -18,90 +18,95 @@ from lib import SRTM from lib import transformations -parser = argparse.ArgumentParser(description='Compute Delauney triangulation of matches.') -parser.add_argument('--project', required=True, help='project directory') -parser.add_argument('--group', type=int, default=0, help='group index') -args = parser.parse_args() +def delaunay(project_dir, group_id): -def gen_ac3d_surface(name, points_group, values_group, tris_group): - kids = len(tris_group) - # write out the ac3d file - f = open( name, "w" ) - f.write("AC3Db\n") - trans = 0.0 - f.write("MATERIAL \"\" rgb 1 1 1 amb 0.6 0.6 0.6 emis 0 0 0 spec 0.5 0.5 0.5 shi 10 trans %.2f\n" % (trans)) - f.write("OBJECT world\n") - f.write("kids " + str(kids) + "\n") + def gen_ac3d_surface(name, points_group, values_group, tris_group): + kids = len(tris_group) + # write out the ac3d file + f = open( name, "w" ) + f.write("AC3Db\n") + trans = 0.0 + f.write("MATERIAL \"\" rgb 1 1 1 amb 0.6 0.6 0.6 emis 0 0 0 spec 0.5 0.5 0.5 shi 10 trans %.2f\n" % (trans)) + f.write("OBJECT world\n") + f.write("kids " + str(kids) + "\n") - for i in range(kids): - points = points_group[i] - values = values_group[i] - tris = tris_group[i] - f.write("OBJECT poly\n") - f.write("loc 0 0 0\n") - f.write("numvert %d\n" % len(points)) - for j in range(len(points)): - f.write("%.3f %.3f %.3f\n" % (points[j][0], points[j][1], - values[j])) - f.write("numsurf %d\n" % len(tris.simplices)) - for tri in tris.simplices: - f.write("SURF 0x30\n") - f.write("mat 0\n") - f.write("refs 3\n") - for t in tri: - f.write("%d 0 0\n" % (t)) - f.write("kids 0\n") - -proj = ProjectMgr.ProjectMgr(args.project) -proj.load_images_info() + for i in range(kids): + points = points_group[i] + values = values_group[i] + tris = tris_group[i] + f.write("OBJECT poly\n") + f.write("loc 0 0 0\n") + f.write("numvert %d\n" % len(points)) + for j in range(len(points)): + f.write("%.3f %.3f %.3f\n" % (points[j][0], points[j][1], + values[j])) + f.write("numsurf %d\n" % len(tris.simplices)) + for tri in tris.simplices: + f.write("SURF 0x30\n") + f.write("mat 0\n") + f.write("refs 3\n") + for t in tri: + f.write("%d 0 0\n" % (t)) + f.write("kids 0\n") + + proj = ProjectMgr.ProjectMgr(project_dir) + proj.load_images_info() -print("Loading optimized points ...") -matches = pickle.load( open( os.path.join(proj.analysis_dir, "matches_grouped"), "rb" ) ) + print("Loading optimized points ...") + matches = pickle.load( open( os.path.join(proj.analysis_dir, "matches_grouped"), "rb" ) ) -# load the group connections within the image set -groups = Groups.load(proj.analysis_dir) + # load the group connections within the image set + groups = Groups.load(proj.analysis_dir) -points_group = [] -values_group = [] -tris_group = [] + points_group = [] + values_group = [] + tris_group = [] -# initialize temporary structures for vanity stats -for image in proj.image_list: - image.raw_points = [] - image.raw_values = [] - image.sum_values = 0.0 - image.sum_count = 0.0 - image.max_z = -9999.0 - image.min_z = 9999.0 + # initialize temporary structures for vanity stats + for image in proj.image_list: + image.raw_points = [] + image.raw_values = [] + image.sum_values = 0.0 + image.sum_count = 0.0 + image.max_z = -9999.0 + image.min_z = 9999.0 -# elevation stats -print("Computing stats...") -ned_list = [] -for match in matches: - if match[1] == args.group: # used by current group - ned_list.append(match[0]) -avg = -np.mean(np.array(ned_list)[:,2]) -std = np.std(np.array(ned_list)[:,2]) -print("Average elevation: %.2f" % avg) -print("Standard deviation: %.2f" % std) + # elevation stats + print("Computing stats...") + ned_list = [] + for match in matches: + if match[1] == group_id: # used by current group + ned_list.append(match[0]) + avg = -np.mean(np.array(ned_list)[:,2]) + std = np.std(np.array(ned_list)[:,2]) + print("Average elevation: %.2f" % avg) + print("Standard deviation: %.2f" % std) -# sort through points -print('Reading feature locations from optimized match points ...') -global_raw_points = [] -global_raw_values = [] -for match in matches: - if match[1] == args.group: # used by current group - ned = match[0] - diff = abs(-ned[2] - avg) - if diff < 5*std: - global_raw_points.append( [ned[1], ned[0]] ) - global_raw_values.append( -ned[2] ) - else: - print("Discarding match with excessive altitude:", match) + # sort through points + print('Reading feature locations from optimized match points ...') + global_raw_points = [] + global_raw_values = [] + for match in matches: + if match[1] == group_id: # used by current group + ned = match[0] + diff = abs(-ned[2] - avg) + if diff < 5*std: + global_raw_points.append( [ned[1], ned[0]] ) + global_raw_values.append( -ned[2] ) + else: + print("Discarding match with excessive altitude:", match) -print('Generating Delaunay meshes ...') -global_tri_list = scipy.spatial.Delaunay(np.array(global_raw_points)) + print('Generating Delaunay meshes ...') + global_tri_list = scipy.spatial.Delaunay(np.array(global_raw_points)) -print('Generating ac3d surface model ...') -name = os.path.join(proj.analysis_dir, "surface-global.ac") -gen_ac3d_surface(name, [global_raw_points], [global_raw_values], [global_tri_list]) + print('Generating ac3d surface model ...') + name = os.path.join(proj.analysis_dir, "surface-global.ac") + gen_ac3d_surface(name, [global_raw_points], [global_raw_values], [global_tri_list]) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Compute Delauney triangulation of matches.') + parser.add_argument('--project', required=True, help='project directory') + parser.add_argument('--group', type=int, default=0, help='group index') + args = parser.parse_args() + + delaunay(args.project, args.group) diff --git a/scripts/7a_explore.py b/scripts/7a_explore.py index 34377377..2fa0f702 100644 --- a/scripts/7a_explore.py +++ b/scripts/7a_explore.py @@ -31,650 +31,654 @@ from explore import reticle from explore import surface -parser = argparse.ArgumentParser(description='Set the initial camera poses.') -parser.add_argument('--project', help='project directory') -args = parser.parse_args() - -if False: - import wx - def get_path(wildcard): - app = wx.App(None) - style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST - dialog = wx.FileDialog(None, 'Open', wildcard=wildcard, style=style) - if dialog.ShowModal() == wx.ID_OK: - path = dialog.GetPath() - else: - path = None - dialog.Destroy() - return path - get_path("*") - print(get_path('*.txt')) - quit() - -tk_root = tk.Tk() -tk_root.withdraw() - -if not args.project: - # file_path = filedialog.askopenfilename() - file_path = filedialog.askdirectory(title="Please open the project directory", mustexist=True) - # print('selected:', type(file_path), len(file_path), file_path) - if file_path: - args.project = file_path - else: - print("no project selected, exiting.") +def run(project_dir): + if False: + import wx + def get_path(wildcard): + app = wx.App(None) + style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST + dialog = wx.FileDialog(None, 'Open', wildcard=wildcard, style=style) + if dialog.ShowModal() == wx.ID_OK: + path = dialog.GetPath() + else: + path = None + dialog.Destroy() + return path + get_path("*") + print(get_path('*.txt')) quit() -proj = ProjectMgr.ProjectMgr(args.project) -proj.load_images_info() - -# lookup ned reference -ref_node = getNode("/config/ned_reference", True) -ned_ref = [ ref_node.getFloat('lat_deg'), - ref_node.getFloat('lon_deg'), - ref_node.getFloat('alt_m') ] - -tcache = {} - -# adaptive equalizer -clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8,8)) - -class MyApp(ShowBase): - - def __init__(self): - ShowBase.__init__(self) - - self.models = [] - self.base_textures = [] - - # window title - props = WindowProperties( ) - props.setTitle( pathlib.Path(args.project).name ) - base.win.requestProperties( props ) - - # we would like an orthographic lens - self.lens = OrthographicLens() - self.lens.setFilmSize(20, 15) - base.camNode.setLens(self.lens) - - self.cam_pos = [ 0.0, 0.0, 1000.0 ] - self.camera.setPos(self.cam_pos[0], self.cam_pos[1], self.cam_pos[2]) - self.camera.setHpr(0, -90.0, 0) - self.view_size = 100.0 - self.last_ysize = 0 - - self.top_image = 0 - - # modules - self.surface = surface.Surface(proj.analysis_dir) - self.annotations = annotations.Annotations(self.render, self.surface, - args.project, - ned_ref, tk_root) - self.reticle = reticle.Reticle(self.render, self.surface, ned_ref) - - #self.messenger.toggleVerbose() - - # event handlers - self.accept('arrow_left', self.cam_move, [-0.1, 0, 0]) - self.accept('arrow_right', self.cam_move, [0.1, 0, 0]) - self.accept('arrow_down', self.cam_move, [0, -0.1, 0]) - self.accept('arrow_up', self.cam_move, [0, 0.1, 0]) - self.accept('=', self.cam_zoom, [1.1]) - self.accept('shift-=', self.cam_zoom, [1.1]) - self.accept('-', self.cam_zoom, [1.0/1.1]) - self.accept('wheel_up', self.cam_zoom, [1.1]) - self.accept('wheel_down', self.cam_zoom, [1.0/1.1]) - self.accept('mouse1', self.mouse_state, [0, 1]) - self.accept('mouse1-up', self.mouse_state, [0, 0]) - self.accept('0', self.image_select, [0]) - self.accept('1', self.image_select, [1]) - self.accept('2', self.image_select, [2]) - self.accept('3', self.image_select, [3]) - self.accept('4', self.image_select, [4]) - self.accept('5', self.image_select, [5]) - self.accept('6', self.image_select, [6]) - self.accept('7', self.image_select, [7]) - self.accept('8', self.image_select, [8]) - self.accept('9', self.image_select, [9]) - self.accept('f', self.toggle_filter) - self.accept('m', self.toggle_view_mode) - self.accept(',', self.update_sequential_num, [-1]) - self.accept('.', self.update_sequential_num, [1]) - self.accept('escape', self.quit) - self.accept('mouse3', self.annotations.toggle, [self.cam_pos]) - - # mouse state - self.last_mpos = [0, 0] - self.mouse = [0, 0, 0] - self.last_mouse = [0, 0, 0] - - # Add the tasks to the task manager. - self.taskMgr.add(self.updateCameraTask, "updateCameraTask") - - # Shader (aka filter?) - self.filter = 'none' - - # Set default view mode - self.view_mode = 'best' - # self.view_mode = 'sequential' - self.sequential_num = 0 - - # dump a summary of supposed card capabilities - self.query_capabilities(display=True) + tk_root = tk.Tk() + tk_root.withdraw() - # test shader - # self.shader = Shader.load(Shader.SL_GLSL, vertex="explore/myshader.vert", fragment="explore/myshader.frag", geometry="explore/myshader.geom") - self.shader = Shader.load(Shader.SL_GLSL, vertex="explore/myshader.vert", fragment="explore/myshader.frag") - - def query_capabilities(self, display=True): - gsg=base.win.getGsg() - print("driver vendor", gsg.getDriverVendor()) - self.driver_vendor = gsg.getDriverVendor() - print("alpha_scale_via_texture", bool(gsg.getAlphaScaleViaTexture())) - print("color_scale_via_lighting", bool(gsg.getColorScaleViaLighting())) - print("copy_texture_inverted", bool(gsg.getCopyTextureInverted())) - print("max_3d_texture_dimension", gsg.getMax3dTextureDimension()) - print("max_clip_planes", gsg.getMaxClipPlanes()) - print("max_cube_map_dimension", gsg.getMaxCubeMapDimension()) - print("max_lights", gsg.getMaxLights()) - print("max_texture_dimension", gsg.getMaxTextureDimension()) - self.max_texture_dimension = gsg.getMaxTextureDimension() - print("max_texture_stages", gsg.getMaxTextureStages()) - print("max_vertex_transform_indices", gsg.getMaxVertexTransformIndices()) - print("max_vertex_transforms", gsg.getMaxVertexTransforms()) - print("shader_model", gsg.getShaderModel()) - print("supports_3d_texture", bool(gsg.getSupports3dTexture())) - print("supports_basic_shaders", bool(gsg.getSupportsBasicShaders())) - print("supports_compressed_texture", bool(gsg.getSupportsCompressedTexture())) - print("supports_cube_map", bool(gsg.getSupportsCubeMap())) - print("supports_depth_stencil", bool(gsg.getSupportsDepthStencil())) - print("supports_depth_texture", bool(gsg.getSupportsDepthTexture())) - print("supports_generate_mipmap", bool(gsg.getSupportsGenerateMipmap())) - #print("supports_render_texture", bool(gsg.getSupportsRenderTexture())) - print("supports_shadow_filter", bool(gsg.getSupportsShadowFilter())) - print("supports_tex_non_pow2", bool(gsg.getSupportsTexNonPow2())) - self.needs_pow2 = not bool(gsg.getSupportsTexNonPow2()) - if self.driver_vendor == 'Intel' and os.name == 'nt': - # windows driver lies! - self.needs_pow2 = True - print("supports_texture_combine", bool(gsg.getSupportsTextureCombine())) - print("supports_texture_dot3", bool(gsg.getSupportsTextureDot3())) - print("supports_texture_saved_result", bool(gsg.getSupportsTextureSavedResult())) - print("supports_two_sided_stencil", bool(gsg.getSupportsTwoSidedStencil())) - print("max_vertices_per_array", gsg.getMaxVerticesPerArray()) - print("max_vertices_per_primitive", gsg.getMaxVerticesPerPrimitive()) - print("supported_geom_rendering", gsg.getSupportedGeomRendering()) - print("supports_multisample", bool(gsg.getSupportsMultisample())) - print("supports_occlusion_query", bool(gsg.getSupportsOcclusionQuery())) - print("prefers_triangle_strips", bool(gsg.prefersTriangleStrips())) - - def tmpItemSel(self, arg): - self.dialog.cleanup() - print('result:', arg) + if not project_dir: + # file_path = filedialog.askopenfilename() + file_path = filedialog.askdirectory(title="Please open the project directory", mustexist=True) + # print('selected:', type(file_path), len(file_path), file_path) + if file_path: + project_dir = file_path + else: + print("no project selected, exiting.") + quit() + + proj = ProjectMgr.ProjectMgr(project_dir) + proj.load_images_info() + + # lookup ned reference + ref_node = getNode("/config/ned_reference", True) + ned_ref = [ ref_node.getFloat('lat_deg'), + ref_node.getFloat('lon_deg'), + ref_node.getFloat('alt_m') ] + + tcache = {} + + # adaptive equalizer + clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8,8)) + + class MyApp(ShowBase): + + def __init__(self): + ShowBase.__init__(self) + + self.models = [] + self.base_textures = [] + + # window title + props = WindowProperties( ) + props.setTitle( pathlib.Path(project_dir).name ) + base.win.requestProperties( props ) + + # we would like an orthographic lens + self.lens = OrthographicLens() + self.lens.setFilmSize(20, 15) + base.camNode.setLens(self.lens) + + self.cam_pos = [ 0.0, 0.0, 1000.0 ] + self.camera.setPos(self.cam_pos[0], self.cam_pos[1], self.cam_pos[2]) + self.camera.setHpr(0, -90.0, 0) + self.view_size = 100.0 + self.last_ysize = 0 + + self.top_image = 0 + + # modules + self.surface = surface.Surface(proj.analysis_dir) + self.annotations = annotations.Annotations(self.render, self.surface, + project_dir, + ned_ref, tk_root) + self.reticle = reticle.Reticle(self.render, self.surface, ned_ref) + + #self.messenger.toggleVerbose() + + # event handlers + self.accept('arrow_left', self.cam_move, [-0.1, 0, 0]) + self.accept('arrow_right', self.cam_move, [0.1, 0, 0]) + self.accept('arrow_down', self.cam_move, [0, -0.1, 0]) + self.accept('arrow_up', self.cam_move, [0, 0.1, 0]) + self.accept('=', self.cam_zoom, [1.1]) + self.accept('shift-=', self.cam_zoom, [1.1]) + self.accept('-', self.cam_zoom, [1.0/1.1]) + self.accept('wheel_up', self.cam_zoom, [1.1]) + self.accept('wheel_down', self.cam_zoom, [1.0/1.1]) + self.accept('mouse1', self.mouse_state, [0, 1]) + self.accept('mouse1-up', self.mouse_state, [0, 0]) + self.accept('0', self.image_select, [0]) + self.accept('1', self.image_select, [1]) + self.accept('2', self.image_select, [2]) + self.accept('3', self.image_select, [3]) + self.accept('4', self.image_select, [4]) + self.accept('5', self.image_select, [5]) + self.accept('6', self.image_select, [6]) + self.accept('7', self.image_select, [7]) + self.accept('8', self.image_select, [8]) + self.accept('9', self.image_select, [9]) + self.accept('f', self.toggle_filter) + self.accept('m', self.toggle_view_mode) + self.accept(',', self.update_sequential_num, [-1]) + self.accept('.', self.update_sequential_num, [1]) + self.accept('escape', self.quit) + self.accept('mouse3', self.annotations.toggle, [self.cam_pos]) + + # mouse state + self.last_mpos = [0, 0] + self.mouse = [0, 0, 0] + self.last_mouse = [0, 0, 0] - def dialog_test(self, tmp): - self.dialog = YesNoDialog(dialogName="YesNoCancelDialog", text="Please choose:", command=self.tmpItemSel) + # Add the tasks to the task manager. + self.taskMgr.add(self.updateCameraTask, "updateCameraTask") + + # Shader (aka filter?) + self.filter = 'none' + + # Set default view mode + self.view_mode = 'best' + # self.view_mode = 'sequential' + self.sequential_num = 0 + + # dump a summary of supposed card capabilities + self.query_capabilities(display=True) + + # test shader + # self.shader = Shader.load(Shader.SL_GLSL, vertex="explore/myshader.vert", fragment="explore/myshader.frag", geometry="explore/myshader.geom") + self.shader = Shader.load(Shader.SL_GLSL, vertex="explore/myshader.vert", fragment="explore/myshader.frag") + + def query_capabilities(self, display=True): + gsg=base.win.getGsg() + print("driver vendor", gsg.getDriverVendor()) + self.driver_vendor = gsg.getDriverVendor() + print("alpha_scale_via_texture", bool(gsg.getAlphaScaleViaTexture())) + print("color_scale_via_lighting", bool(gsg.getColorScaleViaLighting())) + print("copy_texture_inverted", bool(gsg.getCopyTextureInverted())) + print("max_3d_texture_dimension", gsg.getMax3dTextureDimension()) + print("max_clip_planes", gsg.getMaxClipPlanes()) + print("max_cube_map_dimension", gsg.getMaxCubeMapDimension()) + print("max_lights", gsg.getMaxLights()) + print("max_texture_dimension", gsg.getMaxTextureDimension()) + self.max_texture_dimension = gsg.getMaxTextureDimension() + print("max_texture_stages", gsg.getMaxTextureStages()) + print("max_vertex_transform_indices", gsg.getMaxVertexTransformIndices()) + print("max_vertex_transforms", gsg.getMaxVertexTransforms()) + print("shader_model", gsg.getShaderModel()) + print("supports_3d_texture", bool(gsg.getSupports3dTexture())) + print("supports_basic_shaders", bool(gsg.getSupportsBasicShaders())) + print("supports_compressed_texture", bool(gsg.getSupportsCompressedTexture())) + print("supports_cube_map", bool(gsg.getSupportsCubeMap())) + print("supports_depth_stencil", bool(gsg.getSupportsDepthStencil())) + print("supports_depth_texture", bool(gsg.getSupportsDepthTexture())) + print("supports_generate_mipmap", bool(gsg.getSupportsGenerateMipmap())) + #print("supports_render_texture", bool(gsg.getSupportsRenderTexture())) + print("supports_shadow_filter", bool(gsg.getSupportsShadowFilter())) + print("supports_tex_non_pow2", bool(gsg.getSupportsTexNonPow2())) + self.needs_pow2 = not bool(gsg.getSupportsTexNonPow2()) + if self.driver_vendor == 'Intel' and os.name == 'nt': + # windows driver lies! + self.needs_pow2 = True + print("supports_texture_combine", bool(gsg.getSupportsTextureCombine())) + print("supports_texture_dot3", bool(gsg.getSupportsTextureDot3())) + print("supports_texture_saved_result", bool(gsg.getSupportsTextureSavedResult())) + print("supports_two_sided_stencil", bool(gsg.getSupportsTwoSidedStencil())) + print("max_vertices_per_array", gsg.getMaxVerticesPerArray()) + print("max_vertices_per_primitive", gsg.getMaxVerticesPerPrimitive()) + print("supported_geom_rendering", gsg.getSupportedGeomRendering()) + print("supports_multisample", bool(gsg.getSupportsMultisample())) + print("supports_occlusion_query", bool(gsg.getSupportsOcclusionQuery())) + print("prefers_triangle_strips", bool(gsg.prefersTriangleStrips())) + + def tmpItemSel(self, arg): + self.dialog.cleanup() + print('result:', arg) + + def dialog_test(self, tmp): + self.dialog = YesNoDialog(dialogName="YesNoCancelDialog", text="Please choose:", command=self.tmpItemSel) + + def mouse_state(self, index, state): + self.mouse[index] = state - def mouse_state(self, index, state): - self.mouse[index] = state - - def pretty_print(self, node, indent=''): - for child in node.getChildren(): - print(indent, child) + def pretty_print(self, node, indent=''): + for child in node.getChildren(): + print(indent, child) + + def load(self, path): + # vignette mask + self.vignette_mask = None + self.vignette_mask_small = None + vfile = os.path.join(path, "vignette-mask.jpg") + if os.path.exists(vfile): + print("loading vignette correction mask:", vfile) + self.vignette_mask = cv2.imread(vfile, flags=cv2.IMREAD_ANYCOLOR|cv2.IMREAD_ANYDEPTH|cv2.IMREAD_IGNORE_ORIENTATION) + self.vignette_mask_small = cv2.resize(self.vignette_mask, (512, 512)) + files = [] + for file in sorted(os.listdir(path)): + if fnmatch.fnmatch(file, '*.egg'): + # print('load:', file) + files.append(file) + print('Loading models:') + for file in tqdm(files): + # load and reparent each egg file + pandafile = Filename.fromOsSpecific(os.path.join(path, file)) + model = self.loader.loadModel(pandafile) + # model.set_shader(self.shader) + + # print(file) + # self.pretty_print(model, ' ') - def load(self, path): - # vignette mask - self.vignette_mask = None - self.vignette_mask_small = None - vfile = os.path.join(path, "vignette-mask.jpg") - if os.path.exists(vfile): - print("loading vignette correction mask:", vfile) - self.vignette_mask = cv2.imread(vfile, flags=cv2.IMREAD_ANYCOLOR|cv2.IMREAD_ANYDEPTH|cv2.IMREAD_IGNORE_ORIENTATION) - self.vignette_mask_small = cv2.resize(self.vignette_mask, (512, 512)) - files = [] - for file in sorted(os.listdir(path)): - if fnmatch.fnmatch(file, '*.egg'): - # print('load:', file) - files.append(file) - print('Loading models:') - for file in tqdm(files): - # load and reparent each egg file - pandafile = Filename.fromOsSpecific(os.path.join(path, file)) - model = self.loader.loadModel(pandafile) - # model.set_shader(self.shader) - - # print(file) - # self.pretty_print(model, ' ') - - model.reparentTo(self.render) - self.models.append(model) - tex = model.findTexture('*') - if tex != None: + model.reparentTo(self.render) + self.models.append(model) + tex = model.findTexture('*') + if tex != None: + tex.setWrapU(Texture.WM_clamp) + tex.setWrapV(Texture.WM_clamp) + self.base_textures.append(tex) + + # The egg model lists "dummy.jpg" as the texture model which + # doesn't exists. Here we load the actual textures and + # possibly apply vignette correction and adaptive histogram + # equalization. + print('Loading base textures:') + for i, model in enumerate(tqdm(self.models)): + base, ext = os.path.splitext(model.getName()) + image_file = None + dir = os.path.join(proj.analysis_dir, 'models') + tmp1 = os.path.join(dir, base + '.JPG') + tmp2 = os.path.join(dir, base + '.jpg') + if os.path.isfile(tmp1): + image_file = tmp1 + elif os.path.isfile(tmp2): + image_file = tmp2 + #print("texture file:", image_file) + if False: + tex = self.loader.loadTexture(image_file) + else: + rgb = cv2.imread(image_file, flags=cv2.IMREAD_ANYCOLOR|cv2.IMREAD_ANYDEPTH|cv2.IMREAD_IGNORE_ORIENTATION) + rgb = np.flipud(rgb) + # vignette correction + if not self.vignette_mask_small is None: + rgb = rgb.astype('uint16') + self.vignette_mask_small + rgb = np.clip(rgb, 0, 255).astype('uint8') + # adaptive equalization + hsv = cv2.cvtColor(rgb, cv2.COLOR_BGR2HSV) + hue, sat, val = cv2.split(hsv) + aeq = clahe.apply(val) + # recombine + hsv = cv2.merge((hue,sat,aeq)) + # convert back to rgb + rgb = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR) + tex = Texture(base) + tex.setCompression(Texture.CMOff) + tex.setup2dTexture(512, 512, Texture.TUnsignedByte, + Texture.FRgb) + tex.setRamImage(rgb) tex.setWrapU(Texture.WM_clamp) tex.setWrapV(Texture.WM_clamp) - self.base_textures.append(tex) - - # The egg model lists "dummy.jpg" as the texture model which - # doesn't exists. Here we load the actual textures and - # possibly apply vignette correction and adaptive histogram - # equalization. - print('Loading base textures:') - for i, model in enumerate(tqdm(self.models)): - base, ext = os.path.splitext(model.getName()) - image_file = None - dir = os.path.join(proj.analysis_dir, 'models') - tmp1 = os.path.join(dir, base + '.JPG') - tmp2 = os.path.join(dir, base + '.jpg') - if os.path.isfile(tmp1): - image_file = tmp1 - elif os.path.isfile(tmp2): - image_file = tmp2 - #print("texture file:", image_file) - if False: - tex = self.loader.loadTexture(image_file) - else: - rgb = cv2.imread(image_file, flags=cv2.IMREAD_ANYCOLOR|cv2.IMREAD_ANYDEPTH|cv2.IMREAD_IGNORE_ORIENTATION) - rgb = np.flipud(rgb) - # vignette correction - if not self.vignette_mask_small is None: - rgb = rgb.astype('uint16') + self.vignette_mask_small - rgb = np.clip(rgb, 0, 255).astype('uint8') - # adaptive equalization - hsv = cv2.cvtColor(rgb, cv2.COLOR_BGR2HSV) - hue, sat, val = cv2.split(hsv) - aeq = clahe.apply(val) - # recombine - hsv = cv2.merge((hue,sat,aeq)) - # convert back to rgb - rgb = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR) - tex = Texture(base) - tex.setCompression(Texture.CMOff) - tex.setup2dTexture(512, 512, Texture.TUnsignedByte, - Texture.FRgb) - tex.setRamImage(rgb) - tex.setWrapU(Texture.WM_clamp) - tex.setWrapV(Texture.WM_clamp) - model.setTexture(tex, 1) - self.base_textures[i] = tex - self.sortImages() - self.annotations.rebuild(self.view_size) - - def cam_move(self, x, y, z, sort=True): - #print('move:', x, y) - self.cam_pos[0] += x * self.view_size * base.getAspectRatio() - self.cam_pos[1] += y * self.view_size - if self.view_mode == 'best' and sort: - self.image_select(0) + model.setTexture(tex, 1) + self.base_textures[i] = tex self.sortImages() - - def cam_zoom(self, f): - self.view_size /= f - self.annotations.rebuild(self.view_size) - - def cam_fit(self, model): - b = model.getTightBounds() - #print('tight', b) - if b: - center = [ (b[0][0] + b[1][0]) * 0.5, - (b[0][1] + b[1][1]) * 0.5, - (b[0][2] + b[1][2]) * 0.5 ] - self.cam_pos[0] = center[0] - self.cam_pos[1] = center[1] - vol = [ b[1][0] - b[0][0], - b[1][1] - b[0][1], - b[1][2] - b[0][2] ] - if vol[1] * base.getAspectRatio() > vol[0]: - # set by y axis - self.view_size = vol[1] * 1.05 - else: - # set by x axis size - self.view_size = vol[0] * 1.05 / base.getAspectRatio() - print("view_size:", self.view_size) self.annotations.rebuild(self.view_size) - def toggle_filter(self): - if self.filter == 'shader': - self.filter = 'none' - for m in self.models: - m.clear_shader() - else: - self.filter = 'shader' - for m in self.models: - m.set_shader(self.shader) - # print("Setting filter:", self.filter) - - def toggle_view_mode(self): - if self.view_mode == 'best': - self.view_mode = 'sequential' - else: - self.view_mode = 'best' - print("Setting view mode:", self.view_mode) - self.sortImages() - - def update_sequential_num(self, inc): - if self.view_mode == 'sequential': - self.sequential_num += inc - if self.sequential_num < 0: - self.sequential_num = len(self.models) - 1 - elif self.sequential_num >= len(self.models) - 1: - self.sequential_num = 0 - print("Sequential image number:", self.sequential_num) - self.sortImages() - - def quit(self): - raise SystemExit - - def image_select(self, level): - if self.view_mode == 'best': - self.top_image = level - elif self.view_mode == 'sequential': - max = len(self.models) - 1 - self.sequential_num = int(round(float(level) * float(max) / 9.0)) - self.sortImages() + def cam_move(self, x, y, z, sort=True): + #print('move:', x, y) + self.cam_pos[0] += x * self.view_size * base.getAspectRatio() + self.cam_pos[1] += y * self.view_size + if self.view_mode == 'best' and sort: + self.image_select(0) + self.sortImages() - # Define a procedure to move the camera. - def updateCameraTask(self, task): - self.camera.setPos(self.cam_pos[0], self.cam_pos[1], self.cam_pos[2]) - self.camera.setHpr(0, -90, 0) - self.lens.setFilmSize(self.view_size*base.getAspectRatio(), - self.view_size) - # reticle - self.reticle.update(self.cam_pos, self.view_size) - - # annotations - props = base.win.getProperties() - y = props.getYSize() - if y != self.last_ysize: + def cam_zoom(self, f): + self.view_size /= f self.annotations.rebuild(self.view_size) - self.last_ysize = y - mw = base.mouseWatcherNode - if mw.hasMouse(): - mpos = mw.getMouse() - if self.mouse[0]: - dx = self.last_mpos[0] - mpos[0] - dy = self.last_mpos[1] - mpos[1] - self.cam_move( dx * 0.5, dy * 0.5, 0, sort=False) - elif not self.mouse[0] and self.last_mouse[0]: - # button up - self.cam_move( 0, 0, 0, sort=True) - self.last_mpos = list(mpos) - self.last_mouse[0] = self.mouse[0] - return Task.cont - - # return true if cam_pos inside bounding corners - def inbounds(self, b): - if self.cam_pos[0] < b[0][0] or self.cam_pos[0] > b[1][0]: - return False - elif self.cam_pos[1] < b[0][1] or self.cam_pos[1] > b[1][1]: - return False - else: - return True - - def sortImages(self): - # sort images by (hopefully) best covering view center - result_list = [] - for i, m in enumerate(self.models): - b = m.getTightBounds() + + def cam_fit(self, model): + b = model.getTightBounds() #print('tight', b) if b: center = [ (b[0][0] + b[1][0]) * 0.5, - (b[0][1] + b[1][1]) * 0.5, - (b[0][2] + b[1][2]) * 0.5 ] + (b[0][1] + b[1][1]) * 0.5, + (b[0][2] + b[1][2]) * 0.5 ] + self.cam_pos[0] = center[0] + self.cam_pos[1] = center[1] vol = [ b[1][0] - b[0][0], b[1][1] - b[0][1], b[1][2] - b[0][2] ] - span = math.sqrt(vol[0]*vol[0] + vol[1]*vol[1] + vol[2]*vol[2]) - dx = center[0] - self.cam_pos[0] - dy = center[1] - self.cam_pos[1] - dist = math.sqrt(dx*dx + dy*dy) - #print('center:', center, 'span:', span, 'dist:', dist) - if self.view_mode == 'best': - metric = dist + (span * 0.1) - if not self.inbounds(b): - metric += 1000 - elif self.view_mode == 'sequential': - metric = abs(i - self.sequential_num) - result_list.append( [metric, m, i] ) - result_list = sorted(result_list, key=lambda fields: fields[0], - reverse=True) - if self.view_mode == 'best': - top_entry = result_list[-1-self.top_image] - else: - top_entry = result_list[-1] - top = top_entry[1] - top.setColor(1.0, 1.0, 1.0, 1.0) - self.updateTexture(top) - if self.view_mode == 'sequential': - self.cam_fit(top) - - for i, line in enumerate(result_list): - m = line[1] - if m == top: - m.setBin("fixed", 2*len(self.models)) - elif m.getName() in tcache: - # reward draw order for models with high res texture loaded - m.setBin("fixed", i + len(self.models)) - else: - m.setBin("fixed", i) - m.setDepthTest(False) - m.setDepthWrite(False) - if m != top: - gray = 1.0 - m.setColor(gray, gray, gray, 1.0) - - def updateTexture(self, main): - dir_node = getNode('/config/directories', True) - - # reset base textures - for i, m in enumerate(self.models): - if m != main: - if m.getName() in tcache: - fulltex = tcache[m.getName()][1] - self.models[i].setTexture(fulltex, 1) + if vol[1] * base.getAspectRatio() > vol[0]: + # set by y axis + self.view_size = vol[1] * 1.05 else: - if self.base_textures[i] != None: - self.models[i].setTexture(self.base_textures[i], 1) + # set by x axis size + self.view_size = vol[0] * 1.05 / base.getAspectRatio() + print("view_size:", self.view_size) + self.annotations.rebuild(self.view_size) + + def toggle_filter(self): + if self.filter == 'shader': + self.filter = 'none' + for m in self.models: + m.clear_shader() else: - print(m.getName()) - if m.getName() in tcache: - fulltex = tcache[m.getName()][1] - self.models[i].setTexture(fulltex, 1) - continue - base, ext = os.path.splitext(m.getName()) - image_file = None - search = [ args.project, os.path.join(args.project, 'images') ] - for dir in search: - tmp1 = os.path.join(dir, base + '.JPG') - tmp2 = os.path.join(dir, base + '.jpg') - if os.path.isfile(tmp1): - image_file = tmp1 - elif os.path.isfile(tmp2): - image_file = tmp2 - if not image_file: - print('Warning: no full resolution image source file found:', base) + self.filter = 'shader' + for m in self.models: + m.set_shader(self.shader) + # print("Setting filter:", self.filter) + + def toggle_view_mode(self): + if self.view_mode == 'best': + self.view_mode = 'sequential' + else: + self.view_mode = 'best' + print("Setting view mode:", self.view_mode) + self.sortImages() + + def update_sequential_num(self, inc): + if self.view_mode == 'sequential': + self.sequential_num += inc + if self.sequential_num < 0: + self.sequential_num = len(self.models) - 1 + elif self.sequential_num >= len(self.models) - 1: + self.sequential_num = 0 + print("Sequential image number:", self.sequential_num) + self.sortImages() + + def quit(self): + raise SystemExit + + def image_select(self, level): + if self.view_mode == 'best': + self.top_image = level + elif self.view_mode == 'sequential': + max = len(self.models) - 1 + self.sequential_num = int(round(float(level) * float(max) / 9.0)) + self.sortImages() + + # Define a procedure to move the camera. + def updateCameraTask(self, task): + self.camera.setPos(self.cam_pos[0], self.cam_pos[1], self.cam_pos[2]) + self.camera.setHpr(0, -90, 0) + self.lens.setFilmSize(self.view_size*base.getAspectRatio(), + self.view_size) + # reticle + self.reticle.update(self.cam_pos, self.view_size) + + # annotations + props = base.win.getProperties() + y = props.getYSize() + if y != self.last_ysize: + self.annotations.rebuild(self.view_size) + self.last_ysize = y + mw = base.mouseWatcherNode + if mw.hasMouse(): + mpos = mw.getMouse() + if self.mouse[0]: + dx = self.last_mpos[0] - mpos[0] + dy = self.last_mpos[1] - mpos[1] + self.cam_move( dx * 0.5, dy * 0.5, 0, sort=False) + elif not self.mouse[0] and self.last_mouse[0]: + # button up + self.cam_move( 0, 0, 0, sort=True) + self.last_mpos = list(mpos) + self.last_mouse[0] = self.mouse[0] + return Task.cont + + # return true if cam_pos inside bounding corners + def inbounds(self, b): + if self.cam_pos[0] < b[0][0] or self.cam_pos[0] > b[1][0]: + return False + elif self.cam_pos[1] < b[0][1] or self.cam_pos[1] > b[1][1]: + return False + else: + return True + + def sortImages(self): + # sort images by (hopefully) best covering view center + result_list = [] + for i, m in enumerate(self.models): + b = m.getTightBounds() + #print('tight', b) + if b: + center = [ (b[0][0] + b[1][0]) * 0.5, + (b[0][1] + b[1][1]) * 0.5, + (b[0][2] + b[1][2]) * 0.5 ] + vol = [ b[1][0] - b[0][0], + b[1][1] - b[0][1], + b[1][2] - b[0][2] ] + span = math.sqrt(vol[0]*vol[0] + vol[1]*vol[1] + vol[2]*vol[2]) + dx = center[0] - self.cam_pos[0] + dy = center[1] - self.cam_pos[1] + dist = math.sqrt(dx*dx + dy*dy) + #print('center:', center, 'span:', span, 'dist:', dist) + if self.view_mode == 'best': + metric = dist + (span * 0.1) + if not self.inbounds(b): + metric += 1000 + elif self.view_mode == 'sequential': + metric = abs(i - self.sequential_num) + result_list.append( [metric, m, i] ) + result_list = sorted(result_list, key=lambda fields: fields[0], + reverse=True) + if self.view_mode == 'best': + top_entry = result_list[-1-self.top_image] + else: + top_entry = result_list[-1] + top = top_entry[1] + top.setColor(1.0, 1.0, 1.0, 1.0) + self.updateTexture(top) + if self.view_mode == 'sequential': + self.cam_fit(top) + + for i, line in enumerate(result_list): + m = line[1] + if m == top: + m.setBin("fixed", 2*len(self.models)) + elif m.getName() in tcache: + # reward draw order for models with high res texture loaded + m.setBin("fixed", i + len(self.models)) + else: + m.setBin("fixed", i) + m.setDepthTest(False) + m.setDepthWrite(False) + if m != top: + gray = 1.0 + m.setColor(gray, gray, gray, 1.0) + + def updateTexture(self, main): + dir_node = getNode('/config/directories', True) + + # reset base textures + for i, m in enumerate(self.models): + if m != main: + if m.getName() in tcache: + fulltex = tcache[m.getName()][1] + self.models[i].setTexture(fulltex, 1) + else: + if self.base_textures[i] != None: + self.models[i].setTexture(self.base_textures[i], 1) else: - if True: - # example of passing an opencv image as a - # panda texture - print(base, image_file) - #image = proj.findImageByName(base) - #print(image) - rgb = cv2.imread(image_file, flags=cv2.IMREAD_ANYCOLOR|cv2.IMREAD_ANYDEPTH|cv2.IMREAD_IGNORE_ORIENTATION) - rgb = np.flipud(rgb) - # vignette correction - if not self.vignette_mask is None: - rgb = rgb.astype('uint16') + self.vignette_mask - rgb = np.clip(rgb, 0, 255).astype('uint8') - - h, w = rgb.shape[:2] - print('shape: (%d,%d)' % (w, h)) - rescale = False - if h > self.max_texture_dimension: - h = self.max_texture_dimension - rescale = True - if w > self.max_texture_dimension: - w = self.max_texture_dimension - rescale = True - if self.needs_pow2: - h2 = 2**math.floor(math.log(h,2)) - w2 = 2**math.floor(math.log(w,2)) - if h2 != h: - h = h2 + print(m.getName()) + if m.getName() in tcache: + fulltex = tcache[m.getName()][1] + self.models[i].setTexture(fulltex, 1) + continue + base, ext = os.path.splitext(m.getName()) + image_file = None + search = [ project_dir, os.path.join(project_dir, 'images') ] + for dir in search: + tmp1 = os.path.join(dir, base + '.JPG') + tmp2 = os.path.join(dir, base + '.jpg') + if os.path.isfile(tmp1): + image_file = tmp1 + elif os.path.isfile(tmp2): + image_file = tmp2 + if not image_file: + print('Warning: no full resolution image source file found:', base) + else: + if True: + # example of passing an opencv image as a + # panda texture + print(base, image_file) + #image = proj.findImageByName(base) + #print(image) + rgb = cv2.imread(image_file, flags=cv2.IMREAD_ANYCOLOR|cv2.IMREAD_ANYDEPTH|cv2.IMREAD_IGNORE_ORIENTATION) + rgb = np.flipud(rgb) + # vignette correction + if not self.vignette_mask is None: + rgb = rgb.astype('uint16') + self.vignette_mask + rgb = np.clip(rgb, 0, 255).astype('uint8') + + h, w = rgb.shape[:2] + print('shape: (%d,%d)' % (w, h)) + rescale = False + if h > self.max_texture_dimension: + h = self.max_texture_dimension rescale = True - if w2 != w: - w = w2 + if w > self.max_texture_dimension: + w = self.max_texture_dimension rescale = True - if rescale: - print("Notice: rescaling texture to (%d,%d) to honor video card capability." % (w, h)) - rgb = cv2.resize(rgb, (w,h)) - - # filter_by = 'none' - filter_by = 'equalize_value' - # filter_by = 'equalize_rgb' - # filter_by = 'equalize_blue' - # filter_by = 'equalize_green' - # filter_by = 'equalize_blue' - # filter_by = 'equalize_red' - # filter_by = 'red/green' - if filter_by == 'none': - b, g, r = cv2.split(rgb) - result = cv2.merge((b, g, r)) - if filter_by == 'equalize_value': - # equalize val (essentially gray scale level) - hsv = cv2.cvtColor(rgb, cv2.COLOR_BGR2HSV) - hue, sat, val = cv2.split(hsv) - aeq = clahe.apply(val) - # recombine - hsv = cv2.merge((hue,sat,aeq)) - # convert back to rgb - result = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR) - elif filter_by == 'equalize_rgb': - # equalize individual b, g, r channels - b, g, r = cv2.split(rgb) - b = clahe.apply(b) - g = clahe.apply(g) - r = clahe.apply(r) - result = cv2.merge((b,g,r)) - elif filter_by == 'equalize_blue': - # equalize val (essentially gray scale level) - hsv = cv2.cvtColor(rgb, cv2.COLOR_BGR2HSV) - hue, sat, val = cv2.split(hsv) - # blue hue = 120 - - # slide 120 -> 90 (center of 0-180 range - # with mod() roll over) - diff = np.mod(hue.astype('float64') - 30, 180) - # move this center point to 0 (-90 to +90 - # range) and take absolute value - # (distance) - diff = np.abs(diff - 90) - # scale to 0 to 1 (1 being the closest to - # our target hue) - diff = 1.0 - diff / 90 - print('hue:', np.amin(hue), np.amax(hue)) - print('sat:', np.amin(sat), np.amax(sat)) - print('diff:', np.amin(diff), np.amax(diff)) - #print(diff) - #g = (256 - (256.0/90.0)*diff).astype('uint8') - b = (diff * sat).astype('uint8') - g = np.zeros(hue.shape, dtype='uint8') - r = np.zeros(hue.shape, dtype='uint8') - #g = clahe.apply(g) - result = cv2.merge((b,g,r)) - print(result.shape, result.dtype) - elif filter_by == 'equalize_green': - # equalize val (essentially gray scale level) - hsv = cv2.cvtColor(rgb, cv2.COLOR_BGR2HSV) - hue, sat, val = cv2.split(hsv) - # green hue = 60 - - # slide 60 -> 90 (center of 0-180 range - # with mod() roll over) - diff = np.mod(hue.astype('float64') + 30, 180) - # move this center point to 0 (-90 to +90 - # range) and take absolute value - # (distance) - diff = np.abs(diff - 90) - # scale to 0 to 1 (1 being the closest to - # our target hue) - diff = 1.0 - diff / 90 - print('hue:', np.amin(hue), np.amax(hue)) - print('sat:', np.amin(sat), np.amax(sat)) - print('diff:', np.amin(diff), np.amax(diff)) - #print(diff) - b = np.zeros(hue.shape, dtype='uint8') - g = (diff * sat).astype('uint8') - r = np.zeros(hue.shape, dtype='uint8') - #g = clahe.apply(g) - result = cv2.merge((b,g,r)) - print(result.shape, result.dtype) - elif filter_by == 'equalize_red': - # equalize val (essentially gray scale level) - hsv = cv2.cvtColor(rgb, cv2.COLOR_BGR2HSV) - hue, sat, val = cv2.split(hsv) - # red hue = 0 - - # slide 0 -> 90 (center of 0-180 range - # with mod() roll over) - diff = np.mod(hue.astype('float64') + 90, 180) - # move this center point to 0 (-90 to +90 - # range) and take absolute value - # (distance) - diff = np.abs(diff - 90) - # scale to 0 to 1 (1 being the closest to - # our target hue) - diff = 1.0 - diff / 90 - print('hue:', np.amin(hue), np.amax(hue)) - print('sat:', np.amin(sat), np.amax(sat)) - print('diff:', np.amin(diff), np.amax(diff)) - b = np.zeros(hue.shape, dtype='uint8') - g = np.zeros(hue.shape, dtype='uint8') - r = (diff * sat).astype('uint8') - result = cv2.merge((b,g,r)) - print(result.shape, result.dtype) - elif filter_by == 'red/green': - # equalize val (essentially gray scale level) - max = 4.0 - b, g, r = cv2.split(rgb) - ratio = r / (g.astype('float64')+1.0) - ratio = np.clip(ratio, 0, max) - inv = g / (r.astype('float64')+1.0) - inv = np.clip(inv, 0, max) - max_ratio = np.amax(ratio) - max_inv = np.amax(inv) - print(max_ratio, max_inv) - b[:] = 0 - g = (inv * (255/max)).astype('uint8') - r = (ratio * (255/max)).astype('uint8') - result = cv2.merge((b,g,r)) - print(result.shape, result.dtype) - - fulltex = Texture(base) - fulltex.setCompression(Texture.CMOff) - fulltex.setup2dTexture(w, h, Texture.TUnsignedByte, Texture.FRgb) - fulltex.setRamImage(result) - # fulltex.load(rgb) # for loading a pnm image - fulltex.setWrapU(Texture.WM_clamp) - fulltex.setWrapV(Texture.WM_clamp) - m.setTexture(fulltex, 1) - tcache[m.getName()] = [m, fulltex, time.time()] - else: - print(image_file) - fulltex = self.loader.loadTexture(image_file) - fulltex.setWrapU(Texture.WM_clamp) - fulltex.setWrapV(Texture.WM_clamp) - #print('fulltex:', fulltex) - m.setTexture(fulltex, 1) - tcache[m.getName()] = [m, fulltex, time.time()] - cachesize = 10 - while len(tcache) > cachesize: - oldest_time = time.time() - oldest_name = "" - for name in tcache: - if tcache[name][2] < oldest_time: - oldest_time = tcache[name][2] - oldest_name = name - del tcache[oldest_name] - -app = MyApp() -app.load( os.path.join(proj.analysis_dir, "models") ) -app.run() + if self.needs_pow2: + h2 = 2**math.floor(math.log(h,2)) + w2 = 2**math.floor(math.log(w,2)) + if h2 != h: + h = h2 + rescale = True + if w2 != w: + w = w2 + rescale = True + if rescale: + print("Notice: rescaling texture to (%d,%d) to honor video card capability." % (w, h)) + rgb = cv2.resize(rgb, (w,h)) + + # filter_by = 'none' + filter_by = 'equalize_value' + # filter_by = 'equalize_rgb' + # filter_by = 'equalize_blue' + # filter_by = 'equalize_green' + # filter_by = 'equalize_blue' + # filter_by = 'equalize_red' + # filter_by = 'red/green' + if filter_by == 'none': + b, g, r = cv2.split(rgb) + result = cv2.merge((b, g, r)) + if filter_by == 'equalize_value': + # equalize val (essentially gray scale level) + hsv = cv2.cvtColor(rgb, cv2.COLOR_BGR2HSV) + hue, sat, val = cv2.split(hsv) + aeq = clahe.apply(val) + # recombine + hsv = cv2.merge((hue,sat,aeq)) + # convert back to rgb + result = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR) + elif filter_by == 'equalize_rgb': + # equalize individual b, g, r channels + b, g, r = cv2.split(rgb) + b = clahe.apply(b) + g = clahe.apply(g) + r = clahe.apply(r) + result = cv2.merge((b,g,r)) + elif filter_by == 'equalize_blue': + # equalize val (essentially gray scale level) + hsv = cv2.cvtColor(rgb, cv2.COLOR_BGR2HSV) + hue, sat, val = cv2.split(hsv) + # blue hue = 120 + + # slide 120 -> 90 (center of 0-180 range + # with mod() roll over) + diff = np.mod(hue.astype('float64') - 30, 180) + # move this center point to 0 (-90 to +90 + # range) and take absolute value + # (distance) + diff = np.abs(diff - 90) + # scale to 0 to 1 (1 being the closest to + # our target hue) + diff = 1.0 - diff / 90 + print('hue:', np.amin(hue), np.amax(hue)) + print('sat:', np.amin(sat), np.amax(sat)) + print('diff:', np.amin(diff), np.amax(diff)) + #print(diff) + #g = (256 - (256.0/90.0)*diff).astype('uint8') + b = (diff * sat).astype('uint8') + g = np.zeros(hue.shape, dtype='uint8') + r = np.zeros(hue.shape, dtype='uint8') + #g = clahe.apply(g) + result = cv2.merge((b,g,r)) + print(result.shape, result.dtype) + elif filter_by == 'equalize_green': + # equalize val (essentially gray scale level) + hsv = cv2.cvtColor(rgb, cv2.COLOR_BGR2HSV) + hue, sat, val = cv2.split(hsv) + # green hue = 60 + + # slide 60 -> 90 (center of 0-180 range + # with mod() roll over) + diff = np.mod(hue.astype('float64') + 30, 180) + # move this center point to 0 (-90 to +90 + # range) and take absolute value + # (distance) + diff = np.abs(diff - 90) + # scale to 0 to 1 (1 being the closest to + # our target hue) + diff = 1.0 - diff / 90 + print('hue:', np.amin(hue), np.amax(hue)) + print('sat:', np.amin(sat), np.amax(sat)) + print('diff:', np.amin(diff), np.amax(diff)) + #print(diff) + b = np.zeros(hue.shape, dtype='uint8') + g = (diff * sat).astype('uint8') + r = np.zeros(hue.shape, dtype='uint8') + #g = clahe.apply(g) + result = cv2.merge((b,g,r)) + print(result.shape, result.dtype) + elif filter_by == 'equalize_red': + # equalize val (essentially gray scale level) + hsv = cv2.cvtColor(rgb, cv2.COLOR_BGR2HSV) + hue, sat, val = cv2.split(hsv) + # red hue = 0 + + # slide 0 -> 90 (center of 0-180 range + # with mod() roll over) + diff = np.mod(hue.astype('float64') + 90, 180) + # move this center point to 0 (-90 to +90 + # range) and take absolute value + # (distance) + diff = np.abs(diff - 90) + # scale to 0 to 1 (1 being the closest to + # our target hue) + diff = 1.0 - diff / 90 + print('hue:', np.amin(hue), np.amax(hue)) + print('sat:', np.amin(sat), np.amax(sat)) + print('diff:', np.amin(diff), np.amax(diff)) + b = np.zeros(hue.shape, dtype='uint8') + g = np.zeros(hue.shape, dtype='uint8') + r = (diff * sat).astype('uint8') + result = cv2.merge((b,g,r)) + print(result.shape, result.dtype) + elif filter_by == 'red/green': + # equalize val (essentially gray scale level) + max = 4.0 + b, g, r = cv2.split(rgb) + ratio = r / (g.astype('float64')+1.0) + ratio = np.clip(ratio, 0, max) + inv = g / (r.astype('float64')+1.0) + inv = np.clip(inv, 0, max) + max_ratio = np.amax(ratio) + max_inv = np.amax(inv) + print(max_ratio, max_inv) + b[:] = 0 + g = (inv * (255/max)).astype('uint8') + r = (ratio * (255/max)).astype('uint8') + result = cv2.merge((b,g,r)) + print(result.shape, result.dtype) + + fulltex = Texture(base) + fulltex.setCompression(Texture.CMOff) + fulltex.setup2dTexture(w, h, Texture.TUnsignedByte, Texture.FRgb) + fulltex.setRamImage(result) + # fulltex.load(rgb) # for loading a pnm image + fulltex.setWrapU(Texture.WM_clamp) + fulltex.setWrapV(Texture.WM_clamp) + m.setTexture(fulltex, 1) + tcache[m.getName()] = [m, fulltex, time.time()] + else: + print(image_file) + fulltex = self.loader.loadTexture(image_file) + fulltex.setWrapU(Texture.WM_clamp) + fulltex.setWrapV(Texture.WM_clamp) + #print('fulltex:', fulltex) + m.setTexture(fulltex, 1) + tcache[m.getName()] = [m, fulltex, time.time()] + cachesize = 10 + while len(tcache) > cachesize: + oldest_time = time.time() + oldest_name = "" + for name in tcache: + if tcache[name][2] < oldest_time: + oldest_time = tcache[name][2] + oldest_name = name + del tcache[oldest_name] + + app = MyApp() + app.load( os.path.join(proj.analysis_dir, "models") ) + app.run() + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Set the initial camera poses.') + parser.add_argument('--project', help='project directory') + args = parser.parse_args() + + run(args.project) \ No newline at end of file diff --git a/scripts/import_proxy.py b/scripts/import_proxy.py new file mode 100644 index 00000000..64ee239a --- /dev/null +++ b/scripts/import_proxy.py @@ -0,0 +1,43 @@ +# The purpose of this proxy, is to allow the import of python files that have a number at the start + +create_project = __import__('1a_create_project') +globals().update(vars(create_project)) + +set_camera_config = __import__('1b_set_camera_config') +globals().update(vars(set_camera_config)) + +set_poses = __import__('2a_set_poses') +globals().update(vars(set_poses)) + +detect_features = __import__('3a_detect_features') +globals().update(vars(detect_features)) + +matching = __import__('4a_matching') +globals().update(vars(matching)) + +clean_and_combine_matches = __import__('4b_clean_and_combine_matches') +globals().update(vars(clean_and_combine_matches)) + +match_triangulation = __import__('4c_match_triangulation') +globals().update(vars(match_triangulation)) + +image_groups = __import__('4d_image_groups') +globals().update(vars(image_groups)) + +optimize = __import__('5a_optimize') +globals().update(vars(optimize)) + +mre_by_image = __import__('5b_mre_by_image') +globals().update(vars(mre_by_image)) + +colocated_feats = __import__('5b_colocated_feats') +globals().update(vars(colocated_feats)) + +render_model2 = __import__('6a_render_model2') +globals().update(vars(render_model2)) + +delaunay5 = __import__('6b_delaunay5') +globals().update(vars(delaunay5)) + +explore = __import__('7a_explore') +globals().update(vars(explore)) \ No newline at end of file diff --git a/scripts/lib/Optimizer.py b/scripts/lib/Optimizer.py index 129685dc..1466d7f9 100644 --- a/scripts/lib/Optimizer.py +++ b/scripts/lib/Optimizer.py @@ -272,13 +272,16 @@ def setup(self, proj, groups, group_index, matches_list, optimized=False, self.feat_map_fwd[i] = feat_used self.feat_map_rev[feat_used] = i feat_used += 1 - ned = np.array(match[0]) - if np.any(np.isnan(ned)): - print(i, ned) - self.points_3d[point_idx] = ned[0] - self.points_3d[point_idx+1] = ned[1] - self.points_3d[point_idx+2] = ned[2] - point_idx += 3 + try: + ned = np.array(match[0]) + if np.any(np.isnan(ned)): + print(i, ned) + self.points_3d[point_idx] = ned[0] + self.points_3d[point_idx+1] = ned[1] + self.points_3d[point_idx+2] = ned[2] + point_idx += 3 + except: + pass # assemble observations (image index, feature index, u, v) self.by_camera_point_indices = [ [] for i in range(self.n_cameras) ] diff --git a/scripts/lib/ProjectMgr.py b/scripts/lib/ProjectMgr.py index 4a238af6..c3e11d0e 100644 --- a/scripts/lib/ProjectMgr.py +++ b/scripts/lib/ProjectMgr.py @@ -119,6 +119,9 @@ def load(self, create=True): def detect_camera(self): camera = "" + make = "" + model = "" + lens_model = "" image_dir = self.project_dir for file in os.listdir(image_dir): if fnmatch.fnmatch(file, '*.jpg') or fnmatch.fnmatch(file, '*.JPG'): diff --git a/scripts/lib/SRTM.py b/scripts/lib/SRTM.py index 95de7726..4ed309ef 100644 --- a/scripts/lib/SRTM.py +++ b/scripts/lib/SRTM.py @@ -34,11 +34,18 @@ class SRTM(): def __init__(self, lat, lon, dict_path): self.lat, self.lon = lla_ll_corner(lat, lon) self.srtm_dict = {} - self.srtm_cache_dir = '/var/tmp' # unless set otherwise + self.srtm_cache_dir = os.path.join(os.path.dirname(__file__), "tmp") # unless set otherwise + self.check_cache_dir() self.srtm_z = None self.i = None self.load_srtm_dict(dict_path) - + + def check_cache_dir(self): + if os.path.isdir(self.srtm_cache_dir): + pass + else: + os.makedirs(self.srtm_cache_dir) + # load the directory download dictionary (mapping a desired file # to a download path.) def load_srtm_dict(self, dict_path): diff --git a/scripts/project_runner.py b/scripts/project_runner.py new file mode 100644 index 00000000..e440ffb7 --- /dev/null +++ b/scripts/project_runner.py @@ -0,0 +1,161 @@ +# This is to run the string of core functions together as described in the readme as a single run to produce the end result + +import os, sys, argparse +# add the current dir to the sys path to allow for use of the other scripts +sys.path.append(os.path.dirname(__file__)) + +import lib + +from import_proxy import create_project, set_camera_config, \ + set_poses, \ + detect_features, \ + matching, clean_and_combine_matches, match_triangulation, image_groups, \ + optimize, mre_by_image, colocated_feats, \ + render_model2, delaunay5, \ + explore + +def standard_project(project_dir, camera, max_camera_angle, detection_options, matching_options, match_trig_options, optmize_options, mre_options, colocated_options, render_options, delaunay_group): + # 1a-create-project.py + print(" Creating the standard Project") + create_project.new_project(project_dir) + + # 1b-set-camera-config.py + print(" Setting the camera config") + set_camera_config.set_camera(project_dir, camera) + + # 2a-set-poses.py + print(" Setting the camera POSEs") + set_poses.set_pose(project_dir, max_camera_angle) + + # 3a-detect-features.py + print(" Detecting features in the images") + detect_features.detect(project_dir, detection_options) + + # 4a-matching.py + print(" Matching the features in the image") + matching.match(project_dir, matching_options) + + # 4b-clean-and-combine-matches.py + print(" Cleaning and combining the matches") + clean_and_combine_matches.clean(project_dir) + + # 4c-match-triangulation.py + print(" Matching and triangulation") + match_triangulation.match_trig(project_dir, match_trig_options) + + # 4d-image-groups.py + print(" Image Grouping") + image_groups.group(project_dir) + + # 5a-optimize.py + print(" Optimizing") + optimize.optmizer(project_dir, optmize_options) + + # 5b-mre-by-image.py + print(" MRE by Image") + mre_by_image.mre(project_dir, mre_options) + + # 5b-colocated-feats.py + print(" Colocate the features") + colocated_feats.colocated(project_dir, colocated_options) + + # 6a-render-model2.py + print(" Rendering the scene") + render_model2.render(project_dir, render_options) + + # 6b-delaunay5.py + print(" Delaunay 5") + delaunay5.delaunay(project_dir, delaunay_group) + + # 7a-explore.py + print(" Explore your direct projected project") + explore.run(project_dir) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Create an empty project.') + # parser.add_argument('--project', required=True, help='Directory with a set of aerial images.') + parser.add_argument('--project', default = "D:\\test Project", help='project directory') + # parser.add_argument('--camera', help='camera config file path') + parser.add_argument('--camera', default = "C:\\Users\\Skywalker\\OneDrive - InFarm\\Repos\\ImageAnalysis\\cameras\\DJI_FC330.json", help='camera config file path') + parser.add_argument('--yaw-deg', type=float, default=0.0, help='camera yaw mounting offset from aircraft') + parser.add_argument('--pitch-deg', type=float, default=-90.0, help='camera pitch mounting offset from aircraft') + parser.add_argument('--roll-deg', type=float, default=0.0, help='camera roll mounting offset from aircraft') + parser.add_argument('--meta', help='use the specified image-metadata.txt file (lat,lon,alt,yaw,pitch,roll)') + # parser.add_argument('--pix4d', help='use the specified pix4d csv file (lat,lon,alt,roll,pitch,yaw)') + parser.add_argument('--pix4d', default = "D:\\test Project\\pix4d.csv", help='use the specified pix4d csv file (lat,lon,alt,roll,pitch,yaw)') + parser.add_argument('--max-angle', type=float, default=25.0, help='max pitch or roll angle for image inclusion') + parser.add_argument('--scale', type=float, default=0.5, help='scale images before detecting features, this acts much like a noise filter') + # detection options: + parser.add_argument('--detector', default='SIFT', choices=['SIFT', 'SURF', 'ORB', 'Star']) + parser.add_argument('--sift-max-features', default=30000, help='maximum SIFT features') + parser.add_argument('--surf-hessian-threshold', default=600, help='hessian threshold for surf method') + parser.add_argument('--surf-noctaves', default=4, help='use a bigger number to detect bigger features') + parser.add_argument('--orb-max-features', default=2000, help='maximum ORB features') + parser.add_argument('--grid-detect', default=1, help='run detect on gridded squares for (maybe) better feature distribution, 4 is a good starting value, only affects ORB method') + parser.add_argument('--star-max-size', default=16, help='4, 6, 8, 11, 12, 16, 22, 23, 32, 45, 46, 64, 90, 128') + parser.add_argument('--star-response-threshold', default=30) + parser.add_argument('--star-line-threshold-projected', default=10) + parser.add_argument('--star-line-threshold-binarized', default=8) + parser.add_argument('--star-suppress-nonmax-size', default=5) + parser.add_argument('--reject-margin', default=0, help='reject features within this distance of the image outer edge margin') + parser.add_argument('--show', action='store_true', help='show features as we detect them') + # matching options: + parser.add_argument('--matcher', default='FLANN', choices=['FLANN', 'BF']) + parser.add_argument('--match-ratio', default=0.75, type=float, help='match ratio') + parser.add_argument('--min-pairs', default=25, type=int, help='minimum matches between image pairs to keep') + parser.add_argument('--min-dist', default=0, type=float, help='minimum 2d camera distance for pair comparison') + parser.add_argument('--max-dist', default=75, type=float, help='maximum 2d camera distance for pair comparison') + parser.add_argument('--filter', default='gms', choices=['gms', 'homography', 'fundamental', 'essential', 'none']) + parser.add_argument('--min-chain-length', type=int, default=3, help='minimum match chain length (3 recommended)') + #parser.add_argument('--ground', type=float, help='ground elevation in meters') + # match triangulation options: + parser.add_argument('--trig-group', type=int, default=0, help='group number') + parser.add_argument('--method', default='srtm', choices=['srtm', 'triangulate']) + # optmize options: + parser.add_argument('--optmize-group', type=int, default=0, help='group number') + parser.add_argument('--refine', action='store_true', help='refine a previous optimization.') + parser.add_argument('--cam-calibration', action='store_true', help='include camera calibration in the optimization.') + # mre options: + parser.add_argument('--mre-group', type=int, default=0, help='group number') + parser.add_argument('--stddev', type=float, default=5, help='how many stddevs above the mean for auto discarding features') + parser.add_argument('--initial-pose', action='store_true', default=False, help='work on initial pose, not optimized pose') + parser.add_argument('--strong', action='store_true', help='remove entire match chain, not just the worst offending element.') + parser.add_argument('--interactive', action='store_true', help='interactively review reprojection errors from worst to best and select for deletion or keep.') + # colocated features options: + parser.add_argument('--colocated-group', type=int, default=0, help='group index') + parser.add_argument('--min-angle', type=float, default=1.0, help='max feature angle') + # render options: + parser.add_argument('--render-group', type=int, default=0, help='group index') + parser.add_argument('--texture-resolution', type=int, default=512, help='texture resolution (should be 2**n, so numbers like 256, 512, 1024, etc.') + parser.add_argument('--srtm', action='store_true', help='use srtm elevation') + parser.add_argument('--ground', type=float, help='force ground elevation in meters') + parser.add_argument('--direct', action='store_true', help='use direct pose') + # delaunay options: + parser.add_argument('--delaunay-group', type=int, default=0, help='group index') + + + + args = parser.parse_args() + + detection_options = [args.detector, args.scale, args.sift_max_features, + args.surf_hessian_threshold, args.surf_noctaves, args.grid_detect, + args.orb_max_features, args.star_max_size, args.star_response_threshold, + args.star_line_threshold_binarized, args.star_suppress_nonmax_size, + args.show] + + matching_options = [args.matcher, args.match_ratio, args.min_pairs, args.min_dist, + args.max_dist, args.filter, args.min_chain_length] + + match_trig_options = [args.trig_group, args.method] + + optmize_options = [args.optmize_group, args.refine, args.cam_calibration] + + mre_options = [args.mre_group, args.stddev, args.initial_pose, args.strong, args.interactive] + + colocated_options = [args.colocated_group, args.min_angle] + + render_options = [args.render_group, args.texture_resolution, args.srtm, args.ground, args.direct] + + standard_project(args.project, args.camera, args.max_angle, detection_options, + matching_options, match_trig_options, optmize_options, mre_options, + colocated_options, render_options, args.delaunay_group) \ No newline at end of file From 8da1096c4a8a55d11ff00f9e6fe5d2b104fa4d45 Mon Sep 17 00:00:00 2001 From: Ashley Walker Date: Fri, 2 Aug 2019 16:03:27 +1000 Subject: [PATCH 3/4] It runs! --- scripts/3a_detect_features.py | 1 + scripts/lib/Matcher.py | 1 + scripts/project_runner.py | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/3a_detect_features.py b/scripts/3a_detect_features.py index 85690139..3cf358d1 100644 --- a/scripts/3a_detect_features.py +++ b/scripts/3a_detect_features.py @@ -32,6 +32,7 @@ def detect(project_dir, detection_options): detector_node = getNode('/config/detector', True) detector_node.setString('detector', detector) detector_node.setString('scale', scale) + # TODO: Use concurrency, a que to decrease the processing time: if detector == 'SIFT': detector_node.setInt('sift_max_features', sift_max_features) elif detector == 'SURF': diff --git a/scripts/lib/Matcher.py b/scripts/lib/Matcher.py index 612b34c4..61eec28d 100644 --- a/scripts/lib/Matcher.py +++ b/scripts/lib/Matcher.py @@ -629,6 +629,7 @@ def robustGroupMatches(self, image_list, K, dist_stats = np.array(dist_stats) plt.plot(dist_stats[:,0], dist_stats[:,1], 'ro') + # TODO: Make a feature switch to stop showing when using the project runner. plt.show() # remove any match sets shorter than self.min_pairs (this shouldn't diff --git a/scripts/project_runner.py b/scripts/project_runner.py index e440ffb7..29c9c2c3 100644 --- a/scripts/project_runner.py +++ b/scripts/project_runner.py @@ -15,6 +15,8 @@ explore def standard_project(project_dir, camera, max_camera_angle, detection_options, matching_options, match_trig_options, optmize_options, mre_options, colocated_options, render_options, delaunay_group): + # TODO: Find every part of the script that creates a popup or requires interaction, and make into a feature flag that can be turned off for a standard run. + # 1a-create-project.py print(" Creating the standard Project") create_project.new_project(project_dir) @@ -133,8 +135,6 @@ def standard_project(project_dir, camera, max_camera_angle, detection_options, m # delaunay options: parser.add_argument('--delaunay-group', type=int, default=0, help='group index') - - args = parser.parse_args() detection_options = [args.detector, args.scale, args.sift_max_features, From 6f7e0ae3fbdf1786ac17baa3ca610632b6635253 Mon Sep 17 00:00:00 2001 From: Ashley <7260106+skywalkerisnull@users.noreply.github.com> Date: Fri, 2 Aug 2019 16:13:42 +1000 Subject: [PATCH 4/4] Update project_runner.py Removed machine specific arguments --- scripts/project_runner.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/scripts/project_runner.py b/scripts/project_runner.py index 29c9c2c3..c4d94e45 100644 --- a/scripts/project_runner.py +++ b/scripts/project_runner.py @@ -75,16 +75,13 @@ def standard_project(project_dir, camera, max_camera_angle, detection_options, m if __name__ == "__main__": parser = argparse.ArgumentParser(description='Create an empty project.') - # parser.add_argument('--project', required=True, help='Directory with a set of aerial images.') - parser.add_argument('--project', default = "D:\\test Project", help='project directory') - # parser.add_argument('--camera', help='camera config file path') - parser.add_argument('--camera', default = "C:\\Users\\Skywalker\\OneDrive - InFarm\\Repos\\ImageAnalysis\\cameras\\DJI_FC330.json", help='camera config file path') + parser.add_argument('--project', required=True, help='Directory with a set of aerial images.') + parser.add_argument('--camera', help='camera config file path') parser.add_argument('--yaw-deg', type=float, default=0.0, help='camera yaw mounting offset from aircraft') parser.add_argument('--pitch-deg', type=float, default=-90.0, help='camera pitch mounting offset from aircraft') parser.add_argument('--roll-deg', type=float, default=0.0, help='camera roll mounting offset from aircraft') parser.add_argument('--meta', help='use the specified image-metadata.txt file (lat,lon,alt,yaw,pitch,roll)') - # parser.add_argument('--pix4d', help='use the specified pix4d csv file (lat,lon,alt,roll,pitch,yaw)') - parser.add_argument('--pix4d', default = "D:\\test Project\\pix4d.csv", help='use the specified pix4d csv file (lat,lon,alt,roll,pitch,yaw)') + parser.add_argument('--pix4d', help='use the specified pix4d csv file (lat,lon,alt,roll,pitch,yaw)') parser.add_argument('--max-angle', type=float, default=25.0, help='max pitch or roll angle for image inclusion') parser.add_argument('--scale', type=float, default=0.5, help='scale images before detecting features, this acts much like a noise filter') # detection options: @@ -158,4 +155,4 @@ def standard_project(project_dir, camera, max_camera_angle, detection_options, m standard_project(args.project, args.camera, args.max_angle, detection_options, matching_options, match_trig_options, optmize_options, mre_options, - colocated_options, render_options, args.delaunay_group) \ No newline at end of file + colocated_options, render_options, args.delaunay_group)