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 # ----------------------------------------------------------------------------- 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") 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 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 # ----------------------------------------------------------------------------- class CompositeJSON(object): def __init__(self, numberOfLayers): self.nbLayers = numberOfLayers self.encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 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 def getImageSize(self): return self.imageSize def getStackSize(self): return self.stackSize 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() 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 # ----------------------------------------------------------------------------- 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)) def listData(self): return self.data def convert(self): for root, dirs, files in os.walk(self.basePath): if "rgb.png" in files: print("Process", root) self.processDirectory(root) 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 # ----------------------------------------------------------------------------- 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) def listData(self): return self.data def convert(self): for root, dirs, files in os.walk(self.basePath): if "depth_0.float32" in files: print("Process", root) self.processDirectory(root) 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))