Hello Paulo,
thank you for your reply and mentioning that referene.location only means the input in the reference section and position means the actuel postion of the marker in the project.
I have check crs.geoccs - but this does actullay nothing in an already georeferenced project. The output of the orthomoasic is still EPSG: 4978
But here is the updated code for OPEX in Version 3.0 tested with Metashape 2.1.3
If anyone have an idee how we can assign the coordinate systeme in Metashape - please let us know.
A workaroud at the moment would be to load the orthomosaic to GIS an apply or overwrite the coordinate system over there.
"""
/********************************************************************************************************************
OPEX - Orthophotoexport (V. 3.0)
Content:
Processing code to exports orthophoto in planar projection defined by vektor (from three markers) and Z axes.
This code is mainly for exporting archaeological profiles; ready for importing in GIS.
Before using the script you need:
- A georeferenzed Model
- An "A" and "B" Profile Nail (Named "A" and "B" in Photoscan)
- A "Y" Point in Photoscan with the coordinates (X-coord from "A",Y-coord from "A", Z-coord from "A"+ a high number)
Compatibility - Metashape Professional 2.1.3
Version - 3.0
-----------------------------------------------------------------------------------------------
written by : Alexey Pasumansky, AgiSoft LLC,
Marcel C. Hagner, ArchaeoBW GmbH
Florian Tubbesing, ArchaeoBW GmbH
updated by : Philippe Pathé, Bonn Center for Digital Humanities - 2024
copyright : 2019 ArchaeoBW GmbH
email : info@archaeobw.de
********************************************************************************************************************/
/***************************************************************************************
* This program is free software; you can redistribute it and/or modify it under *
* the terms of a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 *
* International License. *
* *
* Attribution - You must give a appropriate credit, provide a link to the license, *
* and indicate of changes were made. You may do so in any reasonable manner, but *
* not in any way that suggests the licensor endorses you or your use *
* *
* NonCommercial - You may not use the material for commercial purposes. *
* *
* ShareAlike - If you remix, transform, or build upon the material, you must *
* distribute your contributions under the same license as the original. *
**************************************************************************************/
"""
import os
import Metashape
from PySide2 import QtCore, QtWidgets
def getMarker(chunk, label):
for marker in chunk.markers:
if marker.label.upper() == label.upper():
return marker
return 0
def vect(a, b):
result = Metashape.Vector([a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x])
return result.normalized()
class ExportOFDlg(QtWidgets.QDialog):
def __init__(self, parent):
QtWidgets.QDialog.__init__(self, parent)
chunk = doc.chunk
self.supported_types = ["jpg", "tif", "png"]
self.blending = Metashape.BlendingMode.AverageBlending
self.setWindowTitle("OPEX")
# GUI initialization remains unchanged...
self.btnQuit = QtWidgets.QPushButton("&Exit")
self.btnQuit.setFixedSize(150,50)
self.btnP1 = QtWidgets.QPushButton("&Export")
self.btnP1.setFixedSize(150,50)
self.resTxt = QtWidgets.QLabel()
self.resTxt.setText("Export resolution (m):")
self.resTxt.setFixedSize(150, 25)
self.vectTxt = QtWidgets.QLabel()
self.vectTxt.setText("Horizontal vector:")
self.vectTxt.setFixedSize(150, 25)
self.vect2Txt = QtWidgets.QLabel()
self.vect2Txt.setText("Vertical vector:")
self.vect2Txt.setFixedSize(150, 25)
self.resEdt = QtWidgets.QLineEdit()
self.resEdt.setText("0.00025")
self.resEdt.setFixedSize(150, 25)
self.radioBtn_v = QtWidgets.QRadioButton("Vertical")
self.radioBtn_h = QtWidgets.QRadioButton("Horizontal")
self.radioBtn_v.setChecked(True)
self.radioBtn_h.setChecked(False)
self.llistV = [None, None]
self.llistV[0] = QtWidgets.QComboBox()
self.llistV[0].resize(150, 30)
self.llistV[1] = QtWidgets.QComboBox()
self.llistV[1].resize(150, 30)
self.llistH = [None, None]
self.llistH[0] = QtWidgets.QComboBox()
self.llistH[0].resize(150, 30)
self.llistH[1] = QtWidgets.QComboBox()
self.llistH[1].resize(150, 30)
self.radio = QtWidgets.QVBoxLayout()
self.radio.addWidget(self.radioBtn_v)
self.radio.addWidget(self.radioBtn_h)
for marker in chunk.markers:
for ilist in self.llistV:
ilist.addItem(marker.label)
for ilist in self.llistH:
ilist.addItem(marker.label)
layout = QtWidgets.QGridLayout() #creating layout
layout.setSpacing(10)
layout.addWidget(self.resTxt, 0, 0)
layout.addWidget(self.resEdt, 0, 1)
layout.addWidget(self.vectTxt, 1, 0)
layout.addWidget(self.vect2Txt, 2, 0)
layout.addWidget(self.llistH[0], 1, 1)
layout.addWidget(self.llistH[1], 1, 2)
layout.addWidget(self.llistV[0], 2, 1)
layout.addWidget(self.llistV[1], 2, 2)
layout.addLayout(self.radio, 3, 0)
layout.addWidget(self.btnP1, 3, 1)
layout.addWidget(self.btnQuit, 3, 2)
self.setLayout(layout)
proc_export = lambda : self.procExport()
QtCore.QObject.connect(self.btnP1, QtCore.SIGNAL("clicked()"), proc_export)
QtCore.QObject.connect(self.btnQuit, QtCore.SIGNAL("clicked()"), self, QtCore.SLOT("reject()"))
self.exec()
def procExport(self):
chunk = doc.chunk
print("Starting export process...")
path = Metashape.app.getExistingDirectory("Bitte wählen Sie den Ordner mit den gesammelten Geo-Cut-Modell aus")
print(f"Selected path: {path}")
#crs = Metashape.app.getCoordinateSystem("Wählen Sie das Koordinatensystem für die Ausgabe aus.")
#print(f"Selected Coordinate System (CRS): {crs}")
filelist = os.listdir(path)
doclist = [os.path.join(path, file) for file in filelist if file.endswith(".psx")]
print(f"Identified Metashape projects: {doclist}")
try:
d_x = d_y = float(self.resEdt.text())
print(f"Set export resolution: d_x = {d_x}, d_y = {d_y}")
except ValueError:
Metashape.app.messageBox("Incorrect export resolution! Please use point delimiter.\n")
print("Script aborted due to invalid resolution input.")
return 0
if self.llistV[0].currentIndex() == self.llistV[1].currentIndex():
Metashape.app.messageBox("Can't use the same marker for vector start and end.\n")
print("Script aborted due to invalid vertical vector markers.")
return 0
if self.llistH[0].currentIndex() == self.llistH[1].currentIndex():
Metashape.app.messageBox("Can't use the same marker for vector start and end.\n")
print("Script aborted due to invalid horizontal vector markers.")
return 0
if len(chunk.markers) < 2:
Metashape.app.messageBox("No markers.\n")
print("Script aborted due to insufficient markers in the chunk.")
return 0
print("Script prerequisites verified. Starting processing...")
type = "_OF.tif"
for doc_path in doclist:
print(f"Processing project: {doc_path}")
doc_unlock = Metashape.Document()
doc_unlock.open(doc_path, read_only=True)
doc_unlock.read_only = False
doc.open(doc_path, read_only=False)
chunk = doc.chunk
#chunk.crs = crs
T = chunk.transform.matrix
path = doc_path[:-4] + type
print(f"Export path for orthomosaic: {path}")
self.btnP1.setDisabled(True)
self.btnQuit.setDisabled(True)
m1 = getMarker(chunk, self.llistH[0].currentText())
m2 = getMarker(chunk, self.llistH[1].currentText())
horizontal = m2.position - m1.position
horizontal = T.mulv(horizontal)
print(f"Horizontal vector calculated: {horizontal}")
m1 = getMarker(chunk, self.llistV[0].currentText())
m2 = getMarker(chunk, self.llistV[1].currentText())
vertical = m2.position - m1.position
vertical = T.mulv(vertical)
print(f"Vertical vector calculated: {vertical}")
normal = vect(vertical, horizontal)
print(f"Normal vector calculated: {normal}")
if self.radioBtn_h.isChecked():
vertical = vect(horizontal, normal)
horizontal = horizontal.normalized()
print("Horizontal orientation selected.")
elif self.radioBtn_v.isChecked():
vertical = vertical.normalized()
horizontal = -vect(vertical, normal)
print("Vertical orientation selected.")
else:
Metashape.app.messageBox("Fehler! Kann das OF nicht exportieren" + path)
print("Script aborted due to invalid orientation selection.")
continue
R = Metashape.Matrix([horizontal, vertical, -normal])
print(f"Rotation matrix R: {R}")
m = -1
for marker in chunk.markers:
m += 1
label = marker.label
if "A" in label:
a = m
origin = T.mulp(chunk.markers[a].position)
print(f"Origin point: {origin}")
A = m1.reference.location * Metashape.Vector([0, 0, 1])
X = (-1) * R * origin
horizontal.size = 4
horizontal.w = X.x
vertical.size = 4
vertical.w = X.y + A
normal.size = 4
normal.w = -X.z
proj = Metashape.Matrix([horizontal, vertical, -normal, Metashape.Vector([0, 0, 0, 1])])
print(f"Projection matrix: {proj}")
projection = Metashape.OrthoProjection()
projection.type = Metashape.OrthoProjection.Type.Planar
projection.matrix = proj
if Metashape.app.document.chunk.crs.geoccs:
projection.crs = Metashape.app.document.chunk.crs.geoccs # case of georeferenced project
else:
projection.crs = Metashape.app.document.chunk.crs
print(f"Projection object created with matrix: {projection.matrix}")
print(f"CRS for project: {projection.crs}")
chunk.buildOrthomosaic(
surface_data=Metashape.DataSource.ModelData,
blending_mode=self.blending,
fill_holes=True,
projection=projection,
resolution_x=d_x,
resolution_y=d_y,
ghosting_filter=False,
cull_faces=False,
refine_seamlines=False
)
print("Orthomosaic built successfully.")
chunk.exportRaster(
path=path,
source_data=Metashape.DataSource.OrthomosaicData,
resolution_x=d_x,
resolution_y=d_y,
save_alpha=True,
image_compression=Metashape.ImageCompression(),
white_background=True
)
print(f"Orthomosaic exported to {path}")
self.btnP1.setDisabled(False)
self.btnQuit.setDisabled(False)
Metashape.app.update()
print("Script completed successfully. Viele Grüße MP & FT")
return 1
def main():
global doc
doc = Metashape.app.document
app = QtWidgets.QApplication.instance()
parent = app.activeWindow()
dlg = ExportOFDlg(parent)
Metashape.app.addMenuItem("ArchaeoBW Toolbox/Profile/OPEX/OPEX ab 2.1.3", main)