mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-07-25 20:47:39 +02:00
Use CamelCase for all labels, always end descriptions with periods, and replace the mixed use of single and double quotes with double quotes only.
305 lines
12 KiB
Python
305 lines
12 KiB
Python
__version__ = "4.1"
|
|
|
|
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 = '''
|
|
Merge LDR images into HDR images.
|
|
'''
|
|
|
|
inputs = [
|
|
desc.File(
|
|
name="input",
|
|
label="SfMData",
|
|
description="Input 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.\n"
|
|
"It is detected automatically from input Viewpoints metadata if 'userNbBrackets'\n"
|
|
"is 0, else it is equal to 'userNbBrackets'.",
|
|
value=0,
|
|
range=(0, 10, 1),
|
|
uid=[0],
|
|
),
|
|
desc.BoolParam(
|
|
name="offsetRefBracketIndexEnabled",
|
|
label="Manually Specify Ref Bracket",
|
|
description="Manually specify the reference bracket index to control the exposure of the HDR image.",
|
|
value=False,
|
|
uid=[0],
|
|
group="user", # not used directly on the command line
|
|
),
|
|
desc.IntParam(
|
|
name="offsetRefBracketIndex",
|
|
label="Offset Ref Bracket Index",
|
|
description="0 to use the center bracket.\n"
|
|
"+N to use a more exposed bracket or -N to use a less exposed bracket.",
|
|
value=1,
|
|
range=(-4, 4, 1),
|
|
uid=[0],
|
|
enabled= lambda node: (node.nbBrackets.value != 1 and node.offsetRefBracketIndexEnabled.value),
|
|
),
|
|
desc.FloatParam(
|
|
name="meanTargetedLumaForMerging",
|
|
label="Targeted Luminance For Merging",
|
|
description="Expected mean luminance of the HDR images used to compute the final panorama.",
|
|
value=0.4,
|
|
range=(0.0, 1.0, 0.01),
|
|
uid=[0],
|
|
enabled= lambda node: (node.nbBrackets.value != 1 and not node.offsetRefBracketIndexEnabled.value),
|
|
),
|
|
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.BoolParam(
|
|
name="keepSourceImageName",
|
|
label="Keep Source Image Name",
|
|
description="Keep the filename of the input image selected as central image for the output image filename.",
|
|
value=False,
|
|
uid=[0],
|
|
),
|
|
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.ChoiceParam(
|
|
name="workingColorSpace",
|
|
label="Working Color Space",
|
|
description="Allows you to choose the color space in which the data are processed.",
|
|
value="sRGB",
|
|
values=["sRGB", "Linear", "ACES2065-1", "ACEScg", "no_conversion"],
|
|
exclusive=True,
|
|
uid=[0],
|
|
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 HDRfusion.\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.",
|
|
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="outputFolder",
|
|
label="Folder",
|
|
description="Path to the folder containing the merged HDR images.",
|
|
value=desc.Node.internalFolder,
|
|
uid=[],
|
|
group="", # do not export on the command line
|
|
),
|
|
desc.File(
|
|
name="outSfMData",
|
|
label="SfMData",
|
|
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")
|
|
|