mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-05-20 12:36:29 +02:00
260 lines
11 KiB
Python
260 lines
11 KiB
Python
__version__ = "4.0"
|
|
|
|
import json
|
|
|
|
from meshroom.core import desc
|
|
|
|
def findMetadata(d, keys, defaultValue):
|
|
v = None
|
|
for key in keys:
|
|
v = d.get(key, None)
|
|
k = key.lower()
|
|
if v is not None:
|
|
return v
|
|
for dk, dv in d.items():
|
|
dkm = dk.lower().replace(" ", "")
|
|
if dkm == key.lower():
|
|
return dv
|
|
dkm = dkm.split(":")[-1]
|
|
dkm = dkm.split("/")[-1]
|
|
if dkm == k:
|
|
return dv
|
|
return defaultValue
|
|
|
|
|
|
class LdrToHdrMerge(desc.AVCommandLineNode):
|
|
commandLine = 'aliceVision_LdrToHdrMerge {allParams}'
|
|
size = desc.DynamicNodeSize('input')
|
|
parallelization = desc.Parallelization(blockSize=2)
|
|
commandLineRange = '--rangeStart {rangeStart} --rangeSize {rangeBlockSize}'
|
|
|
|
category = 'Panorama HDR'
|
|
documentation = '''
|
|
Calibrate LDR to HDR response curve from samples
|
|
'''
|
|
|
|
inputs = [
|
|
desc.File(
|
|
name='input',
|
|
label='Input',
|
|
description='SfMData file.',
|
|
value='',
|
|
uid=[0],
|
|
),
|
|
desc.File(
|
|
name='response',
|
|
label='Response file',
|
|
description='Response file',
|
|
value='',
|
|
uid=[0],
|
|
),
|
|
desc.IntParam(
|
|
name='userNbBrackets',
|
|
label='Number of Brackets',
|
|
description='Number of exposure brackets per HDR image (0 for automatic detection).',
|
|
value=0,
|
|
range=(0, 15, 1),
|
|
uid=[],
|
|
group='user', # not used directly on the command line
|
|
),
|
|
desc.IntParam(
|
|
name='nbBrackets',
|
|
label='Automatic Nb Brackets',
|
|
description='Number of exposure brackets used per HDR image. It is detected automatically from input Viewpoints metadata if "userNbBrackets" is 0, else it is equal to "userNbBrackets".',
|
|
value=0,
|
|
range=(0, 10, 1),
|
|
uid=[0],
|
|
),
|
|
desc.IntParam(
|
|
name='offsetRefBracketIndex',
|
|
label='Offset Ref Bracket Index',
|
|
description='Zero to use the center bracket. +N to use a more exposed bracket or -N to use a less exposed backet.',
|
|
value=1,
|
|
range=(-4, 4, 1),
|
|
uid=[0],
|
|
enabled= lambda node: node.nbBrackets.value != 1,
|
|
),
|
|
desc.BoolParam(
|
|
name='byPass',
|
|
label='Bypass',
|
|
description="Bypass HDR creation and use the medium bracket as the source for the next steps.",
|
|
value=False,
|
|
uid=[0],
|
|
enabled= lambda node: node.nbBrackets.value != 1,
|
|
),
|
|
desc.ChoiceParam(
|
|
name='fusionWeight',
|
|
label='Fusion Weight',
|
|
description="Weight function used to fuse all LDR images together:\n"
|
|
" * gaussian \n"
|
|
" * triangle \n"
|
|
" * plateau",
|
|
value='gaussian',
|
|
values=['gaussian', 'triangle', 'plateau'],
|
|
exclusive=True,
|
|
uid=[0],
|
|
enabled= lambda node: node.byPass.enabled and not node.byPass.value,
|
|
),
|
|
desc.IntParam(
|
|
name='channelQuantizationPower',
|
|
label='Channel Quantization Power',
|
|
description='Quantization level like 8 bits or 10 bits.',
|
|
value=10,
|
|
range=(8, 14, 1),
|
|
uid=[0],
|
|
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=False,
|
|
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',
|
|
description='Pixels saturated in all input images have a partial information about their real luminance.\n'
|
|
'We only know that the value should be >= to the standard hdr fusion.\n'
|
|
'This parameter allows to perform a post-processing step to put saturated pixels to a constant\n'
|
|
'value defined by the `highlightsMaxLuminance` parameter.\n'
|
|
'This parameter is float to enable to weight this correction.',
|
|
value=1.0,
|
|
range=(0.0, 1.0, 0.01),
|
|
uid=[0],
|
|
enabled= lambda node: node.enableHighlight.enabled and node.enableHighlight.value,
|
|
),
|
|
desc.FloatParam(
|
|
name='highlightTargetLux',
|
|
label='Highlight Target Luminance (Lux)',
|
|
description='This is an arbitrary target value (in Lux) used to replace the unknown luminance value of the saturated pixels.\n'
|
|
'\n'
|
|
'Some Outdoor Reference Light Levels:\n'
|
|
' * 120,000 lux: Brightest sunlight\n'
|
|
' * 110,000 lux: Bright sunlight\n'
|
|
' * 20,000 lux: Shade illuminated by entire clear blue sky, midday\n'
|
|
' * 1,000 lux: Typical overcast day, midday\n'
|
|
' * 400 lux: Sunrise or sunset on a clear day\n'
|
|
' * 40 lux: Fully overcast, sunset/sunrise\n'
|
|
'\n'
|
|
'Some Indoor Reference Light Levels:\n'
|
|
' * 20000 lux: Max Usually Used Indoor\n'
|
|
' * 750 lux: Supermarkets\n'
|
|
' * 500 lux: Office Work\n'
|
|
' * 150 lux: Home\n',
|
|
value=120000.0,
|
|
range=(1000.0, 150000.0, 1.0),
|
|
uid=[0],
|
|
enabled= lambda node: node.enableHighlight.enabled and node.enableHighlight.value and node.highlightCorrectionFactor.value != 0,
|
|
),
|
|
desc.ChoiceParam(
|
|
name='storageDataType',
|
|
label='Storage Data Type',
|
|
description='Storage image data type:\n'
|
|
' * float: Use full floating point (32 bits per channel)\n'
|
|
' * half: Use half float (16 bits per channel)\n'
|
|
' * halfFinite: Use half float, but clamp values to avoid non-finite values\n'
|
|
' * auto: Use half float if all values can fit, else use full float\n',
|
|
value='float',
|
|
values=['float', 'half', 'halfFinite', 'auto'],
|
|
exclusive=True,
|
|
uid=[0],
|
|
),
|
|
desc.ChoiceParam(
|
|
name='verboseLevel',
|
|
label='Verbose Level',
|
|
description='verbosity level (fatal, error, warning, info, debug, trace).',
|
|
value='info',
|
|
values=['fatal', 'error', 'warning', 'info', 'debug', 'trace'],
|
|
exclusive=True,
|
|
uid=[],
|
|
)
|
|
]
|
|
|
|
outputs = [
|
|
desc.File(
|
|
name='outSfMData',
|
|
label='SfMData File',
|
|
description='Path to the output sfmdata file',
|
|
value=desc.Node.internalFolder + 'sfmData.sfm',
|
|
uid=[],
|
|
)
|
|
]
|
|
|
|
@classmethod
|
|
def update(cls, node):
|
|
if not isinstance(node.nodeDesc, cls):
|
|
raise ValueError("Node {} is not an instance of type {}".format(node, cls))
|
|
# TODO: use Node version for this test
|
|
if 'userNbBrackets' not in node.getAttributes().keys():
|
|
# Old version of the node
|
|
return
|
|
if node.userNbBrackets.value != 0:
|
|
node.nbBrackets.value = node.userNbBrackets.value
|
|
return
|
|
# logging.info("[LDRToHDR] Update start: version:" + str(node.packageVersion))
|
|
cameraInitOutput = node.input.getLinkParam(recursive=True)
|
|
if not cameraInitOutput:
|
|
node.nbBrackets.value = 0
|
|
return
|
|
if not cameraInitOutput.node.hasAttribute('viewpoints'):
|
|
if cameraInitOutput.node.hasAttribute('input'):
|
|
cameraInitOutput = cameraInitOutput.node.input.getLinkParam(recursive=True)
|
|
if cameraInitOutput and cameraInitOutput.node and cameraInitOutput.node.hasAttribute('viewpoints'):
|
|
viewpoints = cameraInitOutput.node.viewpoints.value
|
|
else:
|
|
# No connected CameraInit
|
|
node.nbBrackets.value = 0
|
|
return
|
|
|
|
# logging.info("[LDRToHDR] Update start: nb viewpoints:" + str(len(viewpoints)))
|
|
inputs = []
|
|
for viewpoint in viewpoints:
|
|
jsonMetadata = viewpoint.metadata.value
|
|
if not jsonMetadata:
|
|
# no metadata, we cannot found the number of brackets
|
|
node.nbBrackets.value = 0
|
|
return
|
|
d = json.loads(jsonMetadata)
|
|
fnumber = findMetadata(d, ["FNumber", "Exif:ApertureValue", "ApertureValue", "Aperture"], "")
|
|
shutterSpeed = findMetadata(d, ["Exif:ShutterSpeedValue", "ShutterSpeedValue", "ShutterSpeed"], "")
|
|
iso = findMetadata(d, ["Exif:ISOSpeedRatings", "ISOSpeedRatings", "ISO"], "")
|
|
if not fnumber and not shutterSpeed:
|
|
# If one image without shutter or fnumber, we cannot found the number of brackets.
|
|
# We assume that there is no multi-bracketing, so nothing to do.
|
|
node.nbBrackets.value = 1
|
|
return
|
|
inputs.append((viewpoint.path.value, (fnumber, shutterSpeed, iso)))
|
|
inputs.sort()
|
|
|
|
exposureGroups = []
|
|
exposures = []
|
|
for path, exp in inputs:
|
|
if exposures and exp != exposures[-1] and exp == exposures[0]:
|
|
exposureGroups.append(exposures)
|
|
exposures = [exp]
|
|
else:
|
|
exposures.append(exp)
|
|
exposureGroups.append(exposures)
|
|
exposures = None
|
|
bracketSizes = set()
|
|
if len(exposureGroups) == 1:
|
|
if len(set(exposureGroups[0])) == 1:
|
|
# Single exposure and multiple views
|
|
node.nbBrackets.value = 1
|
|
else:
|
|
# Single view and multiple exposures
|
|
node.nbBrackets.value = len(exposureGroups[0])
|
|
else:
|
|
for expGroup in exposureGroups:
|
|
bracketSizes.add(len(expGroup))
|
|
if len(bracketSizes) == 1:
|
|
node.nbBrackets.value = bracketSizes.pop()
|
|
# logging.info("[LDRToHDR] nb bracket size:" + str(node.nbBrackets.value))
|
|
else:
|
|
node.nbBrackets.value = 0
|
|
# logging.info("[LDRToHDR] Update end")
|
|
|