├── Modules ├── __init__.py ├── utils.py └── sop.py ├── README.md ├── LICENSE └── PhotoScan_Workflow.py /Modules/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PhotoScan-Workflow 2 | Python Script for automation in Agisoft MetaShape (previous PhotoScan) Pro 3 | The script should work from version 1.3 to 1.8. 4 | 5 | # Usage 6 | This script will process all chunks with the following behaviour: 7 | - **If Photo Alignment is not done yet, align the photos** 8 | - **If there is tie point, do the rest workflow** 9 | 10 | **Note:** The GCP must be marked manually, following camera optimisation 11 | You can change some variables in the script for specific purposes 12 | Run the script in Tools > Run script 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 dobedobedo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PhotoScan_Workflow.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Thu Nov 9 13:21:23 2017 5 | 6 | @author: Yu-Hsuan Tu 7 | 8 | This Python Script was originally developed for Agisoft PhotoScan (current MetaShape) 1.3.4 9 | Python core was 3.5.2. 10 | It was later adapted to Metashape 1.8.2 with Python core 3.8.11 on March 2022. 11 | 12 | 20 March 2022 Update: Clean up the script to different modules and adapt to Metashape 1.8 13 | 21 Auguest 2020 Update: Add compatibility of Metashape 1.6 14 | 22 October 2019 Update: Add tie point error reduction following the USGS guidline 15 | Add the 3D model parameters to user variables 16 | 11 January 2019 Update: Add compatibility of MetaShape 1.5.0 17 | Update: Add compatibility of PhotoScan 1.4.0 18 | 19 | This script runs through all chunks and will do the following: 20 | 1. Align Photos if there's no tie point 21 | 2. Do the rest of standard procedure if there is tie point 22 | 23 | When aligning photos, users can decide whether using image quality to disable bad photos 24 | 25 | Manual intervene for standard workflow: 26 | 1. Load photos 27 | 2. Set CRS 28 | 3. Marking GCP 29 | 4. Set Region 30 | 31 | The standard workflow includes: 32 | Build dense point cloud 33 | Ground point classification 34 | Build model and texture (optional) 35 | Build DSM 36 | Build DEM 37 | Build orthomosaic 38 | 39 | In early versions, the DEM will be generated in duplicated chunk: "chunk name"_DEM respectively 40 | Therefore, please avoid "_DEM" in your chunk name. Otherwise, it will not be processed. 41 | DEM will be created by duplicating DSM and build with ground point in the same chunk in supported versions. 42 | """ 43 | 44 | try: 45 | import Metashape as ps 46 | except ImportError: 47 | import PhotoScan as ps 48 | 49 | from Modules import sop 50 | 51 | ############################################################################### 52 | # 53 | # User variables 54 | # 55 | ############################################################################### 56 | # This section is for variables of SOP 57 | # 58 | # Variable to check if the script will be executed for all chunks 59 | # all_chunk: True, False 60 | all_chunk = False 61 | # 62 | # Variables for image quality filter 63 | # QualityFilter: True, False 64 | # QualityCriteria: float number range from 0 to 1 (default 0.5) 65 | QualityFilter = False 66 | QualityCriteria = 0.5 67 | # 68 | # Variables for photo alignment 69 | # Accuracy: HighestAccuracy, HighAccuracy, MediumAccuracy, LowAccuracy, LowestAccuracy 70 | Accuracy = 'HighAccuracy' 71 | Key_Limit = 40000 72 | Tie_Limit = 4000 73 | # 74 | # Variable for tie point error reduction 75 | # error_reduction: True, False 76 | error_reduction = False 77 | # 78 | # Variables for building dense cloud 79 | # Quality: UltraQuality, HighQuality, MediumQuality, LowQuality, LowestQuality 80 | # Filter: AggressiveFiltering, ModerateFiltering, MildFiltering, NoFiltering 81 | Quality = 'UltraQuality' 82 | FilterMode = ps.FilterMode.MildFiltering 83 | # 84 | # Variables for dense cloud ground point classification 85 | # Maximum distance is usually twice of image resolution 86 | # Which will be calculated later 87 | Max_Angle = 13 88 | Cell_Size = 10 89 | Ero_Radius = 0.05 90 | # 91 | # Variable for building 3D mesh and texture 92 | # create_model: True, False 93 | # Surface: Arbitrary, HeightField 94 | # SurfaceSource: PointCloudData, DenseCloudData, DepthMapsData 95 | # uv_mapping: GenericMapping, OrthophotoMapping, AdaptiveOrthophotoMapping, SphericalMapping, CameraMapping 96 | # texture_blending: AverageBlending, MosaicBlending, MinBlending, MaxBlending, DisabledBlending 97 | create_model = False 98 | Surface = ps.SurfaceType.Arbitrary 99 | SurfaceSource = ps.DataSource.DenseCloudData 100 | uv_mapping = ps.GenericMapping 101 | texture_blending = ps.BlendingMode.MosaicBlending 102 | texture_size = 4096 103 | # 104 | # Variable for building DSM and DEM 105 | # ElevationSurface: PointCloudData, DenseCloudData 106 | ElevationSource = ps.DataSource.DenseCloudData 107 | # 108 | # Variable for building orthomosaic 109 | # Since 1.4.0, users can choose performing color correction (vignetting) and balance separately. 110 | # Blending: AverageBlending, MosaicBlending, MinBlending, MaxBlending, DisabledBlending 111 | # Color_correction: True, False 112 | # correction_source: PointCloudData, ElevationData 113 | # Color_balance: True, False 114 | BlendingMode = ps.BlendingMode.MosaicBlending 115 | Color_correction = True 116 | correction_source = ps.DataSource.ElevationData 117 | Color_balance = False 118 | # 119 | ############################################################################### 120 | 121 | # The following process will only be executed when running script 122 | if __name__ == '__main__': 123 | doc = ps.app.document 124 | 125 | # Prompt the user to select CRS 126 | crs = ps.app.getCoordinateSystem('Select coordinate system', ps.CoordinateSystem('EPSG::4326')) 127 | 128 | # Run SOP workflow 129 | sop.run(doc, all_chunk=all_chunk, error_reduction=error_reduction, create_model=create_model, 130 | QualityFilter=QualityFilter, QualityCriteria=QualityCriteria, crs=crs, 131 | Accuracy=Accuracy, Key_Limit=Key_Limit, Tie_Limit=Tie_Limit, 132 | Quality=Quality, FilterMode=FilterMode, 133 | Max_Angle=Max_Angle, Cell_Size=Cell_Size, Ero_Radius=Ero_Radius, 134 | Surface=Surface, SurfaceSource=SurfaceSource, 135 | uv_mapping=uv_mapping, texture_blending=texture_blending, texture_size=texture_size, 136 | ElevationSource=ElevationSource, 137 | BlendingMode=BlendingMode, Color_correction=Color_correction, correction_source=correction_source, 138 | Color_balance=Color_balance) 139 | 140 | ps.app.update() 141 | -------------------------------------------------------------------------------- /Modules/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Created on Thu Nov 9 13:21:23 2017 6 | 7 | @author: Yu-Hsuan Tu 8 | 9 | This Python Script was originally developed for Agisoft PhotoScan (current MetaShape) 1.3.4 10 | Python core is 3.5.2. It was later adapted to Metashape 1.8.2 with Python core 3.8.11 on March 2022. 11 | This module contains useful utilities for Metashape workflow 12 | """ 13 | 14 | try: 15 | import Metashape as ps 16 | except ImportError: 17 | import PhotoScan as ps 18 | 19 | 20 | def GetResolution(chunk): 21 | try: 22 | DEM_resolution = float(chunk.dense_cloud.meta['dense_cloud/resolution']) * chunk.transform.scale 23 | Image_resolution = DEM_resolution / int(chunk.dense_cloud.meta['dense_cloud/depth_downscale']) 24 | 25 | except TypeError: 26 | DEM_resolution = float(chunk.dense_cloud.meta['BuildDenseCloud/resolution']) * chunk.transform.scale 27 | Image_resolution = DEM_resolution / int(chunk.dense_cloud.meta['BuildDepthMaps/downscale']) 28 | 29 | return DEM_resolution, Image_resolution 30 | 31 | 32 | def ReduceError_RU(chunk, init_threshold=10): 33 | # This is used to reduce error based on reconstruction uncertainty 34 | tie_points = chunk.point_cloud 35 | fltr = ps.PointCloud.Filter() 36 | fltr.init(chunk, ps.PointCloud.Filter.ReconstructionUncertainty) 37 | threshold = init_threshold 38 | while fltr.max_value > 10: 39 | fltr.selectPoints(threshold) 40 | nselected = len([p for p in tie_points.points if p.selected]) 41 | if nselected >= len(tie_points.points) / 2 and threshold <= 50: 42 | fltr.resetSelection() 43 | threshold += 1 44 | continue 45 | UnselectPointMatch(chunk) 46 | nselected = len([p for p in tie_points.points if p.selected]) 47 | if nselected == 0: 48 | break 49 | print('Delete {} tie point(s)'.format(nselected)) 50 | tie_points.removeSelectedPoints() 51 | try: 52 | chunk.optimizeCameras(fit_f=True, fit_cx=True, fit_cy=True, fit_b1=False, fit_b2=False, 53 | fit_k1=True, fit_k2=True, fit_k3=True, fit_k4=False, 54 | fit_p1=True, fit_p2=True, fit_p3=False, fit_p4=False, 55 | adaptive_fitting=False, tiepoint_covariance=False) 56 | except NameError: 57 | chunk.optimizeCameras(fit_f=True, fit_cx=True, fit_cy=True, fit_b1=False, fit_b2=False, 58 | fit_k1=True, fit_k2=True, fit_k3=True, fit_k4=False, 59 | fit_p1=True, fit_p2=True, 60 | adaptive_fitting=False, tiepoint_covariance=False) 61 | fltr.init(chunk, ps.PointCloud.Filter.ReconstructionUncertainty) 62 | threshold = init_threshold 63 | 64 | 65 | def ReduceError_PA(chunk, init_threshold=2.0): 66 | # This is used to reduce error based on projection accuracy 67 | tie_points = chunk.point_cloud 68 | fltr = ps.PointCloud.Filter() 69 | fltr.init(chunk, ps.PointCloud.Filter.ProjectionAccuracy) 70 | threshold = init_threshold 71 | while fltr.max_value > 2.0: 72 | fltr.selectPoints(threshold) 73 | nselected = len([p for p in tie_points.points if p.selected]) 74 | if nselected >= len(tie_points.points) / 2 and threshold <= 3.0: 75 | fltr.resetSelection() 76 | threshold += 0.1 77 | continue 78 | UnselectPointMatch(chunk) 79 | nselected = len([p for p in tie_points.points if p.selected]) 80 | if nselected == 0: 81 | break 82 | print('Delete {} tie point(s)'.format(nselected)) 83 | tie_points.removeSelectedPoints() 84 | try: 85 | chunk.optimizeCameras(fit_f=True, fit_cx=True, fit_cy=True, fit_b1=False, fit_b2=False, 86 | fit_k1=True, fit_k2=True, fit_k3=True, fit_k4=False, 87 | fit_p1=True, fit_p2=True, fit_p3=False, fit_p4=False, 88 | adaptive_fitting=False, tiepoint_covariance=False) 89 | except NameError: 90 | chunk.optimizeCameras(fit_f=True, fit_cx=True, fit_cy=True, fit_b1=False, fit_b2=False, 91 | fit_k1=True, fit_k2=True, fit_k3=True, fit_k4=False, 92 | fit_p1=True, fit_p2=True, 93 | adaptive_fitting=False, tiepoint_covariance=False) 94 | fltr.init(chunk, ps.PointCloud.Filter.ProjectionAccuracy) 95 | threshold = init_threshold 96 | # This is to tighten tie point accuracy value 97 | chunk.tiepoint_accuracy = 0.1 98 | try: 99 | chunk.optimizeCameras(fit_f=True, fit_cx=True, fit_cy=True, fit_b1=True, fit_b2=True, 100 | fit_k1=True, fit_k2=True, fit_k3=True, fit_k4=True, 101 | fit_p1=True, fit_p2=True, fit_p3=True, fit_p4=True, 102 | adaptive_fitting=False, tiepoint_covariance=False) 103 | except NameError: 104 | chunk.optimizeCameras(fit_f=True, fit_cx=True, fit_cy=True, fit_b1=True, fit_b2=True, 105 | fit_k1=True, fit_k2=True, fit_k3=True, fit_k4=True, 106 | fit_p1=True, fit_p2=True, 107 | adaptive_fitting=False, tiepoint_covariance=False) 108 | 109 | 110 | def ReduceError_RE(chunk, init_threshold=0.3, loop_time=2): 111 | # This is used to reduce error based on repeojection error 112 | tie_points = chunk.point_cloud 113 | fltr = ps.PointCloud.Filter() 114 | fltr.init(chunk, ps.PointCloud.Filter.ReprojectionError) 115 | threshold = init_threshold 116 | counter = 0 117 | while fltr.max_value > 0.3 and counter < loop_time: 118 | fltr.selectPoints(threshold) 119 | nselected = len([p for p in tie_points.points if p.selected]) 120 | if nselected >= len(tie_points.points) / 10: 121 | fltr.resetSelection() 122 | threshold += 0.01 123 | continue 124 | UnselectPointMatch(chunk) 125 | nselected = len([p for p in tie_points.points if p.selected]) 126 | if nselected == 0: 127 | break 128 | print('Delete {} tie point(s)'.format(nselected)) 129 | tie_points.removeSelectedPoints() 130 | try: 131 | chunk.optimizeCameras(fit_f=True, fit_cx=True, fit_cy=True, fit_b1=True, fit_b2=True, 132 | fit_k1=True, fit_k2=True, fit_k3=True, fit_k4=True, 133 | fit_p1=True, fit_p2=True, fit_p3=True, fit_p4=True, 134 | adaptive_fitting=False, tiepoint_covariance=False) 135 | except NameError: 136 | chunk.optimizeCameras(fit_f=True, fit_cx=True, fit_cy=True, fit_b1=True, fit_b2=True, 137 | fit_k1=True, fit_k2=True, fit_k3=True, fit_k4=True, 138 | fit_p1=True, fit_p2=True, 139 | adaptive_fitting=False, tiepoint_covariance=False) 140 | fltr.init(chunk, ps.PointCloud.Filter.ReprojectionError) 141 | threshold = init_threshold 142 | counter += 1 143 | 144 | 145 | def UnselectPointMatch(chunk): 146 | point_cloud = chunk.point_cloud 147 | points = point_cloud.points 148 | point_proj = point_cloud.projections 149 | npoints = len(points) 150 | 151 | n_proj = dict() 152 | point_ids = [-1] * len(point_cloud.tracks) 153 | 154 | for point_id in range(0, npoints): 155 | point_ids[points[point_id].track_id] = point_id 156 | 157 | # Find the point ID using projections' track ID 158 | for camera in chunk.cameras: 159 | if camera.type != ps.Camera.Type.Regular: 160 | continue 161 | if not camera.transform: 162 | continue 163 | 164 | for proj in point_proj[camera]: 165 | track_id = proj.track_id 166 | point_id = point_ids[track_id] 167 | if point_id < 0: 168 | continue 169 | if not points[point_id].valid: 170 | continue 171 | 172 | if point_id in n_proj.keys(): 173 | n_proj[point_id] += 1 174 | else: 175 | n_proj[point_id] = 1 176 | 177 | # Unselect points which have less than three projections 178 | for i in n_proj.keys(): 179 | if n_proj[i] < 3: 180 | points[i].selected = False 181 | -------------------------------------------------------------------------------- /Modules/sop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Thu Nov 9 13:21:23 2017 5 | 6 | @author: Yu-Hsuan Tu 7 | 8 | This Python Script was originally developed for Agisoft PhotoScan (current MetaShape) 1.3.4 9 | Python core was 3.5.2. 10 | It was later adapted to Metashape 1.8.2 with Python core 3.8.11 on March 2022. 11 | 12 | 20 March 2022 Update: Clean up the script to different modules and adapt to Metashape 1.8 13 | 21 Auguest 2020 Update: Add compatibility of Metashape 1.6 14 | 22 October 2019 Update: Add tie point error reduction following the USGS guidline 15 | Add the 3D model parameters to user variables 16 | 11 January 2019 Update: Add compatibility of MetaShape 1.5.0 17 | Update: Add compatibility of PhotoScan 1.4.0 18 | 19 | This script runs through all chunks and will do the following: 20 | 1. Align Photos if there's no tie point 21 | 2. Do the rest of standard procedure if there is tie point 22 | 23 | When aligning photos, users can decide whether using image quality to disable bad photos 24 | 25 | Manual intervene for standard workflow: 26 | 1. Load photos 27 | 2. Set CRS 28 | 3. Marking GCP 29 | 4. Set Region 30 | 31 | The standard workflow includes: 32 | Build dense point cloud 33 | Ground point classification 34 | Build model and texture (optional) 35 | Build DSM 36 | Build DEM 37 | Build orthomosaic 38 | 39 | In early versions, the DEM will be generated in duplicated chunk: "chunk name"_DEM respectively 40 | Therefore, please avoid "_DEM" in your chunk name. Otherwise, it will not be processed. 41 | DEM will be created by duplicating DSM and build with ground point in the same chunk in supported versions. 42 | """ 43 | 44 | try: 45 | import Metashape as ps 46 | except ImportError: 47 | import PhotoScan as ps 48 | 49 | from . import utils 50 | 51 | 52 | # Try to set the correct arguments for photo match accuracy and dense cloud quality 53 | try: 54 | AccuracyDict = {'HighestAccuracy': ps.Accuracy.HighestAccuracy, 'HighAccuracy': ps.Accuracy.HighAccuracy, 55 | 'MediumAccuracy': ps.Accuracy.MediumAccuracy, 'LowAccuracy': ps.Accuracy.LowAccuracy, 56 | 'LowestAccuracy': ps.Accuracy.LowestAccuracy} 57 | QualityDict = {'UltraQuality': ps.Quality.UltraQuality, 'HighQuality': ps.Quality.HighQuality, 58 | 'MediumQuality': ps.Quality.MediumQuality, 'LowQuality': ps.Quality.LowQuality, 59 | 'LowestQuality': ps.Quality.LowestQuality} 60 | except AttributeError: 61 | AccuracyDict = {'HighestAccuracy': 0.25, 'HighAccuracy': 1, 'MediumAccuracy': 4, 'LowAccuracy': 16, 'LowestAccuracy': 64} 62 | QualityDict = {'UltraQuality': 1, 'HighQuality': 4, 'MediumQuality': 16, 'LowQuality': 64, 'LowestQuality': 256} 63 | 64 | 65 | def AlignPhoto(chunk, Accuracy, Key_Limit, Tie_Limit, QualityFilter, QualityCriteria, crs): 66 | Accuracy = AccuracyDict[Accuracy] 67 | if QualityFilter: 68 | if chunk.cameras[0].meta['Image/Quality'] is None: 69 | chunk.estimateImageQuality() 70 | for band in [band for camera in chunk.cameras for band in camera.planes]: 71 | if float(band.meta['Image/Quality']) < QualityCriteria: 72 | band.enabled = False 73 | 74 | # Perform CRS conversion 75 | source_crs = chunk.crs 76 | target_crs = crs 77 | if source_crs.authority != target_crs.authority: 78 | for camera in chunk.cameras: 79 | if not camera.reference.location: 80 | continue 81 | camera.reference.location = ps.CoordinateSystem.transform(camera.reference.location, 82 | source_crs, 83 | target_crs) 84 | chunk.crs = target_crs 85 | 86 | try: 87 | chunk.matchPhotos(accuracy=Accuracy, 88 | generic_preselection=True, 89 | reference_preselection=True, 90 | filter_mask=False, 91 | keypoint_limit=Key_Limit, 92 | tiepoint_limit=Tie_Limit) 93 | except NameError: 94 | chunk.matchPhotos(downscale=Accuracy, 95 | generic_preselection=True, 96 | reference_preselection=True, 97 | filter_mask=False, 98 | keypoint_limit=Key_Limit, 99 | tiepoint_limit=Tie_Limit) 100 | chunk.alignCameras() 101 | try: 102 | chunk.optimizeCameras(fit_f=True, fit_cx=True, fit_cy=True, fit_b1=False, fit_b2=False, 103 | fit_k1=True, fit_k2=True, fit_k3=True, fit_k4=False, 104 | fit_p1=True, fit_p2=True, fit_p3=False, fit_p4=False, 105 | adaptive_fitting=False, tiepoint_covariance=False) 106 | except NameError: 107 | chunk.optimizeCameras(fit_f=True, fit_cx=True, fit_cy=True, fit_b1=False, fit_b2=False, 108 | fit_k1=True, fit_k2=True, fit_k3=True, fit_k4=False, 109 | fit_p1=True, fit_p2=True, 110 | adaptive_fitting=False, tiepoint_covariance=False) 111 | 112 | 113 | def BuildDenseCloud(chunk, Quality, FilterMode): 114 | Quality = QualityDict[Quality] 115 | try: 116 | chunk.buildDenseCloud(quality=Quality, 117 | filter=FilterMode, 118 | keep_depth=False, 119 | reuse_depth=False) 120 | except NameError: 121 | try: 122 | chunk.buildDepthMaps(quality=Quality, 123 | filter=FilterMode, 124 | reuse_depth=False) 125 | chunk.buildDenseCloud(point_colors=True) 126 | except NameError: 127 | chunk.buildDepthMaps(downscale=Quality, 128 | filter_mode=FilterMode, 129 | reuse_depth=False) 130 | chunk.buildDenseCloud(point_colors=True) 131 | 132 | 133 | def ClassifyGround(chunk, Max_Angle, Cell_Size, Ero_Radius): 134 | DEM_resolution, Image_resolution = utils.GetResolution(chunk) 135 | try: 136 | chunk.dense_cloud.classifyGroundPoints(max_angle=Max_Angle, 137 | max_distance=2 * Image_resolution, 138 | cell_size=Cell_Size, 139 | erosion_radius=Ero_Radius) 140 | except NameError: 141 | chunk.dense_cloud.classifyGroundPoints(max_angle=Max_Angle, 142 | max_distance=2 * Image_resolution, 143 | cell_size=Cell_Size) 144 | 145 | 146 | def BuildModel(chunk, Surface, SurfaceSource, uv_mapping, texture_blending, texture_size): 147 | try: 148 | chunk.buildModel(surface=Surface, 149 | interpolation=ps.Interpolation.EnabledInterpolation, 150 | face_count=ps.FaceCount.HighFaceCount, 151 | source=SurfaceSource, 152 | vertex_colors=True) 153 | 154 | except NameError: 155 | chunk.buildModel(surface_type=Surface, 156 | interpolation=ps.Interpolation.EnabledInterpolation, 157 | face_count=ps.FaceCount.HighFaceCount, 158 | source_data=SurfaceSource, 159 | vertex_colors=True) 160 | 161 | chunk.buildUV(mapping_mode=uv_mapping) 162 | chunk.buildTexture(blending_mode=texture_blending, texture_size=texture_size) 163 | 164 | 165 | def BuildDSM(chunk, ElevationSource, crs): 166 | try: 167 | chunk.buildDem(source=ElevationSource, 168 | interpolation=ps.Interpolation.EnabledInterpolation, 169 | projection=crs) 170 | 171 | except NameError: 172 | proj = ps.OrthoProjection() 173 | proj.crs = crs 174 | chunk.buildDem(source_data=ElevationSource, 175 | interpolation=ps.Interpolation.EnabledInterpolation, 176 | projection=proj) 177 | 178 | 179 | def BuildDEM(chunk, ElevationSource, crs): 180 | try: 181 | chunk.buildDem(source=ElevationSource, 182 | interpolation=ps.Interpolation.EnabledInterpolation, 183 | projection=crs, 184 | classes=[ps.PointClass.Ground]) 185 | 186 | except NameError: 187 | proj = ps.OrthoProjection() 188 | proj.crs = crs 189 | chunk.buildDem(source_data=ElevationSource, 190 | interpolation=ps.Interpolation.EnabledInterpolation, 191 | projection=proj, 192 | classes=[ps.PointClass.Ground]) 193 | 194 | 195 | def BuildMosaic(chunk, BlendingMode, Color_correction, correction_source, Color_balance, crs): 196 | # Reset color correction 197 | for sensor in chunk.sensors: 198 | sensor.vignetting = [] 199 | for camera in chunk.cameras: 200 | camera.vignetting = [] 201 | 202 | try: 203 | chunk.buildOrthomosaic(surface=ps.DataSource.ElevationData, 204 | blending=BlendingMode, 205 | color_correction=Color_correction, 206 | fill_holes=True, 207 | projection=chunk.crs) 208 | except NameError: 209 | proj = ps.OrthoProjection() 210 | proj.crs = crs 211 | if Color_correction: 212 | try: 213 | chunk.calibrateColors(source_data=correction_source, color_balance=Color_balance) 214 | except NameError: 215 | chunk.calibrateColors(source_data=correction_source, white_balance=Color_balance) 216 | try: 217 | chunk.buildOrthomosaic(surface=ps.DataSource.ElevationData, 218 | blending=BlendingMode, 219 | fill_holes=True, 220 | projection=proj) 221 | except NameError: 222 | chunk.buildOrthomosaic(surface_data=ps.DataSource.ElevationData, 223 | blending_mode=BlendingMode, 224 | fill_holes=True, 225 | projection=proj) 226 | 227 | 228 | def run(doc, all_chunk, error_reduction, create_model, **kwargs): 229 | doc.save() 230 | if all_chunk: 231 | chunk_list = doc.chunks 232 | else: 233 | chunk_list = [doc.chunk] 234 | 235 | # Check if copy method is available for Elevation object 236 | # If not, create separate chunk for DEM 237 | if hasattr(ps.Elevation, 'copy') and callable(getattr(ps.Elevation, 'copy')): 238 | DEM_chunk = False 239 | else: 240 | DEM_chunk = True 241 | 242 | for chunk in chunk_list: 243 | doc.chunk = chunk 244 | 245 | if chunk.point_cloud is None: 246 | AlignPhoto(chunk, Accuracy=kwargs['Accuracy'], Key_Limit=kwargs['Key_Limit'], Tie_Limit=kwargs['Tie_Limit'], 247 | QualityFilter=kwargs['QualityFilter'], QualityCriteria=kwargs['QualityCriteria'], 248 | crs=kwargs['crs']) 249 | if error_reduction: 250 | utils.ReduceError_RU(chunk) 251 | utils.ReduceError_PA(chunk) 252 | 253 | else: 254 | 255 | # Skip the chunk if DEM chunk is needed 256 | if DEM_chunk and ('_DEM' in chunk.label): 257 | pass 258 | else: 259 | if error_reduction: 260 | utils.ReduceError_RE(chunk) 261 | # Optimise the camera again with model C 262 | try: 263 | chunk.optimizeCameras(fit_f=True, fit_cx=True, fit_cy=True, fit_b1=False, fit_b2=False, 264 | fit_k1=True, fit_k2=True, fit_k3=True, fit_k4=False, 265 | fit_p1=True, fit_p2=True, fit_p3=False, fit_p4=False, 266 | adaptive_fitting=False, tiepoint_covariance=False) 267 | except NameError: 268 | chunk.optimizeCameras(fit_f=True, fit_cx=True, fit_cy=True, fit_b1=False, fit_b2=False, 269 | fit_k1=True, fit_k2=True, fit_k3=True, fit_k4=False, 270 | fit_p1=True, fit_p2=True, 271 | adaptive_fitting=False, tiepoint_covariance=False) 272 | 273 | if chunk.dense_cloud is None: 274 | BuildDenseCloud(chunk, Quality=kwargs['Quality'], FilterMode=kwargs['FilterMode']) 275 | # Must save before classification. Otherwise it fails. 276 | doc.save() 277 | ClassifyGround(chunk, Max_Angle=kwargs['Max_Angle'], Cell_Size=kwargs['Cell_Size'], 278 | Ero_Radius=kwargs['Ero_Radius']) 279 | doc.save() 280 | if create_model and chunk.model is None: 281 | BuildModel(chunk, Surface=kwargs['Surface'], SurfaceSource=kwargs['SurfaceSource'], 282 | uv_mapping=kwargs['uv_mapping'], texture_blending=kwargs['texture_blending'], 283 | texture_size=kwargs['texture_size']) 284 | doc.save() 285 | 286 | if chunk.elevation is None: 287 | BuildDSM(chunk, ElevationSource=kwargs['ElevationSource'], crs=kwargs['crs']) 288 | chunk.elevation.label = 'DSM' 289 | chunk_DSM = chunk.elevation 290 | 291 | # Create DEM chunk if needed 292 | # Otherwise, create a copy of DEM in the same chunk if possible 293 | if DEM_chunk: 294 | new_chunk = chunk.copy(items=[ps.DataSource.DenseCloudData]) 295 | new_chunk.label = chunk.label + '_DEM' 296 | doc.save() 297 | BuildDEM(new_chunk, ElevationSource=kwargs['ElevationSource'], crs=kwargs['crs']) 298 | else: 299 | chunk.elevation.copy() 300 | chunk.elevations[-1].label = 'DEM' 301 | chunk.elevation = chunk.elevations[-1] 302 | doc.save() 303 | BuildDEM(chunk, ElevationSource=kwargs['ElevationSource'], crs=kwargs['crs']) 304 | # Set DSM to default elevation object for orthomosaic creation 305 | chunk.elevation = chunk_DSM 306 | 307 | doc.save() 308 | 309 | # Change the active chunk back 310 | doc.chunk = chunk 311 | 312 | if chunk.orthomosaic is None: 313 | BuildMosaic(chunk, BlendingMode=kwargs['BlendingMode'], Color_correction=kwargs['Color_correction'], 314 | correction_source=kwargs['correction_source'], Color_balance=kwargs['Color_balance'], 315 | crs=kwargs['crs']) 316 | doc.save() 317 | --------------------------------------------------------------------------------