Agisoft Metashape

Agisoft Metashape => Python Scripting => Topic started by: Andrew on April 13, 2014, 09:30:33 PM

Title: Python scripts collection?
Post by: Andrew on April 13, 2014, 09:30:33 PM
I use Photoscan Standard edition - looking at feature list (considering my needs) I just couldn't find justification for upgrade. When looking at "Python scripting support" line I assumed it meant basically a more advanced and open alternative to "Batch Process" feature, but every now and then I read it can do lots more, hence my question: are any useful scripts included in Pro demo installation package? Or maybe there is some repository of useful scripts available? Or at least a list of what some more advanced scripts can accomplish? I think more insight into this can help push me and many other over the edge to upgrade.

-Andrew
Title: Re: Python scripts collection?
Post by: Marcel on April 13, 2014, 09:35:20 PM
That would indeed be nice.

We got two scripts from Agisoft directly, one removes duplicate cameras and the other splits up a project into a grid of chunks (for meshing Dense Clouds with a huge amount of points). Both are pretty simple, but big timesavers.

It would be great to have a repository of scripts, it might even spark some more development from us users as well.
Title: Re: Python scripts collection?
Post by: Alexey Pasumansky on April 13, 2014, 10:52:29 PM
Hello Andrew,

Currently there's no script repository or collection available.

Most of scripts that we have were created for special tasks (by user requests or for our internal needs), so they require description that could be even longer than the script itself. Another reason why we do not put all the scripts online, the need to keep them up-to-date. Sometimes PhotoScan Python API changes and older scripts need to be modified according to such changes, but keeping the entire collection updates is not reasonable, since most of scripts will not be used due to their specifics.

But still we have Python scripting sub-forum here and will surely help any PhotoScan user that require some assistance in script creation.
Title: Re: Python scripts collection?
Post by: frank.stremke on April 14, 2014, 01:47:12 PM
I I could imagine that a solution like the plug ins in qgis with an online repository would be grat and I could imagine this would be a good place to share and improve on existing stuff so users could update the scripts if needed if they are able to do so which iam am not honestly ...
but I wold think to have a kind of central registry at least would be good so people could see whats possible using ps pro
yours
frank
Title: Re: Python scripts collection?
Post by: Alexey Pasumansky on January 27, 2015, 12:19:38 PM
Hello Frank,

We've added Python scripting section to our Wiki: http://wiki.agisoft.com/wiki/Python
So everyone is free to post there his own script, also we'll try to publish there some general scripts that might be useful.
Title: Re: Python scripts collection?
Post by: nickc on February 06, 2015, 08:07:41 PM
NICE, thanks Alexey!
Title: Re: Python scripts collection?
Post by: tforward2 on February 11, 2015, 03:00:12 AM
Wouldn't a GIT style environment make more sense? Better collaboration etc.
Title: Re: Python scripts collection?
Post by: bisenberger on June 25, 2015, 10:43:43 AM
The script, PS110_split_in_chunks_dialog.py, leaves a seam between chunks in an ortho created after merged back.
Title: Re: Python scripts collection?
Post by: Alexey Pasumansky on June 25, 2015, 10:47:00 AM
Hello Bill,
It is possible that there's a one pixel wide gap between merged chunks.

The problem can be avoided if region.size is increased, for example, by 1%.
Title: Re: Python scripts collection?
Post by: bisenberger on June 25, 2015, 02:50:53 PM
Thanks Alexey, I'll give it a try.
Title: Re: Python scripts collection?
Post by: bisenberger on November 04, 2015, 09:06:49 PM
What are the variables for changing dense cloud quality?
Title: Re: Python scripts collection?
Post by: bisenberger on November 10, 2015, 06:17:09 PM
Couldn't find where to download a manual on python on the Agisoft web site, but this turned up on a Google search:


downloads.agisoft.ru/pdf/photoscan_python_api_1_0_0.pdf
Title: Re: Python scripts collection?
Post by: Alexey Pasumansky on November 10, 2015, 06:30:06 PM
Hello Bill,

Python API reference can be found here:
http://www.agisoft.com/downloads/user-manuals/

The options for the dense cloud quality are the following:
PhotoScan.Quality.UltraQuality
PhotoScan.Quality.HighQuality
PhotoScan.Quality.MediumQuality
PhotoScan.Quality.LowQuality
PhotoScan.Quality.LowestQuality
Title: Re: Python scripts collection?
Post by: eberkund on January 13, 2016, 06:33:50 PM
Wouldn't a GIT style environment make more sense? Better collaboration etc.

I'd like to second this suggestion. A GitHub repository where people could submit pull requests with new scripts or changes would be great.
Title: Re: Python scripts collection?
Post by: Alexey Pasumansky on January 13, 2016, 06:43:29 PM
If there is any repository with PhotoScan scripts driven by community, we can put a link to it here and in wiki pages.
Title: Re: Python scripts collection?
Post by: bisenberger on January 25, 2016, 03:17:10 PM
Hello Bill,

Python API reference can be found here:
http://www.agisoft.com/downloads/user-manuals/

The options for the dense cloud quality are the following:
PhotoScan.Quality.UltraQuality
PhotoScan.Quality.HighQuality
PhotoScan.Quality.MediumQuality
PhotoScan.Quality.LowQuality
PhotoScan.Quality.LowestQuality
Thanks Alexey
Title: Re: Python scripts collection?
Post by: bisenberger on January 25, 2016, 03:36:55 PM
I'm getting the following error when I run Split in chunks.py:

2016-01-25 06:24:47 Traceback (most recent call last):
2016-01-25 06:24:47   File "E:/Photoscan/scripts/Split in chunks.py", line 80, in <lambda>
2016-01-25 06:24:47     proc_split = lambda : self.splitChunks()
2016-01-25 06:24:47   File "E:/Photoscan/scripts/Split in chunks.py", line 159, in splitChunks
2016-01-25 06:24:47     doc.addChunk(new_chunk)
2016-01-25 06:24:47 TypeError: addChunk() takes no arguments (1 given)
>>>

PhotoScan version 1.2.3
Title: Re: Python scripts collection?
Post by: Alexey Pasumansky on January 25, 2016, 03:54:46 PM
Hello Bill,

You need to comment that line - doc.addChunk(new_chunk), as chunk.copy() operation automatically creates chunk duplicate in the document.
Title: Re: Python scripts collection?
Post by: gatsri on February 01, 2016, 09:33:46 AM
Dear Alexey

Can you please poste the actual "split in chuncs"-Script. for me it does not work with comment that line..:-/

Thank you
Title: Re: Python scripts collection?
Post by: Alexey Pasumansky on February 01, 2016, 11:00:48 AM
Hello ristag,

Which version of PhotoScan you are using? If you are no 1.2, then it should work without commenting or uncommenting anything.
Title: Re: Python scripts collection?
Post by: bisenberger on March 10, 2016, 09:32:58 PM
Hello Bill,

Python API reference can be found here:
http://www.agisoft.com/downloads/user-manuals/

The options for the dense cloud quality are the following:
PhotoScan.Quality.UltraQuality
PhotoScan.Quality.HighQuality
PhotoScan.Quality.MediumQuality
PhotoScan.Quality.LowQuality
PhotoScan.Quality.LowestQuality
http://wiki.agisoft.com/wiki/Split_in_chunks.py (http://wiki.agisoft.com/wiki/Split_in_chunks.py)
Also handy variables for modifying the Split_in_chunks script:
PhotoScan.FilterMode.AggressiveFiltering
PhotoScan.FilterMode.ModerateFiltering
PhotoScan.FilterMode.MildFiltering
PhotoScan.FilterMode.NoFiltering
Title: Re: Python scripts collection?
Post by: enocsanz on May 11, 2016, 03:01:37 PM
Error
Title: Re: Python scripts collection?
Post by: jpvega on June 29, 2016, 04:06:56 PM
Hi everyone,

Hope this helps:

http://www.agisoft.com/forum/index.php?topic=5536
http://www.agisoft.com/forum/index.php?topic=5530
Title: Re: Python scripts collection?
Post by: Ryan Fox on June 24, 2017, 12:19:45 AM
Is there still interest in a repository of scripts?  I'd be willing to maintain it.  I set up a repo here: https://github.com/FoxRow/Agisoft-Scripting (https://github.com/FoxRow/Agisoft-Scripting)

I can pull in the scripts already in http://wiki.agisoft.com/wiki/Python.  If we get a critical mass, it would be nice to release it as a companion package on PyPI.  I don't see any license on the scripts already in the wiki, does maybe someone from Agisoft know their provenance?  I'd like to have a coherent license for the repository in that case.
Title: Re: Python scripts collection?
Post by: Alexey Pasumansky on October 17, 2017, 07:51:27 PM
Hello all,

We have moved the scripts from Wiki to GitHub repository, so that it should be easier to contribute for the community:
https://github.com/agisoft-llc/photoscan-scripts
Title: Re: Python scripts collection?
Post by: SAV on November 01, 2017, 03:45:30 AM
Good to see that it has been moved to GitHub.
 ;D
Title: Re: Python scripts collection?
Post by: Alexey Pasumansky on November 01, 2017, 11:32:47 AM
Now it should be easier for community to contribute.

We are also trying to update the collection with the commonly requested scripts.
Title: Re: Python scripts collection?
Post by: chrisodiachi on February 14, 2019, 12:10:09 PM
please can anyone be kind enough to assist me debug this script


# This is python script for Metashape Pro. Scripts repository: https://github.com/agisoft-llc/metashape-scripts

import Metashape
from PySide2 import QtGui, QtCore, QtWidgets

# Checking compatibility
compatible_major_version = "1.5"
found_major_version = ".".join(Metashape.app.version.split('.')[:2])
if found_major_version != compatible_major_version:
   raise Exception("Incompatible Metashape version: {} != {}".format(found_major_version, compatible_major_version))

QUALITY = {"1":  Metashape.UltraQuality,
         "2":  Metashape.HighQuality,
         "4":  Metashape.MediumQuality,
         "8":  Metashape.LowQuality,
         "16": Metashape.LowestQuality}

FILTERING = {"3": Metashape.NoFiltering,
          "0": Metashape.MildFiltering,
          "1": Metashape.ModerateFiltering,
          "2": Metashape.AggressiveFiltering}

MESH = {"Arbitrary": Metashape.SurfaceType.Arbitrary,
      "Height Field": Metashape.SurfaceType.HeightField}

DENSE = {"Ultra": Metashape.UltraQuality,
       "High": Metashape.HighQuality,
       "Medium": Metashape.MediumQuality,
       "Low": Metashape.LowQuality,
       "Lowest": Metashape.LowestQuality}


def isIdent(matrix):
   """
   Check if the matrix is identity matrix
   """
   for i in range(matrix.size[0]):
      for j in range(matrix.size[1]):
         if i == j:
            if matrix[i, j] != 1.0:
               return False
         elif matrix[i, j]:
            return False
   return True


class SplitDlg(QtWidgets.QDialog):

   def __init__(self, parent):

      QtWidgets.QDialog.__init__(self, parent)
      self.setWindowTitle("Split in chunks")

      self.gridX = 2
      self.gridY = 2
      self.gridWidth = 198
      self.gridHeight = 198

      self.spinX = QtWidgets.QSpinBox()
      self.spinX.setMinimum(1)
      self.spinX.setValue(2)
      self.spinX.setMaximum(20)
      self.spinX.setFixedSize(75, 25)
      self.spinY = QtWidgets.QSpinBox()
      self.spinY.setMinimum(1)
      self.spinY.setValue(2)
      self.spinY.setMaximum(20)
      self.spinY.setFixedSize(75, 25)

      self.chkMesh = QtWidgets.QCheckBox("Build Mesh")
      self.chkMesh.setFixedSize(100, 50)
      self.chkMesh.setToolTip("Generates mesh for each cell in grid")

      self.meshBox = QtWidgets.QComboBox()
      for element in MESH.keys():
         self.meshBox.addItem(element)
      self.meshBox.setFixedSize(100, 25)

      self.chkDense = QtWidgets.QCheckBox("Build Dense Cloud")
      self.chkDense.setFixedSize(120, 50)
      self.chkDense.setWhatsThis("Builds dense cloud for each cell in grid")

      self.denseBox = QtWidgets.QComboBox()
      for element in DENSE.keys():
         self.denseBox.addItem(element)
      self.denseBox.setFixedSize(100, 25)

      self.chkMerge = QtWidgets.QCheckBox("Merge Back")
      self.chkMerge.setFixedSize(90, 50)
      self.chkMerge.setToolTip("Merges back the processing products formed in the individual cells")

      self.chkSave = QtWidgets.QCheckBox("Autosave")
      self.chkSave.setFixedSize(90, 50)
      self.chkSave.setToolTip("Autosaves the project after each operation")

      self.txtOvp = QtWidgets.QLabel()
      self.txtOvp.setText("Overlap (%):")
      self.txtOvp.setFixedSize(90, 25)

      self.edtOvp = QtWidgets.QLineEdit()
      self.edtOvp.setPlaceholderText("0")
      self.edtOvp.setFixedSize(50, 25)

      self.btnQuit = QtWidgets.QPushButton("Close")
      self.btnQuit.setFixedSize(90, 50)

      self.btnP1 = QtWidgets.QPushButton("Split")
      self.btnP1.setFixedSize(90, 50)

      self.grid = QtWidgets.QLabel(" ")
      self.grid.resize(self.gridWidth, self.gridHeight)
      tempPixmap = QtGui.QPixmap(self.gridWidth, self.gridHeight)
      tempImage = tempPixmap.toImage()

      for y in range(self.gridHeight):
         for x in range(self.gridWidth):

            if not (x and y) or (x == self.gridWidth - 1) or (y == self.gridHeight - 1):
               tempImage.setPixel(x, y, QtGui.qRgb(0, 0, 0))
            elif (x == self.gridWidth / 2) or (y == self.gridHeight / 2):
               tempImage.setPixel(x, y, QtGui.qRgb(0, 0, 0))

            else:
               tempImage.setPixel(x, y, QtGui.qRgb(255, 255, 255))

      tempPixmap = tempPixmap.fromImage(tempImage)
      self.grid.setPixmap(tempPixmap)
      self.grid.show()

      layout = QtWidgets.QGridLayout()  # creating layout
      layout.addWidget(self.spinX, 1, 0)
      layout.addWidget(self.spinY, 1, 1, QtCore.Qt.AlignRight)

      layout.addWidget(self.chkDense, 0, 2)
      layout.addWidget(self.chkMesh, 0, 3)
      layout.addWidget(self.chkMerge, 0, 4)

      layout.addWidget(self.meshBox, 1, 3, QtCore.Qt.AlignTop)
      layout.addWidget(self.denseBox, 1, 2, QtCore.Qt.AlignTop)

      layout.addWidget(self.chkSave, 3, 2)
      layout.addWidget(self.btnP1, 3, 3)
      layout.addWidget(self.btnQuit, 3, 4)

      layout.addWidget(self.txtOvp, 0, 0, QtCore.Qt.AlignRight)
      layout.addWidget(self.edtOvp, 0, 1, QtCore.Qt.AlignLeft)

      layout.addWidget(self.grid, 2, 0, 2, 2)
      # layout.setAlignment(QtCore.Qt.AlignTop)
      self.setLayout(layout)

      proc_split = lambda: self.splitChunks()

      self.spinX.valueChanged.connect(self.updateGrid)
      self.spinY.valueChanged.connect(self.updateGrid)

      QtCore.QObject.connect(self.btnP1, QtCore.SIGNAL("clicked()"), proc_split)
      QtCore.QObject.connect(self.btnQuit, QtCore.SIGNAL("clicked()"), self, QtCore.SLOT("reject()"))

      self.exec()

   def updateGrid(self):
      """
      Draw new grid
      """

      self.gridX = self.spinX.value()
      self.gridY = self.spinY.value()

      tempPixmap = QtGui.QPixmap(self.gridWidth, self.gridHeight)
      tempImage = tempPixmap.toImage()
      tempImage.fill(QtGui.qRgb(240, 240, 240))

      for y in range(int(self.gridHeight / self.gridY) * self.gridY):
         for x in range(int(self.gridWidth / self.gridX) * self.gridX):
            if not (x and y) or (x == self.gridWidth - 1) or (y == self.gridHeight - 1):
               tempImage.setPixel(x, y, QtGui.qRgb(0, 0, 0))
            elif y > int(self.gridHeight / self.gridY) * self.gridY:
               tempImage.setPixel(x, y, QtGui.qRgb(240, 240, 240))
            elif x > int(self.gridWidth / self.gridX) * self.gridX:
               tempImage.setPixel(x, y, QtGui.qRgb(240, 240, 240))
            else:
               tempImage.setPixel(x, y, QtGui.qRgb(255, 255, 255))

      for y in range(0, int(self.gridHeight / self.gridY + 1) * self.gridY, int(self.gridHeight / self.gridY)):
         for x in range(int(self.gridWidth / self.gridX) * self.gridX):
            tempImage.setPixel(x, y, QtGui.qRgb(0, 0, 0))

      for x in range(0, int(self.gridWidth / self.gridX + 1) * self.gridX, int(self.gridWidth / self.gridX)):
         for y in range(int(self.gridHeight / self.gridY) * self.gridY):
            tempImage.setPixel(x, y, QtGui.qRgb(0, 0, 0))

      tempPixmap = tempPixmap.fromImage(tempImage)
      self.grid.setPixmap(tempPixmap)
      self.grid.show()

      return True

   def splitChunks(self):

      self.gridX = self.spinX.value()
      self.gridY = self.spinY.value()
      partsX = self.gridX
      partsY = self.gridY

      print("Script started...")

      buildMesh = self.chkMesh.isChecked()
      buildDense = self.chkDense.isChecked()
      mergeBack = self.chkMerge.isChecked()
      autosave = self.chkSave.isChecked()

      quality = DENSE[self.denseBox.currentText()]
      mesh_mode = MESH[self.meshBox.currentText()]

      doc = Metashape.app.document
      chunk = doc.chunk

      if not chunk.transform.translation.norm():
         chunk.transform.matrix = chunk.transform.matrix
      elif chunk.transform.scale == 1:
         chunk.transform.matrix = chunk.transform.matrix
      elif isIdent(chunk.transform.rotation):
         chunk.transform.matrix = chunk.transform.matrix

      region = chunk.region
      r_center = region.center
      r_rotate = region.rot
      r_size = region.size

      x_scale = r_size.x / partsX
      y_scale = r_size.y / partsY
      z_scale = r_size.z

      offset = r_center - r_rotate * r_size / 2.
      
      chunk_labels = [ichunk.label for ichunk in PhotoScan.app.document.chunks]
      if  "Chunk " + str(i) + "_" + str(j) in chunk_labels:
         continue
      
      for j in range(1, partsY + 1):  # creating new chunks and adjusting bounding box
         for i in range(1, partsX + 1):
            if not buildDense:
               new_chunk = chunk.copy(items=[Metashape.DataSource.DenseCloudData, Metashape.DataSource.DepthMapsData])
            else:
               new_chunk = chunk.copy(items=[])
            new_chunk.label = "Chunk " + str(i) + "_" + str(j)
            if new_chunk.model:
               new_chunk.model.clear()

            new_region = Metashape.Region()
            new_rot = r_rotate
            new_center = Metashape.Vector([(i - 0.5) * x_scale, (j - 0.5) * y_scale, 0.5 * z_scale])
            new_center = offset + new_rot * new_center
            new_size = Metashape.Vector([x_scale, y_scale, z_scale])

            if self.edtOvp.text().isdigit():
               new_region.size = new_size * (1 + float(self.edtOvp.text()) / 100)
            else:
               new_region.size = new_size

            new_region.center = new_center
            new_region.rot = new_rot

            new_chunk.region = new_region

            Metashape.app.update()

            if autosave:
               doc.save()

            if buildDense:
               if new_chunk.depth_maps:
                  reuse_depth = True
                  if new_chunk.depth_maps.meta['depth/depth_downscale']:
                     quality = QUALITY[new_chunk.depth_maps.meta['depth/depth_downscale']]
                  if new_chunk.depth_maps.meta['depth/depth_filter_mode']:
                     filtering = FILTERING[new_chunk.depth_maps.meta['depth/depth_filter_mode']]
                  try:
                     new_chunk.buildDepthMaps(quality=quality, filter=filtering, reuse_depth=reuse_depth)
                     new_chunk.buildDenseCloud(max_neighbors=100)  # keep_depth=False
                  except RuntimeError:
                     print("Can't build dense cloud for " + chunk.label)

               else:
                  reuse_depth = False
                  try:
                     new_chunk.buildDepthMaps(quality=quality,
                                        filter=Metashape.FilterMode.ModerateFiltering, reuse_depth=reuse_depth)
                     new_chunk.buildDenseCloud(max_neighbors=100)  # keep_depth=False
                  except RuntimeError:
                     print("Can't build dense cloud for " + chunk.label)

               if autosave:
                  doc.save()

            if buildMesh:
               if new_chunk.dense_cloud:
                  try:
                     new_chunk.buildModel(surface=mesh_mode,
                                     source=Metashape.DataSource.DenseCloudData,
                                     interpolation=Metashape.Interpolation.EnabledInterpolation,
                                     face_count=Metashape.FaceCount.HighFaceCount)
                  except RuntimeError:
                     print("Can't build mesh for " + chunk.label)
               else:
                  try:
                     new_chunk.buildModel(surface=mesh_mode,
                                     source=Metashape.DataSource.PointCloudData,
                                     interpolation=Metashape.Interpolation.EnabledInterpolation,
                                     face_count=Metashape.FaceCount.HighFaceCount)
                  except RuntimeError:
                     print("Can't build mesh for " + chunk.label)
               if autosave:
                  doc.save()

            if not buildDense:
               if new_chunk.dense_cloud:
                  new_chunk.dense_cloud.clear()
            if new_chunk.depth_maps:
               new_chunk.depth_maps.clear()
            # new_chunk = None

      if mergeBack:
         for i in range(1, len(doc.chunks)):
            chunk = doc.chunks
            chunk.remove(chunk.cameras)
         doc.chunks[0].model = None  # hiding the mesh of the original chunk, just for case
         doc.mergeChunks(doc.chunks,
                     merge_dense_clouds=True, merge_models=True, merge_markers=True)  # merging all smaller chunks into single one

         doc.remove(doc.chunks[1:-1])  # removing smaller chunks.
         if autosave:
            doc.save()

      if autosave:
         doc.save()

      print("Script finished!")
      return True


def split_in_chunks():
   global doc
   doc = Metashape.app.documenthttps://www.sublimetext.com/2

   app = QtWidgets.QApplication.instance()
   parent = app.activeWindow()

   dlg = SplitDlg(parent)


label = "Custom menu/Split in chunks"
Metashape.app.addMenuItem(label, split_in_chunks)
print("To execute this script press {}".format(label))



it is the split in chunk script with 3 added lines above line 237 to resume the operation after a power failure. I cannot seem to debug it. Please help!!!!