def oc32dem(project_name, output_path, split_in_blocks=False, resolution=2): import Metashape doc = Metashape.Document() doc.open(output_path + project_name + ".psx") doc.read_only = False chunk = doc.chunk chunk.buildDem(source_data=Metashape.DenseCloudData, interpolation=Metashape.DisabledInterpolation) doc.save() chunk.exportRaster(output_path + project_name + "_DEM.tif", source_data=Metashape.ElevationData, image_format=Metashape.ImageFormatTIFF, format=Metashape.RasterFormatTiles, nodata_value=-32767, save_kml=False, save_world=False, split_in_blocks=split_in_blocks, resolution=resolution)
def align_cameras(chunk, min_latitude, min_longitude): if chunk.transform.scale is None: chunk.transform.scale = 1 chunk.transform.rotation = ps.Matrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) chunk.transform.translation = ps.Vector([0, 0, 0]) i, j, k = get_chunk_vectors(min_latitude, min_longitude) # i || North estimate_rotation_matrices(chunk, i, j) for c in chunk.cameras: if c.transform is not None: continue location = c.reference.location if location is None: continue chunk_coordinates = wgs_to_chunk(chunk, location) fi = c.reference.rotation.x + 90 fi = math.radians(fi) roll = math.radians(c.reference.rotation.z) pitch = math.radians(c.reference.rotation.y) roll_mat = ps.Matrix([[1, 0, 0], [0, math.cos(roll), -math.sin(roll)], [0, math.sin(roll), math.cos(roll)]]) pitch_mat = ps.Matrix([[math.cos(pitch), 0, math.sin(pitch)], [0, 1, 0], [-math.sin(pitch), 0, math.cos(pitch)]]) yaw_mat = ps.Matrix([[math.cos(fi), -math.sin(fi), 0], [math.sin(fi), math.cos(fi), 0], [0, 0, 1]]) r = roll_mat * pitch_mat * yaw_mat ii = r[0, 0] * i + r[1, 0] * j + r[2, 0] * k jj = r[0, 1] * i + r[1, 1] * j + r[2, 1] * k kk = r[0, 2] * i + r[1, 2] * j + r[2, 2] * k c.transform = ps.Matrix([[ii.x, jj.x, kk.x, chunk_coordinates[0]], [ii.y, jj.y, kk.y, chunk_coordinates[1]], [ii.z, jj.z, kk.z, chunk_coordinates[2]], [0, 0, 0, 1]])
doc.save() chunk = doc.chunk chunk.buildDem(source_data=Metashape.DenseCloudData, interpolation=Metashape.EnabledInterpolation) doc.save() if chunk.orthomosaic is None: chunk.buildOrthomosaic(surface_data=Metashape.ElevationData, blending_mode=Metashape.MosaicBlending) doc.save() if __name__ == '__main__': app = Metashape.Application() doc = Metashape.app.document project_path = Metashape.app.getSaveFileName() if project_path[-4:].lower() != ".psx": project_path += ".psx" doc.save(project_path) chunk_list = doc.chunks for chunk in chunk_list: doc.chunk = chunk out_crs = Metashape.app.getCoordinateSystem( 'choose CRS like your GCP', chunk.crs ) convertCRS(chunk, out_crs) GCP = Metashape.app.getBool(label='If yot have GCP select "yes", program will BREAK to give you import GCP after align photo done. Select "No" program will run buil all finish.')
import Metashape # Use this to create a metashape.lic file for running headless python scripts Metashape.License().activate("AAAAA-BBBBB-CCCCC-DDDDD-EEEEE")
import Metashape import os print(Metashape.app.enumGPUDevices()) Metashape.app.gpu_mask = 1 if not Metashape.app.activated: raise ('No license') doc = Metashape.Document() todo_list = range(20, 30) pic_dir = '' for num in todo_list: chunk = doc.addChunk() chunk.label = "pic_{}".format(num) path_photos = pic_dir + "pic_{}".format(num) print(path_photos) image_list = os.listdir(path_photos) chunk.addPhotos([ path_photos + '/' + x for x in image_list if x.split('.')[-1].lower() == 'jpg' ]) for carmera in chunk.cameras: if "GOPR" in carmera.label: carmera.sensor.type = Metashape.Sensor.Type.Fisheye for frame in chunk.frames: frame.matchPhotos(accuracy=Metashape.HighestAccuracy, keypoint_limit=40000, tiepoint_limit=40000) # HighestAccuracy
def main() -> bool: """Main function for handling align and delete This will create a new chunk, add and align photos, create tie points, estimate camera locations and delete all tie points outside bounding box. Then it will delete pixels above a 0.5 reprojection error. Returns ------- bool False if issue with photo paths and True if align and delete completed """ path_photos, path_export = prompt_path() if path_photos == "" or path_export == "": return False # logging logger = logging.getLogger() logger.handlers.clear() f_formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") f_handler = logging.FileHandler( filename=path_photos + divider + "align_and_delete.log", mode="a" ) f_handler.setFormatter(f_formatter) logger.addHandler(f_handler) logger.setLevel(logging.DEBUG) # get rid of pesky files like .DS_Store fold_list = filter( lambda x: x[0] != "." and x[-3::] != "log", os.listdir(path_photos) ) logger.info("starting align_and_delete") for folder in fold_list: logger.info(folder) if not os.path.isfile(folder): # loading images folderPath = path_photos + divider + folder if os.path.isfile(folderPath): # skip because it should be folder not file continue photo_list = [] # getting rid of pesky files like .DS_Store image_list = filter(lambda x: x[0] != ".", os.listdir(folderPath)) for photo in image_list: if ("jpg" or "jpeg") in photo.lower(): photo_list.append(os.path.join(folderPath, photo)) # only runs program on folder with photos in it (photo_list in chunk.addPhotos(photo_list) can't be empty) if not photo_list: print("found non photo folder") logger.info("found non photo folder") continue doc = meta.Document() doc.save(path_export + divider + folder + ".psx") try: chunk = doc.addChunk() chunk.addPhotos(photo_list) # align photos chunk.matchPhotos( downscale=DOWNSCALE, generic_preselection=GENERIC_PRESELECTION, filter_mask=False, keypoint_limit=KEYPOINTS, tiepoint_limit=TIEPOINTS, ) chunk.alignCameras() chunk = doc.chunks[-1] # delete points outside bounding box # https://www.agisoft.com/forum/index.php?topic=9030.0 R = chunk.region.rot # Bounding box rotation matrix C = chunk.region.center # Bounding box center vertor size = chunk.region.size if not (chunk.point_cloud and chunk.enabled): continue elif not chunk.point_cloud.points: continue for point in chunk.point_cloud.points: if point.valid: v = point.coord v.size = 3 v_c = v - C v_r = R.t() * v_c if abs(v_r.x) > abs(size.x / 2.0): point.valid = False elif abs(v_r.y) > abs(size.y / 2.0): point.valid = False elif abs(v_r.z) > abs(size.z / 2.0): point.valid = False else: continue # Points outside the region were removed. Read reprojection error and delete any 0.5 or greater f = meta.PointCloud.Filter() f.init(chunk, criterion=meta.PointCloud.Filter.ReprojectionError) f.removePoints(THRESHOLD) doc.save() print("completed align_and_delete for:", folder) logger.info(folder + ": completed align_and_delete") except RuntimeError as r_err: logger.error(folder + ": " + str(r_err)) return True
def align_two_point_clouds(points1_source, points2_target, scale_ratio=None, target_resolution=None, no_global_alignment=False, preview_intermidiate_alignment=True): # For example let: # - points2_target - tree with height1=10 and resolution1=0.1 (in its coordinates system) # - points2_target - the same tree but with height2=50 (because of another coordinates system) and resolution2=1.0 # Then: # - scale_ratio should be height2/height1=50/10=5 or (if scale_ratio=None) it will be guessed based on convex hulls (this works good only for closed objects without noise - like furniture object or house without ground surface around it) # - target_resolution=resolution2=1.0 or (if target_resolution=None) it will be guessed as rough average distance between two points # # So if you want to align two models/point clouds with not-100% overlap (or for not closed objects) - you should measure and specify scale ratio # (note that between LIDAR point clouds scale ratio is mostly 1.0) assert (isinstance(points1_source, np.ndarray) and isinstance(points2_target, np.ndarray)) assert (points1_source.shape[1] == points2_target.shape[1] == 3) v1, v2 = points1_source, points2_target c1 = np.mean(v1, axis=0) c2 = np.mean(v2, axis=0) if no_global_alignment: c1[:] = 0.0 c2[:] = 0.0 v1 = v1 - c1 v2 = v2 - c2 if scale_ratio is None: if no_global_alignment: scale_ratio = 1.0 else: print("Warning! No scale ratio!") print( "It will be estimated based on convex hulls of point clouds/models and so alignment may fail if object is not closed!" ) print( "So if alignment will fail - please manually measure and specify scale ratio!" ) start = time.time() v1_subsampled = subsample_points(v1, 100000) v2_subsampled = subsample_points(v2, 100000) hull_size1 = estimate_convex_hull_size(v1_subsampled) hull_size2 = estimate_convex_hull_size(v2_subsampled) scale_ratio = hull_size2 / hull_size1 print(" scale_ratio={} (size1={}, size2={})".format( scale_ratio, hull_size1, hull_size2)) print(" estimated in {} s".format(time.time() - start)) if target_resolution is None: print("Warning! No target resolution!") print( "It will be estimated based on rough average distance between points!" ) start = time.time() v1_subsampled = subsample_points(v1, 1000) source_resolution = 1.5 * estimate_resolution(v1_subsampled) / np.sqrt( len(v1) / len(v1_subsampled)) * scale_ratio v2_subsampled = subsample_points(v2, 1000) target_resolution = 1.5 * estimate_resolution(v2_subsampled) / np.sqrt( len(v2) / len(v2_subsampled)) resolution = np.max([source_resolution, target_resolution]) print( " target_resolution={} (resolution1={}, resolution2={})".format( resolution, source_resolution, target_resolution)) print(" estimated in {} s".format(time.time() - start)) target_resolution = resolution print("scale_ratio={} target_resolution={}".format(scale_ratio, target_resolution)) Metashape.app.update() v1 = v1 * scale_ratio stage = 0 total_stages = 2 if no_global_alignment else 3 if no_global_alignment: transformation = np.eye(4) if preview_intermidiate_alignment: print("Initial objects shown!") draw_registration_result(v1, v2, title="Initial alignment") else: stage += 1 print("{}/{}: Global registration...".format(stage, total_stages)) start = time.time() source_down1, target_down1, global_registration_result = global_registration( v1, v2, global_voxel_size=64.0 * target_resolution) print(" estimated in {} s".format(time.time() - start)) Metashape.app.update() if preview_intermidiate_alignment: print("{}/{}: Global registration shown!".format( stage, total_stages)) draw_registration_result(source_down1, target_down1, global_registration_result.transformation, title="Initial global alignment") transformation = global_registration_result.transformation downscale1 = 8.0 stage += 1 print("{}/{}: Coarse ICP registration...".format(stage, total_stages)) start = time.time() icp_voxel_size1 = downscale1 * target_resolution source_down1 = downscale_point_cloud(to_point_cloud(v1), icp_voxel_size1) target_down1 = downscale_point_cloud(to_point_cloud(v2), icp_voxel_size1) icp_result1 = icp_registration(source_down1, target_down1, voxel_size=icp_voxel_size1, transform_init=transformation, max_iterations=100) print(" estimated in {} s".format(time.time() - start)) Metashape.app.update() if preview_intermidiate_alignment: print("{}/{}: Coarse ICP registration shown!".format( stage, total_stages)) draw_registration_result(source_down1, target_down1, icp_result1.transformation, title="Intermidiate ICP alignment") transformation = icp_result1.transformation downscale2 = 1.0 stage += 1 print("{}/{}: Fine ICP registration...".format(stage, total_stages)) start = time.time() icp_voxel_size2 = downscale2 * target_resolution icp_result2 = icp_registration(to_point_cloud(v1), to_point_cloud(v2), voxel_size=icp_voxel_size2, transform_init=transformation, max_iterations=100) print(" estimated in {} s".format(time.time() - start)) Metashape.app.update() if preview_intermidiate_alignment: print("{}/{}: Fine ICP registration shown!".format( stage, total_stages)) draw_registration_result(v1, v2, icp_result2.transformation, title="Resulting alignment") transformation = icp_result2.transformation T1 = np.diag([1.0, 1.0, 1.0, 1.0]) T1[:3, 3] = -c1.reshape(3) S = np.diag([scale_ratio, scale_ratio, scale_ratio, 1.0]) T2 = np.diag([1.0, 1.0, 1.0, 1.0]) T2[:3, 3] = c2.reshape(3) M = np.dot(T2, np.dot(transformation, np.dot(S, T1))) M = Metashape.Matrix(M) print("Estimated transformation matrix:") print(M) Metashape.app.update() return M
def removeLicense(): Metashape.License().deactivate(key) for licence in licenses: os.remove(licence.as_posix())
def update_boundbox(): doc = Metashape.app.document chunk = doc.chunk # This points to the first chunk! print(banner1, "Updating boundbox using markers...") #ground targets for finding horizontal center '''p0 = "target 1" p1 = "target 8" ''' #below: special case for strawberry p0 = "target 1" p1 = "target 19" fp0 = fp1 = 0 #setting for Y up, Z forward -> needed for mixamo/unity c = 0 #find center points (my way) c1 = 0 c2 = 0 print(p0, p1) for m in chunk.markers: if m.label == p0: c1 = c fp0 = 1 elif m.label == p1: c2 = c fp1 = 1 c = c + 1 #check markers found (my way) if fp0 and fp1: print("Found all markers") print(c1, c2) chunk.updateTransform() else: print("Error: not all markers found") print(fp0, fp1) newregion = chunk.region T = chunk.transform.matrix #v_t = T * Metashape.Vector( [0,0,0,1] ) m = Metashape.Matrix().Diag([1, 1, 1, 1]) m = m * T s = math.sqrt(m[0, 0]**2 + m[0, 1]**2 + m[0, 2]**2) #scale factor R = Metashape.Matrix([[m[0, 0], m[0, 1], m[0, 2]], [m[1, 0], m[1, 1], m[1, 2]], [m[2, 0], m[2, 1], m[2, 2]]]) R = R * (1. / s) newregion.rot = R.t() # Calculate center point of the bounding box, by taking the average of 2 left and 2 right markers mx = (chunk.markers[c1].position + chunk.markers[c2].position) / 2 #my way print('x', chunk.markers[c2].position[0], chunk.markers[c1].position[0]) print('y', chunk.markers[c2].position[1], chunk.markers[c1].position[1]) box_X_length = math.fabs( chunk.markers[c2].position[0] - chunk.markers[c1].position[0] ) #x-axis. may return negative number, so absoluted box_Y_length = math.fabs( chunk.markers[c2].position[1] - chunk.markers[c1].position[1] ) #y-axis. may return negative number, so absoluted print('lengths:', box_X_length, box_Y_length) '''if box_Y_length > box_X_length: swap_length = box_X_length box_X_length = box_Y_length box_Y_length = swap_length''' #disabled for strawberry box_Z_length = box_Y_length #same as Y length #chunk.markers[c2].position[2] - chunk.markers[c1].position[2] #z-axis print('lengths:', box_X_length, box_Y_length, box_Z_length) Z_ratio = 4 #my way. Change this to adjust box height (default=1) mx = Metashape.Vector( [mx[0], mx[1], mx[2] + (0 * Z_ratio * box_Z_length / 2)] ) #adjusting the zratio thing shifts the whole boundbox on a diagonal. not so easy. set to 0 for now. newregion.center = mx newregion.size = Metashape.Vector( [box_X_length, box_Y_length, box_Z_length * Z_ratio]) #my way chunk.region = newregion chunk.updateTransform() #disabled for strawberry print("Bounding box should be aligned now")
import os import Metashape, sys ##################################################################################### ##this line is pointing to the path of drone photos that you specified in the Command Prompt code path_photos = sys.argv[1] # set new working directory to be in working folder set in command line os.chdir(path_photos) ##Define: PhotoScan Project File MetashapeProjectFile = ".\\Metashape_Project.psx" ##get main app objects doc = Metashape.Document() doc.save(MetashapeProjectFile) app = Metashape.Application() ## create chunk chunk = doc.addChunk() ## loading images image_list = os.listdir(path_photos) photo_list = list() for photo in image_list: if ("jpg" or "jpeg" or "JPG" or "JPEG") in photo.lower(): photo_list.append(path_photos + "/" + photo) chunk.addPhotos(photo_list)
def align(self): print("Script started...") (key1, isModel1, label1) = self.objects[self.fromObject.currentIndex()] (key2, isModel2, label2) = self.objects[self.toObject.currentIndex()] print("Aligning {} to {}...".format(label1, label2)) region_size = Metashape.app.document.chunk.region.size try: tmp1 = tempfile.NamedTemporaryFile(delete=False) tmp1.close() tmp2 = tempfile.NamedTemporaryFile(delete=False) tmp2.close() Metashape.app.document.chunk.region.size = Metashape.Vector([0.0, 0.0, 0.0]) for (key, isModel, filename) in [(key2, isModel2, tmp2.name), (key1, isModel1, tmp1.name)]: if isModel: self.chunk.model = None for model in self.chunk.models: if model.key == key: self.chunk.model = model assert(self.chunk.model is not None) self.chunk.exportModel(path=filename, binary=True, save_texture=False, save_uv=False, save_normals=False, save_colors=False, save_cameras=False, save_markers=False, save_udim=False, save_alpha=False, save_comment=False, format=Metashape.ModelFormatPLY) else: self.chunk.dense_cloud = None for dense_cloud in self.chunk.dense_clouds: if dense_cloud.key == key: self.chunk.dense_cloud = dense_cloud assert(self.chunk.dense_cloud is not None) self.chunk.exportPoints(path=filename, source_data=Metashape.DenseCloudData, binary=True, save_normals=False, save_colors=False, save_classes=False, save_confidence=False, save_comment=False, format=Metashape.PointsFormatPLY) v1 = read_ply(tmp1.name) v2 = read_ply(tmp2.name) os.remove(tmp1.name) os.remove(tmp2.name) Metashape.app.document.chunk.region.size = region_size except: os.remove(tmp1.name) os.remove(tmp2.name) Metashape.app.document.chunk.region.size = region_size raise print("Vertices number: {}, {}".format(len(v1), len(v2))) scale_ratio = None if self.edtScaleRatio.text() == '' else float(self.edtScaleRatio.text()) target_resolution = None if self.edtTargetResolution.text() == '' else float(self.edtTargetResolution.text()) no_global_alignment = self.chkUseInitialAlignment.isChecked() preview_intermidiate_alignment = self.chkPreview.isChecked() M12 = align_two_point_clouds(v1, v2, scale_ratio, target_resolution, no_global_alignment, preview_intermidiate_alignment) if isModel1: assert(self.chunk.model.key == key1) try: matrix = self.chunk.transform.matrix if self.chunk.model.transform is not None: matrix = self.chunk.model.transform * matrix self.chunk.model.transform = Metashape.Matrix(M12) * matrix except AttributeError: nvertices = len(self.chunk.model.vertices) matrix = Metashape.Matrix(M12) vertices = self.chunk.model.vertices for i in range(nvertices): vertices[i].coord = matrix.mulp(vertices[i].coord) vertices.resize(nvertices) self.chunk.model = None else: assert(self.chunk.dense_cloud.key == key1) matrix = self.chunk.transform.matrix if self.chunk.dense_cloud.transform is not None: matrix = self.chunk.dense_cloud.transform * matrix self.chunk.dense_cloud.transform = Metashape.Matrix(M12) * matrix self.chunk.dense_cloud = None print("Script finished!") self.reject()
def create_footprints(): """ Creates four-vertex shape for each aligned camera (footprint) in the active chunk and puts all these shapes to a new separate shape layer """ doc = Metashape.app.document if not len(doc.chunks): raise Exception("No chunks!") print("Script started...") chunk = doc.chunk if not chunk.shapes: chunk.shapes = Metashape.Shapes() chunk.shapes.crs = chunk.crs T = chunk.transform.matrix footprints = chunk.shapes.addGroup() footprints.label = "Footprints" footprints.color = (30, 239, 30) if chunk.model: surface = chunk.model elif chunk.dense_cloud: surface = chunk.dense_cloud else: surface = chunk.point_cloud for camera in chunk.cameras: if not camera.transform: continue # skipping NA cameras sensor = camera.sensor corners = list() for i in [[0, 0], [sensor.width - 1, 0], [sensor.width - 1, sensor.height - 1], [0, sensor.height - 1]]: corners.append( surface.pickPoint( camera.center, camera.transform.mulp( sensor.calibration.unproject(Metashape.Vector(i))))) if not corners[-1]: corners[-1] = chunk.point_cloud.pickPoint( camera.center, camera.transform.mulp( sensor.calibration.unproject(Metashape.Vector(i)))) if not corners[-1]: break corners[-1] = chunk.crs.project(T.mulp(corners[-1])) if not all(corners): print("Skipping camera " + camera.label) continue if len(corners) == 4: shape = chunk.shapes.addShape() shape.label = camera.label shape.attributes["Photo"] = camera.label shape.type = Metashape.Shape.Type.Polygon shape.group = footprints shape.vertices = corners shape.has_z = True Metashape.app.update() print("Script finished!")
argumentFile.close() # Strip new line character at the end of the path name addrDir = addrDir.strip('\n') # Combine the path with the name of the address folder containing the IP Address (address.txt) and store it in the variable (addrDir) # It will always be named address.txt becuase that is the name designated in initServerNode2.sh addrDir = (addrDir + "/address.txt") # Opens the address file addr_file = open(addrDir, "r") # Reads the IP Address and stores it in a variable addr_str = addr_file.readline() # Closes the address file addr_file.close() # Create a Metashape Network Client object named 'client' client = Metashape.NetworkClient() # Combines the path to where the project is to be saved with the name of the project in order to save, open, and access the project in the script filePath = (path + docName) # Create a Metashape Document object and name it 'doc' doc = Metashape.app.document # Set the read only variable to false in order to enable editing doc.read_only = False # Creates the project because this is the first processing task to be done doc.save(filePath) # Add a chunk to the project chunk = doc.addChunk() # Create a list to store our photos photos = [] # Using glob, grab all the photos from the photos folder and add them to the list for photo in glob.glob(sourceFolderPath):
def mark_Images(chunk, GCPpath, path): # Load the GCP Locations try: chunk.importReference(GCPpath, format=PhotoScan.ReferenceFormatCSV, delimiter=",", columns="nxyz", create_markers=True, crs=PhotoScan.CoordinateSystem("EPSG::32611")) #, crs=PhotoScan.CoordinateSystem("EPSG::32611") #, threshold = 3 except: print("GCP Coordinate File Not Found.") file = open(path, "rt") #input file eof = False line = file.readline() line = file.readline() if not len(line): eof = True while not eof: sp_line = line.rsplit(",", 4) #splitting read line by five parts print(sp_line) y = float( sp_line[4]) #y- coordinate of the current projection in pixels x = float( sp_line[3]) #x- coordinate of the current projection in pixels path = sp_line[2] #camera label marker_name = sp_line[1] #marker label flag = 0 for i in range(len(chunk.cameras)): if chunk.cameras[i].label == path: #searching for the camera print("found camera") for j in range( len(chunk.markers) ): #searching for the marker (comparing with all the marker labels in chunk) if chunk.markers[j].label == marker_name: print("Found Marker") chunk.markers[j].projections[ chunk.cameras[i]] = PhotoScan.Marker.Projection( PhotoScan.Vector([x, y]), True ) #setting up marker projection of the correct photo) flag = 1 break if not flag: print("Not Flag") marker = chunk.addMarker() marker.label = marker_name marker.projections[ chunk.cameras[i]] = PhotoScan.Marker.Projection( PhotoScan.Vector([x, y]), True) break line = file.readline() #reading the line from input file if not len(line): eof = True break # EOF ## Disable GCP-t and TCPs for marker in chunk.markers: # Set the Marker Accuracy marker.reference.accuracy = PhotoScan.Vector([3, 3, 3]) if marker.label == 'Hhh': marker.reference.enabled = False elif marker.label == '100': marker.reference.enabled = False elif marker.label == 'gcp0t': marker.reference.enabled = False elif marker.label == 'gcp0-t': marker.reference.enabled = False elif marker.label == 'gcp1t': marker.reference.enabled = False elif marker.label == 'gcp2t': marker.reference.enabled = False elif marker.label == 'gcp3t': marker.reference.enabled = False elif marker.label == 'gcp4t': marker.reference.enabled = False elif marker.label == 'tcp1': marker.reference.enabled = False elif marker.label == 'tcp2': marker.reference.enabled = False elif marker.label == 'tcp3': marker.reference.enabled = False elif marker.label == 'tcp4': marker.reference.enabled = False else: pass file.close() return
def AlignPhoto(locFold, ProcessDate, typeFolder, chunk, Accuracy, Key_Limit, Tie_Limit, QualityFilter, QualityCriteria, new_crs): # Set the camera and marker projections wgs_84 = PhotoScan.CoordinateSystem("EPSG::4326") for camera in chunk.cameras: if camera.reference.location: camera.reference.location = PhotoScan.CoordinateSystem.transform( camera.reference.location, wgs_84, new_crs) # Filter out poor qualtiy Images if QualityFilter: if chunk.cameras[0].meta['Image/Quality'] is None: try: chunk.estimateImageQuality() # Metashape 1.5 except: chunk.analyzePhotos() # Metashape 1.6 for camera in chunk.cameras: if float(camera.meta["Image/Quality"]) < QualityCriteria: camera.enabled = False ## For Multispectral camera # for band in [band for camera in chunk.cameras for band in camera.planes]: # if float(band.meta['Image/Quality']) < QualityCriteria: # band.enabled = False # Get list of images in chunk originalImgList = list() for camera in chunk.cameras: if not camera.transform: originalImgList.append(camera) originalCount = len(originalImgList) # Metashape 1.5 try: chunk.matchPhotos(accuracy=Accuracy, generic_preselection=True, reference_preselection=False, filter_mask=False, keypoint_limit=Key_Limit, tiepoint_limit=Tie_Limit) # Metashape 1.6 except: chunk.matchPhotos(downscale=Accuracy, generic_preselection=True, reference_preselection=False, filter_mask=False, keypoint_limit=Key_Limit, tiepoint_limit=Tie_Limit) chunk.alignCameras() # Realign non-aligned images realign_list = list() for camera in chunk.cameras: if not camera.transform: realign_list.append(camera) chunk.alignCameras(cameras=realign_list) realign_list = list() for camera in chunk.cameras: if not camera.transform: realign_list.append(camera) chunk.alignCameras(cameras=realign_list) aligned_list = list() for camera in chunk.cameras: if camera.transform: aligned_list.append(camera) align2 = len(aligned_list) # if more than 60% of images were aligned if align2 >= originalCount * 0.6: successAlignment = True # enabling rolling shutter compensation try: for sensor in chunk.sensors: sensor.rolling_shutter = True except: print("Error applying rolling shutter correction") chunk.optimizeCameras(fit_f=True, fit_cx=True, fit_cy=True, fit_b1=False, fit_b2=False, fit_k1=True, fit_k2=True, fit_k3=True, fit_k4=False, fit_p1=True, fit_p2=True, fit_p3=False, fit_p4=False, adaptive_fitting=False, tiepoint_covariance=False) else: successAlignment = False #Remove method variables realign_list = None aligned_list = None originalImgList = None originalCount = None return successAlignment
# Variable for building 3D mesh # Surface: Arbitrary, HeightField # SurfaceSource: PointCloudData, DenseCloudData, DepthMapsData Surface = PhotoScan.SurfaceType.Arbitrary SurfaceSource = PhotoScan.DataSource.DepthMapsData # # Variable for building orthomosaic # Since 1.4.0, users can choose performing color correction (vignetting) and balance separately. # Blending: AverageBlending, MosaicBlending, MinBlending, MaxBlending, DisabledBlending # Color_correction: True, False # Color_balance: True, False BlendingMode = PhotoScan.BlendingMode.MosaicBlending Color_correction = False Color_balance = False # Set the project projection new_crs = PhotoScan.CoordinateSystem("EPSG::32611") #UTM11N #####--------------------------------------------------Processing---------------------------------------------------------------####### ## Define Folders to Process mainFold = "/SNOWDATA/SnowDrones_Processing/" Loc = "LDP" locFold = mainFold + Loc + "/" ## Read in the GCP RTK Target GPS coordinates GCP_coordFN = locFold + Loc + "_unprocessed.csv" ## List of dates to run code AllDates = [ dI for dI in sorted(os.listdir(locFold)) if os.path.isdir(os.path.join(locFold, dI)) ]
optimise_k1 = True optimise_k2 = True optimise_k3 = True optimise_k4 = False optimise_p1 = False optimise_p2 = False optimise_p3 = False optimise_p4 = False # Points are exported as floats in binary ply files for speed and size, and thus cannot represent very small changes # in large geographic coordinates. Thus, the offset below can be set to form a local origin and ensure numerical # precision is not lost when coordinates are saved as floats. The offset will be subtracted from point coordinates. # [RECOMMENDED] - Leave as NaN; the script will automatically calculate and apply a suitable offset, which will be saved # as a text file for further processing, OR edit the line to impose a specific offset of your choice - # e.g. pts_offset = Metashape.Vector( [266000, 4702000, 0] ) pts_offset = Metashape.Vector([NaN, NaN, NaN]) ################################### END OF SETUP ################################### ######################################################################################## # Initialisation filename = os.path.abspath("C:/HG_Projects/CWC_Drone_work/pia_plots/P3E1.psz") doc = Metashape.app.document doc.open(filename, read_only=False) chunk = doc.chunk # chunk = Metashape.app.document.chunk point_proj = chunk.point_cloud.projections # Need CoordinateSystem object, but PS only returns 'None' if an arbitrary coordinate system is being used # thus need to set manually in this case; otherwise use the Chunk coordinate system. if chunk.crs == None:
def main(camera_extension, aligned_camera_threshold): global start_time start_time = time.time() project_filepath = get_project_filepath() log_file = open(project_filepath.replace('.psx', '.log'), 'w') # Enable all GPUs Metashape.app.gpu_mask = 2 ** (len(Metashape.app.enumGPUDevices())) - 1 doc = Metashape.Document() if os.path.isfile(project_filepath): doc.open(project_filepath) # Open exisiting project else: doc.save(project_filepath) # Create new project if not doc.chunk: chunk = doc.addChunk() # Add chunk if it does not already exists else: chunk = doc.chunk if len(chunk.cameras) == 0: camera_list = convert_cameras(camera_extension) chunk.addPhotos(camera_list) doc.save() aligned_cameras, non_aligned_cameras = get_aligned_and_non_aligned_cameras(chunk) if len(aligned_cameras) == 0: start_next_step("Match photos", log_file) chunk.matchPhotos(downscale = 1, # Image alignment accuracy = High generic_preselection = True, # Enable generic preselection reference_preselection = False, # Disable reference preselection filter_mask = False, # Disable filtering points by mask mask_tiepoints = False, # Disable applying mask filter to tie points keypoint_limit = 5000, tiepoint_limit = 0, keep_keypoints = False, # Do not store keypoints in the project guided_matching = False, # Disable guided image matching reset_matches = True, # Resent current matches progress = progress_print) doc.save() start_time = time.time() start_next_step("Align photos", log_file) chunk.alignCameras(adaptive_fitting = True, # Enable adaptive fitting of distortion coefficients reset_alignment = True, # Reset current alignment progress = progress_print) doc.save() start_time = time.time() aligned_cameras, non_aligned_cameras = get_aligned_and_non_aligned_cameras(chunk) if (len(aligned_cameras) / len(chunk.cameras)) < aligned_camera_threshold: sys.exit('Unsufficient cameras aligned: {0} aligned out of {1}'.format(len(aligned_cameras), len(chunk.cameras))) start_next_step("Build dense maps", log_file) chunk.buildDepthMaps(downscale = 2, # Depth map quality = High (2) filter_mode = Metashape.MildFiltering, reuse_depth = False, # Disable reuse depth maps option progress = progress_print) doc.save() start_time = time.time() start_next_step("Build dense maps", log_file) chunk.buildDenseCloud(point_colors = True, # Enable point colors calculation point_confidence = True, # Enable point confidence calculation keep_depth = True, # Enable store depth maps option progress = progress_print) doc.save() start_time = time.time() start_next_step("Export points to PLY file", log_file) chunk.exportPoints(path = project_filepath.replace('.psx', '.ply'), source_data = Metashape.DenseCloudData, binary = True, save_normals = True, save_colors = True, save_classes = False, save_confidence = True, raster_transform = Metashape.RasterTransformNone, colors_rgb_8bit = True, format = Metashape.PointsFormatPLY, split_in_blocks = False, progress = progress_print) start_next_step("Export cameras positions", log_file) chunk.exportCameras(project_filepath.replace('.psx', '.cams.xml')) start_next_step("Export camera metadata", log_file) output_camera_metadata(project_filepath.replace('.psx', '.meta.json'), chunk) doc.save() start_time = time.time() log_file.close()
global appFile appFile = [f.as_posix() for f in list (Path(path).glob('Metashape.exe'))] global licenses licenses = [Path(f) for f in list (Path(path).glob('*.lic')) if Path(f).stem != 'rlm_roam'] for licence in licenses: os.remove(licence.as_posix()) global app app = QtCore.QCoreApplication.instance() global key key = 'XXXXX-XXXXX-XXXXX-XXXXX-XXXXX' if Metashape.app.activated == False: locale = QtCore.QLocale.system().name().split('_')[0] if locale =='fr': print("Activation de la clé de licence...") else: print(" Activating License Key...") Metashape.License().activate(key) def removeLicense(): Metashape.License().deactivate(key) for licence in licenses: os.remove(licence.as_posix()) app.aboutToQuit.connect(removeLicense)
def build_dense_cloud(doc, log_file, run_id, cfg): ''' Build depth maps and dense cloud ''' ### Build depth maps # get a beginning time stamp for the next step timer2a = time.time() # build depth maps only instead of also building the dense cloud ##?? what does doc.chunk.buildDepthMaps( downscale=cfg["buildDenseCloud"]["downscale"], filter_mode=cfg["buildDenseCloud"]["filter_mode"], reuse_depth=cfg["buildDenseCloud"]["reuse_depth"], max_neighbors=cfg["buildDenseCloud"]["max_neighbors"], subdivide_task=cfg["subdivide_task"]) doc.save() # get an ending time stamp for the previous step timer2b = time.time() # calculate difference between end and start time to 1 decimal place time2 = diff_time(timer2b, timer2a) # record results to file with open(log_file, 'a') as file: file.write(sep.join(['Build Depth Maps', time2]) + '\n') ### Build dense cloud # get a beginning time stamp for the next step timer3a = time.time() # build dense cloud doc.chunk.buildDenseCloud( max_neighbors=cfg["buildDenseCloud"]["max_neighbors"], keep_depth=cfg["buildDenseCloud"]["keep_depth"], subdivide_task=cfg["subdivide_task"], point_colors=True) doc.save() # get an ending time stamp for the previous step timer3b = time.time() # calculate difference between end and start time to 1 decimal place time3 = diff_time(timer3b, timer3a) # record results to file with open(log_file, 'a') as file: file.write(sep.join(['Build Dense Cloud', time3]) + '\n') ### Classify ground points if cfg["buildDenseCloud"]["classify"]: # get a beginning time stamp for the next step timer_a = time.time() doc.chunk.dense_cloud.classifyGroundPoints( max_angle=cfg["buildDenseCloud"]["max_angle"], max_distance=cfg["buildDenseCloud"]["max_distance"], cell_size=cfg["buildDenseCloud"]["cell_size"]) doc.save() # get an ending time stamp for the previous step timer_b = time.time() # calculate difference between end and start time to 1 decimal place time_tot = diff_time(timer_b, timer_a) # record results to file with open(log_file, 'a') as file: file.write(sep.join(['Classify Ground Points', time_tot]) + '\n') ### Export points if cfg["buildDenseCloud"]["export"]: output_file = os.path.join(cfg["output_path"], run_id + '_points.las') if cfg["buildDenseCloud"]["classes"] == "ALL": # call without classes argument (Metashape then defaults to all classes) doc.chunk.exportPoints(path=output_file, source_data=Metashape.DenseCloudData, format=Metashape.PointsFormatLAS, crs=Metashape.CoordinateSystem( cfg["project_crs"]), subdivide_task=cfg["subdivide_task"]) else: # call with classes argument doc.chunk.exportPoints(path=output_file, source_data=Metashape.DenseCloudData, format=Metashape.PointsFormatLAS, crs=Metashape.CoordinateSystem( cfg["project_crs"]), clases=cfg["buildDenseCloud"]["classes"], subdivide_task=cfg["subdivide_task"]) return True
horizontal = get_marker(horiz[0], chunk).position - get_marker( horiz[1], chunk).position #!disabled for strawberry vertical = get_marker(vert[0], chunk).position - get_marker(vert[1], chunk).position #!special for strawberry normal = get_marker(horiz[0], chunk).position normal[2] = normal[2] + 1 '''normal = vect(horizontal, vertical) vertical = vertical.normalized() horizontal = vect(vertical, normal)''' #!disabled for strawberry normal = vect(horizontal, vertical) vertical = -vect(horizontal, normal) horizontal = -horizontal.normalized() #!disabled for strawberry vertical = - vect(horizontal, normal) normal = -vect(horizontal, vertical) R = Metashape.Matrix([horizontal, vertical, normal ]) #horizontal = X, vertical = Y, normal = Z print(R.det()) region.rot = R.t() chunk.region = region R = chunk.region.rot #Bounding box rotation matrix C = chunk.region.center #Bounding box center vector if chunk.transform.matrix: T = chunk.transform.matrix s = math.sqrt(T[0, 0]**2 + T[0, 1]**2 + T[0, 2]**2) #scaling # T.scale() S = Metashape.Matrix().Diag([s, s, s, 1]) #scale matrix else: S = Metashape.Matrix().Diag([1, 1, 1, 1]) T = Metashape.Matrix([[R[0, 0], R[0, 1], R[0, 2], C[0]],
def project_setup(cfg): ''' Create output and project paths, if they don't exist Define a project ID based on photoset name and timestamp Define a project filename and a log filename Create the project Start a log file ''' # Make project directories (necessary even if loading an existing project because this workflow saves a new project based on the old one, leaving the old one intact if not os.path.exists(cfg["output_path"]): os.makedirs(cfg["output_path"]) if not os.path.exists(cfg["project_path"]): os.makedirs(cfg["project_path"]) ### Set a filename template for project files and output files ## Get the first parts of the filename (the photoset ID and location string) run_name = cfg["run_name"] ## Project file example to make: "projectID_YYYYMMDDtHHMM-jobID.psx" timestamp = stamp_time() run_id = "_".join([run_name, timestamp]) # TODO: If there is a slurm JobID, append to time (separated with "-", not "_"). This will keep jobs initiated in the same minute distinct project_file = os.path.join(cfg["project_path"], '.'.join([run_id, 'psx'])) log_file = os.path.join(cfg["output_path"], '.'.join([run_id + "_log", 'txt'])) ''' Create a doc and a chunk ''' # create a handle to the Metashape object doc = Metashape.Document( ) #When running via Metashape, can use: doc = Metashape.app.document # If specified, open existing project if cfg["load_project"] != "": doc.open(cfg["load_project"]) else: # Initialize a chunk, set its CRS as specified chunk = doc.addChunk() chunk.crs = Metashape.CoordinateSystem(cfg["project_crs"]) chunk.marker_crs = Metashape.CoordinateSystem( cfg["addGCPs"]["gcp_crs"]) # Save doc doc as new project (even if we opened an existing project, save as a separate one so the existing project remains accessible in its original state) doc.save(project_file) ''' Log specs except for GPU ''' # log Metashape version, CPU specs, time, and project location to results file # open the results file # TODO: records the Slurm values for actual cpus and ram allocated # https://slurm.schedmd.com/sbatch.html#lbAI with open(log_file, 'a') as file: # write a line with the Metashape version file.write(sep.join(['Project', run_id]) + '\n') file.write( sep.join([ 'Agisoft Metashape Professional Version', Metashape.app.version ]) + '\n') # write a line with the date and time file.write(sep.join(['Processing started', stamp_time()]) + '\n') # write a line with CPU info - if possible, improve the way the CPU info is found / recorded file.write(sep.join(['Node', platform.node()]) + '\n') file.write(sep.join(['CPU', platform.processor()]) + '\n') # write two lines with GPU info: count and model names - this takes multiple steps to make it look clean in the end return doc, log_file, run_id
def generate_ply(filepath, output_dir): """ uses metashape to read a professor clipped file, find the filepaths, add the images to a chunk, and generate a dense cloud out of it parameters: filepath (str): a filepath the a professor clipped csv output_dir (str): a directory to where the ply generated from this file will end up returns: none """ # building path to save to basename = os.path.basename(filepath) basename = os.path.splitext(basename)[0] filename = basename + ".ply" save_path = os.path.join(output_dir, filename) print("################################################") print(save_path) print("################################################") # creating a chunk to work with chunk = Metashape.app.document.addChunk() chunk.label = basename with open(filepath, "r") as csvfile: csvreader = csv.DictReader(csvfile) n1_paths = [] n2_paths = [] n3_paths = [] for row in csvreader: if "gopro" not in row["filepath"].lower(): if "n1" in row["filepath"].lower(): n1_paths.append(row["filepath"]) if "n2" in row["filepath"].lower(): n2_paths.append(row["filepath"]) if "n3" in row["filepath"].lower(): n3_paths.append(row["filepath"]) paths = n2_paths + n3_paths + n1_paths # add photos from csv here chunk.addPhotos(paths) # disabling exif gps data for camera in chunk.cameras: camera.reference.enabled = False # creating a local coordinate system chunk.crs = Metashape.CoordinateSystem() # Perform image matching for the chunk frame. chunk.matchPhotos( accuracy=Metashape.HighAccuracy, generic_preselection=True, reference_preselection=False, tiepoint_limit=10000, ) chunk.alignCameras() print("ALIGNING COORDINATE SYSTEM BOUNDING BOX") # https://github.com/agisoft-llc/metashape-scripts/blob/master/src/coordinate_system_to_bounding_box.py R = chunk.region.rot # Bounding box rotation matrix C = chunk.region.center # Bounding box center vector if chunk.transform.matrix: T = chunk.transform.matrix s = math.sqrt( T[0, 0] ** 2 + T[0, 1] ** 2 + T[0, 2] ** 2 ) # scaling # T.scale() S = Metashape.Matrix().Diag([s, s, s, 1]) # scale matrix else: S = Metashape.Matrix().Diag([1, 1, 1, 1]) T = Metashape.Matrix( [ [R[0, 0], R[0, 1], R[0, 2], C[0]], [R[1, 0], R[1, 1], R[1, 2], C[1]], [R[2, 0], R[2, 1], R[2, 2], C[2]], [0, 0, 0, 1], ] ) chunk.transform.matrix = ( S * T.inv() ) # resulting chunk transformation matrix print("FINISHED ALIGNING COORDINATE SYSTEM BOUNDING BOX") # Generate depth maps for the chunk. chunk.buildDepthMaps( quality=Metashape.MediumQuality, filter=Metashape.AggressiveFiltering ) # generating the cloud chunk.buildDenseCloud() # saving chunk.exportPoints(save_path) print("done processing. point cloud exported to " + output_dir)
# SurfaceSource: PointCloudData, DenseCloudData, DepthMapsData Surface = PhotoScan.SurfaceType.Arbitrary SurfaceSource = PhotoScan.DataSource.DepthMapsData # # Variable for building orthomosaic # Since 1.4.0, users can choose performing color correction (vignetting) and balance separately. # Blending: AverageBlending, MosaicBlending, MinBlending, MaxBlending, DisabledBlending # Color_correction: True, False # Color_balance: True, False BlendingMode = PhotoScan.BlendingMode.MosaicBlending Color_correction = True Color_balance = False # ####################################################### wgs_84 = PhotoScan.CoordinateSystem("EPSG::4326") def AlignPhoto(chunk, Accuracy, Key_Limit, Tie_Limit, QualityFilter=QualityFilter, QualityCriteria=QualityCriteria): if QualityFilter: if chunk.cameras[0].meta['Image/Quality'] is None: chunk.estimateImageQuality() for band in [ band for camera in chunk.cameras for band in camera.planes ]: if float(band.meta['Image/Quality']) < QualityCriteria:
def build_dem(doc, log_file, run_id, cfg): ''' Build end export DEM ''' # get a beginning time stamp for the next step timer5a = time.time() #prepping params for buildDem projection = Metashape.OrthoProjection() projection.crs = Metashape.CoordinateSystem(cfg["project_crs"]) #prepping params for export compression = Metashape.ImageCompression() compression.tiff_big = cfg["buildDem"]["tiff_big"] compression.tiff_tiled = cfg["buildDem"]["tiff_tiled"] compression.tiff_overviews = cfg["buildDem"]["tiff_overviews"] if (cfg["buildDem"]["type"] == "DSM") | (cfg["buildDem"]["type"] == "both"): # call without classes argument (Metashape then defaults to all classes) doc.chunk.buildDem(source_data=Metashape.DenseCloudData, subdivide_task=cfg["subdivide_task"], projection=projection) output_file = os.path.join(cfg["output_path"], run_id + '_dsm.tif') if cfg["buildDem"]["export"]: doc.chunk.exportRaster(path=output_file, projection=projection, nodata_value=cfg["buildDem"]["nodata"], source_data=Metashape.ElevationData, image_compression=compression) if (cfg["buildDem"]["type"] == "DTM") | (cfg["buildDem"]["type"] == "both"): # call with classes argument doc.chunk.buildDem(source_data=Metashape.DenseCloudData, classes=Metashape.PointClass.Ground, subdivide_task=cfg["subdivide_task"], projection=projection) output_file = os.path.join(cfg["output_path"], run_id + '_dtm.tif') if cfg["buildDem"]["export"]: doc.chunk.exportRaster(path=output_file, projection=projection, nodata_value=cfg["buildDem"]["nodata"], source_data=Metashape.ElevationData, image_compression=compression) if (cfg["buildDem"]["type"] != "DTM") & (cfg["buildDem"]["type"] == "both") & (cfg["buildDem"]["type"] == "DSM"): raise ValueError("DEM type must be either 'DSM' or 'DTM' or 'both'") doc.save() # get an ending time stamp for the previous step timer5b = time.time() # calculate difference between end and start time to 1 decimal place time5 = diff_time(timer5b, timer5a) # record results to file with open(log_file, 'a') as file: file.write(sep.join(['Build DEM', time5]) + '\n') return True
scale = chunk.transform.scale ############################################################# # Here you can specify the cameras for which you want to export depth cameraNames = ['T_S03447', 'T_S03448', 'T_S03497'] ############################################################## cameraList = list() for camera in chunk.cameras: thisLabel = camera.label cameraList.append(thisLabel) for counter, value in enumerate(cameraNames): cameraLabel = cameraNames[counter] index = cameraList.index(cameraLabel) camera = chunk.cameras[index] depth = chunk.model.renderDepth(camera.transform, camera.sensor.calibration) depth = depth * scale depth = depth.convert(" ", "F16") compr = Metashape.ImageCompression() compr.tiff_compression = Metashape.ImageCompression( ).TiffCompressionDeflate ####### Remember to update the path below to which you want to export the depth maps ######## depth.save("/Users/deryaakkaynak/Desktop/depth" + camera.label + ".tif", compression=compr)
def project_setup(cfg): ''' Create output and project paths, if they don't exist Define a project ID based on photoset name and timestamp Define a project filename and a log filename Create the project Start a log file ''' if not os.path.exists(cfg["output_path"]): os.makedirs(cfg["output_path"]) if not os.path.exists(cfg["project_path"]): os.makedirs(cfg["project_path"]) ### Set a filename template for project files and output files ## Get the first parts of the filename (the photoset ID and location string) if (cfg["photoset_id"] == "%lookup%") or cfg["location"] == "%lookup%": path_parts = cfg["photo_path"].split("/") photoset_name = path_parts[-1] photoset_parts = photoset_name.split("_") if cfg["photoset_id"] == "%lookup%": set_id = photoset_parts[0] else: set_id = cfg["photoset_id"] if cfg["location"] == "%lookup%": location = photoset_parts[1] else: location = cfg["location"] ## Project file example to make: "01c_ChipsA_YYYYMMDD-jobid.psx" timestamp = stamp_time() # TODO: allow a nonexistent location string run_id = "_".join([timestamp, set_id, location]) # TODO: If there is a JobID, append to time (separated with "-", not "_"). ##?? This will keep jobs initiated in the same minute distinct # TODO: Allow to specify a mnemonic for the end of the project name (from YAML?) project_file = os.path.join(cfg["project_path"], '.'.join([run_id, 'psx'])) log_file = os.path.join(cfg["output_path"], '.'.join([run_id + "_log", 'txt'])) ''' Create a doc and a chunk ''' # create a handle to the Metashape object doc = Metashape.Document( ) #When running via Metashape, can use: doc = Metashape.app.document # Save doc (necessary for steps after point cloud because there needs to be a project file) doc.save(project_file) # Initialize a chunk, set its CRS as specified chunk = doc.addChunk() chunk.crs = Metashape.CoordinateSystem(cfg["project_crs"]) ''' Log specs except for GPU ''' # log Metashape version, CPU specs, time, and project location to results file # open the results file # TODO: records the Slurm values for actual cpus and ram allocated # https://slurm.schedmd.com/sbatch.html#lbAI with open(log_file, 'a') as file: # write a line with the Metashape version file.write(sep.join(['Project', run_id]) + '\n') file.write( sep.join([ 'Agisoft Metashape Professional Version', Metashape.app.version ]) + '\n') # write a line with the date and time file.write(sep.join(['Processing started', stamp_time()]) + '\n') # write a line with CPU info - if possible, improve the way the CPU info is found / recorded file.write(sep.join(['Node', platform.node()]) + '\n') file.write(sep.join(['CPU', platform.processor()]) + '\n') # write two lines with GPU info: count and model names - this takes multiple steps to make it look clean in the end return doc, log_file, run_id
def align_ground(path, section='DEFAULT'): print(banner1, 'Aligning ground (XY) plane with coded targets...') region = chunk.region path = path + 'skip/orientation.ini' print('path: ', path) config = configparser.ConfigParser() config.read(path) # print(config.sections()) horiz0 = config[section]['horiz0'] horiz1 = config[section]['horiz1'] vert0 = config[section]['vert0'] vert1 = config[section]['vert1'] del config print('x-axis:', horiz0, 'to', horiz1) print('y-axis:', vert0, 'to', vert1) H0_pos = get_marker(horiz0, chunk).position H1_pos = get_marker(horiz1, chunk).position V0_pos = get_marker(vert0, chunk).position V1_pos = get_marker(vert1, chunk).position print('H0: ', H0_pos) print('H1: ', H1_pos) print('V0: ', V0_pos) print('V1: ', V1_pos) horizontal = H1_pos - H0_pos vertical = V1_pos - V0_pos normal = vect(horizontal, vertical) vertical = -vect(horizontal, normal) horizontal = horizontal.normalized() R = Metashape.Matrix([horizontal, vertical, normal]) #horizontal = X, vertical = Y, normal = Z print(R.det()) #should be 1.0 region.rot = R.t() chunk.region = region R = chunk.region.rot #Bounding box rotation matrix C = chunk.region.center #Bounding box center vector if chunk.transform.matrix: T = chunk.transform.matrix s = math.sqrt(T[0, 0]**2 + T[0, 1]**2 + T[0, 2]**2) #scaling # T.scale() S = Metashape.Matrix().Diag([s, s, s, 1]) #scale matrix else: S = Metashape.Matrix().Diag([1, 1, 1, 1]) T = Metashape.Matrix([[R[0, 0], R[0, 1], R[0, 2], C[0]], [R[1, 0], R[1, 1], R[1, 2], C[1]], [R[2, 0], R[2, 1], R[2, 2], C[2]], [0, 0, 0, 1]]) chunk.transform.matrix = S * T.inv( ) #resulting chunk transformation matrix print("\nGround alignment finished\n")
def align(self): print("Script started...") (key1, isModel1, label1) = self.objects[self.fromObject.currentIndex()] (key2, isModel2, label2) = self.objects[self.toObject.currentIndex()] print("Aligning {} to {}...".format(label1, label2)) region_size = Metashape.app.document.chunk.region.size try: tmp1 = tempfile.NamedTemporaryFile(delete=False) tmp1.close() tmp2 = tempfile.NamedTemporaryFile(delete=False) tmp2.close() Metashape.app.document.chunk.region.size = Metashape.Vector( [0.0, 0.0, 0.0]) for (key, isModel, filename) in [(key2, isModel2, tmp2.name), (key1, isModel1, tmp1.name)]: if isModel: self.chunk.model = None for model in self.chunk.models: if model.key == key: self.chunk.model = model assert (self.chunk.model is not None) self.chunk.exportModel(path=filename, binary=True, save_texture=False, save_uv=False, save_normals=False, save_colors=False, save_cameras=False, save_markers=False, save_udim=False, save_alpha=False, save_comment=False, format=Metashape.ModelFormatPLY) else: self.chunk.dense_cloud = None for dense_cloud in self.chunk.dense_clouds: if dense_cloud.key == key: self.chunk.dense_cloud = dense_cloud assert (self.chunk.dense_cloud is not None) self.chunk.exportPoints( path=filename, source_data=Metashape.DenseCloudData, binary=True, save_normals=False, save_colors=False, save_classes=False, save_confidence=False, save_comment=False, format=Metashape.PointsFormatPLY) v1 = read_ply(tmp1.name) v2 = read_ply(tmp2.name) os.remove(tmp1.name) os.remove(tmp2.name) Metashape.app.document.chunk.region.size = region_size except: os.remove(tmp1.name) os.remove(tmp2.name) Metashape.app.document.chunk.region.size = region_size raise print("Vertices number: {}, {}".format(len(v1), len(v2))) scale_ratio = None if self.edtScaleRatio.text() == '' else float( self.edtScaleRatio.text()) target_resolution = None if self.edtTargetResolution.text( ) == '' else float(self.edtTargetResolution.text()) no_global_alignment = self.chkUseInitialAlignment.isChecked() preview_intermidiate_alignment = self.chkPreview.isChecked() M12 = align_two_point_clouds(v1, v2, scale_ratio, target_resolution, no_global_alignment, preview_intermidiate_alignment) if isModel1: assert (self.chunk.model.key == key1) model1 = self.chunk.model T1, Tlocal1 = self.get_model_T(model1) else: assert (self.chunk.dense_cloud.key == key1) dense_cloud1 = self.chunk.dense_cloud T1, Tlocal1 = self.get_dense_cloud_T(dense_cloud1) if isModel2: model2 = None for model in self.chunk.models: if model.key == key2: model2 = model assert model2 is not None _, Tlocal2 = self.get_model_T(model2) else: dense_cloud2 = None for dense_cloud in self.chunk.dense_clouds: if dense_cloud.key == key2: dense_cloud2 = dense_cloud assert dense_cloud2 is not None _, Tlocal2 = self.get_dense_cloud_T(dense_cloud2) shift21 = Tlocal2 * Tlocal1.inv() if isModel1: assert (self.chunk.model.key == key1) self.chunk.model.transform(T1.inv() * shift21.inv() * M12 * T1) self.chunk.model = None else: assert (self.chunk.dense_cloud.key == key1) self.chunk.dense_cloud.transform = self.chunk.dense_cloud.transform * T1.inv( ) * shift21.inv() * M12 * T1 print("Script finished!") self.reject()
def update_boundbox_by_markers(path, chunk, section='DEFAULT'): print(banner1, "Updating boundbox using markers...") #ground targets for finding horizontal center path = path + 'skip/orientation.ini' print('path: ', path) config = configparser.ConfigParser() config.read(path) p0 = config[section]['p0'] p1 = config[section]['p1'] buffer = config[section].getint('buffer') print(p0, p1, buffer) del config print('p0:', p0, 'p1:', p1) fp0 = fp1 = 0 #initialize binary values for finding the markers to 0 c = 0 #find center points c1 = 0 c2 = 0 for m in chunk.markers: if m.label == p0: c1 = c fp0 = 1 elif m.label == p1: c2 = c fp1 = 1 c = c + 1 #check markers found if fp0 and fp1: print("Found all markers") print(c1, c2) chunk.updateTransform() else: print("Error: not all markers found") print(fp0, fp1) newregion = chunk.region T = chunk.transform.matrix #v_t = T * Metashape.Vector( [0,0,0,1] ) m = Metashape.Matrix().Diag([1, 1, 1, 1]) m = m * T s = math.sqrt(m[0, 0]**2 + m[0, 1]**2 + m[0, 2]**2) #scale factor R = Metashape.Matrix([[m[0, 0], m[0, 1], m[0, 2]], [m[1, 0], m[1, 1], m[1, 2]], [m[2, 0], m[2, 1], m[2, 2]]]) R = R * (1. / s) newregion.rot = R.t() # Calculate center point of the bounding box, by taking the average of 2 left and 2 right markers mx = (chunk.markers[c1].position + chunk.markers[c2].position) / 2 print('x', chunk.markers[c2].position[0], chunk.markers[c1].position[0]) print('y', chunk.markers[c2].position[1], chunk.markers[c1].position[1]) box_X_length = math.fabs( chunk.markers[c2].position[0] - chunk.markers[c1].position[0] ) * (100 + buffer) / 100 #x-axis. may return negative number, so absoluted box_Y_length = math.fabs( chunk.markers[c2].position[1] - chunk.markers[c1].position[1] ) * (100 + buffer) / 100 #y-axis. may return negative number, so absoluted print('lengths:', box_X_length, box_Y_length) if box_Y_length > box_X_length: swap_length = box_X_length box_X_length = box_Y_length box_Y_length = swap_length del swap_length Z_ratio = 1 #my way. Change this to adjust box height (default=1) box_Z_length = box_Y_length * Z_ratio #same as Y length #chunk.markers[c2].position[2] - chunk.markers[c1].position[2] #z-axis print('lengths:', box_X_length, box_Y_length, box_Z_length) mx = Metashape.Vector( [mx[0], mx[1], mx[2]] ) #adjusting the zratio thing shifts the whole boundbox on a diagonal. not so easy. set to 0 for now. newregion.center = mx print('cent1', newregion.center) newregion.size = Metashape.Vector( [box_X_length, box_Y_length, box_Z_length]) print('nrsize1', newregion.size) print('cent[2]', newregion.center[2], 'size[2]', newregion.size[2]) newregion.center = Metashape.Vector([ newregion.center[0], newregion.center[1], newregion.center[2] + (newregion.size[2] / 2) ]) print('cent2', newregion.center) chunk.region = newregion chunk.updateTransform() print("Bounding box should be aligned now")