Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Panorama: new options to init with known poses #1230

Merged
merged 14 commits into from
Jan 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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