sliceMapCreator.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. import io
  2. from skimage import img_as_ubyte
  3. from skimage.transform import resize
  4. from PIL import Image
  5. from django.conf import settings
  6. from multiprocessing import Process, cpu_count, Queue
  7. import numpy as np
  8. import math
  9. import copy
  10. import logging
  11. import pdb
  12. logger = logging.getLogger(__name__)
  13. class Sprite(object):
  14. imagesAsArrays = None
  15. imagesToProcess = None
  16. imagesPerRow = 0
  17. currentRow = 0
  18. imagesInCurRow = 0
  19. totalRows = 0
  20. numberOfImages = 0
  21. numberOfImagesInSprite = 0
  22. destFilename = None
  23. buffers = []
  24. contenttype = None,
  25. numberOfSprites = 0
  26. slicesPerSprite = 0
  27. thresholdIndex_otsu = 255
  28. thresholdIndex_yen = 255
  29. thresholdIndex_isodata = 255
  30. def __initializeSprite(imageFrom, imageTo, sliceShape, dtype):
  31. imagesPerRow, totalRows, imagesToProcess, numberOfSprites, slicesPerSprite, zDownScalingFactor = __calculateRowsAndColsOfSpriteMap(imageFrom, imageTo)
  32. logger.debug('sliceMapCreator opened source image, writing all data into one array now')
  33. outputImageArrays = []
  34. for counter in range(0, numberOfSprites):
  35. outputImageArray = np.empty((sliceShape[0] * totalRows, sliceShape[1] * imagesPerRow), dtype=dtype)
  36. outputImageArray.setflags(write=True)
  37. outputImageArrays.append(outputImageArray)
  38. sprite = Sprite()
  39. sprite.imagesToProcess = imagesToProcess
  40. sprite.imagesPerRow = imagesPerRow
  41. sprite.totalRows = totalRows
  42. sprite.numberOfSprites = numberOfSprites
  43. sprite.slicesPerSprite = slicesPerSprite
  44. sprite.imagesAsArrays = outputImageArrays
  45. return sprite
  46. def __calculateRowsAndColsOfSpriteMap(imageFrom, imageTo):
  47. imagesToProcess = imageTo - imageFrom + 1
  48. slicesPerSprite = imagesToProcess
  49. # upperbound are 25 ^ 2 slices per map
  50. maxSlicesPerMap = settings.MAX_NUMBER_SLICES_PER_MAP
  51. zDownScalingActivated = settings.Z_DOWNSCALING_ACTIVATED
  52. zDownScalingFactor = 1
  53. if zDownScalingActivated is False:
  54. # calculate the number of sprites for one frame
  55. numberOfSprites = int(math.ceil(imagesToProcess / maxSlicesPerMap))
  56. else:
  57. numberOfSprites = 1
  58. zDownScalingFactor = int(math.ceil(imagesToProcess / maxSlicesPerMap))
  59. imagesToProcess = int(math.floor(imagesToProcess / float(zDownScalingFactor)))
  60. # stretch the whole number of images on to the sprites
  61. slicesPerSprite = imagesToProcess / numberOfSprites
  62. imagesPerRow = int(math.ceil(math.sqrt(slicesPerSprite)))
  63. # now i has a squared number of images
  64. slicesPerSprite = math.pow(imagesPerRow, 2)
  65. maxImageCount = slicesPerSprite * numberOfSprites
  66. imagesLeft = maxImageCount - imagesToProcess
  67. logger.debug('sliceMapCreator: imagesToProcess: %d, emptyImages in the last rows: %d ' % (imagesToProcess, imagesLeft))
  68. totalRows = int(imagesPerRow)
  69. logger.debug('sliceMapCreator imagesPerRow: %d, totalRows: %d' % (imagesPerRow, totalRows))
  70. return (imagesPerRow, totalRows, imagesToProcess, numberOfSprites, slicesPerSprite, zDownScalingFactor)
  71. def __copyImageToSprite(sliceImage, destArray, sprite):
  72. numY, numX = sliceImage.shape
  73. pixelRow = sprite.imagesInCurRow * numX
  74. pixelCol = sprite.currentRow * numY
  75. destArray[pixelCol:pixelCol+numY, pixelRow:pixelRow+numX] = sliceImage
  76. return destArray
  77. def processFrames(frames, frameFrom, frameTo, imageFrom, imageTo, imgFormat, destSize):
  78. if len(frames.shape) != 4:
  79. raise ValueError('due to memory restrictions and new implementation, currently only frames of 4 dimensions supported')
  80. sprites = []
  81. imagesPerRow, totalRows, imagesToProcess, numberOfSprites, slicesPerSprite, zDownScalingFactor = __calculateRowsAndColsOfSpriteMap(imageFrom, imageTo)
  82. upperBoundPixels = 16384
  83. if frames.shape[3] * imagesPerRow > upperBoundPixels:
  84. pixelsPerImage = int(math.ceil(upperBoundPixels / imagesPerRow))
  85. newImageShape = (pixelsPerImage, pixelsPerImage)
  86. newFrames = np.empty((frames.shape[0], frames.shape[1], newImageShape[0], newImageShape[1]), dtype=frames.dtype)
  87. frameCounter = 0
  88. logger.debug('sliceMapCreator resizing slices to (%d, %d)' % newImageShape)
  89. for frameCounter in range(0, frames.shape[0]):
  90. imageCounter = 0
  91. cpus = cpu_count()
  92. imagesPerCpu = int(math.ceil(frames.shape[1] / float(cpus)))
  93. processes = []
  94. queues = [Queue() for i in range(0, cpus)]
  95. for cpu in range(0, cpus):
  96. fromImage = imagesPerCpu * cpu
  97. toImage = (imagesPerCpu * (cpu + 1)) - 1
  98. if fromImage >= frames.shape[1]:
  99. break
  100. if toImage >= frames.shape[1]:
  101. toImage = frames.shape[1] - 1
  102. process = Process(target=framePreprocessor, args=(frames[frameCounter], queues[cpu], fromImage, toImage, newImageShape,))
  103. processes.append(process)
  104. logger.debug('sliceMapCreator before start loop')
  105. for process in processes:
  106. process.start()
  107. resultFrames = []
  108. logger.debug('sliceMapCreator empty queues')
  109. for queue in queues:
  110. resultFrames.append(queue.get())
  111. logger.debug('sliceMapCreator merging results')
  112. for resultFrame in resultFrames:
  113. imageCounter = resultFrame.fromImage
  114. frame = resultFrame.frame
  115. for image in frame:
  116. newFrames[frameCounter][imageCounter] = image
  117. imageCounter += 1
  118. for process in processes:
  119. process.join()
  120. '''
  121. old sequential implementation
  122. for imageCounter in range(0, frames.shape[1]):
  123. image = resize(frames[frameCounter][imageCounter], (pixelsPerImage, pixelsPerImage))
  124. newFrames[frameCounter][imageCounter] = img_as_ubyte(image)
  125. frames[frameCounter][imageCounter][:][:] = np.empty((frames.shape[2], frames.shape[3]), dtype=frames.dtype)[:][:]
  126. '''
  127. del frames
  128. frames = newFrames
  129. else:
  130. logger.debug('sliceMapCreator no need to resize images to lower shape')
  131. if frameFrom is not None and len(frames.shape) > 3:
  132. frameNumber = frameFrom
  133. for frame in frames:
  134. sprite = processFrame(frame, frameNumber, imageFrom, imageTo, zDownScalingFactor)
  135. sprites.append(sprite)
  136. del frame
  137. frameNumber += 1
  138. else:
  139. sprite = processFrame(frames, None, imageFrom, imageTo)
  140. sprites.append(sprite)
  141. del frames
  142. return __resizeSprites(sprites, destSize, imgFormat)
  143. def processFrame(frame, frameNumber, imageFrom, imageTo, zDownScalingFactor):
  144. logger.debug('sliceMapCreator frameNumber %d' % frameNumber)
  145. if len(frame) is 0:
  146. raise ValueError('could not find any frame')
  147. firstSlice = frame[0]
  148. sprite = __initializeSprite(imageFrom, imageTo, firstSlice.shape, frame.dtype)
  149. imageCounter = 0
  150. frameLength = len(frame)
  151. while imageCounter < frameLength:
  152. image = frame[imageCounter]
  153. arrayIndex = int(math.floor(float(sprite.numberOfImages) / sprite.slicesPerSprite))
  154. __copyImageToSprite(image, sprite.imagesAsArrays[arrayIndex], sprite)
  155. sprite.imagesInCurRow += 1
  156. sprite.numberOfImages += 1
  157. sprite.numberOfImagesInSprite += 1
  158. imageCounter += (1 * zDownScalingFactor)
  159. if sprite.imagesInCurRow == sprite.imagesPerRow:
  160. if sprite.numberOfImages >= sprite.imagesToProcess:
  161. logger.debug('sliceMapCreator copy end reached, breaking now')
  162. break
  163. sprite.currentRow += 1
  164. sprite.imagesInCurRow = 0
  165. if sprite.numberOfImagesInSprite == sprite.slicesPerSprite:
  166. logger.debug('sliceMapCreator resetting currentrow to 0')
  167. sprite.currentRow = 0
  168. sprite.numberOfImagesInSprite = 0
  169. sprite = calculateThresholdIndexes(sprite)
  170. return sprite
  171. def calculateThresholdIndexes(sprite):
  172. from skimage.filters import threshold_otsu, threshold_yen, threshold_isodata
  173. # pdb.set_trace()
  174. image = sprite.imagesAsArrays[0]
  175. sliceMapWidth = len(image[0])
  176. sliceMapHeight = len(image)
  177. numberSlicesInSliceMap = sprite.numberOfImages if sprite.numberOfImages >= 1 else 1
  178. RowInSliceMap = sprite.imagesPerRow
  179. ColInSliceMap = sprite.imagesPerRow
  180. sliceWidth = sliceMapWidth / ColInSliceMap;
  181. sliceHeight = sliceMapHeight / RowInSliceMap;
  182. numberOfCentralSlice = (numberSlicesInSliceMap - 1) / 2
  183. x = numberOfCentralSlice / RowInSliceMap
  184. xp = x * sliceWidth
  185. y = numberOfCentralSlice - (x * RowInSliceMap)
  186. yp = y * sliceHeight
  187. centralSliceX0 = xp
  188. centralSliceX1 = xp + sliceWidth
  189. centralSliceY0 = yp
  190. centralSliceY1 = yp + sliceHeight
  191. centerSliceImage = image[centralSliceX0 : centralSliceX1, centralSliceY0 : centralSliceY1]
  192. sprite.thresholdIndex_otsu = threshold_otsu(centerSliceImage)
  193. sprite.thresholdIndex_yen = threshold_yen(centerSliceImage)
  194. sprite.thresholdIndex_isodata = threshold_isodata(centerSliceImage)
  195. print("thresholdIndex_otsu = " + str(sprite.thresholdIndex_otsu))
  196. print("thresholdIndex_yen = " + str(sprite.thresholdIndex_yen))
  197. print("thresholdIndex_isodata = " + str(sprite.thresholdIndex_isodata))
  198. return sprite
  199. def generateInfo(sprites, frameFrom, frameTo, imageFrom, imageTo):
  200. logger.debug('sliceMapCreator generating and saving .js info file')
  201. firstSprite = list(sprites.values())[0][0]
  202. infoObject = {}
  203. infoObject['slicesOverX'] = firstSprite.imagesPerRow
  204. infoObject['slicesOverY'] = firstSprite.totalRows
  205. infoObject['numberOfSlices'] = firstSprite.numberOfImages
  206. infoObject['imgMin'] = imageFrom
  207. infoObject['imgMax'] = imageTo
  208. infoObject['numberOfSprites'] = firstSprite.numberOfSprites
  209. infoObject['slicesPerSprite'] = firstSprite.slicesPerSprite
  210. multipleFrames = len(sprites) > 1
  211. infoObject['multipleFrames'] = multipleFrames
  212. if multipleFrames:
  213. infoObject['frameFrom'] = frameFrom
  214. infoObject['frameTo'] = frameTo
  215. infoObject['thresholdIndex_otsu'] = firstSprite.thresholdIndex_otsu
  216. infoObject['thresholdIndex_yen'] = firstSprite.thresholdIndex_yen
  217. infoObject['thresholdIndex_isodata'] = firstSprite.thresholdIndex_isodata
  218. return infoObject
  219. def __resizeSprites(sprites, destSize, imgFormat):
  220. returnSprites = {}
  221. exponentMin = 9
  222. exponent = exponentMin
  223. firstSprite = sprites[0]
  224. # hardcoded value, 2^12
  225. # 2^13 webp files force error during saving
  226. widthUpperBound = 4096
  227. generatorSprite = copy.deepcopy(firstSprite)
  228. del generatorSprite.imagesAsArrays
  229. logger.debug('sliceMapCreator resizeSprites, shape of input sprites is (%d, %d)' % sprites[0].imagesAsArrays[0].shape)
  230. if destSize is not None:
  231. widthUpperBound = destSize
  232. exponentFound = False
  233. # calculate the power of 2 size of the sprite map
  234. while not exponentFound:
  235. pot = math.pow(2, exponent)
  236. if pot > widthUpperBound:
  237. exponentFound = True
  238. exponent = exponent
  239. else:
  240. exponent += 1
  241. for exponentToStore in range(exponent - 1, exponentMin - 1, -1):
  242. destSize = math.pow(2, exponentToStore)
  243. logger.debug('sliceMapCreator resize sprites to %d' % destSize)
  244. for sprite in sprites:
  245. buffers = []
  246. for arrayCounter in range(0, sprite.numberOfSprites):
  247. imageArray = sprite.imagesAsArrays[arrayCounter]
  248. # for lower memory consumption, we resize the image continuesly --> written right?
  249. imageArray = resize(imageArray, (destSize, destSize))
  250. imageArray = img_as_ubyte(imageArray)
  251. buff = io.BytesIO()
  252. quality = 95
  253. imgFormat = imgFormat.lower()
  254. pilImage = Image.fromarray(imageArray)
  255. contenttype = 'image/'
  256. if imgFormat == 'webp':
  257. pilImage = pilImage.convert('RGBA')
  258. try:
  259. pilImage.save(buff, format='WEBP', quality=quality)
  260. except OSError:
  261. # due to a bug in the library of webp, the webp encoder fails for high file sizes
  262. # --> if this is a big 'destSize', we have to try it again with lower quality levels
  263. logger.debug('sliceMapCreator failed to store webp with size of %dpx and quality value of %d' % (destSize, quality))
  264. logger.debug('sliceMapCreator will not use it, skipping further processing of %dpx sprite' % destSize)
  265. continue
  266. contenttype += 'webp'
  267. elif imgFormat == 'png':
  268. pilImage.save(buff, format='PNG')
  269. contenttype += 'png'
  270. else:
  271. pilImage.save(buff, format='JPEG', quality=quality, progressive=True)
  272. contenttype += 'jpeg'
  273. logger.debug('sliceMapCreator image with res %d has size of: %d' % (destSize, buff.tell()))
  274. buff.seek(0, 0)
  275. buffers.append(buff)
  276. # end for each array
  277. spriteToAdd = copy.deepcopy(generatorSprite)
  278. spriteToAdd.buffers = buffers
  279. spriteToAdd.contenttype = contenttype
  280. # sprite.image
  281. if '%d' % destSize not in returnSprites:
  282. returnSprites['%d' % destSize] = []
  283. returnSprites['%d' % destSize].append(spriteToAdd)
  284. for sprite in sprites:
  285. del sprite.imagesAsArrays
  286. return returnSprites
  287. def calculateXYDimensions(frame):
  288. if len(frame.shape) != 3:
  289. raise ValueError('frame must have the dimension 3')
  290. new_height = frame.shape[1]
  291. new_width = frame.shape[2]
  292. max_size = max(new_height, new_width)
  293. pixel_offset = [0, 0]
  294. if new_width < max_size:
  295. pixel_offset[0] = math.ceil((max_size - new_width) / 2)
  296. new_width = max_size
  297. if new_height < max_size:
  298. pixel_offset[1] = math.ceil((max_size - new_height) / 2)
  299. new_height = max_size
  300. logger.debug('sliceMapCreator pixeloffsets %d, %d, newWidth %d' % (pixel_offset[0], pixel_offset[1], new_width))
  301. return pixel_offset, (new_width, new_height)
  302. def crop_frames(frames, pixel_offset, new_size):
  303. if len(frames.shape) != 4:
  304. raise ValueError('frame must have the dimension 3')
  305. offset_x = pixel_offset[0]
  306. offset_y = pixel_offset[1]
  307. t, z, y, x = frames.shape
  308. squared_frames = np.empty((t, z, new_size[1], new_size[0]), dtype=frames.dtype)
  309. logger.debug('squared_frames.shape: %d, %d, %d, %d' % squared_frames.shape)
  310. logger.debug(' frames.shape: %d, %d, %d, %d' % frames.shape)
  311. logger.debug('offsety: %d, offsetx: %d ' % (offset_y, offset_x))
  312. '''for frameNumber in range(0, frames.shape[0]):
  313. squared_frames[frameNumber] = sliceMapCreator.cropFrame(frames[frameNumber], pixelOffset, newSize)'''
  314. squared_frames[:, :, offset_y: y + offset_y, offset_x: x + offset_x] = frames
  315. '''newFrame = np.zeros((z, y + 2 * offset_y, x + 2 * offset_x), dtype=frame.dtype)
  316. newFrame[:, offset_y : offset_y + y, offset_x : offset_x + x]'''
  317. return squared_frames
  318. class ResultFrame():
  319. def __init__(self, frame, fromImage):
  320. self.frame = frame
  321. self.fromImage = fromImage
  322. def framePreprocessor(inFrame, queue, fromImage, toImage, destShape):
  323. numberOfImages = toImage - fromImage + 1
  324. outFrame = np.empty((numberOfImages, destShape[0], destShape[1]), dtype=inFrame.dtype)
  325. outputCounter = 0
  326. for imageNumber in range(int(fromImage), int(toImage) + 1):
  327. image = resize(inFrame[imageNumber], destShape)
  328. outFrame[outputCounter] = img_as_ubyte(image)
  329. outputCounter += 1
  330. queue.put(ResultFrame(outFrame, fromImage))