Skip to content

Commit

Permalink
Merge pull request #1230 from alicevision/dev/panoInitManualPoses
Browse files Browse the repository at this point in the history
Panorama: new options to init with known poses
  • Loading branch information
fabiencastan authored Jan 22, 2021
2 parents 45308f7 + d9018ff commit 8d0cd74
Show file tree
Hide file tree
Showing 23 changed files with 529 additions and 337 deletions.
10 changes: 5 additions & 5 deletions bin/meshroom_photogrammetry
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ args = parser.parse_args()

def getOnlyNodeOfType(g, nodeType):
""" Helper function to get a node of 'nodeType' in the graph 'g' and raise if no or multiple candidates. """
nodes = g.nodesByType(nodeType)
nodes = g.nodesOfType(nodeType)
if len(nodes) != 1:
raise RuntimeError("meshroom_photogrammetry requires a pipeline graph with exactly one '{}' node, {} found."
.format(nodeType, len(nodes)))
Expand Down Expand Up @@ -163,10 +163,10 @@ with multiview.GraphModification(graph):
raise ValueError('Invalid param override: ' + str(p))
node, t, param, value = result.groups()
if t == ':':
nodesByType = graph.nodesByType(node)
if not nodesByType:
nodesOfType = graph.nodesOfType(node)
if not nodesOfType:
raise ValueError('No node with the type "{}" in the scene.'.format(node))
for n in nodesByType:
for n in nodesOfType:
print('Overrides {node}.{param}={value}'.format(node=node, param=param, value=value))
n.attribute(param).value = value
elif t == '.':
Expand All @@ -178,7 +178,7 @@ with multiview.GraphModification(graph):

# setup DepthMap downscaling
if args.scale > 0:
for node in graph.nodesByType('DepthMap'):
for node in graph.nodesOfType('DepthMap'):
node.downscale.value = args.scale

# setup cache directory
Expand Down
4 changes: 4 additions & 0 deletions meshroom/core/attribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ def getName(self):
def getType(self):
return self.attributeDesc.__class__.__name__

def _isReadOnly(self):
return not self._isOutput and self.node.isCompatibilityNode

def getBaseType(self):
return self.getType()

Expand Down Expand Up @@ -262,6 +265,7 @@ def updateInternals(self):
label = Property(str, getLabel, constant=True)
type = Property(str, getType, constant=True)
baseType = Property(str, getType, constant=True)
isReadOnly = Property(bool, _isReadOnly, constant=True)
desc = Property(desc.Attribute, lambda self: self.attributeDesc, constant=True)
valueChanged = Signal()
value = Property(Variant, _get_value, _set_value, notify=valueChanged)
Expand Down
2 changes: 1 addition & 1 deletion meshroom/core/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,7 @@ def sortNodesByIndex(nodes):
"""
return sorted(nodes, key=lambda x: Graph.getNodeIndexFromName(x.name))

def nodesByType(self, nodeType, sortedByIndex=True):
def nodesOfType(self, nodeType, sortedByIndex=True):
"""
Returns all Nodes of the given nodeType.
Expand Down
101 changes: 70 additions & 31 deletions meshroom/core/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,12 @@ class ExecMode(Enum):
EXTERN = 2


class StatusData:
class StatusData(BaseObject):
"""
"""
dateTimeFormatting = '%Y-%m-%d %H:%M:%S.%f'

def __init__(self, nodeName, nodeType, packageName, packageVersion):
def __init__(self, nodeName='', nodeType='', packageName='', packageVersion=''):
self.status = Status.NONE
self.execMode = ExecMode.NONE
self.nodeName = nodeName
Expand All @@ -79,6 +79,11 @@ def __init__(self, nodeName, nodeType, packageName, packageVersion):
self.hostname = ""
self.sessionUid = meshroom.core.sessionUid

def merge(self, other):
self.startDateTime = min(self.startDateTime, other.startDateTime)
self.endDateTime = max(self.endDateTime, other.endDateTime)
self.elapsedTime += other.elapsedTime

def reset(self):
self.status = Status.NONE
self.execMode = ExecMode.NONE
Expand Down Expand Up @@ -112,8 +117,12 @@ def toDict(self):
return d

def fromDict(self, d):
self.status = getattr(Status, d.get('status', ''), Status.NONE)
self.execMode = getattr(ExecMode, d.get('execMode', ''), ExecMode.NONE)
self.status = d.get('status', Status.NONE)
if not isinstance(self.status, Status):
self.status = Status[self.status]
self.execMode = d.get('execMode', ExecMode.NONE)
if not isinstance(self.execMode, ExecMode):
self.execMode = ExecMode[self.execMode]
self.nodeName = d.get('nodeName', '')
self.nodeType = d.get('nodeType', '')
self.packageName = d.get('packageName', '')
Expand Down Expand Up @@ -236,7 +245,7 @@ def __init__(self, node, range, parent=None):
self.node = node
self.range = range
self.logManager = LogManager(self)
self.status = StatusData(node.name, node.nodeType, node.packageName, node.packageVersion)
self._status = StatusData(node.name, node.nodeType, node.packageName, node.packageVersion)
self.statistics = stats.Statistics()
self.statusFileLastModTime = -1
self._subprocess = None
Expand All @@ -258,32 +267,32 @@ def name(self):

@property
def statusName(self):
return self.status.status.name
return self._status.status.name

@property
def logger(self):
return self.logManager.logger

@property
def execModeName(self):
return self.status.execMode.name
return self._status.execMode.name

def updateStatusFromCache(self):
"""
Update node status based on status file content/existence.
"""
statusFile = self.statusFile
oldStatus = self.status.status
oldStatus = self._status.status
# No status file => reset status to Status.None
if not os.path.exists(statusFile):
self.statusFileLastModTime = -1
self.status.reset()
self._status.reset()
else:
with open(statusFile, 'r') as jsonFile:
statusData = json.load(jsonFile)
self.status.fromDict(statusData)
self._status.fromDict(statusData)
self.statusFileLastModTime = os.path.getmtime(statusFile)
if oldStatus != self.status.status:
if oldStatus != self._status.status:
self.statusChanged.emit()

@property
Expand Down Expand Up @@ -311,7 +320,7 @@ def saveStatusFile(self):
"""
Write node status on disk.
"""
data = self.status.toDict()
data = self._status.toDict()
statusFilepath = self.statusFile
folder = os.path.dirname(statusFilepath)
if not os.path.exists(folder):
Expand All @@ -322,16 +331,16 @@ def saveStatusFile(self):
renameWritingToFinalPath(statusFilepathWriting, statusFilepath)

def upgradeStatusTo(self, newStatus, execMode=None):
if newStatus.value <= self.status.status.value:
print('WARNING: downgrade status on node "{}" from {} to {}'.format(self.name, self.status.status,
newStatus))
if newStatus.value <= self._status.status.value:
logging.warning('Downgrade status on node "{}" from {} to {}'.format(self.name, self._status.status,
newStatus))

if newStatus == Status.SUBMITTED:
self.status = StatusData(self.node.name, self.node.nodeType, self.node.packageName, self.node.packageVersion)
self._status = StatusData(self.node.name, self.node.nodeType, self.node.packageName, self.node.packageVersion)
if execMode is not None:
self.status.execMode = execMode
self._status.execMode = execMode
self.execModeNameChanged.emit()
self.status.status = newStatus
self._status.status = newStatus
self.saveStatusFile()
self.statusChanged.emit()

Expand Down Expand Up @@ -360,41 +369,41 @@ def saveStatistics(self):
renameWritingToFinalPath(statisticsFilepathWriting, statisticsFilepath)

def isAlreadySubmitted(self):
return self.status.status in (Status.SUBMITTED, Status.RUNNING)
return self._status.status in (Status.SUBMITTED, Status.RUNNING)

def isAlreadySubmittedOrFinished(self):
return self.status.status in (Status.SUBMITTED, Status.RUNNING, Status.SUCCESS)
return self._status.status in (Status.SUBMITTED, Status.RUNNING, Status.SUCCESS)

def isFinishedOrRunning(self):
return self.status.status in (Status.SUCCESS, Status.RUNNING)
return self._status.status in (Status.SUCCESS, Status.RUNNING)

def isStopped(self):
return self.status.status == Status.STOPPED
return self._status.status == Status.STOPPED

def process(self, forceCompute=False):
if not forceCompute and self.status.status == Status.SUCCESS:
print("Node chunk already computed:", self.name)
if not forceCompute and self._status.status == Status.SUCCESS:
logging.info("Node chunk already computed: {}".format(self.name))
return
global runningProcesses
runningProcesses[self.name] = self
self.status.initStartCompute()
self._status.initStartCompute()
startTime = time.time()
self.upgradeStatusTo(Status.RUNNING)
self.statThread = stats.StatisticsThread(self)
self.statThread.start()
try:
self.node.nodeDesc.processChunk(self)
except Exception as e:
if self.status.status != Status.STOPPED:
if self._status.status != Status.STOPPED:
self.upgradeStatusTo(Status.ERROR)
raise
except (KeyboardInterrupt, SystemError, GeneratorExit) as e:
self.upgradeStatusTo(Status.STOPPED)
raise
finally:
self.status.initEndCompute()
self.status.elapsedTime = time.time() - startTime
print(' - elapsed time:', self.status.elapsedTimeStr)
self._status.initEndCompute()
self._status.elapsedTime = time.time() - startTime
logging.info(' - elapsed time: {}'.format(self._status.elapsedTimeStr))
# ask and wait for the stats thread to stop
self.statThread.stopRequest()
self.statThread.join()
Expand All @@ -408,9 +417,10 @@ def stopProcess(self):
self.node.nodeDesc.stopProcess(self)

def isExtern(self):
return self.status.execMode == ExecMode.EXTERN
return self._status.execMode == ExecMode.EXTERN

statusChanged = Signal()
status = Property(Variant, lambda self: self._status, notify=statusChanged)
statusName = Property(str, statusName.fget, notify=statusChanged)
execModeNameChanged = Signal()
execModeName = Property(str, execModeName.fget, notify=execModeNameChanged)
Expand All @@ -422,7 +432,7 @@ def isExtern(self):
statisticsFile = Property(str, statisticsFile.fget, notify=nodeFolderChanged)

nodeName = Property(str, lambda self: self.node.name, constant=True)
statusNodeName = Property(str, lambda self: self.status.nodeName, constant=True)
statusNodeName = Property(str, lambda self: self._status.nodeName, constant=True)


# simple structure for storing node position
Expand Down Expand Up @@ -837,6 +847,27 @@ def getGlobalStatus(self):

return Status.NONE

@Slot(result=StatusData)
def getFusedStatus(self):
fusedStatus = StatusData()
if self._chunks:
fusedStatus.fromDict(self._chunks[0].status.toDict())
for chunk in self._chunks[1:]:
fusedStatus.merge(chunk.status)
fusedStatus.status = self.getGlobalStatus()
return fusedStatus

@Slot(result=StatusData)
def getRecursiveFusedStatus(self):
fusedStatus = self.getFusedStatus()
nodes = self.getInputNodes(recursive=True, dependenciesOnly=True)
for node in nodes:
fusedStatus.merge(node.fusedStatus)
return fusedStatus

def _isCompatibilityNode(self):
return False

@property
def globalExecMode(self):
return self._chunks.at(0).execModeName
Expand Down Expand Up @@ -1000,6 +1031,11 @@ def canBeCanceled(self):
size = Property(int, getSize, notify=sizeChanged)
globalStatusChanged = Signal()
globalStatus = Property(str, lambda self: self.getGlobalStatus().name, notify=globalStatusChanged)
fusedStatus = Property(StatusData, getFusedStatus, notify=globalStatusChanged)
elapsedTime = Property(float, lambda self: self.getFusedStatus().elapsedTime, notify=globalStatusChanged)
recursiveElapsedTime = Property(float, lambda self: self.getRecursiveFusedStatus().elapsedTime, notify=globalStatusChanged)
isCompatibilityNode = Property(bool, lambda self: self._isCompatibilityNode(), constant=True) # need lambda to evaluate the virtual function

globalExecModeChanged = Signal()
globalExecMode = Property(str, globalExecMode.fget, notify=globalExecModeChanged)
isComputed = Property(bool, _isComputed, notify=globalStatusChanged)
Expand Down Expand Up @@ -1135,6 +1171,9 @@ def __init__(self, nodeType, nodeDict, position=None, issue=CompatibilityIssue.U
for i in range(self.splitCount)
])

def _isCompatibilityNode(self):
return True

@staticmethod
def attributeDescFromValue(attrName, value, isOutput):
"""
Expand Down
4 changes: 2 additions & 2 deletions meshroom/multiview.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,11 @@ def panoramaFisheyeHdr(inputImages=None, inputViewpoints=None, inputIntrinsics=N
graph = Graph('PanoramaFisheyeHDR')
with GraphModification(graph):
panoramaHdr(inputImages, inputViewpoints, inputIntrinsics, output, graph)
for panoramaInit in graph.nodesByType("PanoramaInit"):
for panoramaInit in graph.nodesOfType("PanoramaInit"):
panoramaInit.attribute("useFisheye").value = True
# when using fisheye images, the overlap between images can be small
# and thus requires many features to get enough correspondances for cameras estimation
for featureExtraction in graph.nodesByType("FeatureExtraction"):
for featureExtraction in graph.nodesOfType("FeatureExtraction"):
featureExtraction.attribute("describerPreset").value = 'high'
return graph

Expand Down
13 changes: 11 additions & 2 deletions meshroom/nodes/aliceVision/LdrToHdrMerge.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,15 @@ class LdrToHdrMerge(desc.CommandLineNode):
advanced=True,
enabled= lambda node: node.byPass.enabled and not node.byPass.value,
),
desc.BoolParam(
name='enableHighlight',
label='Enable Highlight',
description="Enable highlights correction.",
value=True,
uid=[0],
group='user', # not used directly on the command line
enabled= lambda node: node.byPass.enabled and not node.byPass.value,
),
desc.FloatParam(
name='highlightCorrectionFactor',
label='Highlights Correction',
Expand All @@ -115,7 +124,7 @@ class LdrToHdrMerge(desc.CommandLineNode):
value=1.0,
range=(0.0, 1.0, 0.01),
uid=[0],
enabled= lambda node: node.byPass.enabled and not node.byPass.value,
enabled= lambda node: node.enableHighlight.enabled and node.enableHighlight.value,
),
desc.FloatParam(
name='highlightTargetLux',
Expand All @@ -138,7 +147,7 @@ class LdrToHdrMerge(desc.CommandLineNode):
value=120000.0,
range=(1000.0, 150000.0, 1.0),
uid=[0],
enabled= lambda node: node.byPass.enabled and not node.byPass.value and node.highlightCorrectionFactor.value != 0,
enabled= lambda node: node.enableHighlight.enabled and node.enableHighlight.value and node.highlightCorrectionFactor.value != 0,
),
desc.ChoiceParam(
name='storageDataType',
Expand Down
15 changes: 15 additions & 0 deletions meshroom/nodes/aliceVision/PanoramaEstimation.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,21 @@ class PanoramaEstimation(desc.CommandLineNode):
uid=[0],
advanced=True,
),
desc.BoolParam(
name='rotationAveragingWeighting',
label='Rotation Averaging Weighting',
description='Rotation averaging weighting based on the number of feature matches.',
value=True,
uid=[0],
advanced=True,
),
desc.BoolParam(
name='filterMatches',
label='Filter Matches',
description='Filter Matches',
value=False,
uid=[0],
),
desc.BoolParam(
name='refine',
label='Refine',
Expand Down
Loading

0 comments on commit 8d0cd74

Please sign in to comment.