├── Agisoft Metashape Python Scripts - Usage Notes.docx ├── LICENSE ├── README.md ├── _Workflow-IntheRound ├── Metashape_PythonScripts_01-CreateProject-SaveAs.py ├── Metashape_PythonScripts_02-LoadPhotos-AnalyzeImages-CameraGroups.py ├── Metashape_PythonScripts_03-MatchPhotos-AlignCameras.py ├── Metashape_PythonScripts_04-Part_01-OptimizeCameras-FilterPointCloud.py ├── Metashape_PythonScripts_04-Part_02_DetectMarkers-OptimizeMarkerError.py ├── Metashape_PythonScripts_04-Part_03-Optimizecameras-FilterPointCloud-TenPercent.py ├── Metashape_PythonScripts_05-CreateMasksFromSparseCloud.py ├── Metashape_PythonScripts_06-BuildDepthMaps-BuildDenseCloud.py ├── Metashape_PythonScripts_07-BuildModel-DecimateModel.py ├── Metashape_PythonScripts_08-BuildTextures.py ├── Metashape_PythonScripts_09-ExportModels-withTextures.py └── Metashape_PythonScripts_10-ExportDenseCloud.py └── _singleActions-AsScripts ├── 01_GetProjectFileName.py ├── 02_LoadPhotosInNewChunk.py ├── 03_CreateCameraGroups.py ├── 04_AnalyzePhotos.py ├── 05_MatchPhotos.py ├── 06_AlignPhotos.py ├── 07_DuplicateActiveChunk.py ├── 08_GradualSelection-ReconstructionUncertainty.py ├── 09_GradualSelection-ProjectionAccuracy.py ├── 10_GradualSelection-ReprojectionError.py ├── 11_OptimizeCameras-Basic.py ├── 12_OptimizeCameras-Full.py ├── 13_DetectMarkers-CircularCoded12bit.py ├── 14_DetectMarkers-CircularNonCoded-CrossTarget.py ├── 15_RemoveMarkers-LowProjections.py ├── 16_OptimizeMarkers-ProjectionError.py ├── 17_CreateModel-FromSparseCloud.py ├── 18_ImportMasks-ModelAsSource.py ├── 19_Export-ImageMasks.py ├── 20_BuildDepthMaps.py ├── 21_BuildDenseCloud.py ├── 22_BuildModel-DenseCloud-HighFaceCount.py ├── 23_BuildModel-DenseCloud-CustomFaceCount.py ├── 24_BuildModel-DepthMaps-HighFaceCount.py ├── 25_BuildModel-DepthMaps-CustomFaceCount.py ├── 26_DecimateModel-CustomFaceCount.py ├── 27_BuildUV-UnwrapModel.py ├── 28_BuildTexture-DiffuseMap.py ├── 29_BuildTexture-NormalMap.py ├── 30_BuildTexture-OcclusionMap.py ├── 31_ExportModel-WithTextures.py ├── 32_Export-DensePointCloud.py └── 33_GenerateMasks-FromModel.py /Agisoft Metashape Python Scripts - Usage Notes.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/campbell-ja/MetashapePythonScripts/b4a54e49558a8a7d1c5dc2327f878a8c354bbe58/Agisoft Metashape Python Scripts - Usage Notes.docx -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MetashapePythonScripts 2 | Set of Python scripts for use with Agisoft Metashape. 3 | 4 | The scripts held within this repository are meant as a learning tool and are very much a work in progress. 5 | 6 | *"Single Action"* Scripts covers a single action and when combined it is possible to create a full workflow from project creation to the final output of a 3D model. 7 | 8 | *"Workflow - In the Round"* Scripts are a complete processing workflow where multiple single action scripts have been combined into relevant parts. 9 | 10 | **Note** The settings and variable values held within each script generally run the action at the highest quality level. Please be aware of this and make any changes you may need to match your processing hardware. 11 | -------------------------------------------------------------------------------- /_Workflow-IntheRound/Metashape_PythonScripts_01-CreateProject-SaveAs.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | # Use this as a learning tool only. 3 | # I am not responsible for any damage to data or hardware if the script is not properly utilized. 4 | # Following Code tested and based on Metashape Pro 1.6.2 using Windows 10 Pro 5 | 6 | """ # # # # # SET UP THE WORKING ENVIRONMENT # # # # # """ 7 | 8 | # import Metashape library module 9 | import Metashape 10 | 11 | # create a reference to the current project 12 | # done via the Document Class 13 | doc = Metashape.app.document 14 | 15 | # prompt the user with the 'save as' gui and store the project file name 16 | # returns the full path as is. 17 | projectFileName = Metashape.app.getSaveFileName("Please input the Project File Name", "Metashape Project (*.psx)") 18 | try: 19 | doc.save(projectFileName) 20 | except RuntimeError: 21 | Metashape.app.messageBox("Can't save project") 22 | 23 | 24 | """ Save the Project """ 25 | # save document assuming the file has already been saved at least once. 26 | # if the document has NOT been saved previously.. 27 | # then provide the path between the (). 28 | doc.save() -------------------------------------------------------------------------------- /_Workflow-IntheRound/Metashape_PythonScripts_02-LoadPhotos-AnalyzeImages-CameraGroups.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | # Use this as a learning tool only. 3 | # I am not responsible for any damage to data or hardware if the script is not properly utilized. 4 | # Following Code tested and based on Metashape Pro 1.6.2 using Windows 10 Pro 5 | 6 | """ # # # # # SET UP THE WORKING ENVIRONMENT # # # # # """ 7 | 8 | # import Metashape library module 9 | import Metashape 10 | 11 | # create a reference to the current project 12 | # done via the Document Class 13 | doc = Metashape.app.document 14 | 15 | """ # # # # # START THE MAIN PIPELINE # # # # # """ 16 | 17 | """ ADD Photos to a new Chunk """ 18 | # create array/list of images via user input gui 19 | images = Metashape.app.getOpenFileNames() 20 | 21 | # create a new chunk 22 | newChunk = doc.addChunk() 23 | 24 | # get full list of chunks 25 | chunkList = doc.chunks 26 | 27 | # rename the new chunk,but only if it exists 28 | # also add the integer position in chunks list 29 | # this ensures each time the script runs each chunk has a unique name. 30 | if newChunk in chunkList: 31 | newChunk.label = 'pyChunk_' + str(len(chunkList)-1) 32 | 33 | # swap the reference for the currently active chunk 34 | activeChunk = newChunk 35 | 36 | # add images to chunk from array/list created earlier 37 | activeChunk.addPhotos(images) 38 | 39 | """ Create new Camera Groups """ 40 | # create a camera group for each camera sensor focal length 41 | # each sensor is found under the 'Camera Calibrations...' menu option 42 | # and is actually sorted and grouped by the lens and horizontal/vertical position of camera 43 | for sensor in activeChunk.sensors: 44 | # create a camera group for the current sensor focal length 45 | cameraGroup = activeChunk.addCameraGroup() 46 | # rename the group to match the sensor focal length 47 | cameraGroup.label = str(str(int(sensor.focal_length)) + " mm") 48 | # loop through cameras and move any camera with matching sensor focal_length to new camera group 49 | for camera in activeChunk.cameras: 50 | if camera.sensor.focal_length == sensor.focal_length: 51 | camera.group = cameraGroup 52 | 53 | # Estimate image quality 54 | # this populates the 'Quality' column in the photos pane, under 'details' view 55 | # this is not indicative of the actual image quality and is just here for example 56 | activeChunk.analyzePhotos() 57 | 58 | """ Save the Project """ 59 | # save document assuming the file has already been saved at least once. 60 | # if the document has NOT been saved previously.. 61 | # then provide the path between the (). 62 | doc.save() 63 | -------------------------------------------------------------------------------- /_Workflow-IntheRound/Metashape_PythonScripts_03-MatchPhotos-AlignCameras.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | # Use this as a learning tool only. 3 | # I am not responsible for any damage to data or hardware if the script is not properly utilized. 4 | # Following Code tested and based on Metashape Pro 1.6.2 using Windows 10 Pro 5 | 6 | """ # # # # # SET UP THE WORKING ENVIRONMENT # # # # # """ 7 | 8 | 9 | # import Metashape library module 10 | import Metashape 11 | 12 | # create a reference to the current project 13 | # done via the Document Class 14 | doc = Metashape.app.document 15 | 16 | # set reference to currently selected chunk 17 | activeChunk = Metashape.app.document.chunk 18 | 19 | 20 | """ # # # # # START THE MAIN PIPELINE # # # # # """ 21 | # match photos for newest chunk 22 | # 'downscale' controls the quality level. Options are 0=Highest, 1=High, 2=Medium, 3=Low, 4=Lowest 23 | # WARNING - downscale quality 0=Highest, up-scales the images. 24 | # the settings here also effect the .alignCameras() method 25 | activeChunk.matchPhotos\ 26 | ( 27 | downscale=1, 28 | generic_preselection=False, 29 | reference_preselection=True, 30 | reference_preselection_mode=Metashape.ReferencePreselectionSource, 31 | filter_mask=False, 32 | mask_tiepoints=False, 33 | keypoint_limit=100000, 34 | tiepoint_limit=25000, 35 | keep_keypoints=False, 36 | guided_matching=False, 37 | reset_matches=False, 38 | subdivide_task=True, 39 | workitem_size_cameras=20, 40 | workitem_size_pairs=80, 41 | max_workgroup_size=100 42 | ) 43 | 44 | # align the matched image pairs 45 | activeChunk.alignCameras() 46 | 47 | """ Save the Project """ 48 | # save document assuming the file has already been saved at least once. 49 | # if the document has NOT been saved previously.. 50 | # then provide the path between the (). 51 | doc.save() 52 | -------------------------------------------------------------------------------- /_Workflow-IntheRound/Metashape_PythonScripts_04-Part_01-OptimizeCameras-FilterPointCloud.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | # this script references https://www.agisoft.com/forum/index.php?topic=10564.msg47949#msg47949 3 | # Use this as a learning tool only. 4 | # I am not responsible for any damage to data or hardware if the script is not properly utilized. 5 | # Following Code tested and based on Metashape Pro 1.6.2 using Windows 10 Pro 6 | """ 7 | Avoid removing too many points, as it will destroy the alignment 8 | Also, if you see ripple like effects in the data then you've iterated too many times 9 | 10 | """ 11 | 12 | """ 13 | # # # # # # # # # # # # # # # 14 | SET UP THE WORKING ENVIRONMENT 15 | # # # # # # # # # # # # # # # 16 | """ 17 | 18 | import Metashape 19 | 20 | """create a reference to the current project""" 21 | doc = Metashape.app.document 22 | # create reference for list of chunks in project 23 | chunkList = Metashape.app.document.chunks 24 | # set reference to the currently selected chunk 25 | activeChunk = Metashape.app.document.chunk 26 | 27 | """ Duplicate Chunk to preserve original Alignment """ 28 | # create a reference to the original chunk name 29 | activeChunk_label = activeChunk.label 30 | # set the label (name) of the original alignment 31 | activeChunk.label = str(activeChunk.label + "-Source") 32 | # Duplicate the source chunk 33 | activeChunk.copy() 34 | # update reference for list of chunks in project 35 | chunkList = Metashape.app.document.chunks 36 | # set reference to the last chunk in list, assuming this is the most recent copied chunk 37 | dupeChunk = Metashape.app.document.chunks[len(Metashape.app.document.chunks)-1] 38 | # rename the new copied chunk to match the chunk naming structure 39 | if dupeChunk in chunkList: 40 | dupeChunk.label = str(activeChunk_label) + "-Filtered" 41 | 42 | """Set up point cloud filter and Gradual Selection options and values""" 43 | # create a reference to the point cloud, and filter method 44 | selection = Metashape.PointCloud.Filter() 45 | # create list of gradual selection filter options and their threshold values. set the values in variables above this. 46 | # options = Metashape.PointCloud.Filter(.ReprojectionError,.ReconstructionUncertainty,.ImageCount,.ProjectionAccuracy) 47 | filterOptions = \ 48 | [ 49 | [Metashape.PointCloud.Filter.ReconstructionUncertainty, 10], 50 | [Metashape.PointCloud.Filter.ProjectionAccuracy, 4.0], 51 | [Metashape.PointCloud.Filter.ProjectionAccuracy, 3.0], 52 | ] 53 | 54 | 55 | """ Loop through gradual Selection list and 56 | apply filter, remove points, optimize cameras; 57 | update the reference pane settings in preparation for setting up scale bars; 58 | repeat Reprojection Error selection until MAX error is less than 0.3 59 | """ 60 | 61 | for index, filterOption in enumerate(filterOptions): 62 | 63 | print("processing filter index " + str(index) + ", " + str(filterOption[0])) 64 | 65 | # only run if current filterOption is not reprojection error, otherwise skip ahead 66 | if filterOption[0] is not Metashape.PointCloud.Filter.ReprojectionError: 67 | # perform filter selection 68 | selection.init\ 69 | ( 70 | dupeChunk, 71 | criterion=filterOption[0] 72 | ) 73 | # selects all points above the passed threshold value 74 | selection.selectPoints(filterOption[1]) 75 | # remove selected/filtered points above the passed threshold value 76 | selection.removePoints(filterOption[1]) 77 | # optimize cameras using basic settings 78 | dupeChunk.optimizeCameras\ 79 | ( 80 | fit_f=True, 81 | fit_cx=True, 82 | fit_cy=True, 83 | fit_b1=False, 84 | fit_b2=False, 85 | fit_k1=True, 86 | fit_k2=True, 87 | fit_k3=True, 88 | fit_k4=False, 89 | fit_p1=True, 90 | fit_p2=True, 91 | fit_corrections=False, 92 | adaptive_fitting=False, 93 | tiepoint_covariance=False 94 | ) 95 | 96 | """ update the reference pane settings to match CHI scale bar workflow """ 97 | # this is done right before processing reprojection error and creating scale bars. 98 | 99 | # set 'Scale Bar Accuracy' to 0.0001 100 | dupeChunk.scalebar_accuracy = 0.0001 101 | # set 'Tie Point Accuracy' to 0.1 102 | dupeChunk.tiepoint_accuracy = 0.1 103 | # set 'Marker Projection Accuracy' to 0.1 104 | dupeChunk.marker_projection_accuracy = 0.1 105 | 106 | # Save 107 | doc.save() 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /_Workflow-IntheRound/Metashape_PythonScripts_04-Part_02_DetectMarkers-OptimizeMarkerError.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | # Use this as a learning tool only. 3 | # I am not responsible for any damage to data or hardware if the script is not properly utilized. 4 | # Following Code tested and based on Metashape Pro 1.6.2 using Windows 10 Pro 5 | 6 | """ With Help from: 7 | https://www.agisoft.com/forum/index.php?topic=11075.msg50002#msg50002 8 | https://www.agisoft.com/forum/index.php?topic=3618.msg18952#msg18952 9 | """ 10 | 11 | import Metashape 12 | 13 | """create a reference to the current project""" 14 | doc = Metashape.app.document 15 | # set reference to the active chunk 16 | activeChunk = Metashape.app.document.chunk 17 | 18 | """Detect Markers""" 19 | # Detect Circular 12bit coded markers 20 | # Coded target options: [CircularTarget12bit, CircularTarget14bit, CircularTarget16bit, CircularTarget20bit] 21 | activeChunk.detectMarkers\ 22 | ( 23 | target_type=Metashape.TargetType.CircularTarget12bit, 24 | tolerance=25, 25 | filter_mask=False, 26 | inverted=False, 27 | noparity=False, 28 | maximum_residual=5, 29 | minimum_size=0, 30 | minimum_dist=5 31 | ) 32 | 33 | # Detect Cross non-coded markers 34 | # Non-coded target options: [CircularTarget, CrossTarget] 35 | activeChunk.detectMarkers\ 36 | ( 37 | target_type=Metashape.TargetType.CrossTarget, 38 | tolerance=25, 39 | filter_mask=False, 40 | inverted=False, 41 | noparity=False, 42 | maximum_residual=5, 43 | minimum_size=0, 44 | minimum_dist=5 45 | ) 46 | 47 | """Remove Markers with less than X number of projections""" 48 | for marker in activeChunk.markers: 49 | # if marker has less than 9 projections, remove from chunk 50 | if len(marker.projections) < 9: 51 | activeChunk.remove(marker) 52 | #print(" ^^^^ Removed: " + marker.label + " for having only " + str(len(marker.projections)) + " projections ^^^^ ") 53 | 54 | 55 | """Optimize Marker Error Per Camera""" 56 | # print out the current projection count for each marker 57 | for m in activeChunk.markers: 58 | print("Marker: " + m.label + " has " + str(len(m.projections)) + " projections") 59 | 60 | 61 | # for each marker in list of markers for active chunk, remove markers from each camera with error greater than 0.5 62 | for marker in activeChunk.markers: 63 | # skip marker if it has no position 64 | if not marker.position: 65 | print(marker.label + " is not defined in 3D, skipping...") 66 | continue 67 | 68 | # reference the position of the marker 69 | position = marker.position 70 | 71 | # for each camera in the list of cameras for current marker 72 | for camera in marker.projections.keys(): 73 | if not camera.transform: 74 | continue 75 | """print("marker is " + str(marker.label)) 76 | print("camera is " + str(camera)) 77 | print("marker position = " + str(position)) 78 | print("proj = " + str(marker.projections[camera].coord)) 79 | print("reproj = " + str(camera.project(position)))""" 80 | proj = marker.projections[camera].coord 81 | reproj = camera.project(position) 82 | error = (proj - reproj).norm() 83 | 84 | # remove markers with projection error greater than 0.5 85 | if error > 0.5: 86 | # set the current marker projection to none for current camera/marker combination 87 | marker.projections[camera] = None 88 | #print("**** Removed: " + str(marker) + " with error of : " + str(error) + " ****") 89 | 90 | """Remove Markers with less than X number of projections""" 91 | for marker in activeChunk.markers: 92 | # if marker has less than 9 projections, remove from chunk 93 | if len(marker.projections) < 9: 94 | activeChunk.remove(marker) 95 | #print(" ^^^^ Removed: " + marker.label + " for having only " + str(len(marker.projections)) + " projections ^^^^ ") 96 | 97 | # print out the adjusted projection count for each marker 98 | for marker in activeChunk.markers: 99 | print("Marker: " + marker.label + " now has " + str(len(marker.projections)) + " projections") 100 | 101 | 102 | # save 103 | doc.save() 104 | -------------------------------------------------------------------------------- /_Workflow-IntheRound/Metashape_PythonScripts_04-Part_03-Optimizecameras-FilterPointCloud-TenPercent.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | # this script references https://www.agisoft.com/forum/index.php?topic=10564.msg47949#msg47949 3 | # Use this as a learning tool only. 4 | # I am not responsible for any damage to data or hardware if the script is not properly utilized. 5 | # Following Code tested and based on Metashape Pro 1.6.2 using Windows 10 Pro 6 | """ 7 | Avoid removing too many points, as it will destroy the alignment 8 | Also, if you see ripple like effects in the data then you've iterated too many times 9 | 10 | """ 11 | 12 | """ With Help from: 13 | https://www.agisoft.com/forum/index.php?topic=12725.msg56460#msg56460 14 | """ 15 | 16 | import Metashape 17 | 18 | # import just the specific 'def' (function) from helper scripts 19 | from Metashape_PythonScripts_helper_01_DisableLowCameraProjections import DisableCameras_WithLowProjections 20 | 21 | """create a reference to the current project""" 22 | doc = Metashape.app.document 23 | # create reference for list of chunks in project 24 | chunkList = Metashape.app.document.chunks 25 | # set reference to the currently selected 26 | # chunk -- this should be the duplicated chunk from part-01 27 | activeChunk = Metashape.app.document.chunk 28 | # percentage values for selection 29 | THRESHOLD = 10 30 | 31 | # create a reference to the point cloud, and filter method 32 | point_Filter = Metashape.PointCloud.Filter() 33 | # initialize the filter for Reprojection Error 34 | point_Filter.init\ 35 | ( 36 | activeChunk, 37 | criterion=Metashape.PointCloud.Filter.ReprojectionError 38 | ) 39 | # get the initial max RMS error value from the filter 40 | max_error = point_Filter.max_value 41 | # copy the point cloud filter values into new variable 42 | filterValues = point_Filter.values.copy() 43 | # sort the point cloud filter values 44 | filterValues.sort() 45 | # take 10 percent of points, with worst Reprojection Error, create value 46 | thresh = filterValues[int(len(filterValues) * (1 - THRESHOLD / 100))] 47 | 48 | # while the MAX RMS error is greater than 0.301, keep iterating reprojection error filter and selection 49 | while max_error > 0.301: 50 | # selects all points above the passed threshold value 51 | point_Filter.selectPoints(thresh) 52 | # remove selected/filtered points above the passed threshold value 53 | point_Filter.removePoints(thresh) 54 | # optimize cameras using full settings 55 | activeChunk.optimizeCameras \ 56 | ( 57 | fit_f=True, 58 | fit_cx=True, 59 | fit_cy=True, 60 | fit_b1=True, 61 | fit_b2=True, 62 | fit_k1=True, 63 | fit_k2=True, 64 | fit_k3=True, 65 | fit_k4=False, 66 | fit_p1=True, 67 | fit_p2=True, 68 | fit_corrections=False, 69 | adaptive_fitting=False, 70 | tiepoint_covariance=False 71 | ) 72 | # recreate a reference to the point cloud, and filter method 73 | point_Filter = Metashape.PointCloud.Filter() 74 | # re-initialize the filter for Reprojection Error 75 | point_Filter.init \ 76 | ( 77 | activeChunk, 78 | criterion=Metashape.PointCloud.Filter.ReprojectionError 79 | ) 80 | # get the initial max RMS error value from the filter 81 | max_error = point_Filter.max_value 82 | # copy the point cloud filter values into new variable 83 | filterValues = point_Filter.values.copy() 84 | # sort the point cloud filter values 85 | filterValues.sort() 86 | # take 10 percent of points, with worst Reprojection Error, create value 87 | thresh = filterValues[int(len(filterValues) * (1 - THRESHOLD / 100))] 88 | print("MAX RMS ERROR IS: " + str(max_error)) 89 | 90 | """ disable cameras with low camera projections below 200 91 | # but this only disables the lowest 10% of all cameras""" 92 | DisableCameras_WithLowProjections(activeChunk) 93 | 94 | """ reset region ( a.k.a reset the bounding box ) """ 95 | # hopefully, this will size the bounding box region proportional to the filtered point cloud 96 | activeChunk.resetRegion() 97 | 98 | 99 | # save the document 100 | doc.save() 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /_Workflow-IntheRound/Metashape_PythonScripts_05-CreateMasksFromSparseCloud.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | # this script references https://www.agisoft.com/forum/index.php?topic=10564.msg47949#msg47949 3 | # Use this as a learning tool only. 4 | # I am not responsible for any damage to data or hardware if the script is not properly utilized. 5 | # Following Code tested and based on Metashape Pro 1.6.2 using Windows 10 Pro 6 | 7 | 8 | """ With Help from: 9 | https://www.agisoft.com/forum/index.php?topic=12027.msg53791#msg53791 10 | """ 11 | 12 | """ 13 | # # # # # # # # # # # # # # # 14 | SET UP THE WORKING ENVIRONMENT 15 | # # # # # # # # # # # # # # # 16 | """ 17 | 18 | import Metashape 19 | 20 | """create a reference to the current project""" 21 | doc = Metashape.app.document 22 | # create reference for list of chunks in project 23 | chunkList = Metashape.app.document.chunks 24 | # set reference to the currently selected chunk -- this should be the duplicated chunk from part-01 25 | activeChunk = Metashape.app.document.chunk 26 | 27 | # must include this line between each attempt to build a model. or it overwrites last created model 28 | activeChunk.model = None 29 | # using optimized sparse cloud, create lower resolution model 30 | activeChunk.buildModel\ 31 | ( 32 | surface_type=Metashape.Arbitrary, 33 | interpolation=Metashape.EnabledInterpolation, 34 | face_count=Metashape.FaceCount.LowFaceCount, 35 | face_count_custom=200000, 36 | source_data=Metashape.PointCloudData, 37 | vertex_colors=True, 38 | vertex_confidence=True, 39 | volumetric_masks=False, 40 | keep_depth=True, 41 | trimming_radius=10, 42 | subdivide_task=True, 43 | workitem_size_cameras=20, 44 | max_workgroup_size=100 45 | ) 46 | 47 | # import masks function using lower resolution model as source for all cameras in chunk 48 | activeChunk.importMasks\ 49 | ( 50 | path='{filename}_mask.png', 51 | source=Metashape.MaskSourceModel, 52 | operation=Metashape.MaskOperationReplacement, 53 | tolerance=10 54 | ) 55 | 56 | 57 | # get the current Chunks label ( name ) 58 | currentChunkLabel = activeChunk.label 59 | 60 | # get the current (saved) project's parent folder URL via python3 pathLib 61 | # this path variable is used when exporting the 3D model later in the script. 62 | # 'parent' will return the parent folder the project lives in 63 | # 'name' will return the saved project name and extension 64 | # 'stem' will return just the project name without extension 65 | from pathlib import Path 66 | parentFolderPath = str(Path(Metashape.app.document.path).parent) 67 | print("parent Folder is : " + parentFolderPath) 68 | 69 | # set reference to the output folders as string 70 | outputFolder = Path(str(parentFolderPath) + "\\" + "_Output") 71 | outputChunkFolder = Path(str(outputFolder) + "\\" + "_" + str(currentChunkLabel)) 72 | outputMaskfolder = Path(str(outputChunkFolder) + "\\" + "_Masks") 73 | 74 | print("output folder: " + str(outputFolder)) 75 | print("output chunk folder: " + str(outputChunkFolder)) 76 | print("model output folder is: " + str(outputMaskfolder)) 77 | 78 | # create an 'output' sub-folder for exported data from project 79 | # also create sub-folder for model export within 'output' sub-folder 80 | # this method will create the folder if doesnt exist, and also do nothing if it does exist 81 | Path(outputFolder).mkdir(exist_ok=True) 82 | Path(outputChunkFolder).mkdir(exist_ok=True) 83 | Path(outputMaskfolder).mkdir(exist_ok=True) 84 | 85 | # export masks to output mask folder 86 | # this uses the Metashape Task class, otherwise loop through every camera in chunk and save mask as image file 87 | # create a reference to the Tasks ExportMasks method 88 | mask_task = Metashape.Tasks.ExportMasks() 89 | # define which cameras to export masks for 90 | mask_task.cameras = activeChunk.cameras 91 | # define the output path for the exported mask files 92 | mask_task.path = str(str(outputMaskfolder) + "\\" + "{filename}.png") 93 | # activate the task for the active chunk to export the masks 94 | mask_task.apply(object=activeChunk) 95 | 96 | # delete lower resolution model 97 | activeChunk.remove(activeChunk.models[0]) 98 | 99 | # save document 100 | doc.save() -------------------------------------------------------------------------------- /_Workflow-IntheRound/Metashape_PythonScripts_06-BuildDepthMaps-BuildDenseCloud.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | # Use this as a learning tool only. 3 | # I am not responsible for any damage to data or hardware if the script is not properly utilized. 4 | # Following Code tested and based on Metashape Pro 1.6.2 using Windows 10 Pro 5 | 6 | """ # # # # # SET UP THE WORKING ENVIRONMENT # # # # # """ 7 | 8 | 9 | # import Metashape library module 10 | import Metashape 11 | 12 | # create a reference to the current project 13 | # done via the Document Class 14 | doc = Metashape.app.document 15 | 16 | # set reference to the currently selected chunk 17 | activeChunk = Metashape.app.document.chunk 18 | 19 | 20 | """ # # # # # START THE MAIN PIPELINE # # # # # """ 21 | 22 | """ Build Dense Cloud Process""" 23 | # build depth maps 24 | # downscale = # 1=UltraHigh, 2=High, 4=Medium, 8=low 25 | # Ultrahigh setting loads the image data at full resolution, High downsamples x2, medium downsamples x4, low x8 26 | activeChunk.buildDepthMaps\ 27 | ( 28 | downscale=1, 29 | filter_mode=Metashape.AggressiveFiltering, 30 | reuse_depth=False, 31 | max_neighbors=-1, 32 | subdivide_task=True, 33 | workitem_size_cameras=20, 34 | max_workgroup_size=100 35 | ) 36 | 37 | # save now in case the machine freezes up during dense cloud processing. then re-use depth maps upon restart 38 | doc.save() 39 | 40 | # build dense cloud 41 | # the quality of the dense cloud is determined by the quality of the depth maps 42 | # "max_neighbors" value of '-1' will evaluate ALL IMAGES in parallel. 200-300 is good when there is a lot of image overlap. 43 | # setting this value will fix an issue where there is excessive 'fuzz' in the dense cloud. the default value is 100. 44 | activeChunk.buildDenseCloud\ 45 | ( 46 | point_colors=True, 47 | point_confidence=True, 48 | keep_depth=True, 49 | max_neighbors=300, 50 | subdivide_task=True, 51 | workitem_size_cameras=20, 52 | max_workgroup_size=100 53 | ) 54 | 55 | """ Save the Project """ 56 | # save document assuming the file has already been saved at least once. 57 | # if the document has NOT been saved previously.. 58 | # then provide the path between the (). 59 | doc.save() 60 | 61 | -------------------------------------------------------------------------------- /_Workflow-IntheRound/Metashape_PythonScripts_07-BuildModel-DecimateModel.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | # Use this as a learning tool only. 3 | # I am not responsible for any damage to data or hardware if the script is not properly utilized. 4 | # Following Code tested and based on Metashape Pro 1.6.2 using Windows 10 Pro 5 | 6 | """ # # # # # SET UP THE WORKING ENVIRONMENT # # # # # """ 7 | 8 | # import Metashape library module 9 | import Metashape 10 | 11 | # create a reference to the current project 12 | # done via the Document Class 13 | doc = Metashape.app.document 14 | 15 | # set reference to the currently selected chunk 16 | activeChunk = Metashape.app.document.chunk 17 | 18 | 19 | """ # # # # # START THE MAIN PIPELINE # # # # # """ 20 | 21 | """ Build Model Process """ 22 | # create array of desired face count quality for models 23 | # Options:['HighFaceCount','MediumFaceCount','LowFaceCount','CustomFaceCount'] 24 | modelQuality = [Metashape.FaceCount.HighFaceCount] 25 | 26 | # set the desired face counts and respective model name (label) for each decimated model 27 | targetModel = [[0, "_DefaultFaceCount"], [8000001,"_MASTER"], [1000001,"_RENDER"], [500001,"_PRINT"], [200001,"_WEB"]] 28 | 29 | # build the hi-res model , or build a model for each model quality in modelQuality list above 30 | # providing the 'num' index value via the enumerate() method 31 | for num, qual in enumerate(modelQuality): 32 | # must include this line between each attempt to build a model. or it overwrites last created model 33 | activeChunk.model = None 34 | # build model for current face count quality level in array modelQuality via i 35 | activeChunk.buildModel\ 36 | ( 37 | surface_type=Metashape.Arbitrary, 38 | interpolation=Metashape.EnabledInterpolation, 39 | face_count=qual, 40 | face_count_custom=200000, 41 | source_data=Metashape.DenseCloudData, 42 | vertex_colors=True, 43 | vertex_confidence=True, 44 | volumetric_masks=False, 45 | keep_depth=True, 46 | trimming_radius=10, 47 | subdivide_task=True, 48 | workitem_size_cameras=20, 49 | max_workgroup_size=100 50 | ) 51 | # update the label (aka name) for the model using the index 'num' 52 | activeChunk.models[num].label = str(activeChunk.label) + str(targetModel[0][1]) 53 | 54 | # get the face count for hi-res model, assuming its the first model in the list 'models' 55 | modelFaceCount = [len(activeChunk.models[0].faces)] 56 | 57 | """ Decimate Hi-Res Model to create derivatives """ 58 | # create index to iterate through available models. this needs to be manual as the number of models will be unknown 59 | index = 0 60 | # iterate through the target models list, starting at index one. skipping the hi-res option 61 | for fCount in targetModel[1:]: 62 | print("current ModelFaceCount is: " + str(modelFaceCount)) 63 | print("target fCount[0] is: " + str(fCount[0])) 64 | print("loop index is: " + str(index)) 65 | 66 | # if the current model face count is less than decimate target goal, then skip decimation 67 | if modelFaceCount[index] < fCount[0]: 68 | continue 69 | 70 | # if the current face count is greater than the target face count, then proceed 71 | if modelFaceCount[index] >= fCount[0]: 72 | print("Decimating the Current Model") 73 | # must include this line between each attempt to create a model. or it overwrites last created model 74 | activeChunk.model = None 75 | # decimate the most recent model 76 | activeChunk.decimateModel\ 77 | ( 78 | face_count=fCount[0], 79 | asset=activeChunk.models[index] 80 | ) 81 | # insert the new models face count into the modelFaceCount list 82 | modelFaceCount.append(len(activeChunk.models[index + 1].faces)) 83 | #iterate to the next model now that we have created it 84 | index = index + 1 85 | 86 | 87 | """Rename the Models to match their intended usage""" 88 | # get the difference between the length of the models list and target output list 89 | diff = (len(targetModel) - len(activeChunk.models)) 90 | 91 | # loop through list of models and update the label 92 | for val, model in enumerate(activeChunk.models): 93 | # rename the new model to match Face Count usage by calling the correct index in list targetModel[] 94 | # only change name IF not hi-res model, aka if index is One or greater 95 | if not (str(activeChunk.models[val].label))==(str(activeChunk.label) + "_DefaultFaceCount"): 96 | # update the model name(label), skipping the HighFaceCount Model 97 | # also add the difference between target models length and model count to account for skipped decimation 98 | model.label = str(activeChunk.label) + str(targetModel[(val+diff)][1]) 99 | print("Naming model: " + str(model.label)) 100 | print("val is: " + str(val)) 101 | print("diff is: " + str(diff)) 102 | 103 | """ Save the Project """ 104 | # save document assuming the file has already been saved at least once. 105 | # if the document has NOT been saved previously.. 106 | # then provide the path between the (). 107 | doc.save() -------------------------------------------------------------------------------- /_Workflow-IntheRound/Metashape_PythonScripts_08-BuildTextures.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | # Use this as a learning tool only. 3 | # I am not responsible for any damage to data or hardware if the script is not properly utilized. 4 | # Following Code tested and based on Metashape Pro 1.6.2 using Windows 10 Pro 5 | 6 | """ # # # # # SET UP THE WORKING ENVIRONMENT # # # # # """ 7 | 8 | 9 | # import Metashape library module 10 | import Metashape 11 | 12 | # create a reference to the current project 13 | # done via the Document Class 14 | doc = Metashape.app.document 15 | 16 | # set reference to the currently selected chunk 17 | activeChunk = Metashape.app.document.chunk 18 | 19 | 20 | """ # # # # # START THE MAIN PIPELINE # # # # # """ 21 | 22 | """ Build Texture Process """ 23 | # create array of desired texture type for low-res models 24 | # Options:n ['DiffuseMap', 'NormalMap', 'OcclusionMap'] 25 | textureType = [Metashape.Model.NormalMap, Metashape.Model.OcclusionMap] 26 | 27 | # loop through each model and uv unwrap, then build texture. 28 | for index, model in enumerate(activeChunk.models): 29 | # select the current model as the active model, as we iterate through the available models 30 | activeChunk.model = model 31 | # UV unwrap model 32 | activeChunk.buildUV\ 33 | ( 34 | mapping_mode=Metashape.GenericMapping, 35 | page_count=1, 36 | adaptive_resolution=False 37 | ) 38 | # build higher resolution 8192k texture maps for both hi-res and MASTER 3D models 39 | # this assumes they are in the 0 and 1 position of the models list 40 | if index <= 1: 41 | # build diffuse texture map 42 | activeChunk.buildTexture\ 43 | ( 44 | blending_mode=Metashape.MosaicBlending, 45 | texture_size=8192, 46 | fill_holes=True, 47 | ghosting_filter=True, 48 | texture_type=Metashape.Model.DiffuseMap, 49 | transfer_texture=True 50 | ) 51 | # build 4096k resolution texture maps for remaining models 52 | else: 53 | # build diffuse texture map 54 | activeChunk.buildTexture \ 55 | ( 56 | blending_mode=Metashape.MosaicBlending, 57 | texture_size=4096, 58 | fill_holes=True, 59 | ghosting_filter=True, 60 | texture_type=Metashape.Model.DiffuseMap, 61 | transfer_texture=True 62 | ) 63 | 64 | # loop through textureType array and build texture for each texture type 65 | # source_model= determines which model to use for baking texture maps, 66 | # this uses the previous model quality to build the normal and diffuse map, to avoid errors in the maps from 67 | # using too high of a face count as a source for low poly meshes 68 | # Options: NormalMap(requires 2 models), Occlusion Map 69 | for tex in textureType: 70 | # you have to create the texture in memory before filling with data 71 | activeChunk.model.addTexture(tex) 72 | # build the texture, pulling the texture_type from the array with 't' 73 | activeChunk.buildTexture\ 74 | ( 75 | blending_mode=Metashape.MosaicBlending, 76 | texture_size=4096, 77 | fill_holes=True, 78 | ghosting_filter=True, 79 | texture_type=tex, 80 | source_model=activeChunk.models[index-1], 81 | transfer_texture=True 82 | ) 83 | 84 | """ Save the Project """ 85 | # save document assuming the file has already been saved at least once. 86 | # if the document has NOT been saved previously.. 87 | # then provide the path between the (). 88 | doc.save() 89 | -------------------------------------------------------------------------------- /_Workflow-IntheRound/Metashape_PythonScripts_09-ExportModels-withTextures.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | # Use this as a learning tool only. 3 | # I am not responsible for any damage to data or hardware if the script is not properly utilized. 4 | # Following Code tested and based on Metashape Pro 1.6.2 using Windows 10 Pro 5 | 6 | """ # # # # # SET UP THE WORKING ENVIRONMENT # # # # # """ 7 | 8 | 9 | # import Metashape library module 10 | import Metashape 11 | 12 | # create a reference to the current project 13 | # done via the Document Class 14 | doc = Metashape.app.document 15 | 16 | # set reference to the currently selected chunk 17 | activeChunk = Metashape.app.document.chunk 18 | 19 | # get the current Chunks label ( name ) 20 | currentChunkLabel = activeChunk.label 21 | 22 | # get the current (saved) project's parent folder URL via python3 pathLib 23 | # this path variable is used when exporting the 3D model later in the script. 24 | # 'parent' will return the parent folder the project lives in 25 | # 'name' will return the saved project name and extension 26 | # 'stem' will return just the project name without extension 27 | from pathlib import Path 28 | parentFolderPath = str(Path(Metashape.app.document.path).parent) 29 | print("parent Folder is : " + parentFolderPath) 30 | 31 | # set reference to the output folders as string 32 | outputFolder = Path(str(parentFolderPath) + "\\" + "_Output") 33 | outputChunkFolder = Path(str(outputFolder) + "\\" + "_" + str(currentChunkLabel)) 34 | outputSubfolder = Path(str(outputChunkFolder) + "\\" + "_Models") 35 | 36 | print("output folder: " + str(outputFolder)) 37 | print("output chunk folder: " + str(outputChunkFolder)) 38 | print("model output folder is: " + str(outputSubfolder)) 39 | 40 | # create an 'output' sub-folder for exported data from project 41 | # also create sub-folder for model export within 'output' sub-folder 42 | # this method will create the folder if doesnt exist, and also do nothing if it does exist 43 | Path(outputFolder).mkdir(exist_ok=True) 44 | Path(outputChunkFolder).mkdir(exist_ok=True) 45 | Path(outputSubfolder).mkdir(exist_ok=True) 46 | 47 | 48 | """ # # # # # START THE MAIN PIPELINE # # # # # """ 49 | 50 | """Export Model Process """ 51 | # export Wavefront .OBJ model(s) 52 | # the model is named after its label (name) 53 | # Hi-Res models are saved with .TIFF texture files. 54 | # Low-Res models are saved with .JPEG texture files 55 | 56 | # loop through the available models for this chunk and export 57 | for model in activeChunk.models: 58 | # select the current model in the loop as the active model 59 | activeChunk.model = model 60 | # get the face count for current model in loop 61 | modelFaceCount = len(model.faces) 62 | # set reference to the output filename and path for 3D model and texture 63 | outputFileName = str(str(outputSubfolder) + "\\" + str(model.label) + ".obj") 64 | print("model output folder and name is : " + outputFileName) 65 | 66 | # if model has more than X number of faces, then save texture as tiff on export 67 | # else if model has less than X number of faces, then save texture as JPEG. 68 | if modelFaceCount > 300000: 69 | activeChunk.exportModel\ 70 | ( 71 | path=outputFileName, 72 | binary=True, 73 | precision=6, 74 | texture_format=Metashape.ImageFormatTIFF, 75 | save_texture=True, 76 | save_uv=True, 77 | save_normals=True, 78 | save_colors=True, 79 | save_cameras=True, 80 | save_markers=True, 81 | save_udim=False, 82 | save_alpha=False, 83 | strip_extensions=False, 84 | raster_transform=Metashape.RasterTransformNone, 85 | colors_rgb_8bit=True, 86 | comment="Created via Metashape python", 87 | save_comment=True, 88 | format=Metashape.ModelFormatOBJ, 89 | ) 90 | else: 91 | activeChunk.exportModel \ 92 | ( 93 | path=outputFileName, 94 | binary=True, 95 | precision=6, 96 | texture_format=Metashape.ImageFormatJPEG, 97 | save_texture=True, 98 | save_uv=True, 99 | save_normals=True, 100 | save_colors=True, 101 | save_cameras=True, 102 | save_markers=True, 103 | save_udim=False, 104 | save_alpha=False, 105 | strip_extensions=False, 106 | raster_transform=Metashape.RasterTransformNone, 107 | colors_rgb_8bit=True, 108 | comment="Created via Metashape python", 109 | save_comment=True, 110 | format=Metashape.ModelFormatOBJ, 111 | ) 112 | 113 | """ Save the Project """ 114 | # save document assuming the file has already been saved at least once. 115 | # if the document has NOT been saved previously.. 116 | # then provide the path between the (). 117 | doc.save() -------------------------------------------------------------------------------- /_Workflow-IntheRound/Metashape_PythonScripts_10-ExportDenseCloud.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | # Use this as a learning tool only. 3 | # I am not responsible for any damage to data or hardware if the script is not properly utilized. 4 | # Following Code tested and based on Metashape Pro 1.6.2 using Windows 10 Pro 5 | 6 | """ # # # # # SET UP THE WORKING ENVIRONMENT # # # # # """ 7 | 8 | # import Metashape library module 9 | import Metashape 10 | 11 | # create a reference to the current project 12 | # done via the Document Class 13 | doc = Metashape.app.document 14 | 15 | # set reference to the currently selected chunk 16 | activeChunk = Metashape.app.document.chunk 17 | 18 | # get the current Chunks label ( name ) 19 | currentChunkLabel = activeChunk.label 20 | 21 | # get the current (saved) project's parent folder URL via python3 pathLib 22 | # this path variable is used when exporting the 3D model later in the script. 23 | # 'parent' will return the parent folder the project lives in 24 | # 'name' will return the saved project name and extension 25 | # 'stem' will return just the project name without extension 26 | from pathlib import Path 27 | parentFolderPath = str(Path(Metashape.app.document.path).parent) 28 | print("parent Folder is : " + parentFolderPath) 29 | 30 | # set reference to the output folders as string 31 | outputFolder = Path(str(parentFolderPath) + "\\" + "_Output") 32 | outputChunkFolder = Path(str(outputFolder) + "\\" + "_" + str(currentChunkLabel)) 33 | outputSubfolder = Path(str(outputChunkFolder) + "\\" + "_Models") 34 | 35 | print("output folder: " + str(outputFolder)) 36 | print("output chunk folder: " + str(outputChunkFolder)) 37 | print("model output folder is: " + str(outputSubfolder)) 38 | 39 | # create an 'output' sub-folder for exported data from project 40 | # also create sub-folder for model export within 'output' sub-folder 41 | # this method will create the folder if doesnt exist, and also do nothing if it does exist 42 | Path(outputFolder).mkdir(exist_ok=True) 43 | Path(outputChunkFolder).mkdir(exist_ok=True) 44 | Path(outputSubfolder).mkdir(exist_ok=True) 45 | 46 | # create the string path and file name for export 47 | outputFileName = str(str(outputSubfolder) + "\\" + str(activeChunk.label) + "_DENSECLOUD.ply") 48 | print("model output folder and name is : " + outputFileName) 49 | 50 | """ # # # # # START THE MAIN PIPELINE # # # # # """ 51 | 52 | activeChunk.exportPoints\ 53 | ( 54 | path=outputFileName, 55 | source_data=Metashape.DenseCloudData, 56 | binary=True, 57 | save_normals=True, 58 | save_colors=True, 59 | save_classes=True, 60 | save_confidence=True, 61 | raster_transform=Metashape.RasterTransformNone, 62 | colors_rgb_8bit=True, 63 | comment="Exported Dense Point Cloud", 64 | save_comment=True, 65 | format=Metashape.PointsFormatPLY, 66 | image_format=Metashape.ImageFormatJPEG, 67 | clip_to_boundary=True, 68 | block_width=1000, 69 | block_height=1000, 70 | split_in_blocks=False, 71 | save_images=False, 72 | subdivide_task=True 73 | ) 74 | 75 | """ Save the Project """ 76 | # save document assuming the file has already been saved at least once. 77 | # if the document has NOT been saved previously.. 78 | # then provide the path between the (). 79 | doc.save() 80 | -------------------------------------------------------------------------------- /_singleActions-AsScripts/01_GetProjectFileName.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ Set up Working Environment """ 4 | # import Metashape library module 5 | import Metashape 6 | # create a reference to the current project via Document Class 7 | doc = Metashape.app.document 8 | # set reference for the currently active chunk 9 | activeChunk = Metashape.app.document.chunk 10 | 11 | """ Get the Project File Name from User Input """ 12 | # prompt the user with the 'save as' gui and store the project file name 13 | # returns the full path as is. 14 | projectFileName = Metashape.app.getSaveFileName("Please input the Project File Name", "Metashape Project (*.psx)") 15 | try: 16 | doc.save(projectFileName) 17 | except RuntimeError: 18 | Metashape.app.messageBox("Can't save project") -------------------------------------------------------------------------------- /_singleActions-AsScripts/02_LoadPhotosInNewChunk.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 03/2021 2 | 3 | """ Set up Working Environment """ 4 | # import Metashape library module 5 | import Metashape 6 | # create a reference to the current project via Document Class 7 | doc = Metashape.app.document 8 | 9 | """ Prompt User to Select Images """ 10 | # create array/list of images via user input gui 11 | images = Metashape.app.getOpenFileNames() 12 | 13 | """ Create New Chunk and Rename It """ 14 | # create new chunk to avoid changing any currently existing chunks 15 | newChunk = doc.addChunk() 16 | # set new chunk as active chunk 17 | Metashape.app.document.chunk = newChunk 18 | # set a new reference for the new chunk 19 | activeChunk = newChunk 20 | 21 | """Rename the New Active Chunk""" 22 | # get full list of chunks 23 | chunkList = doc.chunks 24 | # rename the new chunk,but only if it exists 25 | # also add the integer position in chunks list 26 | # this ensures each time the script runs each chunk has a unique name. 27 | if activeChunk in chunkList: 28 | activeChunk.label = 'pyChunk_' + str(len(chunkList)-1) 29 | 30 | """ Add User Selected Photos to Active Chunk""" 31 | # add images to chunk from array/list created earlier 32 | activeChunk.addPhotos(images) 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /_singleActions-AsScripts/03_CreateCameraGroups.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 03/2021 2 | 3 | """ Set up Working Environment """ 4 | # import Metashape library module 5 | import Metashape 6 | # create a reference to the current project via Document Class 7 | doc = Metashape.app.document 8 | # set reference for the currently active chunk 9 | activeChunk = Metashape.app.document.chunk 10 | 11 | """ Create new Camera Groups """ 12 | # create a camera group for each camera sensor focal length 13 | # each sensor is found under the 'Camera Calibrations...' menu option 14 | for sensor in activeChunk.sensors: 15 | # create a camera group for the current sensor focal length 16 | cameraGroup = activeChunk.addCameraGroup() 17 | # rename the group to match the sensor focal length 18 | cameraGroup.label = str(str(int(sensor.focal_length)) + " mm") 19 | # loop through cameras and move any camera with matching sensor focal_length to new camera group 20 | for camera in activeChunk.cameras: 21 | if camera.sensor.focal_length == sensor.focal_length: 22 | camera.group = cameraGroup 23 | 24 | -------------------------------------------------------------------------------- /_singleActions-AsScripts/04_AnalyzePhotos.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ Set up Working Environment """ 4 | # import Metashape library module 5 | import Metashape 6 | # create a reference to the current project via Document Class 7 | doc = Metashape.app.document 8 | # set reference for the currently active chunk 9 | activeChunk = Metashape.app.document.chunk 10 | 11 | # Estimate image quality 12 | # this populates the 'Quality' column in the photos pane, under 'details' view 13 | # this is not indicative of the actual image quality and is just here for example 14 | activeChunk.analyzePhotos() -------------------------------------------------------------------------------- /_singleActions-AsScripts/05_MatchPhotos.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ Set up Working Environment """ 4 | # import Metashape library module 5 | import Metashape 6 | # create a reference to the current project via Document Class 7 | doc = Metashape.app.document 8 | # set reference for the currently active chunk 9 | activeChunk = Metashape.app.document.chunk 10 | 11 | """ Match Photos for Active Chunk """ 12 | # 'downscale' controls the quality level. Options are 0=Highest, 1=High, 2=Medium, 3=Low, 4=Lowest 13 | # WARNING - downscale quality 0=Highest, up-scales the images x4. where 1=High uses the true pixel data. 14 | # the settings here also determine the .alignCameras() method quality 15 | activeChunk.matchPhotos\ 16 | ( 17 | downscale=1, 18 | generic_preselection=False, 19 | reference_preselection=True, 20 | reference_preselection_mode=Metashape.ReferencePreselectionSource, 21 | filter_mask=False, 22 | mask_tiepoints=False, 23 | keypoint_limit=100000, 24 | tiepoint_limit=25000, 25 | keep_keypoints=False, 26 | guided_matching=False, 27 | reset_matches=False, 28 | subdivide_task=True, 29 | workitem_size_cameras=20, 30 | workitem_size_pairs=80, 31 | max_workgroup_size=100 32 | ) -------------------------------------------------------------------------------- /_singleActions-AsScripts/06_AlignPhotos.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ Set up Working Environment """ 4 | # import Metashape library module 5 | import Metashape 6 | # create a reference to the current project via Document Class 7 | doc = Metashape.app.document 8 | # set reference for the currently active chunk 9 | activeChunk = Metashape.app.document.chunk 10 | 11 | """ Align Cameras for Active Chunk """ 12 | # align the matched image pairs 13 | activeChunk.alignCameras() -------------------------------------------------------------------------------- /_singleActions-AsScripts/07_DuplicateActiveChunk.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ Set up Working Environment """ 4 | # import Metashape library module 5 | import Metashape 6 | # create a reference to the current project via Document Class 7 | doc = Metashape.app.document 8 | # set reference for the currently active chunk 9 | activeChunk = Metashape.app.document.chunk 10 | 11 | """ Duplicate Active Chunk to Preserve Original Alignment """ 12 | # create a reference to the original chunk name 13 | activeChunk_label = activeChunk.label 14 | # update the label (name) of the original alignment to indicate as source 15 | activeChunk.label = str(activeChunk.label + "-Source") 16 | # Duplicate the source chunk 17 | activeChunk.copy() 18 | # update reference for list of chunks in project 19 | chunkList = Metashape.app.document.chunks 20 | # set reference to the last chunk in list, assuming this is the most recent copied chunk 21 | dupeChunk = Metashape.app.document.chunks[len(Metashape.app.document.chunks)-1] 22 | # rename the new copied chunk to match the chunk naming structure 23 | if dupeChunk in chunkList: 24 | dupeChunk.label = str(activeChunk_label) + "-Filtered" -------------------------------------------------------------------------------- /_singleActions-AsScripts/08_GradualSelection-ReconstructionUncertainty.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ Set up Working Environment """ 4 | # import Metashape library module 5 | import Metashape, sys 6 | 7 | # create a reference to the current project via Document Class 8 | doc = Metashape.app.document 9 | # set reference for the currently active chunk 10 | activeChunk = Metashape.app.document.chunk 11 | # set reference to slider threshold value for gradual selection filter 12 | THRESHOLD = 10 13 | 14 | """ Set up Point Cloud Filter and Gradual Selection Options """ 15 | # create a reference to the point cloud, and filter method 16 | point_filter = Metashape.PointCloud.Filter() 17 | 18 | """ Initialize the Filter and Select Points """ 19 | # perform filter selection 20 | point_filter.init \ 21 | ( 22 | activeChunk, 23 | criterion=Metashape.PointCloud.Filter.ReconstructionUncertainty 24 | ) 25 | 26 | """ Determine if User Argument Overides Default Threshold Value""" 27 | # If the script is run via the 'Run Script..' command in Metashape 28 | # then the user may input the threshold value using the 'Arguments' field 29 | # If no arguments were given, then use the provided Threshold value of 10 30 | # Else, if user has given a value, then use that instead 31 | if len(sys.argv) == 1: 32 | thresh = THRESHOLD 33 | else: 34 | thresh = float(sys.argv[1]) 35 | 36 | # selects all points above the passed threshold value 37 | point_filter.selectPoints(thresh) 38 | 39 | """ Check if Selected Points are more than Half of Point Cloud """ 40 | # use list comprehension to get total number of selected points 41 | nselected = len([p for p in activeChunk.point_cloud.points if p.selected]) 42 | 43 | """ Set Reference Value for Half of Point Cloud """ 44 | half_points = (len(activeChunk.point_cloud.points)/2) 45 | 46 | """ Remove Selected Points -But Only Up to Half of Point Cloud """ 47 | if nselected < half_points: 48 | # remove selected/filtered points above the passed threshold value 49 | point_filter.removePoints(thresh) 50 | else: 51 | # copy the current selection of points 52 | s_copy = point_filter.values.copy() 53 | # sort the copied values 54 | s_copy.sort() 55 | # get the threshold value for current filter, which gives 50% of points 56 | thresh50 = s_copy[int(len(s_copy) * 0.5)] 57 | # select 50% of point cloud using threshold 58 | point_filter.selectPoints(thresh50) 59 | # remove selected/filtered points 60 | point_filter.removePoints(thresh50) 61 | -------------------------------------------------------------------------------- /_singleActions-AsScripts/09_GradualSelection-ProjectionAccuracy.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ Set up Working Environment """ 4 | # import Metashape library module 5 | import Metashape, sys 6 | # create a reference to the current project via Document Class 7 | doc = Metashape.app.document 8 | # set reference for the currently active chunk 9 | activeChunk = Metashape.app.document.chunk 10 | # set reference to slider threshold value for gradual selection filter 11 | THRESHOLD = 4 12 | 13 | """ Set up Point Cloud Filter and Gradual Selection Options """ 14 | # create a reference to the point cloud, and filter method 15 | point_filter = Metashape.PointCloud.Filter() 16 | 17 | """ Initialize the Filter and Select Points """ 18 | # perform filter selection 19 | point_filter.init \ 20 | ( 21 | activeChunk, 22 | criterion=Metashape.PointCloud.Filter.ProjectionAccuracy 23 | ) 24 | 25 | """ Determine if User Argument Overides Default Threshold Value""" 26 | # If the script is run via the 'Run Script..' command in Metashape 27 | # then the user may input the threshold value using the 'Arguments' field 28 | # If no arguments were given, then use the provided Threshold value of 4 29 | # Else, if user has given a value, then use that instead 30 | if len(sys.argv) == 1: 31 | thresh = THRESHOLD 32 | else: 33 | thresh = float(sys.argv[1]) 34 | 35 | # selects all points above the passed threshold value 36 | point_filter.selectPoints(thresh) 37 | 38 | """ Check if Selected Points are more than Half of Point Cloud """ 39 | # use list comprehension to get total number of selected points 40 | nselected = len([p for p in activeChunk.point_cloud.points if p.selected]) 41 | 42 | """ Set Reference Value for Half of Point Cloud """ 43 | half_points = (len(activeChunk.point_cloud.points)/2) 44 | 45 | """ Remove Selected Points -But Only Up to Half of Point Cloud """ 46 | if nselected < half_points: 47 | # remove selected/filtered points above the passed threshold value 48 | point_filter.removePoints(thresh) 49 | else: 50 | # copy the current selection of points 51 | s_copy = point_filter.values.copy() 52 | # sort the copied values 53 | s_copy.sort() 54 | # get the threshold value for current filter, which gives 50% of points 55 | thresh50 = s_copy[int(len(s_copy) * 0.5)] 56 | # select 50% of point cloud using threshold 57 | point_filter.selectPoints(thresh50) 58 | # remove selected/filtered points 59 | point_filter.removePoints(thresh50) 60 | -------------------------------------------------------------------------------- /_singleActions-AsScripts/10_GradualSelection-ReprojectionError.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ Set up Working Environment """ 4 | # import Metashape library module 5 | import Metashape 6 | # create a reference to the current project via Document Class 7 | doc = Metashape.app.document 8 | # set reference for the currently active chunk 9 | activeChunk = Metashape.app.document.chunk 10 | # set reference to slider threshold value in percentage, for the Reprojection Filter 11 | THRESHOLD = 10 12 | 13 | """ Set up Point Cloud Filter and Gradual Selection Options """ 14 | # create a reference to the point cloud, and filter method 15 | point_filter = Metashape.PointCloud.Filter() 16 | 17 | """ Initialize the Filter """ 18 | # perform filter selection 19 | point_filter.init \ 20 | ( 21 | activeChunk, 22 | criterion=Metashape.PointCloud.Filter.ReprojectionError 23 | ) 24 | 25 | """ Determine if User Argument Overides Default Threshold Value""" 26 | # If the script is run via the 'Run Script..' command in Metashape 27 | # then the user may input the threshold value using the 'Arguments' field 28 | # If no arguments were given, then use the provided Threshold value of 4 29 | # Else, if user has given a value, then use that instead 30 | if len(sys.argv) == 1: 31 | thresh = THRESHOLD 32 | else: 33 | thresh = float(sys.argv[1]) 34 | 35 | """ Calculate Filter Slider Value that Equals 10% of Points""" 36 | # copy the point cloud filter values into new variable 37 | filterValues = point_filter.values.copy() 38 | # sort the point cloud filter values so points with highest error are selected 39 | filterValues.sort() 40 | # find relative slider value that gives 10 percent of points 41 | thresh10 = filterValues[int(len(filterValues) * (1 - thresh / 100))] 42 | 43 | """ Select Points""" 44 | # selects all points above the passed threshold value 45 | point_filter.selectPoints(thresh10) 46 | 47 | """ Check if Selected Points are more than Half of Point Cloud """ 48 | # use list comprehension to get total number of selected points 49 | nselected = len([p for p in activeChunk.point_cloud.points if p.selected]) 50 | 51 | """ Set Reference Value for Half of Point Cloud """ 52 | half_points = (len(activeChunk.point_cloud.points)/2) 53 | 54 | """ Remove Selected Points -But Only Up to Half of Point Cloud """ 55 | if nselected < half_points: 56 | # remove selected/filtered points above the passed threshold value 57 | point_filter.removePoints(thresh10) 58 | else: 59 | # copy the current selection of points 60 | s_copy = point_filter.values.copy() 61 | # sort the copied values 62 | s_copy.sort() 63 | # get the threshold value for current filter, which gives 50% of points 64 | thresh50 = s_copy[int(len(s_copy) * 0.5)] 65 | # select 50% of point cloud using threshold 66 | point_filter.selectPoints(thresh50) 67 | # remove selected/filtered points 68 | point_filter.removePoints(thresh50) 69 | -------------------------------------------------------------------------------- /_singleActions-AsScripts/11_OptimizeCameras-Basic.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ Set up Working Environment """ 4 | # import Metashape library module 5 | import Metashape 6 | # create a reference to the current project via Document Class 7 | doc = Metashape.app.document 8 | # set reference for the currently active chunk 9 | activeChunk = Metashape.app.document.chunk 10 | 11 | """ Optimize Cameras with Basic Fittings""" 12 | activeChunk.optimizeCameras \ 13 | ( 14 | fit_f=True, 15 | fit_cx=True, 16 | fit_cy=True, 17 | fit_b1=False, 18 | fit_b2=False, 19 | fit_k1=True, 20 | fit_k2=True, 21 | fit_k3=True, 22 | fit_k4=False, 23 | fit_p1=True, 24 | fit_p2=True, 25 | fit_corrections=False, 26 | adaptive_fitting=False, 27 | tiepoint_covariance=False 28 | ) -------------------------------------------------------------------------------- /_singleActions-AsScripts/12_OptimizeCameras-Full.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ Set up Working Environment """ 4 | # import Metashape library module 5 | import Metashape 6 | # create a reference to the current project via Document Class 7 | doc = Metashape.app.document 8 | # set reference for the currently active chunk 9 | activeChunk = Metashape.app.document.chunk 10 | 11 | """ Optimize Cameras with Basic Fittings""" 12 | activeChunk.optimizeCameras \ 13 | ( 14 | fit_f=True, 15 | fit_cx=True, 16 | fit_cy=True, 17 | fit_b1=True, 18 | fit_b2=True, 19 | fit_k1=True, 20 | fit_k2=True, 21 | fit_k3=True, 22 | fit_k4=False, 23 | fit_p1=True, 24 | fit_p2=True, 25 | fit_corrections=False, 26 | adaptive_fitting=False, 27 | tiepoint_covariance=False 28 | ) -------------------------------------------------------------------------------- /_singleActions-AsScripts/13_DetectMarkers-CircularCoded12bit.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ Set up Working Environment """ 4 | # import Metashape library module 5 | import Metashape 6 | # create a reference to the current project via Document Class 7 | doc = Metashape.app.document 8 | # set reference for the currently active chunk 9 | activeChunk = Metashape.app.document.chunk 10 | 11 | """Detect Markers""" 12 | # Detect Circular 12bit coded markers 13 | # Coded target options: [CircularTarget12bit, CircularTarget14bit, CircularTarget16bit, CircularTarget20bit] 14 | activeChunk.detectMarkers\ 15 | ( 16 | target_type=Metashape.TargetType.CircularTarget12bit, 17 | tolerance=25, 18 | filter_mask=False, 19 | inverted=False, 20 | noparity=False, 21 | maximum_residual=5, 22 | minimum_size=0, 23 | minimum_dist=5 24 | ) -------------------------------------------------------------------------------- /_singleActions-AsScripts/14_DetectMarkers-CircularNonCoded-CrossTarget.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ Set up Working Environment """ 4 | # import Metashape library module 5 | import Metashape 6 | # create a reference to the current project via Document Class 7 | doc = Metashape.app.document 8 | # set reference for the currently active chunk 9 | activeChunk = Metashape.app.document.chunk 10 | 11 | """Detect Markers""" 12 | # Detect Cross non-coded markers 13 | # Non-coded target options: [CircularTarget, CrossTarget] 14 | activeChunk.detectMarkers\ 15 | ( 16 | target_type=Metashape.TargetType.CrossTarget, 17 | tolerance=25, 18 | filter_mask=False, 19 | inverted=False, 20 | noparity=False, 21 | maximum_residual=5, 22 | minimum_size=0, 23 | minimum_dist=5 24 | ) -------------------------------------------------------------------------------- /_singleActions-AsScripts/15_RemoveMarkers-LowProjections.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ Set up Working Environment """ 4 | # import Metashape library module 5 | import Metashape 6 | # create a reference to the current project via Document Class 7 | doc = Metashape.app.document 8 | # set reference for the currently active chunk 9 | activeChunk = Metashape.app.document.chunk 10 | # set reference for projection value 11 | THRESHOLD = 9 12 | 13 | """ Determine if User Argument Overides Default Threshold Value""" 14 | # If the script is run via the 'Run Script..' command in Metashape 15 | # then the user may input the threshold value using the 'Arguments' field 16 | # If no arguments were given, then use the provided Threshold value of 10 17 | # Else, if user has given a value, then use that instead 18 | if len(sys.argv) == 1: 19 | thresh = THRESHOLD 20 | else: 21 | thresh = float(sys.argv[1]) 22 | 23 | 24 | """Remove Markers with less than X number of projections""" 25 | for marker in activeChunk.markers: 26 | # if marker has less than 9 projections, remove from chunk 27 | if len(marker.projections) < thresh: 28 | activeChunk.remove(marker) 29 | 30 | 31 | -------------------------------------------------------------------------------- /_singleActions-AsScripts/16_OptimizeMarkers-ProjectionError.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ With Help from Agisoft Forum @: 4 | https://www.agisoft.com/forum/index.php?topic=11075.msg50002#msg50002 5 | https://www.agisoft.com/forum/index.php?topic=3618.msg18952#msg18952 6 | """ 7 | 8 | """ Set up Working Environment """ 9 | # import Metashape library module 10 | import Metashape 11 | # create a reference to the current project via Document Class 12 | doc = Metashape.app.document 13 | # set reference for the currently active chunk 14 | activeChunk = Metashape.app.document.chunk 15 | # set reference for projection error value 16 | THESHOLD = 0.5 17 | 18 | """ Determine if User Argument Overides Default Threshold Value""" 19 | # If the script is run via the 'Run Script..' command in Metashape 20 | # then the user may input the threshold value using the 'Arguments' field 21 | # If no arguments were given, then use the provided Threshold value of 10 22 | # Else, if user has given a value, then use that instead 23 | if len(sys.argv) == 1: 24 | thresh = THRESHOLD 25 | else: 26 | thresh = float(sys.argv[1]) 27 | 28 | """Print out the current projection count for each marker""" 29 | for m in activeChunk.markers: 30 | print("Marker: " + m.label + " has " + str(len(m.projections)) + " projections") 31 | 32 | """Optimize Marker Error Per Camera""" 33 | # for each marker in list of markers for active chunk, remove markers from each camera with error greater than 0.5 34 | for marker in activeChunk.markers: 35 | # skip marker if it has no position 36 | if not marker.position: 37 | print(marker.label + " is not defined in 3D, skipping...") 38 | continue 39 | 40 | # reference the position of the marker 41 | position = marker.position 42 | 43 | # for each camera in the list of cameras for current marker 44 | for camera in marker.projections.keys(): 45 | if not camera.transform: 46 | continue 47 | proj = marker.projections[camera].coord 48 | reproj = camera.project(position) 49 | error = (proj - reproj).norm() 50 | 51 | # remove markers with projection error greater than 0.5 52 | if error > thresh: 53 | # set the current marker projection to none for current camera/marker combination 54 | marker.projections[camera] = None 55 | 56 | """Print out the adjusted projection count for each marker""" 57 | for marker in activeChunk.markers: 58 | print("Marker: " + marker.label + " now has " + str(len(marker.projections)) + " projections") 59 | -------------------------------------------------------------------------------- /_singleActions-AsScripts/17_CreateModel-FromSparseCloud.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ With Help from Agisoft Forum @: 4 | https://www.agisoft.com/forum/index.php?topic=12027.msg53791#msg53791 5 | """ 6 | 7 | """ Set up Working Environment """ 8 | # import Metashape library module 9 | import Metashape 10 | # create a reference to the current project via Document Class 11 | doc = Metashape.app.document 12 | # set reference for the currently active chunk 13 | activeChunk = Metashape.app.document.chunk 14 | # set reference to custom face count for low res model 15 | THRESHOLD = 200000 16 | 17 | """ Determine if User Argument Overides Default Threshold Value""" 18 | # If the script is run via the 'Run Script..' command in Metashape 19 | # then the user may input the threshold value using the 'Arguments' field 20 | # If no arguments were given, then use the provided Threshold value of 10 21 | # Else, if user has given a value, then use that instead 22 | if len(sys.argv) == 1: 23 | thresh = THRESHOLD 24 | else: 25 | thresh = float(sys.argv[1]) 26 | 27 | 28 | """Create model from Sparse Cloud""" 29 | # must include this line between each attempt to build a model. or it overwrites last created model 30 | activeChunk.model = None 31 | 32 | # create lower resolution model 33 | activeChunk.buildModel\ 34 | ( 35 | surface_type=Metashape.Arbitrary, 36 | interpolation=Metashape.EnabledInterpolation, 37 | face_count=Metashape.FaceCount.LowFaceCount, 38 | face_count_custom=thresh, 39 | source_data=Metashape.PointCloudData, 40 | vertex_colors=True, 41 | vertex_confidence=True, 42 | volumetric_masks=False, 43 | keep_depth=True, 44 | trimming_radius=10, 45 | subdivide_task=True, 46 | workitem_size_cameras=20, 47 | max_workgroup_size=100 48 | ) 49 | 50 | -------------------------------------------------------------------------------- /_singleActions-AsScripts/18_ImportMasks-ModelAsSource.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ With Help from Agisoft Forum @: 4 | https://www.agisoft.com/forum/index.php?topic=12027.msg53791#msg53791 5 | """ 6 | 7 | """ Set up Working Environment """ 8 | # import Metashape library module 9 | import Metashape 10 | # create a reference to the current project via Document Class 11 | doc = Metashape.app.document 12 | # set reference for the currently active chunk 13 | activeChunk = Metashape.app.document.chunk 14 | #set reference for tolerance value for import mask function 15 | THESHOLD = 25 16 | 17 | """ Determine if User Argument Overides Default Threshold Value""" 18 | # If the script is run via the 'Run Script..' command in Metashape 19 | # then the user may input the threshold value using the 'Arguments' field 20 | # If no arguments were given, then use the provided Threshold value of 10 21 | # Else, if user has given a value, then use that instead 22 | if len(sys.argv) == 1: 23 | thresh = THRESHOLD 24 | else: 25 | thresh = float(sys.argv[1]) 26 | 27 | """Import Masks""" 28 | # import masks function using active 3D model as source for all cameras in chunk 29 | activeChunk.importMasks\ 30 | ( 31 | path='{filename}_mask.png', 32 | source=Metashape.MaskSourceModel, 33 | operation=Metashape.MaskOperationReplacement, 34 | tolerance=thresh 35 | ) -------------------------------------------------------------------------------- /_singleActions-AsScripts/19_Export-ImageMasks.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ With Help from Agisoft Forum @: 4 | https://www.agisoft.com/forum/index.php?topic=12027.msg53791#msg53791 5 | """ 6 | 7 | """ Set up Working Environment """ 8 | # import Metashape library module 9 | import Metashape 10 | # create a reference to the current project via Document Class 11 | doc = Metashape.app.document 12 | # set reference for the currently active chunk 13 | activeChunk = Metashape.app.document.chunk 14 | 15 | # get the current Chunks label ( name ) 16 | currentChunkLabel = activeChunk.label 17 | 18 | # get the current (saved) project's parent folder URL via python3 pathLib 19 | # this path variable is used when exporting the 3D model later in the script. 20 | # 'parent' will return the parent folder the project lives in 21 | # 'name' will return the saved project name and extension 22 | # 'stem' will return just the project name without extension 23 | from pathlib import Path 24 | parentFolderPath = str(Path(Metashape.app.document.path).parent) 25 | print("parent Folder is : " + parentFolderPath) 26 | 27 | # set reference to the output folders as string 28 | outputFolder = Path(str(parentFolderPath) + "\\" + "_Output") 29 | outputChunkFolder = Path(str(outputFolder) + "\\" + "_" + str(currentChunkLabel)) 30 | outputMaskfolder = Path(str(outputChunkFolder) + "\\" + "_Masks") 31 | 32 | print("output folder: " + str(outputFolder)) 33 | print("output chunk folder: " + str(outputChunkFolder)) 34 | print("mask output folder is: " + str(outputMaskfolder)) 35 | 36 | # create an 'output' sub-folder for exported data from project 37 | # also create sub-folder for model export within 'output' sub-folder 38 | # this method will create the folder if doesnt exist, and also do nothing if it does exist 39 | Path(outputFolder).mkdir(exist_ok=True) 40 | Path(outputChunkFolder).mkdir(exist_ok=True) 41 | Path(outputMaskfolder).mkdir(exist_ok=True) 42 | 43 | # export masks to output mask folder 44 | # this uses the Metashape Task class, otherwise loop through every camera in chunk and save mask as image file 45 | # create a reference to the Tasks ExportMasks method 46 | mask_task = Metashape.Tasks.ExportMasks() 47 | # define which cameras to export masks for 48 | mask_task.cameras = activeChunk.cameras 49 | # define the output path for the exported mask files 50 | mask_task.path = str(str(outputMaskfolder) + "\\" + "{filename}.png") 51 | # activate the task for the active chunk to export the masks 52 | mask_task.apply(object=activeChunk) 53 | 54 | -------------------------------------------------------------------------------- /_singleActions-AsScripts/20_BuildDepthMaps.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ Set up Working Environment """ 4 | # import Metashape library module 5 | import Metashape 6 | # create a reference to the current project via Document Class 7 | doc = Metashape.app.document 8 | # set reference for the currently active chunk 9 | activeChunk = Metashape.app.document.chunk 10 | #set reference for max neighbors value 11 | THESHOLD = -1 12 | 13 | """ Determine if User Argument Overides Default Threshold Value""" 14 | # If the script is run via the 'Run Script..' command in Metashape 15 | # then the user may input the threshold value using the 'Arguments' field 16 | # If no arguments were given, then use the provided Threshold value of 10 17 | # Else, if user has given a value, then use that instead 18 | if len(sys.argv) == 1: 19 | thresh = THRESHOLD 20 | else: 21 | thresh = float(sys.argv[1]) 22 | 23 | """Build Depth Maps""" 24 | # downscale = # 1=UltraHigh, 2=High, 4=Medium, 8=low 25 | # Ultrahigh setting loads the image data at full resolution, High down-samples x2, medium down-samples x4, low x8 26 | # the 'max_neighbors' determines how many cameras it compares in parallel. 100 is default value. 27 | # setting 'max_neighbors' to -1 compares all cameras as same time. 28 | activeChunk.buildDepthMaps\ 29 | ( 30 | downscale=1, 31 | filter_mode=Metashape.AggressiveFiltering, 32 | reuse_depth=False, 33 | max_neighbors=thresh, 34 | subdivide_task=True, 35 | workitem_size_cameras=20, 36 | max_workgroup_size=100 37 | ) -------------------------------------------------------------------------------- /_singleActions-AsScripts/21_BuildDenseCloud.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ Set up Working Environment """ 4 | # import Metashape library module 5 | import Metashape 6 | # create a reference to the current project via Document Class 7 | doc = Metashape.app.document 8 | # set reference for the currently active chunk 9 | activeChunk = Metashape.app.document.chunk 10 | #set reference for max neighbors value 11 | THESHOLD = 300 12 | 13 | """ Determine if User Argument Overides Default Threshold Value""" 14 | # If the script is run via the 'Run Script..' command in Metashape 15 | # then the user may input the threshold value using the 'Arguments' field 16 | # If no arguments were given, then use the provided Threshold value of 10 17 | # Else, if user has given a value, then use that instead 18 | if len(sys.argv) == 1: 19 | thresh = THRESHOLD 20 | else: 21 | thresh = float(sys.argv[1]) 22 | 23 | """Build Dense Cloud""" 24 | # the quality of the dense cloud is determined by the quality of the depth maps 25 | # "max_neighbors" value of '-1' will evaluate ALL IMAGES in parallel. 26 | # 200-300 is good when there is a lot of image overlap. 27 | # setting this value will fix an issue where there is excessive 'fuzz' in the dense cloud. 28 | # the default value is 100. 29 | activeChunk.buildDenseCloud\ 30 | ( 31 | point_colors=True, 32 | point_confidence=True, 33 | keep_depth=True, 34 | max_neighbors=thresh, 35 | subdivide_task=True, 36 | workitem_size_cameras=20, 37 | max_workgroup_size=100 38 | ) -------------------------------------------------------------------------------- /_singleActions-AsScripts/22_BuildModel-DenseCloud-HighFaceCount.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ Set up Working Environment """ 4 | # import Metashape library module 5 | import Metashape 6 | # create a reference to the current project via Document Class 7 | doc = Metashape.app.document 8 | # set reference for the currently active chunk 9 | activeChunk = Metashape.app.document.chunk 10 | 11 | """Build Model Process""" 12 | # must include this line between each attempt to build a model. or it overwrites last created model 13 | activeChunk.model = None 14 | # build model with high face count 15 | activeChunk.buildModel \ 16 | ( 17 | surface_type=Metashape.Arbitrary, 18 | interpolation=Metashape.EnabledInterpolation, 19 | face_count=Metashape.FaceCount.HighFaceCount, 20 | face_count_custom=200000, 21 | source_data=Metashape.DenseCloudData, 22 | vertex_colors=True, 23 | vertex_confidence=True, 24 | volumetric_masks=False, 25 | keep_depth=True, 26 | trimming_radius=10, 27 | subdivide_task=True, 28 | workitem_size_cameras=20, 29 | max_workgroup_size=100 30 | ) -------------------------------------------------------------------------------- /_singleActions-AsScripts/23_BuildModel-DenseCloud-CustomFaceCount.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ Set up Working Environment """ 4 | # import Metashape library module 5 | import Metashape 6 | # create a reference to the current project via Document Class 7 | doc = Metashape.app.document 8 | # set reference for the currently active chunk 9 | activeChunk = Metashape.app.document.chunk 10 | # set reference to custom face count 11 | FACE_COUNT = 200000 12 | 13 | """ Determine if User Argument Overides Default Threshold Value""" 14 | # If the script is run via the 'Run Script..' command in Metashape 15 | # then the user may input the threshold value using the 'Arguments' field 16 | # If no arguments were given, then use the provided Threshold value of 10 17 | # Else, if user has given a value, then use that instead 18 | if len(sys.argv) == 1: 19 | thresh = FACE_COUNT 20 | else: 21 | thresh = float(sys.argv[1]) 22 | 23 | """Build Model Process""" 24 | # must include this line between each attempt to build a model. or it overwrites last created model 25 | activeChunk.model = None 26 | # build model with custom face count 27 | activeChunk.buildModel \ 28 | ( 29 | surface_type=Metashape.Arbitrary, 30 | interpolation=Metashape.EnabledInterpolation, 31 | face_count=Metashape.FaceCount.CustomFaceCount, 32 | face_count_custom=thresh, 33 | source_data=Metashape.DenseCloudData, 34 | vertex_colors=True, 35 | vertex_confidence=True, 36 | volumetric_masks=False, 37 | keep_depth=True, 38 | trimming_radius=10, 39 | subdivide_task=True, 40 | workitem_size_cameras=20, 41 | max_workgroup_size=100 42 | ) -------------------------------------------------------------------------------- /_singleActions-AsScripts/24_BuildModel-DepthMaps-HighFaceCount.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ Set up Working Environment """ 4 | # import Metashape library module 5 | import Metashape 6 | # create a reference to the current project via Document Class 7 | doc = Metashape.app.document 8 | # set reference for the currently active chunk 9 | activeChunk = Metashape.app.document.chunk 10 | 11 | """Build Model Process""" 12 | # must include this line between each attempt to build a model. or it overwrites last created model 13 | activeChunk.model = None 14 | # build model with high face count 15 | activeChunk.buildModel \ 16 | ( 17 | surface_type=Metashape.Arbitrary, 18 | interpolation=Metashape.EnabledInterpolation, 19 | face_count=Metashape.FaceCount.HighFaceCount, 20 | face_count_custom=200000, 21 | source_data=Metashape.DepthMapsData, 22 | vertex_colors=True, 23 | vertex_confidence=True, 24 | volumetric_masks=False, 25 | keep_depth=True, 26 | trimming_radius=10, 27 | subdivide_task=True, 28 | workitem_size_cameras=20, 29 | max_workgroup_size=100 30 | ) -------------------------------------------------------------------------------- /_singleActions-AsScripts/25_BuildModel-DepthMaps-CustomFaceCount.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ Set up Working Environment """ 4 | # import Metashape library module 5 | import Metashape 6 | # create a reference to the current project via Document Class 7 | doc = Metashape.app.document 8 | # set reference for the currently active chunk 9 | activeChunk = Metashape.app.document.chunk 10 | # set reference to custom face count 11 | FACE_COUNT = 200000 12 | 13 | """ Determine if User Argument Overides Default Threshold Value""" 14 | # If the script is run via the 'Run Script..' command in Metashape 15 | # then the user may input the threshold value using the 'Arguments' field 16 | # If no arguments were given, then use the provided Threshold value of 10 17 | # Else, if user has given a value, then use that instead 18 | if len(sys.argv) == 1: 19 | thresh = FACE_COUNT 20 | else: 21 | thresh = float(sys.argv[1]) 22 | 23 | """Build Model Process""" 24 | # must include this line between each attempt to build a model. or it overwrites last created model 25 | activeChunk.model = None 26 | # build model with custom face count 27 | activeChunk.buildModel \ 28 | ( 29 | surface_type=Metashape.Arbitrary, 30 | interpolation=Metashape.EnabledInterpolation, 31 | face_count=Metashape.FaceCount.CustomFaceCount, 32 | face_count_custom=thresh, 33 | source_data=Metashape.DepthMapsData, 34 | vertex_colors=True, 35 | vertex_confidence=True, 36 | volumetric_masks=False, 37 | keep_depth=True, 38 | trimming_radius=10, 39 | subdivide_task=True, 40 | workitem_size_cameras=20, 41 | max_workgroup_size=100 42 | ) -------------------------------------------------------------------------------- /_singleActions-AsScripts/26_DecimateModel-CustomFaceCount.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ Set up Working Environment """ 4 | # import Metashape library module 5 | import Metashape 6 | # create a reference to the current project via Document Class 7 | doc = Metashape.app.document 8 | # set reference for the currently active chunk 9 | activeChunk = Metashape.app.document.chunk 10 | # set reference to target face count 11 | FACE_COUNT = 200000 12 | 13 | """ Determine if User Argument Overides Default Threshold Value""" 14 | # If the script is run via the 'Run Script..' command in Metashape 15 | # then the user may input the threshold value using the 'Arguments' field 16 | # If no arguments were given, then use the provided Threshold value of 10 17 | # Else, if user has given a value, then use that instead 18 | if len(sys.argv) == 1: 19 | thresh = FACE_COUNT 20 | else: 21 | thresh = float(sys.argv[1]) 22 | 23 | """ Decimate selected model """ 24 | activeChunk.decimateModel \ 25 | ( 26 | face_count=thresh, 27 | asset=activeChunk.model 28 | ) -------------------------------------------------------------------------------- /_singleActions-AsScripts/27_BuildUV-UnwrapModel.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ Set up Working Environment """ 4 | # import Metashape library module 5 | import Metashape 6 | # create a reference to the current project via Document Class 7 | doc = Metashape.app.document 8 | # set reference for the currently active chunk 9 | activeChunk = Metashape.app.document.chunk 10 | 11 | """Unwrap selected model""" 12 | activeChunk.buildUV \ 13 | ( 14 | mapping_mode=Metashape.GenericMapping, 15 | page_count=1, 16 | adaptive_resolution=False 17 | ) -------------------------------------------------------------------------------- /_singleActions-AsScripts/28_BuildTexture-DiffuseMap.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ Set up Working Environment """ 4 | # import Metashape library module 5 | import Metashape 6 | # create a reference to the current project via Document Class 7 | doc = Metashape.app.document 8 | # set reference for the currently active chunk 9 | activeChunk = Metashape.app.document.chunk 10 | # set reference to texture size, aka the pixel dimension 11 | SIZE = 8192 12 | 13 | """ Determine if User Argument Overides Default Threshold Value""" 14 | # If the script is run via the 'Run Script..' command in Metashape 15 | # then the user may input the threshold value using the 'Arguments' field 16 | # If no arguments were given, then use the provided Threshold value of 10 17 | # Else, if user has given a value, then use that instead 18 | if len(sys.argv) == 1: 19 | thresh = SIZE 20 | else: 21 | thresh = float(sys.argv[1]) 22 | 23 | """Build Texture""" 24 | # Options: ['DiffuseMap', 'NormalMap', 'OcclusionMap'] 25 | activeChunk.buildTexture\ 26 | ( 27 | blending_mode=Metashape.MosaicBlending, 28 | texture_size=thresh, 29 | fill_holes=True, 30 | ghosting_filter=True, 31 | texture_type=Metashape.Model.DiffuseMap, 32 | transfer_texture=True 33 | ) -------------------------------------------------------------------------------- /_singleActions-AsScripts/29_BuildTexture-NormalMap.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ Set up Working Environment """ 4 | # import Metashape library module 5 | import Metashape 6 | # create a reference to the current project via Document Class 7 | doc = Metashape.app.document 8 | # set reference for the currently active chunk 9 | activeChunk = Metashape.app.document.chunk 10 | # set reference to texture size, aka the pixel dimension 11 | SIZE = 8192 12 | 13 | """ Determine if User Argument Overides Default Threshold Value""" 14 | # If the script is run via the 'Run Script..' command in Metashape 15 | # then the user may input the threshold value using the 'Arguments' field 16 | # If no arguments were given, then use the provided Threshold value of 10 17 | # Else, if user has given a value, then use that instead 18 | if len(sys.argv) == 1: 19 | thresh = SIZE 20 | else: 21 | thresh = float(sys.argv[1]) 22 | 23 | """Build Texture""" 24 | # get the index value of the currently selected model relative to the chunk.models list 25 | index = activeChunk.models.index(activeChunk.model) 26 | 27 | # start, get the face count of the current model 28 | currentFaceCount = [len(activeChunk.models[index].faces)] 29 | # get the face count of the previous model in the list of models 30 | previousFaceCount = [len(activeChunk.models[index-1].faces)] 31 | 32 | # determine if the currently selected model has a lower face count than the previous model 33 | # if the current model has a lower face count, then proceed to create the texture map 34 | if currentFaceCount < previousFaceCount: 35 | # the source model is set to the index of the previously created model in the list of models. 36 | # this assumes that each subsequent model was created with a lower face count than the previous. 37 | # Options: ['DiffuseMap', 'NormalMap', 'OcclusionMap'] 38 | activeChunk.buildTexture \ 39 | ( 40 | blending_mode=Metashape.MosaicBlending, 41 | texture_size=thresh, 42 | fill_holes=True, 43 | ghosting_filter=True, 44 | texture_type=Metashape.Model.NormalMap, 45 | source_model=activeChunk.models[index - 1], 46 | transfer_texture=True 47 | ) 48 | else: 49 | print(" PYTHON SCRIPT ERROR: Check the face count of source model is more than the selected model ") 50 | 51 | -------------------------------------------------------------------------------- /_singleActions-AsScripts/30_BuildTexture-OcclusionMap.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ Set up Working Environment """ 4 | # import Metashape library module 5 | import Metashape 6 | # create a reference to the current project via Document Class 7 | doc = Metashape.app.document 8 | # set reference for the currently active chunk 9 | activeChunk = Metashape.app.document.chunk 10 | # set reference to texture size, aka the pixel dimension 11 | SIZE = 8192 12 | 13 | """ Determine if User Argument Overides Default Threshold Value""" 14 | # If the script is run via the 'Run Script..' command in Metashape 15 | # then the user may input the threshold value using the 'Arguments' field 16 | # If no arguments were given, then use the provided Threshold value of 10 17 | # Else, if user has given a value, then use that instead 18 | if len(sys.argv) == 1: 19 | thresh = SIZE 20 | else: 21 | thresh = float(sys.argv[1]) 22 | 23 | """Build Texture""" 24 | # get the index value of the currently selected model relative to the chunk.models list 25 | index = activeChunk.models.index(activeChunk.model) 26 | 27 | # start, get the face count of the current model 28 | currentFaceCount = [len(activeChunk.models[index].faces)] 29 | # get the face count of the previous model in the list of models 30 | previousFaceCount = [len(activeChunk.models[index-1].faces)] 31 | 32 | # determine if the currently selected model has a lower face count than the previous model 33 | # if the current model has a lower face count, then proceed to create the texture map 34 | if currentFaceCount < previousFaceCount: 35 | # the source model is set to the index of the previously created model in the list of models. 36 | # this assumes that each subsequent model was created with a lower face count than the previous. 37 | # Options: ['DiffuseMap', 'NormalMap', 'OcclusionMap'] 38 | activeChunk.buildTexture \ 39 | ( 40 | blending_mode=Metashape.MosaicBlending, 41 | texture_size=thresh, 42 | fill_holes=True, 43 | ghosting_filter=True, 44 | texture_type=Metashape.Model.OcclusionMap, 45 | source_model=activeChunk.models[index - 1], 46 | transfer_texture=True 47 | ) 48 | else: 49 | print(" PYTHON SCRIPT ERROR: Check face count of source model is more than the selected model ") 50 | 51 | -------------------------------------------------------------------------------- /_singleActions-AsScripts/31_ExportModel-WithTextures.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ Set up Working Environment """ 4 | # import Metashape library module 5 | import Metashape 6 | # create a reference to the current project via Document Class 7 | doc = Metashape.app.document 8 | # set reference for the currently active chunk 9 | activeChunk = Metashape.app.document.chunk 10 | 11 | """ Setup the Storage Environment """ 12 | # get the current Chunks label ( name ) 13 | currentChunkLabel = activeChunk.label 14 | 15 | # get the current (saved) project's parent folder URL via python3 pathLib 16 | # this path variable is used when exporting the 3D model later in the script. 17 | # 'parent' will return the parent folder the project lives in 18 | # 'name' will return the saved project name and extension 19 | # 'stem' will return just the project name without extension 20 | from pathlib import Path 21 | parentFolderPath = str(Path(Metashape.app.document.path).parent) 22 | print("parent Folder is : " + parentFolderPath) 23 | 24 | # set reference to the output folders as string 25 | outputFolder = Path(str(parentFolderPath) + "\\" + "_Output") 26 | outputChunkFolder = Path(str(outputFolder) + "\\" + "_" + str(currentChunkLabel)) 27 | outputSubfolder = Path(str(outputChunkFolder) + "\\" + "_Models") 28 | 29 | print("output folder: " + str(outputFolder)) 30 | print("output chunk folder: " + str(outputChunkFolder)) 31 | print("model output folder is: " + str(outputSubfolder)) 32 | 33 | # create an 'output' sub-folder for exported data from project 34 | # also create sub-folder for model export within 'output' sub-folder 35 | # this method will create the folder if doesnt exist, and also do nothing if it does exist 36 | Path(outputFolder).mkdir(exist_ok=True) 37 | Path(outputChunkFolder).mkdir(exist_ok=True) 38 | Path(outputSubfolder).mkdir(exist_ok=True) 39 | 40 | """Export the Model with Texture""" 41 | # select the current model in the loop as the active model 42 | model = activeChunk.model 43 | 44 | # set reference to the output filename and path for 3D model and texture 45 | outputFileName = str(str(outputSubfolder) + "\\" + str(model.label) + ".obj") 46 | 47 | # export the 3D model 48 | activeChunk.exportModel\ 49 | ( 50 | path=outputFileName, 51 | binary=True, 52 | precision=6, 53 | texture_format=Metashape.ImageFormatTIFF, 54 | save_texture=True, 55 | save_uv=True, 56 | save_normals=True, 57 | save_colors=True, 58 | save_cameras=True, 59 | save_markers=True, 60 | save_udim=False, 61 | save_alpha=False, 62 | strip_extensions=False, 63 | raster_transform=Metashape.RasterTransformNone, 64 | colors_rgb_8bit=True, 65 | comment="Created via Metashape python", 66 | save_comment=True, 67 | format=Metashape.ModelFormatOBJ, 68 | ) -------------------------------------------------------------------------------- /_singleActions-AsScripts/32_Export-DensePointCloud.py: -------------------------------------------------------------------------------- 1 | # This script created by Joseph Aaron Campbell - 10/2020 2 | 3 | """ Set up Working Environment """ 4 | # import Metashape library module 5 | import Metashape 6 | # create a reference to the current project via Document Class 7 | doc = Metashape.app.document 8 | # set reference for the currently active chunk 9 | activeChunk = Metashape.app.document.chunk 10 | 11 | """ Setup the Storage Environment """ 12 | # get the current Chunks label ( name ) 13 | currentChunkLabel = activeChunk.label 14 | 15 | # get the current (saved) project's parent folder URL via python3 pathLib 16 | # this path variable is used when exporting the 3D model later in the script. 17 | # 'parent' will return the parent folder the project lives in 18 | # 'name' will return the saved project name and extension 19 | # 'stem' will return just the project name without extension 20 | from pathlib import Path 21 | parentFolderPath = str(Path(Metashape.app.document.path).parent) 22 | print("parent Folder is : " + parentFolderPath) 23 | 24 | # set reference to the output folders as string 25 | outputFolder = Path(str(parentFolderPath) + "\\" + "_Output") 26 | outputChunkFolder = Path(str(outputFolder) + "\\" + "_" + str(currentChunkLabel)) 27 | outputSubfolder = Path(str(outputChunkFolder) + "\\" + "_Models") 28 | 29 | print("output folder: " + str(outputFolder)) 30 | print("output chunk folder: " + str(outputChunkFolder)) 31 | print("model output folder is: " + str(outputSubfolder)) 32 | 33 | # create an 'output' sub-folder for exported data from project 34 | # also create sub-folder for model export within 'output' sub-folder 35 | # this method will create the folder if doesnt exist, and also do nothing if it does exist 36 | Path(outputFolder).mkdir(exist_ok=True) 37 | Path(outputChunkFolder).mkdir(exist_ok=True) 38 | Path(outputSubfolder).mkdir(exist_ok=True) 39 | 40 | """Export the Dense Point Cloud""" 41 | # set reference to the output filename and path for point cloud 42 | outputFileName = str(str(outputSubfolder) + "\\" + str(activeChunk.label) + "_DENSECLOUD.ply") 43 | 44 | # export dense point cloud 45 | activeChunk.exportPoints\ 46 | ( 47 | path=outputFileName, 48 | source_data=Metashape.DenseCloudData, 49 | binary=True, 50 | save_normals=True, 51 | save_colors=True, 52 | save_classes=True, 53 | save_confidence=True, 54 | raster_transform=Metashape.RasterTransformNone, 55 | colors_rgb_8bit=True, 56 | comment="Exported Dense Point Cloud", 57 | save_comment=True, 58 | format=Metashape.PointsFormatPLY, 59 | image_format=Metashape.ImageFormatJPEG, 60 | clip_to_boundary=True, 61 | block_width=1000, 62 | block_height=1000, 63 | split_in_blocks=False, 64 | save_images=False, 65 | subdivide_task=True 66 | ) -------------------------------------------------------------------------------- /_singleActions-AsScripts/33_GenerateMasks-FromModel.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | 1. create model 4 | 2. tools menu 5 | ------Mesh 6 | -----------'Generate masks' 7 | 8 | 9 | Is this any different than # 18 - import masks? 10 | 11 | 12 | 13 | """ --------------------------------------------------------------------------------