[ui] Load image sequence from node output in SequencePlayer

This commit is contained in:
Aurore LAFAURIE 2024-04-16 16:51:24 +02:00
parent 3eb90322a8
commit b0063b0dde
3 changed files with 81 additions and 30 deletions

View file

@ -4,6 +4,7 @@ from PySide2.QtCore import QUrl, QFileInfo
from PySide2.QtCore import QObject, Slot from PySide2.QtCore import QObject, Slot
import os import os
import glob
class FilepathHelper(QObject): class FilepathHelper(QObject):
@ -103,19 +104,44 @@ class FilepathHelper(QObject):
def resolve(self, path, vp): def resolve(self, path, vp):
# Resolve dynamic path that depends on viewpoint # Resolve dynamic path that depends on viewpoint
vpPath = vp.childAttribute("path").value replacements = {}
replacements = { if vp == None:
"<VIEW_ID>": str(vp.childAttribute("viewId").value), replacements = FilepathHelper.getFilenamesFromFolder(FilepathHelper, FilepathHelper.dirname(FilepathHelper, path), FilepathHelper.extension(FilepathHelper, path))
"<INTRINSIC_ID>": str(vp.childAttribute("intrinsicId").value), resolved = [path for i in range(len(replacements))]
"<POSE_ID>": str(vp.childAttribute("poseId").value), for key in replacements:
"<PATH>": vpPath, for i in range(len(resolved)):
"<FILENAME>": FilepathHelper.basename(FilepathHelper, vpPath), resolved[i] = resolved[i].replace("<FRAMEID>", replacements[i])
"<FILESTEM>": FilepathHelper.removeExtension(FilepathHelper, FilepathHelper.basename(FilepathHelper, vpPath)), return resolved
"<EXTENSION>": FilepathHelper.extension(FilepathHelper, vpPath), else:
}
vpPath = vp.childAttribute("path").value
filename = FilepathHelper.basename(FilepathHelper, vpPath)
replacements = {
"<VIEW_ID>": str(vp.childAttribute("viewId").value),
"<INTRINSIC_ID>": str(vp.childAttribute("intrinsicId").value),
"<POSE_ID>": str(vp.childAttribute("poseId").value),
"<PATH>": vpPath,
"<FILENAME>": filename,
"<FILESTEM>": FilepathHelper.removeExtension(FilepathHelper, filename),
"<EXTENSION>": FilepathHelper.extension(FilepathHelper, filename),
}
resolved = path resolved = path
for key in replacements: for key in replacements:
resolved = resolved.replace(key, replacements[key]) resolved = resolved.replace(key, replacements[key])
return resolved return resolved
@Slot(str, result="QVariantList")
@Slot(str, str, result="QVariantList")
def getFilenamesFromFolder(self, folderPath: str, extension: str = None):
"""
Get all filenames from a folder with a specific extension.
:param folderPath: Path to the folder.
:param extension: Extension of the files to get.
:return: List of filenames.
"""
if extension is None:
extension = ".*"
return [self.removeExtension(FilepathHelper, self.basename(FilepathHelper, f)) for f in glob.glob(os.path.join(folderPath, f"*{extension}")) if os.path.isfile(f)]

View file

@ -27,6 +27,7 @@ FloatingPane {
readonly property alias syncFeaturesSelected: m.syncFeaturesSelected readonly property alias syncFeaturesSelected: m.syncFeaturesSelected
property bool loading: fetchButton.checked || m.playing property bool loading: fetchButton.checked || m.playing
property alias settings_SequencePlayer: settings_SequencePlayer property alias settings_SequencePlayer: settings_SequencePlayer
property alias frameId: m.frame
Settings { Settings {
id: settings_SequencePlayer id: settings_SequencePlayer
@ -94,7 +95,7 @@ FloatingPane {
id: timer id: timer
repeat: true repeat: true
running: m.playing running: m.playing && root.visible
interval: 1000 / m.fps interval: 1000 / m.fps
onTriggered: { onTriggered: {

View file

@ -30,6 +30,8 @@ FocusScope {
property bool enableSequencePlayer: enableSequencePlayerAction.checked property bool enableSequencePlayer: enableSequencePlayerAction.checked
readonly property alias sync3DSelected: sequencePlayer.sync3DSelected readonly property alias sync3DSelected: sequencePlayer.sync3DSelected
property var sequence: []
property int currentFrame: sequencePlayer.frameId
QtObject { QtObject {
id: m id: m
@ -181,7 +183,7 @@ FocusScope {
} }
// node must have at least one output attribute with the image semantic // node must have at least one output attribute with the image semantic
if (!node.hasImageOutput) { if (!node.hasImageOutput && !node.hasSequenceOutput) {
return false return false
} }
@ -223,7 +225,6 @@ FocusScope {
return undefined return undefined
} }
function getImageFile() { function getImageFile() {
// Entry point for getting the image file URL // Entry point for getting the image file URL
@ -237,6 +238,11 @@ FocusScope {
return Filepath.stringToUrl(path) return Filepath.stringToUrl(path)
} }
if (_reconstruction && displayedNode && displayedNode.hasSequenceOutput) {
var path = sequence[currentFrame]
return Filepath.stringToUrl(path)
}
if (_reconstruction) { if (_reconstruction) {
let vp = getViewpoint(_reconstruction.pickedViewId) let vp = getViewpoint(_reconstruction.pickedViewId)
let attr = getAttributeByName(displayedNode, outputAttribute.name) let attr = getAttributeByName(displayedNode, outputAttribute.name)
@ -253,17 +259,24 @@ FocusScope {
// ordered by path // ordered by path
let objs = [] let objs = []
for (let i = 0; i < _reconstruction.viewpoints.count; i++) {
objs.push(_reconstruction.viewpoints.at(i))
}
objs.sort((a, b) => { return a.childAttribute("path").value < b.childAttribute("path").value ? -1 : 1; })
let seq = []; if (displayedNode && displayedNode.hasSequenceOutput) {
for (let i = 0; i < objs.length; i++) { objs = Filepath.resolve(path_template, null)
seq.push(Filepath.resolve(path_template, objs[i])) //order by path
} objs.sort()
return objs
} else {
for (let i = 0; i < _reconstruction.viewpoints.count; i++) {
objs.push(_reconstruction.viewpoints.at(i))
}
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]))
}
return seq return seq
}
} }
function getSequence() { function getSequence() {
@ -300,12 +313,23 @@ FocusScope {
if (attr.isOutput && attr.desc.semantic === "image" && attr.enabled) { if (attr.isOutput && attr.desc.semantic === "image" && attr.enabled) {
names.push(attr.name) names.push(attr.name)
} }
if (attr.isOutput && attr.desc.semantic === "sequence" && attr.enabled) {
names.push(attr.name)
}
} }
} }
if (!displayedNode || displayedNode.isComputable) names.push("gallery")
outputAttribute.names = names
root.source = getImageFile() if (!displayedNode || displayedNode.isComputable) names.push("gallery")
outputAttribute.names = names
if (displayedNode && !displayedNode.hasSequenceOutput) {
root.source = getImageFile()
} else {
root.sequence = getSequence()
enableSequencePlayerAction.checked = true
}
} }
Connections { Connections {
@ -457,11 +481,11 @@ FocusScope {
'sfmRequired': Qt.binding(function() { return displayLensDistortionViewer.checked ? true : false }), 'sfmRequired': Qt.binding(function() { return displayLensDistortionViewer.checked ? true : false }),
'surface.msfmData': Qt.binding(function() { return (msfmDataLoader.status === Loader.Ready && msfmDataLoader.item != null && msfmDataLoader.item.status === 2) ? msfmDataLoader.item : null }), 'surface.msfmData': Qt.binding(function() { return (msfmDataLoader.status === Loader.Ready && msfmDataLoader.item != null && msfmDataLoader.item.status === 2) ? msfmDataLoader.item : null }),
'canBeHovered': false, 'canBeHovered': false,
'idView': Qt.binding(function() { return (_reconstruction ? _reconstruction.selectedViewId : -1) }), 'idView': Qt.binding(function() { return ((root.displayedNode && !root.displayedNode.hasSequenceOutput && _reconstruction) ? _reconstruction.selectedViewId : -1) }),
'cropFisheye': false, 'cropFisheye': false,
'sequence': Qt.binding(function() { return ((root.enableSequencePlayer && _reconstruction && _reconstruction.viewpoints.count > 0) ? getSequence() : []) }), 'sequence': Qt.binding(function() { return ((root.enableSequencePlayer && (_reconstruction || (root.displayedNode && root.displayedNode.hasSequenceOutput))) ? getSequence() : []) }),
'targetSize': Qt.binding(function() { return floatImageViewerLoader.targetSize }), 'targetSize': Qt.binding(function() { return floatImageViewerLoader.targetSize }),
'useSequence': Qt.binding(function() { return root.enableSequencePlayer && !useExternal && _reconstruction }), 'useSequence': Qt.binding(function() { return (root.enableSequencePlayer && !useExternal && (_reconstruction || (root.displayedNode && root.displayedNode.hasSequenceOutput))) }),
'fetchingSequence': Qt.binding(function() { return sequencePlayer.loading }), 'fetchingSequence': Qt.binding(function() { return sequencePlayer.loading }),
'memoryLimit': Qt.binding(function() { return sequencePlayer.settings_SequencePlayer.maxCacheMemory }), 'memoryLimit': Qt.binding(function() { return sequencePlayer.settings_SequencePlayer.maxCacheMemory }),
}) })
@ -1434,7 +1458,7 @@ FocusScope {
id: sequencePlayer id: sequencePlayer
anchors.margins: 0 anchors.margins: 0
Layout.fillWidth: true Layout.fillWidth: true
sortedViewIds: (root.enableSequencePlayer && _reconstruction && _reconstruction.viewpoints.count > 0) ? buildOrderedSequence("<VIEW_ID>") : [] sortedViewIds: { return (root.enableSequencePlayer && (root.displayedNode && root.displayedNode.hasSequenceOutput)) ? root.sequence : (_reconstruction && _reconstruction.viewpoints.count > 0) ? buildOrderedSequence("<VIEW_ID>") : [] }
viewer: floatImageViewerLoader.status === Loader.Ready ? floatImageViewerLoader.item : null viewer: floatImageViewerLoader.status === Loader.Ready ? floatImageViewerLoader.item : null
visible: root.enableSequencePlayer visible: root.enableSequencePlayer
enabled: root.enableSequencePlayer enabled: root.enableSequencePlayer