data_converter.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. from vtkmodules.vtkIOImage import vtkPNGReader
  2. from vtkmodules.vtkCommonCore import vtkFloatArray, vtkUnsignedCharArray
  3. from vtkmodules.vtkCommonDataModel import vtkImageData
  4. from vtkmodules.vtkIOLegacy import vtkDataSetWriter
  5. from vtkmodules.web.camera import normalize, vectProduct, dotProduct
  6. from vtkmodules.web import iteritems
  7. import json, os, math, array
  8. # -----------------------------------------------------------------------------
  9. # Helper function
  10. # -----------------------------------------------------------------------------
  11. def getScalarFromRGB(rgb, scalarRange=[-1.0, 1.0]):
  12. delta = (scalarRange[1] - scalarRange[0]) / 16777215.0 # 2^24 - 1 => 16,777,215
  13. if rgb[0] != 0 or rgb[1] != 0 or rgb[2] != 0:
  14. # Decode encoded value
  15. return scalarRange[0] + delta * float(
  16. rgb[0] * 65536 + rgb[1] * 256 + rgb[2] - 1
  17. )
  18. else:
  19. # No value
  20. return float("NaN")
  21. def convertImageToFloat(srcPngImage, destFile, scalarRange=[0.0, 1.0]):
  22. reader = vtkPNGReader()
  23. reader.SetFileName(srcPngImage)
  24. reader.Update()
  25. rgbArray = reader.GetOutput().GetPointData().GetArray(0)
  26. stackSize = rgbArray.GetNumberOfTuples()
  27. size = reader.GetOutput().GetDimensions()
  28. outputArray = vtkFloatArray()
  29. outputArray.SetNumberOfComponents(1)
  30. outputArray.SetNumberOfTuples(stackSize)
  31. for idx in range(stackSize):
  32. outputArray.SetTuple1(
  33. idx, getScalarFromRGB(rgbArray.GetTuple(idx), scalarRange)
  34. )
  35. # Write float file
  36. with open(destFile, "wb") as f:
  37. f.write(memoryview(outputArray))
  38. return size
  39. def convertRGBArrayToFloatArray(rgbArray, scalarRange=[0.0, 1.0]):
  40. linearSize = rgbArray.GetNumberOfTuples()
  41. outputArray = vtkFloatArray()
  42. outputArray.SetNumberOfComponents(1)
  43. outputArray.SetNumberOfTuples(linearSize)
  44. for idx in range(linearSize):
  45. outputArray.SetTuple1(
  46. idx, getScalarFromRGB(rgbArray.GetTuple(idx), scalarRange)
  47. )
  48. return outputArray
  49. # -----------------------------------------------------------------------------
  50. # Composite.json To order.array
  51. # -----------------------------------------------------------------------------
  52. class CompositeJSON(object):
  53. def __init__(self, numberOfLayers):
  54. self.nbLayers = numberOfLayers
  55. self.encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  56. def load(self, file):
  57. with open(file, "r") as f:
  58. composite = json.load(f)
  59. self.width = composite["dimensions"][0]
  60. self.height = composite["dimensions"][1]
  61. self.pixels = composite["pixel-order"].split("+")
  62. self.imageSize = self.width * self.height
  63. self.stackSize = self.imageSize * self.nbLayers
  64. def getImageSize(self):
  65. return self.imageSize
  66. def getStackSize(self):
  67. return self.stackSize
  68. def writeOrderSprite(self, path):
  69. ds = vtkImageData()
  70. ds.SetDimensions(self.width, self.height, self.nbLayers)
  71. ds.GetPointData().AddArray(self.getSortedOrderArray())
  72. writer = vtkDataSetWriter()
  73. writer.SetInputData(ds)
  74. writer.SetFileName(path)
  75. writer.Update()
  76. def getSortedOrderArray(self):
  77. sortedOrder = vtkUnsignedCharArray()
  78. sortedOrder.SetName("layerIdx")
  79. sortedOrder.SetNumberOfTuples(self.stackSize)
  80. # Reset content
  81. for idx in range(self.stackSize):
  82. sortedOrder.SetValue(idx, 255)
  83. idx = 0
  84. for pixel in self.pixels:
  85. x = idx % self.width
  86. y = idx / self.width
  87. flipYIdx = self.width * (self.height - y - 1) + x
  88. if "@" in pixel:
  89. idx += int(pixel[1:])
  90. else:
  91. # Need to decode the order
  92. layerIdx = 0
  93. for layer in pixel:
  94. sortedOrder.SetValue(
  95. flipYIdx + self.imageSize * layerIdx, self.encoding.index(layer)
  96. )
  97. layerIdx += 1
  98. # Move to next pixel
  99. idx += 1
  100. return sortedOrder
  101. # -----------------------------------------------------------------------------
  102. # Composite Sprite to Sorted Composite Dataset Builder
  103. # -----------------------------------------------------------------------------
  104. class ConvertCompositeSpriteToSortedStack(object):
  105. def __init__(self, directory):
  106. self.basePath = directory
  107. self.layers = []
  108. self.data = []
  109. self.imageReader = vtkPNGReader()
  110. # Load JSON metadata
  111. with open(os.path.join(directory, "config.json"), "r") as f:
  112. self.config = json.load(f)
  113. self.nbLayers = len(self.config["scene"])
  114. while len(self.layers) < self.nbLayers:
  115. self.layers.append({})
  116. with open(os.path.join(directory, "index.json"), "r") as f:
  117. self.info = json.load(f)
  118. with open(os.path.join(directory, "offset.json"), "r") as f:
  119. offsets = json.load(f)
  120. for key, value in iteritems(offsets):
  121. meta = key.split("|")
  122. if len(meta) == 2:
  123. self.layers[int(meta[0])][meta[1]] = value
  124. elif meta[1] in self.layers[int(meta[0])]:
  125. self.layers[int(meta[0])][meta[1]][int(meta[2])] = value
  126. else:
  127. self.layers[int(meta[0])][meta[1]] = [value, value, value]
  128. self.composite = CompositeJSON(len(self.layers))
  129. def listData(self):
  130. return self.data
  131. def convert(self):
  132. for root, dirs, files in os.walk(self.basePath):
  133. if "rgb.png" in files:
  134. print("Process", root)
  135. self.processDirectory(root)
  136. def processDirectory(self, directory):
  137. self.imageReader.SetFileName(os.path.join(directory, "rgb.png"))
  138. self.imageReader.Update()
  139. rgbArray = self.imageReader.GetOutput().GetPointData().GetArray(0)
  140. self.composite.load(os.path.join(directory, "composite.json"))
  141. orderArray = self.composite.getSortedOrderArray()
  142. imageSize = self.composite.getImageSize()
  143. stackSize = self.composite.getStackSize()
  144. # Write order (sorted order way)
  145. with open(os.path.join(directory, "order.uint8"), "wb") as f:
  146. f.write(memoryview(orderArray))
  147. self.data.append(
  148. {"name": "order", "type": "array", "fileName": "/order.uint8"}
  149. )
  150. # Encode Normals (sorted order way)
  151. if "normal" in self.layers[0]:
  152. sortedNormal = vtkUnsignedCharArray()
  153. sortedNormal.SetNumberOfComponents(3) # x,y,z
  154. sortedNormal.SetNumberOfTuples(stackSize)
  155. # Get Camera orientation and rotation information
  156. camDir = [0, 0, 0]
  157. worldUp = [0, 0, 0]
  158. with open(os.path.join(directory, "camera.json"), "r") as f:
  159. camera = json.load(f)
  160. camDir = normalize(
  161. [camera["position"][i] - camera["focalPoint"][i] for i in range(3)]
  162. )
  163. worldUp = normalize(camera["viewUp"])
  164. # [ camRight, camUp, camDir ] will be our new orthonormal basis for normals
  165. camRight = vectProduct(camDir, worldUp)
  166. camUp = vectProduct(camRight, camDir)
  167. # Tmp structure to capture (x,y,z) normal
  168. normalByLayer = vtkFloatArray()
  169. normalByLayer.SetNumberOfComponents(3)
  170. normalByLayer.SetNumberOfTuples(stackSize)
  171. # Capture all layer normals
  172. layerIdx = 0
  173. zPosCount = 0
  174. zNegCount = 0
  175. for layer in self.layers:
  176. normalOffset = layer["normal"]
  177. for idx in range(imageSize):
  178. normalByLayer.SetTuple3(
  179. layerIdx * imageSize + idx,
  180. getScalarFromRGB(
  181. rgbArray.GetTuple(idx + normalOffset[0] * imageSize)
  182. ),
  183. getScalarFromRGB(
  184. rgbArray.GetTuple(idx + normalOffset[1] * imageSize)
  185. ),
  186. getScalarFromRGB(
  187. rgbArray.GetTuple(idx + normalOffset[2] * imageSize)
  188. ),
  189. )
  190. # Re-orient normal to be view based
  191. vect = normalByLayer.GetTuple3(layerIdx * imageSize + idx)
  192. if not math.isnan(vect[0]):
  193. # Express normal in new basis we computed above
  194. rVect = normalize(
  195. [
  196. -dotProduct(vect, camRight),
  197. dotProduct(vect, camUp),
  198. dotProduct(vect, camDir),
  199. ]
  200. )
  201. # Need to reverse vector ?
  202. if rVect[2] < 0:
  203. normalByLayer.SetTuple3(
  204. layerIdx * imageSize + idx,
  205. -rVect[0],
  206. -rVect[1],
  207. -rVect[2],
  208. )
  209. else:
  210. normalByLayer.SetTuple3(
  211. layerIdx * imageSize + idx, rVect[0], rVect[1], rVect[2]
  212. )
  213. layerIdx += 1
  214. # Sort normals and encode them as 3 bytes ( -1 < xy < 1 | 0 < z < 1)
  215. for idx in range(stackSize):
  216. layerIdx = int(orderArray.GetValue(idx))
  217. if layerIdx == 255:
  218. # No normal => same as view direction
  219. sortedNormal.SetTuple3(idx, 128, 128, 255)
  220. else:
  221. offset = layerIdx * imageSize
  222. imageIdx = idx % imageSize
  223. vect = normalByLayer.GetTuple3(imageIdx + offset)
  224. if (
  225. not math.isnan(vect[0])
  226. and not math.isnan(vect[1])
  227. and not math.isnan(vect[2])
  228. ):
  229. sortedNormal.SetTuple3(
  230. idx,
  231. int(127.5 * (vect[0] + 1)),
  232. int(127.5 * (vect[1] + 1)),
  233. int(255 * vect[2]),
  234. )
  235. else:
  236. print(
  237. "WARNING: encountered NaN in normal of layer ",
  238. layerIdx,
  239. ": [",
  240. vect[0],
  241. ",",
  242. vect[1],
  243. ",",
  244. vect[2],
  245. "]",
  246. )
  247. sortedNormal.SetTuple3(idx, 128, 128, 255)
  248. # Write the sorted data
  249. with open(os.path.join(directory, "normal.uint8"), "wb") as f:
  250. f.write(memoryview(sortedNormal))
  251. self.data.append(
  252. {
  253. "name": "normal",
  254. "type": "array",
  255. "fileName": "/normal.uint8",
  256. "categories": ["normal"],
  257. }
  258. )
  259. # Encode Intensity (sorted order way)
  260. if "intensity" in self.layers[0]:
  261. intensityOffsets = []
  262. sortedIntensity = vtkUnsignedCharArray()
  263. sortedIntensity.SetNumberOfTuples(stackSize)
  264. for layer in self.layers:
  265. intensityOffsets.append(layer["intensity"])
  266. for idx in range(stackSize):
  267. layerIdx = int(orderArray.GetValue(idx))
  268. if layerIdx == 255:
  269. sortedIntensity.SetValue(idx, 255)
  270. else:
  271. offset = 3 * intensityOffsets[layerIdx] * imageSize
  272. imageIdx = idx % imageSize
  273. sortedIntensity.SetValue(
  274. idx, rgbArray.GetValue(imageIdx * 3 + offset)
  275. )
  276. with open(os.path.join(directory, "intensity.uint8"), "wb") as f:
  277. f.write(memoryview(sortedIntensity))
  278. self.data.append(
  279. {
  280. "name": "intensity",
  281. "type": "array",
  282. "fileName": "/intensity.uint8",
  283. "categories": ["intensity"],
  284. }
  285. )
  286. # Encode Each layer Scalar
  287. layerIdx = 0
  288. for layer in self.layers:
  289. for scalar in layer:
  290. if scalar not in ["intensity", "normal"]:
  291. offset = imageSize * layer[scalar]
  292. scalarRange = self.config["scene"][layerIdx]["colors"][scalar][
  293. "range"
  294. ]
  295. delta = (
  296. scalarRange[1] - scalarRange[0]
  297. ) / 16777215.0 # 2^24 - 1 => 16,777,215
  298. scalarArray = vtkFloatArray()
  299. scalarArray.SetNumberOfTuples(imageSize)
  300. for idx in range(imageSize):
  301. rgb = rgbArray.GetTuple(idx + offset)
  302. if rgb[0] != 0 or rgb[1] != 0 or rgb[2] != 0:
  303. # Decode encoded value
  304. value = scalarRange[0] + delta * float(
  305. rgb[0] * 65536 + rgb[1] * 256 + rgb[2] - 1
  306. )
  307. scalarArray.SetValue(idx, value)
  308. else:
  309. # No value
  310. scalarArray.SetValue(idx, float("NaN"))
  311. with open(
  312. os.path.join(directory, "%d_%s.float32" % (layerIdx, scalar)),
  313. "wb",
  314. ) as f:
  315. f.write(memoryview(scalarArray))
  316. self.data.append(
  317. {
  318. "name": "%d_%s" % (layerIdx, scalar),
  319. "type": "array",
  320. "fileName": "/%d_%s.float32" % (layerIdx, scalar),
  321. "categories": ["%d_%s" % (layerIdx, scalar)],
  322. }
  323. )
  324. layerIdx += 1
  325. # -----------------------------------------------------------------------------
  326. # Composite Sprite to Sorted Composite Dataset Builder
  327. # -----------------------------------------------------------------------------
  328. class ConvertCompositeDataToSortedStack(object):
  329. def __init__(self, directory):
  330. self.basePath = directory
  331. self.layers = []
  332. self.data = []
  333. self.imageReader = vtkPNGReader()
  334. # Load JSON metadata
  335. with open(os.path.join(directory, "config.json"), "r") as f:
  336. self.config = json.load(f)
  337. self.nbLayers = len(self.config["scene"])
  338. while len(self.layers) < self.nbLayers:
  339. self.layers.append({})
  340. with open(os.path.join(directory, "index.json"), "r") as f:
  341. self.info = json.load(f)
  342. def listData(self):
  343. return self.data
  344. def convert(self):
  345. for root, dirs, files in os.walk(self.basePath):
  346. if "depth_0.float32" in files:
  347. print("Process", root)
  348. self.processDirectory(root)
  349. def processDirectory(self, directory):
  350. # Load depth
  351. depthStack = []
  352. imageSize = self.config["size"]
  353. linearSize = imageSize[0] * imageSize[1]
  354. nbLayers = len(self.layers)
  355. stackSize = nbLayers * linearSize
  356. layerList = range(nbLayers)
  357. for layerIdx in layerList:
  358. with open(
  359. os.path.join(directory, "depth_%d.float32" % layerIdx), "rb"
  360. ) as f:
  361. a = array.array("f")
  362. a.fromfile(f, linearSize)
  363. depthStack.append(a)
  364. orderArray = vtkUnsignedCharArray()
  365. orderArray.SetName("layerIdx")
  366. orderArray.SetNumberOfComponents(1)
  367. orderArray.SetNumberOfTuples(stackSize)
  368. pixelSorter = [(i, i) for i in layerList]
  369. for pixelId in range(linearSize):
  370. # Fill pixelSorter
  371. for layerIdx in layerList:
  372. if depthStack[layerIdx][pixelId] < 1.0:
  373. pixelSorter[layerIdx] = (layerIdx, depthStack[layerIdx][pixelId])
  374. else:
  375. pixelSorter[layerIdx] = (255, 1.0)
  376. # Sort pixel layers
  377. pixelSorter.sort(key=lambda tup: tup[1])
  378. # Fill sortedOrder array
  379. for layerIdx in layerList:
  380. orderArray.SetValue(
  381. layerIdx * linearSize + pixelId, pixelSorter[layerIdx][0]
  382. )
  383. # Write order (sorted order way)
  384. with open(os.path.join(directory, "order.uint8"), "wb") as f:
  385. f.write(memoryview(orderArray))
  386. self.data.append(
  387. {"name": "order", "type": "array", "fileName": "/order.uint8"}
  388. )
  389. # Remove depth files
  390. for layerIdx in layerList:
  391. os.remove(os.path.join(directory, "depth_%d.float32" % layerIdx))
  392. # Encode Normals (sorted order way)
  393. if "normal" in self.config["light"]:
  394. sortedNormal = vtkUnsignedCharArray()
  395. sortedNormal.SetNumberOfComponents(3) # x,y,z
  396. sortedNormal.SetNumberOfTuples(stackSize)
  397. # Get Camera orientation and rotation information
  398. camDir = [0, 0, 0]
  399. worldUp = [0, 0, 0]
  400. with open(os.path.join(directory, "camera.json"), "r") as f:
  401. camera = json.load(f)
  402. camDir = normalize(
  403. [camera["position"][i] - camera["focalPoint"][i] for i in range(3)]
  404. )
  405. worldUp = normalize(camera["viewUp"])
  406. # [ camRight, camUp, camDir ] will be our new orthonormal basis for normals
  407. camRight = vectProduct(camDir, worldUp)
  408. camUp = vectProduct(camRight, camDir)
  409. # Tmp structure to capture (x,y,z) normal
  410. normalByLayer = vtkFloatArray()
  411. normalByLayer.SetNumberOfComponents(3)
  412. normalByLayer.SetNumberOfTuples(stackSize)
  413. # Capture all layer normals
  414. zPosCount = 0
  415. zNegCount = 0
  416. for layerIdx in layerList:
  417. # Load normal(x,y,z) from current layer
  418. normalLayer = []
  419. for comp in [0, 1, 2]:
  420. with open(
  421. os.path.join(
  422. directory, "normal_%d_%d.float32" % (layerIdx, comp)
  423. ),
  424. "rb",
  425. ) as f:
  426. a = array.array("f")
  427. a.fromfile(f, linearSize)
  428. normalLayer.append(a)
  429. # Store normal inside vtkArray
  430. offset = layerIdx * linearSize
  431. for idx in range(linearSize):
  432. normalByLayer.SetTuple3(
  433. idx + offset,
  434. normalLayer[0][idx],
  435. normalLayer[1][idx],
  436. normalLayer[2][idx],
  437. )
  438. # Re-orient normal to be view based
  439. vect = normalByLayer.GetTuple3(layerIdx * linearSize + idx)
  440. if not math.isnan(vect[0]):
  441. # Express normal in new basis we computed above
  442. rVect = normalize(
  443. [
  444. -dotProduct(vect, camRight),
  445. dotProduct(vect, camUp),
  446. dotProduct(vect, camDir),
  447. ]
  448. )
  449. # Need to reverse vector ?
  450. if rVect[2] < 0:
  451. normalByLayer.SetTuple3(
  452. layerIdx * linearSize + idx,
  453. -rVect[0],
  454. -rVect[1],
  455. -rVect[2],
  456. )
  457. else:
  458. normalByLayer.SetTuple3(
  459. layerIdx * linearSize + idx,
  460. rVect[0],
  461. rVect[1],
  462. rVect[2],
  463. )
  464. # Sort normals and encode them as 3 bytes ( -1 < xy < 1 | 0 < z < 1)
  465. for idx in range(stackSize):
  466. layerIdx = int(orderArray.GetValue(idx))
  467. if layerIdx == 255:
  468. # No normal => same as view direction
  469. sortedNormal.SetTuple3(idx, 128, 128, 255)
  470. else:
  471. offset = layerIdx * linearSize
  472. imageIdx = idx % linearSize
  473. vect = normalByLayer.GetTuple3(imageIdx + offset)
  474. if (
  475. not math.isnan(vect[0])
  476. and not math.isnan(vect[1])
  477. and not math.isnan(vect[2])
  478. ):
  479. sortedNormal.SetTuple3(
  480. idx,
  481. int(127.5 * (vect[0] + 1)),
  482. int(127.5 * (vect[1] + 1)),
  483. int(255 * vect[2]),
  484. )
  485. else:
  486. print(
  487. "WARNING: encountered NaN in normal of layer ",
  488. layerIdx,
  489. ": [",
  490. vect[0],
  491. ",",
  492. vect[1],
  493. ",",
  494. vect[2],
  495. "]",
  496. )
  497. sortedNormal.SetTuple3(idx, 128, 128, 255)
  498. # Write the sorted data
  499. with open(os.path.join(directory, "normal.uint8"), "wb") as f:
  500. f.write(memoryview(sortedNormal))
  501. self.data.append(
  502. {
  503. "name": "normal",
  504. "type": "array",
  505. "fileName": "/normal.uint8",
  506. "categories": ["normal"],
  507. }
  508. )
  509. # Remove depth files
  510. for layerIdx in layerList:
  511. os.remove(
  512. os.path.join(directory, "normal_%d_%d.float32" % (layerIdx, 0))
  513. )
  514. os.remove(
  515. os.path.join(directory, "normal_%d_%d.float32" % (layerIdx, 1))
  516. )
  517. os.remove(
  518. os.path.join(directory, "normal_%d_%d.float32" % (layerIdx, 2))
  519. )
  520. # Encode Intensity (sorted order way)
  521. if "intensity" in self.config["light"]:
  522. sortedIntensity = vtkUnsignedCharArray()
  523. sortedIntensity.SetNumberOfTuples(stackSize)
  524. intensityLayers = []
  525. for layerIdx in layerList:
  526. with open(
  527. os.path.join(directory, "intensity_%d.uint8" % layerIdx), "rb"
  528. ) as f:
  529. a = array.array("B")
  530. a.fromfile(f, linearSize)
  531. intensityLayers.append(a)
  532. for idx in range(stackSize):
  533. layerIdx = int(orderArray.GetValue(idx))
  534. if layerIdx == 255:
  535. sortedIntensity.SetValue(idx, 255)
  536. else:
  537. imageIdx = idx % linearSize
  538. sortedIntensity.SetValue(idx, intensityLayers[layerIdx][imageIdx])
  539. with open(os.path.join(directory, "intensity.uint8"), "wb") as f:
  540. f.write(memoryview(sortedIntensity))
  541. self.data.append(
  542. {
  543. "name": "intensity",
  544. "type": "array",
  545. "fileName": "/intensity.uint8",
  546. "categories": ["intensity"],
  547. }
  548. )
  549. # Remove depth files
  550. for layerIdx in layerList:
  551. os.remove(os.path.join(directory, "intensity_%d.uint8" % layerIdx))