Merge pull request #2449 from alicevision/dev/accessCurrentFrame

Provide access to the path of the currently displayed frame
This commit is contained in:
Fabien Castan 2024-06-28 10:59:21 +02:00 committed by GitHub
commit ccf76ab3b5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 72 additions and 47 deletions

View file

@ -1,3 +1,5 @@
# [Viewer] Clean-up: Harmonize syntax for the Viewer2D
9af65092b9e881c828430f54a73fb4522bc1e370
# [nodes] Harmonize the use of trailing commas across all the nodes
61a8dcd4e2878f80b2f320f2b1c3c9b41e999b82
# [nodes] Clean-up: Harmonize nodes' descriptions

View file

@ -226,8 +226,6 @@ class Graph(BaseObject):
self._fileDateVersion = 0
self.header = {}
self._selectedViewpoint = None
def clear(self):
self.header.clear()
self._compatibilityNodes.clear()
@ -1566,12 +1564,6 @@ class Graph(BaseObject):
self.updateStatusFromCache(force=True)
self.cacheDirChanged.emit()
@property
def selectedViewpoint(self):
""" Return the attribute describing the viewpoint that is
currently set as the 'selected viewpoint'. """
return self._selectedViewpoint
@property
def fileDateVersion(self):
return self._fileDateVersion

View file

@ -6,6 +6,8 @@ from meshroom.common import Backend
meshroom.setupEnvironment(backend=Backend.PYSIDE)
signal.signal(signal.SIGINT, signal.SIG_DFL)
from meshroom.ui.app import MeshroomApp
app = MeshroomApp(sys.argv)
app.exec_()
import meshroom.ui
import meshroom.ui.app
meshroom.ui.uiInstance = meshroom.ui.app.MeshroomApp(sys.argv)
meshroom.ui.uiInstance.exec_()

View file

@ -4,13 +4,14 @@ import re
import argparse
from PySide2 import QtCore
from PySide2.QtCore import Qt, QUrl, Slot, QJsonValue, Property, Signal, qInstallMessageHandler, QtMsgType, QSettings
from PySide2.QtCore import Qt, QUrl, QJsonValue, qInstallMessageHandler, QtMsgType, QSettings
from PySide2.QtGui import QIcon
from PySide2.QtWidgets import QApplication
import meshroom
from meshroom.core import nodesDesc
from meshroom.core.taskManager import TaskManager
from meshroom.common import Property, Variant, Signal, Slot
from meshroom.ui import components
from meshroom.ui.components.clipboard import ClipboardHelper
@ -143,9 +144,9 @@ class MeshroomApp(QApplication):
# instantiate Reconstruction object
self._undoStack = commands.UndoStack(self)
self._taskManager = TaskManager(self)
r = Reconstruction(undoStack=self._undoStack, taskManager=self._taskManager, defaultPipeline=args.pipeline, parent=self)
r.setSubmitLabel(args.submitLabel)
self.engine.rootContext().setContextProperty("_reconstruction", r)
self._activeProject = Reconstruction(undoStack=self._undoStack, taskManager=self._taskManager, defaultPipeline=args.pipeline, parent=self)
self._activeProject.setSubmitLabel(args.submitLabel)
self.engine.rootContext().setContextProperty("_reconstruction", self._activeProject)
# those helpers should be available from QML Utils module as singletons, but:
# - qmlRegisterUncreatableType is not yet available in PySide2
@ -162,7 +163,7 @@ class MeshroomApp(QApplication):
self.engine.rootContext().setContextProperty("MeshroomApp", self)
# request any potential computation to stop on exit
self.aboutToQuit.connect(r.stopChildThreads)
self.aboutToQuit.connect(self._activeProject.stopChildThreads)
if args.project and not os.path.isfile(args.project):
raise RuntimeError(
@ -171,17 +172,17 @@ class MeshroomApp(QApplication):
if args.project:
args.project = os.path.abspath(args.project)
r.load(args.project)
self._activeProject.load(args.project)
self.addRecentProjectFile(args.project)
else:
r.new()
self._activeProject.new()
# import is a python keyword, so we have to access the attribute by a string
if getattr(args, "import", None):
r.importImagesFromFolder(getattr(args, "import"), recursive=False)
self._activeProject.importImagesFromFolder(getattr(args, "import"), recursive=False)
if args.importRecursive:
r.importImagesFromFolder(args.importRecursive, recursive=True)
self._activeProject.importImagesFromFolder(args.importRecursive, recursive=True)
if args.save:
if os.path.isfile(args.save):
@ -195,7 +196,7 @@ class MeshroomApp(QApplication):
"Meshroom Command Line Error: Cannot save the new Meshroom project file (.mg) as the parent of the folder does not exists.\n"
"Invalid value: '{}'".format(args.save))
os.mkdir(projectFolder)
r.saveAs(args.save)
self._activeProject.saveAs(args.save)
self.addRecentProjectFile(args.save)
self.engine.load(os.path.normpath(url))
@ -459,7 +460,8 @@ class MeshroomApp(QApplication):
def _default8bitViewerEnabled(self):
return bool(os.environ.get("MESHROOM_USE_8BIT_VIEWER", False))
activeProjectChanged = Signal()
activeProject = Property(Variant, lambda self: self._activeProject, notify=activeProjectChanged)
changelogModel = Property("QVariantList", _changelogModel, constant=True)
licensesModel = Property("QVariantList", _licensesModel, constant=True)
pipelineTemplateFilesChanged = Signal()

View file

@ -137,8 +137,8 @@ FocusScope {
if (Math.min(imgContainer.width, imgContainer.image.height) * imgContainer.scale * zoomFactor < 10)
return
var point = mapToItem(imgContainer, wheel.x, wheel.y)
imgContainer.x += (1-zoomFactor) * point.x * imgContainer.scale
imgContainer.y += (1-zoomFactor) * point.y * imgContainer.scale
imgContainer.x += (1 - zoomFactor) * point.x * imgContainer.scale
imgContainer.y += (1 - zoomFactor) * point.y * imgContainer.scale
imgContainer.scale *= zoomFactor
}
}
@ -224,12 +224,15 @@ FocusScope {
// Entry point for getting the image file from the gallery
let vp = getViewpoint(_reconstruction.pickedViewId)
let path = vp ? vp.childAttribute("path").value : ""
_reconstruction.currentViewPath = path
return Filepath.stringToUrl(path)
}
if (_reconstruction && displayedNode && displayedNode.hasSequenceOutput && displayedAttr && (displayedAttr.desc.semantic === "imageList" || displayedAttr.desc.semantic === "sequence")) {
if (_reconstruction && displayedNode && displayedNode.hasSequenceOutput && displayedAttr &&
(displayedAttr.desc.semantic === "imageList" || displayedAttr.desc.semantic === "sequence")) {
// Entry point for getting the image file from a sequence defined by an output attribute
var path = sequence[currentFrame-frameRange.min]
var path = sequence[currentFrame - frameRange.min]
_reconstruction.currentViewPath = path
return Filepath.stringToUrl(path)
}
@ -238,20 +241,22 @@ FocusScope {
let vp = getViewpoint(_reconstruction.pickedViewId)
let path = displayedAttr ? displayedAttr.value : ""
let resolved = vp ? Filepath.resolve(path, vp) : path
_reconstruction.currentViewPath = resolved
return Filepath.stringToUrl(resolved)
}
return undefined
}
function buildOrderedSequence(path_template) {
function buildOrderedSequence(pathTemplate) {
// Resolve the path template on the sequence of viewpoints
// ordered by path
let objs = []
if (displayedNode && displayedNode.hasSequenceOutput && displayedAttr && (displayedAttr.desc.semantic === "imageList" || displayedAttr.desc.semantic === "sequence")) {
let sequence = Filepath.resolveSequence(path_template)
if (displayedNode && displayedNode.hasSequenceOutput && displayedAttr &&
(displayedAttr.desc.semantic === "imageList" || displayedAttr.desc.semantic === "sequence")) {
let sequence = Filepath.resolveSequence(pathTemplate)
let ids = sequence[0]
let resolved = sequence[1]
@ -260,7 +265,7 @@ FocusScope {
// concat in one array all sequences in resolved
resolved = [].concat.apply([], resolved)
frameRange.min = 0
frameRange.max = resolved.length-1
frameRange.max = resolved.length - 1
currentFrame = 0
}
@ -270,7 +275,7 @@ FocusScope {
resolved = resolved[0]
ids = ids[0]
frameRange.min = ids[0]
frameRange.max = ids[ids.length-1]
frameRange.max = ids[ids.length - 1]
currentFrame = frameRange.min
}
@ -282,11 +287,11 @@ FocusScope {
objs.sort((a, b) => { return a.childAttribute("path").value < b.childAttribute("path").value ? -1 : 1; })
let seq = [];
for (let i = 0; i < objs.length; i++) {
seq.push(Filepath.resolve(path_template, objs[i]))
seq.push(Filepath.resolve(pathTemplate, objs[i]))
}
frameRange.min = 0
frameRange.max = seq.length-1
frameRange.max = seq.length - 1
currentFrame = 0
return seq
@ -322,13 +327,15 @@ FocusScope {
// store attr name for output attributes that represent images
for (var i = 0; i < displayedNode.attributes.count; i++) {
var attr = displayedNode.attributes.at(i)
if (attr.isOutput && (attr.desc.semantic === "image" || attr.desc.semantic === "sequence" || attr.desc.semantic === "imageList") && attr.enabled) {
if (attr.isOutput && (attr.desc.semantic === "image" || attr.desc.semantic === "sequence" ||
attr.desc.semantic === "imageList") && attr.enabled) {
names.push(attr.name)
}
}
}
if (!displayedNode || displayedNode.isComputable) names.push("gallery")
if (!displayedNode || displayedNode.isComputable)
names.push("gallery")
outputAttribute.names = names
}
@ -399,7 +406,8 @@ FocusScope {
if (floatImageViewerLoader.item.containsMouse === false) {
return null
}
var pix = floatImageViewerLoader.item.pixelValueAt(Math.floor(floatImageViewerLoader.item.mouseX), Math.floor(floatImageViewerLoader.item.mouseY))
var pix = floatImageViewerLoader.item.pixelValueAt(Math.floor(floatImageViewerLoader.item.mouseX),
Math.floor(floatImageViewerLoader.item.mouseY))
return pix
}
}
@ -444,7 +452,8 @@ FocusScope {
// qtAliceVision Image Viewer
ExifOrientedViewer {
id: floatImageViewerLoader
active: root.aliceVisionPluginAvailable && (root.useFloatImageViewer || root.useLensDistortionViewer) && !panoramaViewerLoader.active
active: root.aliceVisionPluginAvailable &&
(root.useFloatImageViewer || root.useLensDistortionViewer) && !panoramaViewerLoader.active
visible: (floatImageViewerLoader.status === Loader.Ready) && active
anchors.centerIn: parent
orientationTag: imgContainer.orientationTag
@ -467,7 +476,8 @@ FocusScope {
* opened, for example, and the images have a different size), then another auto-fit needs to be
* performed */
if ((!fittedOnce && imgContainer.image && imgContainer.image.height > 0) ||
(fittedOnce && ((width > 1 && previousWidth != width) || (height > 1 && previousHeight != height)))) {
(fittedOnce && ((width > 1 && previousWidth != width) ||
(height > 1 && previousHeight != height)))) {
fit()
fittedOnce = true
previousWidth = width
@ -515,7 +525,8 @@ FocusScope {
// qtAliceVision Panorama Viewer
Loader {
id: panoramaViewerLoader
active: root.aliceVisionPluginAvailable && root.usePanoramaViewer && _reconstruction.activeNodes.get('sfm').node
active: root.aliceVisionPluginAvailable && root.usePanoramaViewer &&
_reconstruction.activeNodes.get('sfm').node
visible: (panoramaViewerLoader.status === Loader.Ready) && active
anchors.centerIn: parent
@ -689,7 +700,7 @@ FocusScope {
onJsonFolderChanged: {
json = null
if(activeNode.attribute("autoDetect").value) {
if (activeNode.attribute("autoDetect").value) {
// auto detection enabled
var jsonPath = activeNode.attribute("output").value + "/detection.json"
Request.get(Filepath.stringToUrl(jsonPath), function(xhr) {
@ -711,12 +722,12 @@ FocusScope {
onNodeCircleRadiusChanged : { updateGizmo() }
function updateGizmo() {
if(activeNode.attribute("autoDetect").value) {
if (activeNode.attribute("autoDetect").value) {
// update gizmo from auto detection json file
if(json) {
if (json) {
// json file found
var data = json[currentViewId]
if(data && data[0]) {
if (data && data[0]) {
// current view id found
circleX = data[0].x
circleY= data[0].y
@ -738,11 +749,13 @@ FocusScope {
}
onMoved: {
_reconstruction.setAttribute(activeNode.attribute("sphereCenter"), JSON.stringify([xoffset, yoffset]))
_reconstruction.setAttribute(activeNode.attribute("sphereCenter"),
JSON.stringify([xoffset, yoffset]))
}
onIncrementRadius: {
_reconstruction.setAttribute(activeNode.attribute("sphereRadius"), activeNode.attribute("sphereRadius").value + radiusOffset)
_reconstruction.setAttribute(activeNode.attribute("sphereRadius"),
activeNode.attribute("sphereRadius").value + radiusOffset)
}
}
}
@ -1087,7 +1100,8 @@ FocusScope {
left: parent.left
margins: 2
}
active: root.aliceVisionPluginAvailable && displayFeatures.checked && featuresViewerLoader.status === Loader.Ready
active: root.aliceVisionPluginAvailable && displayFeatures.checked &&
featuresViewerLoader.status === Loader.Ready
sourceComponent: FeaturesInfoOverlay {
pluginStatus: featuresViewerLoader.status

View file

@ -464,6 +464,8 @@ class Reconstruction(UIGraph):
self._pickedViewId = None
self._liveSfmManager = LiveSfmManager(self)
self._currentViewPath = ""
self._workerThreads = ThreadPool(processes=1)
# react to internal graph changes to update those variables
@ -1158,7 +1160,6 @@ class Reconstruction(UIGraph):
# Reconstruction has ownership of Viewpoint object - destroy it when not needed anymore
self._selectedViewpoint.deleteLater()
self._selectedViewpoint = ViewpointWrapper(viewpointAttribute, self) if viewpointAttribute else None
self._graph._selectedViewpoint = self._selectedViewpoint.attribute if viewpointAttribute else None
self.selectedViewpointChanged.emit()
def setPickedViewId(self, viewId):
@ -1217,6 +1218,12 @@ class Reconstruction(UIGraph):
return R, T
def setCurrentViewPath(self, path):
if self._currentViewPath == path:
return
self._currentViewPath = path
self.currentViewPathChanged.emit()
selectedViewIdChanged = Signal()
selectedViewId = Property(str, lambda self: self._selectedViewId, setSelectedViewId, notify=selectedViewIdChanged)
selectedViewpointChanged = Signal()
@ -1234,6 +1241,12 @@ class Reconstruction(UIGraph):
nbCameras = Property(int, reconstructedCamerasCount, notify=sfmReportChanged)
# Provides the path of the image that is currently displayed
# This is an alternative to "selectedViewpoint.attribute.path.value" for images that are displayed
# but not part of the list of viewpoints of a CameraInit node (i.e. "sequence" node outputs)
currentViewPathChanged = Signal()
currentViewPath = Property(str, lambda self: self._currentViewPath, setCurrentViewPath, notify=currentViewPathChanged)
# Signals to propagate high-level messages
error = Signal(Message)
warning = Signal(Message)