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 # [nodes] Harmonize the use of trailing commas across all the nodes
61a8dcd4e2878f80b2f320f2b1c3c9b41e999b82 61a8dcd4e2878f80b2f320f2b1c3c9b41e999b82
# [nodes] Clean-up: Harmonize nodes' descriptions # [nodes] Clean-up: Harmonize nodes' descriptions

View file

@ -226,8 +226,6 @@ class Graph(BaseObject):
self._fileDateVersion = 0 self._fileDateVersion = 0
self.header = {} self.header = {}
self._selectedViewpoint = None
def clear(self): def clear(self):
self.header.clear() self.header.clear()
self._compatibilityNodes.clear() self._compatibilityNodes.clear()
@ -1566,12 +1564,6 @@ class Graph(BaseObject):
self.updateStatusFromCache(force=True) self.updateStatusFromCache(force=True)
self.cacheDirChanged.emit() 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 @property
def fileDateVersion(self): def fileDateVersion(self):
return self._fileDateVersion return self._fileDateVersion

View file

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

View file

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

View file

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

View file

@ -464,6 +464,8 @@ class Reconstruction(UIGraph):
self._pickedViewId = None self._pickedViewId = None
self._liveSfmManager = LiveSfmManager(self) self._liveSfmManager = LiveSfmManager(self)
self._currentViewPath = ""
self._workerThreads = ThreadPool(processes=1) self._workerThreads = ThreadPool(processes=1)
# react to internal graph changes to update those variables # 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 # Reconstruction has ownership of Viewpoint object - destroy it when not needed anymore
self._selectedViewpoint.deleteLater() self._selectedViewpoint.deleteLater()
self._selectedViewpoint = ViewpointWrapper(viewpointAttribute, self) if viewpointAttribute else None self._selectedViewpoint = ViewpointWrapper(viewpointAttribute, self) if viewpointAttribute else None
self._graph._selectedViewpoint = self._selectedViewpoint.attribute if viewpointAttribute else None
self.selectedViewpointChanged.emit() self.selectedViewpointChanged.emit()
def setPickedViewId(self, viewId): def setPickedViewId(self, viewId):
@ -1217,6 +1218,12 @@ class Reconstruction(UIGraph):
return R, T return R, T
def setCurrentViewPath(self, path):
if self._currentViewPath == path:
return
self._currentViewPath = path
self.currentViewPathChanged.emit()
selectedViewIdChanged = Signal() selectedViewIdChanged = Signal()
selectedViewId = Property(str, lambda self: self._selectedViewId, setSelectedViewId, notify=selectedViewIdChanged) selectedViewId = Property(str, lambda self: self._selectedViewId, setSelectedViewId, notify=selectedViewIdChanged)
selectedViewpointChanged = Signal() selectedViewpointChanged = Signal()
@ -1234,6 +1241,12 @@ class Reconstruction(UIGraph):
nbCameras = Property(int, reconstructedCamerasCount, notify=sfmReportChanged) 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 # Signals to propagate high-level messages
error = Signal(Message) error = Signal(Message)
warning = Signal(Message) warning = Signal(Message)