from vtkmodules.vtkIOImage import vtkPNGReader
from vtkmodules.vtkCommonCore import vtkFloatArray, vtkUnsignedCharArray
from vtkmodules.vtkCommonDataModel import vtkImageData
from vtkmodules.vtkIOLegacy import vtkDataSetWriter
from vtkmodules.web.camera import normalize, vectProduct, dotProduct
from vtkmodules.web import iteritems
import json, os, math, array
# -----------------------------------------------------------------------------
# Helper function
# -----------------------------------------------------------------------------
[docs]def getScalarFromRGB(rgb, scalarRange=[-1.0, 1.0]):
delta = (scalarRange[1] - scalarRange[0]) / 16777215.0 # 2^24 - 1 => 16,777,215
if rgb[0] != 0 or rgb[1] != 0 or rgb[2] != 0:
# Decode encoded value
return scalarRange[0] + delta * float(
rgb[0] * 65536 + rgb[1] * 256 + rgb[2] - 1
)
else:
# No value
return float("NaN")
[docs]def convertImageToFloat(srcPngImage, destFile, scalarRange=[0.0, 1.0]):
reader = vtkPNGReader()
reader.SetFileName(srcPngImage)
reader.Update()
rgbArray = reader.GetOutput().GetPointData().GetArray(0)
stackSize = rgbArray.GetNumberOfTuples()
size = reader.GetOutput().GetDimensions()
outputArray = vtkFloatArray()
outputArray.SetNumberOfComponents(1)
outputArray.SetNumberOfTuples(stackSize)
for idx in range(stackSize):
outputArray.SetTuple1(
idx, getScalarFromRGB(rgbArray.GetTuple(idx), scalarRange)
)
# Write float file
with open(destFile, "wb") as f:
f.write(memoryview(outputArray))
return size
[docs]def convertRGBArrayToFloatArray(rgbArray, scalarRange=[0.0, 1.0]):
linearSize = rgbArray.GetNumberOfTuples()
outputArray = vtkFloatArray()
outputArray.SetNumberOfComponents(1)
outputArray.SetNumberOfTuples(linearSize)
for idx in range(linearSize):
outputArray.SetTuple1(
idx, getScalarFromRGB(rgbArray.GetTuple(idx), scalarRange)
)
return outputArray
# -----------------------------------------------------------------------------
# Composite.json To order.array
# -----------------------------------------------------------------------------
[docs]class CompositeJSON(object):
def __init__(self, numberOfLayers):
self.nbLayers = numberOfLayers
self.encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
[docs] def load(self, file):
with open(file, "r") as f:
composite = json.load(f)
self.width = composite["dimensions"][0]
self.height = composite["dimensions"][1]
self.pixels = composite["pixel-order"].split("+")
self.imageSize = self.width * self.height
self.stackSize = self.imageSize * self.nbLayers
[docs] def getImageSize(self):
return self.imageSize
[docs] def getStackSize(self):
return self.stackSize
[docs] def writeOrderSprite(self, path):
ds = vtkImageData()
ds.SetDimensions(self.width, self.height, self.nbLayers)
ds.GetPointData().AddArray(self.getSortedOrderArray())
writer = vtkDataSetWriter()
writer.SetInputData(ds)
writer.SetFileName(path)
writer.Update()
[docs] def getSortedOrderArray(self):
sortedOrder = vtkUnsignedCharArray()
sortedOrder.SetName("layerIdx")
sortedOrder.SetNumberOfTuples(self.stackSize)
# Reset content
for idx in range(self.stackSize):
sortedOrder.SetValue(idx, 255)
idx = 0
for pixel in self.pixels:
x = idx % self.width
y = idx / self.width
flipYIdx = self.width * (self.height - y - 1) + x
if "@" in pixel:
idx += int(pixel[1:])
else:
# Need to decode the order
layerIdx = 0
for layer in pixel:
sortedOrder.SetValue(
flipYIdx + self.imageSize * layerIdx, self.encoding.index(layer)
)
layerIdx += 1
# Move to next pixel
idx += 1
return sortedOrder
# -----------------------------------------------------------------------------
# Composite Sprite to Sorted Composite Dataset Builder
# -----------------------------------------------------------------------------
[docs]class ConvertCompositeSpriteToSortedStack(object):
def __init__(self, directory):
self.basePath = directory
self.layers = []
self.data = []
self.imageReader = vtkPNGReader()
# Load JSON metadata
with open(os.path.join(directory, "config.json"), "r") as f:
self.config = json.load(f)
self.nbLayers = len(self.config["scene"])
while len(self.layers) < self.nbLayers:
self.layers.append({})
with open(os.path.join(directory, "index.json"), "r") as f:
self.info = json.load(f)
with open(os.path.join(directory, "offset.json"), "r") as f:
offsets = json.load(f)
for key, value in iteritems(offsets):
meta = key.split("|")
if len(meta) == 2:
self.layers[int(meta[0])][meta[1]] = value
elif meta[1] in self.layers[int(meta[0])]:
self.layers[int(meta[0])][meta[1]][int(meta[2])] = value
else:
self.layers[int(meta[0])][meta[1]] = [value, value, value]
self.composite = CompositeJSON(len(self.layers))
[docs] def listData(self):
return self.data
[docs] def convert(self):
for root, dirs, files in os.walk(self.basePath):
if "rgb.png" in files:
print("Process", root)
self.processDirectory(root)
[docs] def processDirectory(self, directory):
self.imageReader.SetFileName(os.path.join(directory, "rgb.png"))
self.imageReader.Update()
rgbArray = self.imageReader.GetOutput().GetPointData().GetArray(0)
self.composite.load(os.path.join(directory, "composite.json"))
orderArray = self.composite.getSortedOrderArray()
imageSize = self.composite.getImageSize()
stackSize = self.composite.getStackSize()
# Write order (sorted order way)
with open(os.path.join(directory, "order.uint8"), "wb") as f:
f.write(memoryview(orderArray))
self.data.append(
{"name": "order", "type": "array", "fileName": "/order.uint8"}
)
# Encode Normals (sorted order way)
if "normal" in self.layers[0]:
sortedNormal = vtkUnsignedCharArray()
sortedNormal.SetNumberOfComponents(3) # x,y,z
sortedNormal.SetNumberOfTuples(stackSize)
# Get Camera orientation and rotation information
camDir = [0, 0, 0]
worldUp = [0, 0, 0]
with open(os.path.join(directory, "camera.json"), "r") as f:
camera = json.load(f)
camDir = normalize(
[camera["position"][i] - camera["focalPoint"][i] for i in range(3)]
)
worldUp = normalize(camera["viewUp"])
# [ camRight, camUp, camDir ] will be our new orthonormal basis for normals
camRight = vectProduct(camDir, worldUp)
camUp = vectProduct(camRight, camDir)
# Tmp structure to capture (x,y,z) normal
normalByLayer = vtkFloatArray()
normalByLayer.SetNumberOfComponents(3)
normalByLayer.SetNumberOfTuples(stackSize)
# Capture all layer normals
layerIdx = 0
zPosCount = 0
zNegCount = 0
for layer in self.layers:
normalOffset = layer["normal"]
for idx in range(imageSize):
normalByLayer.SetTuple3(
layerIdx * imageSize + idx,
getScalarFromRGB(
rgbArray.GetTuple(idx + normalOffset[0] * imageSize)
),
getScalarFromRGB(
rgbArray.GetTuple(idx + normalOffset[1] * imageSize)
),
getScalarFromRGB(
rgbArray.GetTuple(idx + normalOffset[2] * imageSize)
),
)
# Re-orient normal to be view based
vect = normalByLayer.GetTuple3(layerIdx * imageSize + idx)
if not math.isnan(vect[0]):
# Express normal in new basis we computed above
rVect = normalize(
[
-dotProduct(vect, camRight),
dotProduct(vect, camUp),
dotProduct(vect, camDir),
]
)
# Need to reverse vector ?
if rVect[2] < 0:
normalByLayer.SetTuple3(
layerIdx * imageSize + idx,
-rVect[0],
-rVect[1],
-rVect[2],
)
else:
normalByLayer.SetTuple3(
layerIdx * imageSize + idx, rVect[0], rVect[1], rVect[2]
)
layerIdx += 1
# Sort normals and encode them as 3 bytes ( -1 < xy < 1 | 0 < z < 1)
for idx in range(stackSize):
layerIdx = int(orderArray.GetValue(idx))
if layerIdx == 255:
# No normal => same as view direction
sortedNormal.SetTuple3(idx, 128, 128, 255)
else:
offset = layerIdx * imageSize
imageIdx = idx % imageSize
vect = normalByLayer.GetTuple3(imageIdx + offset)
if (
not math.isnan(vect[0])
and not math.isnan(vect[1])
and not math.isnan(vect[2])
):
sortedNormal.SetTuple3(
idx,
int(127.5 * (vect[0] + 1)),
int(127.5 * (vect[1] + 1)),
int(255 * vect[2]),
)
else:
print(
"WARNING: encountered NaN in normal of layer ",
layerIdx,
": [",
vect[0],
",",
vect[1],
",",
vect[2],
"]",
)
sortedNormal.SetTuple3(idx, 128, 128, 255)
# Write the sorted data
with open(os.path.join(directory, "normal.uint8"), "wb") as f:
f.write(memoryview(sortedNormal))
self.data.append(
{
"name": "normal",
"type": "array",
"fileName": "/normal.uint8",
"categories": ["normal"],
}
)
# Encode Intensity (sorted order way)
if "intensity" in self.layers[0]:
intensityOffsets = []
sortedIntensity = vtkUnsignedCharArray()
sortedIntensity.SetNumberOfTuples(stackSize)
for layer in self.layers:
intensityOffsets.append(layer["intensity"])
for idx in range(stackSize):
layerIdx = int(orderArray.GetValue(idx))
if layerIdx == 255:
sortedIntensity.SetValue(idx, 255)
else:
offset = 3 * intensityOffsets[layerIdx] * imageSize
imageIdx = idx % imageSize
sortedIntensity.SetValue(
idx, rgbArray.GetValue(imageIdx * 3 + offset)
)
with open(os.path.join(directory, "intensity.uint8"), "wb") as f:
f.write(memoryview(sortedIntensity))
self.data.append(
{
"name": "intensity",
"type": "array",
"fileName": "/intensity.uint8",
"categories": ["intensity"],
}
)
# Encode Each layer Scalar
layerIdx = 0
for layer in self.layers:
for scalar in layer:
if scalar not in ["intensity", "normal"]:
offset = imageSize * layer[scalar]
scalarRange = self.config["scene"][layerIdx]["colors"][scalar][
"range"
]
delta = (
scalarRange[1] - scalarRange[0]
) / 16777215.0 # 2^24 - 1 => 16,777,215
scalarArray = vtkFloatArray()
scalarArray.SetNumberOfTuples(imageSize)
for idx in range(imageSize):
rgb = rgbArray.GetTuple(idx + offset)
if rgb[0] != 0 or rgb[1] != 0 or rgb[2] != 0:
# Decode encoded value
value = scalarRange[0] + delta * float(
rgb[0] * 65536 + rgb[1] * 256 + rgb[2] - 1
)
scalarArray.SetValue(idx, value)
else:
# No value
scalarArray.SetValue(idx, float("NaN"))
with open(
os.path.join(directory, "%d_%s.float32" % (layerIdx, scalar)),
"wb",
) as f:
f.write(memoryview(scalarArray))
self.data.append(
{
"name": "%d_%s" % (layerIdx, scalar),
"type": "array",
"fileName": "/%d_%s.float32" % (layerIdx, scalar),
"categories": ["%d_%s" % (layerIdx, scalar)],
}
)
layerIdx += 1
# -----------------------------------------------------------------------------
# Composite Sprite to Sorted Composite Dataset Builder
# -----------------------------------------------------------------------------
[docs]class ConvertCompositeDataToSortedStack(object):
def __init__(self, directory):
self.basePath = directory
self.layers = []
self.data = []
self.imageReader = vtkPNGReader()
# Load JSON metadata
with open(os.path.join(directory, "config.json"), "r") as f:
self.config = json.load(f)
self.nbLayers = len(self.config["scene"])
while len(self.layers) < self.nbLayers:
self.layers.append({})
with open(os.path.join(directory, "index.json"), "r") as f:
self.info = json.load(f)
[docs] def listData(self):
return self.data
[docs] def convert(self):
for root, dirs, files in os.walk(self.basePath):
if "depth_0.float32" in files:
print("Process", root)
self.processDirectory(root)
[docs] def processDirectory(self, directory):
# Load depth
depthStack = []
imageSize = self.config["size"]
linearSize = imageSize[0] * imageSize[1]
nbLayers = len(self.layers)
stackSize = nbLayers * linearSize
layerList = range(nbLayers)
for layerIdx in layerList:
with open(
os.path.join(directory, "depth_%d.float32" % layerIdx), "rb"
) as f:
a = array.array("f")
a.fromfile(f, linearSize)
depthStack.append(a)
orderArray = vtkUnsignedCharArray()
orderArray.SetName("layerIdx")
orderArray.SetNumberOfComponents(1)
orderArray.SetNumberOfTuples(stackSize)
pixelSorter = [(i, i) for i in layerList]
for pixelId in range(linearSize):
# Fill pixelSorter
for layerIdx in layerList:
if depthStack[layerIdx][pixelId] < 1.0:
pixelSorter[layerIdx] = (layerIdx, depthStack[layerIdx][pixelId])
else:
pixelSorter[layerIdx] = (255, 1.0)
# Sort pixel layers
pixelSorter.sort(key=lambda tup: tup[1])
# Fill sortedOrder array
for layerIdx in layerList:
orderArray.SetValue(
layerIdx * linearSize + pixelId, pixelSorter[layerIdx][0]
)
# Write order (sorted order way)
with open(os.path.join(directory, "order.uint8"), "wb") as f:
f.write(memoryview(orderArray))
self.data.append(
{"name": "order", "type": "array", "fileName": "/order.uint8"}
)
# Remove depth files
for layerIdx in layerList:
os.remove(os.path.join(directory, "depth_%d.float32" % layerIdx))
# Encode Normals (sorted order way)
if "normal" in self.config["light"]:
sortedNormal = vtkUnsignedCharArray()
sortedNormal.SetNumberOfComponents(3) # x,y,z
sortedNormal.SetNumberOfTuples(stackSize)
# Get Camera orientation and rotation information
camDir = [0, 0, 0]
worldUp = [0, 0, 0]
with open(os.path.join(directory, "camera.json"), "r") as f:
camera = json.load(f)
camDir = normalize(
[camera["position"][i] - camera["focalPoint"][i] for i in range(3)]
)
worldUp = normalize(camera["viewUp"])
# [ camRight, camUp, camDir ] will be our new orthonormal basis for normals
camRight = vectProduct(camDir, worldUp)
camUp = vectProduct(camRight, camDir)
# Tmp structure to capture (x,y,z) normal
normalByLayer = vtkFloatArray()
normalByLayer.SetNumberOfComponents(3)
normalByLayer.SetNumberOfTuples(stackSize)
# Capture all layer normals
zPosCount = 0
zNegCount = 0
for layerIdx in layerList:
# Load normal(x,y,z) from current layer
normalLayer = []
for comp in [0, 1, 2]:
with open(
os.path.join(
directory, "normal_%d_%d.float32" % (layerIdx, comp)
),
"rb",
) as f:
a = array.array("f")
a.fromfile(f, linearSize)
normalLayer.append(a)
# Store normal inside vtkArray
offset = layerIdx * linearSize
for idx in range(linearSize):
normalByLayer.SetTuple3(
idx + offset,
normalLayer[0][idx],
normalLayer[1][idx],
normalLayer[2][idx],
)
# Re-orient normal to be view based
vect = normalByLayer.GetTuple3(layerIdx * linearSize + idx)
if not math.isnan(vect[0]):
# Express normal in new basis we computed above
rVect = normalize(
[
-dotProduct(vect, camRight),
dotProduct(vect, camUp),
dotProduct(vect, camDir),
]
)
# Need to reverse vector ?
if rVect[2] < 0:
normalByLayer.SetTuple3(
layerIdx * linearSize + idx,
-rVect[0],
-rVect[1],
-rVect[2],
)
else:
normalByLayer.SetTuple3(
layerIdx * linearSize + idx,
rVect[0],
rVect[1],
rVect[2],
)
# Sort normals and encode them as 3 bytes ( -1 < xy < 1 | 0 < z < 1)
for idx in range(stackSize):
layerIdx = int(orderArray.GetValue(idx))
if layerIdx == 255:
# No normal => same as view direction
sortedNormal.SetTuple3(idx, 128, 128, 255)
else:
offset = layerIdx * linearSize
imageIdx = idx % linearSize
vect = normalByLayer.GetTuple3(imageIdx + offset)
if (
not math.isnan(vect[0])
and not math.isnan(vect[1])
and not math.isnan(vect[2])
):
sortedNormal.SetTuple3(
idx,
int(127.5 * (vect[0] + 1)),
int(127.5 * (vect[1] + 1)),
int(255 * vect[2]),
)
else:
print(
"WARNING: encountered NaN in normal of layer ",
layerIdx,
": [",
vect[0],
",",
vect[1],
",",
vect[2],
"]",
)
sortedNormal.SetTuple3(idx, 128, 128, 255)
# Write the sorted data
with open(os.path.join(directory, "normal.uint8"), "wb") as f:
f.write(memoryview(sortedNormal))
self.data.append(
{
"name": "normal",
"type": "array",
"fileName": "/normal.uint8",
"categories": ["normal"],
}
)
# Remove depth files
for layerIdx in layerList:
os.remove(
os.path.join(directory, "normal_%d_%d.float32" % (layerIdx, 0))
)
os.remove(
os.path.join(directory, "normal_%d_%d.float32" % (layerIdx, 1))
)
os.remove(
os.path.join(directory, "normal_%d_%d.float32" % (layerIdx, 2))
)
# Encode Intensity (sorted order way)
if "intensity" in self.config["light"]:
sortedIntensity = vtkUnsignedCharArray()
sortedIntensity.SetNumberOfTuples(stackSize)
intensityLayers = []
for layerIdx in layerList:
with open(
os.path.join(directory, "intensity_%d.uint8" % layerIdx), "rb"
) as f:
a = array.array("B")
a.fromfile(f, linearSize)
intensityLayers.append(a)
for idx in range(stackSize):
layerIdx = int(orderArray.GetValue(idx))
if layerIdx == 255:
sortedIntensity.SetValue(idx, 255)
else:
imageIdx = idx % linearSize
sortedIntensity.SetValue(idx, intensityLayers[layerIdx][imageIdx])
with open(os.path.join(directory, "intensity.uint8"), "wb") as f:
f.write(memoryview(sortedIntensity))
self.data.append(
{
"name": "intensity",
"type": "array",
"fileName": "/intensity.uint8",
"categories": ["intensity"],
}
)
# Remove depth files
for layerIdx in layerList:
os.remove(os.path.join(directory, "intensity_%d.uint8" % layerIdx))