Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
frcroth committed Jan 31, 2023
1 parent 2b80411 commit 2d0e09b
Show file tree
Hide file tree
Showing 14 changed files with 190 additions and 59 deletions.
4 changes: 2 additions & 2 deletions conf/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -287,5 +287,5 @@ pidfile.path = "/dev/null"


# uncomment these lines for faster restart during local backend development (but beware the then-missing features):
#slick.checkSchemaOnStartup = false
#play.modules.disabled += "play.modules.swagger.SwaggerModule"
slick.checkSchemaOnStartup = false
play.modules.disabled += "play.modules.swagger.SwaggerModule"
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
"build-watch": "node_modules/.bin/webpack -w",
"listening": "lsof -i:5005,7155,9000,9001,9002",
"kill-listeners": "kill -9 $(lsof -t -i:5005,7155,9000,9001,9002)",
"rm-lock": "rm fossildb/data/LOCK",
"test": "tools/test.sh test --timeout=30s",
"test-changed": "tools/test.sh test-changed --timeout=30s",
"test-verbose": "xvfb-run -s '-ac -screen 0 1280x1024x24' tools/test.sh test --timeout=60s --verbose",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ object BoundingBox {
else
None

def fromOffsetAndShape(offset: Vec3Int, shape: Vec3Int): BoundingBox =
BoundingBox(offset, shape.x, shape.y, shape.z)

def union(bbs: List[BoundingBox]): BoundingBox =
bbs match {
case head :: tail =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ case class Vec3Int(x: Int, y: Int, z: Int) {
def *(that: Int): Vec3Int =
Vec3Int(x * that, y * that, z * that)

def /(that: Vec3Int): Vec3Int =
Vec3Int(x / that.x, y / that.y, z / that.z)

def +(that: Vec3Int): Vec3Int =
Vec3Int(x + that.x, y + that.y, z + that.z)

def -(that: Vec3Int): Vec3Int =
Vec3Int(x - that.x, y - that.y, z - that.z)

def scale(s: Float): Vec3Int =
Vec3Int((x * s).toInt, (y * s).toInt, (z * s).toInt)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ class PrecomputedBucketProvider(layer: PrecomputedLayer)
case None => Empty
case Some(magPath) =>
tryo(onError = e => logError(e))(
PrecomputedArray.open(magPath, precomputedMag.axisOrder, precomputedMag.channelIndex))
PrecomputedArray
.open(magPath, precomputedMag.axisOrder, precomputedMag.channelIndex, readInstruction.bucket.mag))
.map(new PrecomputedCubeHandle(_))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ class ChunkReader(val header: DatasetHeader, val store: FileSystemStore, val typ
lazy val chunkSize: Int = header.chunkSize.toList.product

@throws[IOException]
def read(path: String): Future[MultiArray] =
typedChunkReader.read(readBytes(path))
def read(path: String, chunkShape: Array[Int]): Future[MultiArray] =
typedChunkReader.read(readBytes(path), chunkShape)

protected def readBytes(path: String): Option[Array[Byte]] =
Using.Manager { use =>
Expand All @@ -44,104 +44,107 @@ class ChunkReader(val header: DatasetHeader, val store: FileSystemStore, val typ
abstract class TypedChunkReader {
val header: DatasetHeader

var chunkShape: Array[Int] = header.chunkShapeOrdered
def ma2DataType: MADataType
def read(bytes: Option[Array[Byte]]): Future[MultiArray]
def read(bytes: Option[Array[Byte]], chunkShape: Array[Int]): Future[MultiArray]

def createFilled(dataType: MADataType): MultiArray =
def createFilled(dataType: MADataType, chunkShape: Array[Int]): MultiArray =
MultiArrayUtils.createFilledArray(dataType, chunkShape, header.fillValueNumber)

// Chunk shape in header is in C-Order (XYZ), but data may be in F-Order (ZYX), so the chunk shape
// associated with the array needs to be adjusted for non-isotropic chunk sizes.
def chunkDataShape(chunkShape: Array[Int]) = if (header.order == ArrayOrder.F) chunkShape.reverse else chunkShape
}

class ByteChunkReader(val header: DatasetHeader) extends TypedChunkReader {
val ma2DataType: MADataType = MADataType.BYTE

def read(bytes: Option[Array[Byte]]): Future[MultiArray] =
def read(bytes: Option[Array[Byte]], chunkShape: Array[Int]): Future[MultiArray] =
Future.successful(bytes.map { result =>
MultiArray.factory(ma2DataType, chunkShape, result)
}.getOrElse(createFilled(ma2DataType)))
MultiArray.factory(ma2DataType, chunkDataShape(chunkShape), result)
}.getOrElse(createFilled(ma2DataType, chunkDataShape(chunkShape))))
}

class DoubleChunkReader(val header: DatasetHeader) extends TypedChunkReader {

val ma2DataType: MADataType = MADataType.DOUBLE

def read(bytes: Option[Array[Byte]]): Future[MultiArray] =
def read(bytes: Option[Array[Byte]], chunkShape: Array[Int]): Future[MultiArray] =
Future.successful(Using.Manager { use =>
bytes.map { result =>
val typedStorage = new Array[Double](chunkShape.product)
val bais = use(new ByteArrayInputStream(result))
val iis = use(new MemoryCacheImageInputStream(bais))
iis.setByteOrder(header.byteOrder)
iis.readFully(typedStorage, 0, typedStorage.length)
MultiArray.factory(ma2DataType, chunkShape, typedStorage)
}.getOrElse(createFilled(ma2DataType))
MultiArray.factory(ma2DataType, chunkDataShape(chunkShape), typedStorage)
}.getOrElse(createFilled(ma2DataType, chunkDataShape(chunkShape)))
}.get)
}

class ShortChunkReader(val header: DatasetHeader) extends TypedChunkReader with LazyLogging {

val ma2DataType: MADataType = MADataType.SHORT

def read(bytes: Option[Array[Byte]]): Future[MultiArray] =
def read(bytes: Option[Array[Byte]], chunkShape: Array[Int]): Future[MultiArray] =
Future.successful(Using.Manager { use =>
bytes.map { result =>
val typedStorage = new Array[Short](chunkShape.product)
val bais = use(new ByteArrayInputStream(result))
val iis = use(new MemoryCacheImageInputStream(bais))
iis.setByteOrder(header.byteOrder)
iis.readFully(typedStorage, 0, typedStorage.length)
MultiArray.factory(ma2DataType, chunkShape, typedStorage)
}.getOrElse(createFilled(ma2DataType))
MultiArray.factory(ma2DataType, chunkDataShape(chunkShape), typedStorage)
}.getOrElse(createFilled(ma2DataType, chunkDataShape(chunkShape)))
}.get)
}

class IntChunkReader(val header: DatasetHeader) extends TypedChunkReader {

val ma2DataType: MADataType = MADataType.INT

def read(bytes: Option[Array[Byte]]): Future[MultiArray] =
def read(bytes: Option[Array[Byte]], chunkShape: Array[Int]): Future[MultiArray] =
Future.successful(Using.Manager { use =>
bytes.map { result =>
val typedStorage = new Array[Int](chunkShape.product)
val bais = use(new ByteArrayInputStream(result))
val iis = use(new MemoryCacheImageInputStream(bais))
iis.setByteOrder(header.byteOrder)
iis.readFully(typedStorage, 0, typedStorage.length)
MultiArray.factory(ma2DataType, chunkShape, typedStorage)
}.getOrElse(createFilled(ma2DataType))
MultiArray.factory(ma2DataType, chunkDataShape(chunkShape), typedStorage)
}.getOrElse(createFilled(ma2DataType, chunkDataShape(chunkShape)))
}.get)
}

class LongChunkReader(val header: DatasetHeader) extends TypedChunkReader {

val ma2DataType: MADataType = MADataType.LONG

def read(bytes: Option[Array[Byte]]): Future[MultiArray] =
def read(bytes: Option[Array[Byte]], chunkShape: Array[Int]): Future[MultiArray] =
Future.successful(Using.Manager { use =>
bytes.map { result =>
val typedStorage = new Array[Long](chunkShape.product)
val bais = use(new ByteArrayInputStream(result))
val iis = use(new MemoryCacheImageInputStream(bais))
iis.setByteOrder(header.byteOrder)
iis.readFully(typedStorage, 0, typedStorage.length)
MultiArray.factory(ma2DataType, chunkShape, typedStorage)
}.getOrElse(createFilled(ma2DataType))
MultiArray.factory(ma2DataType, chunkDataShape(chunkShape), typedStorage)
}.getOrElse(createFilled(ma2DataType, chunkDataShape(chunkShape)))
}.get)
}

class FloatChunkReader(val header: DatasetHeader) extends TypedChunkReader {

val ma2DataType: MADataType = MADataType.FLOAT

def read(bytes: Option[Array[Byte]]): Future[MultiArray] =
def read(bytes: Option[Array[Byte]], chunkShape: Array[Int]): Future[MultiArray] =
Future.successful(Using.Manager { use =>
bytes.map { result =>
val typedStorage = new Array[Float](chunkShape.product)
val bais = use(new ByteArrayInputStream(result))
val iis = use(new MemoryCacheImageInputStream(bais))
iis.setByteOrder(header.byteOrder)
iis.readFully(typedStorage, 0, typedStorage.length)
MultiArray.factory(ma2DataType, chunkShape, typedStorage)
}.getOrElse(createFilled(ma2DataType))
MultiArray.factory(ma2DataType, chunkDataShape(chunkShape), typedStorage)
}.getOrElse(createFilled(ma2DataType, chunkDataShape(chunkShape)))
}.get)
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,29 @@ object ChunkUtils extends LazyLogging {
}
chunkIndices.toList
}
/*
def calculatePrecomputedChunks(arrayShape: Array[Int],
arrayChunkSize: Array[Int],
selectedShape: Array[Int],
selectedOffset: Array[Int],
mag: Vec3Int): Iterable[BoundingBox] = {
val offset = (selectedOffset, mag.toList).zipped.map((o, m) => o / m)
val requested = BoundingBox.fromOffsetAndShape(Vec3Int.fromList(offset.toList).getOrElse(Vec3Int(0, 0, 0)),
Vec3Int.fromList(selectedShape.toList).getOrElse(Vec3Int(0, 0, 0)))
val boundingBox = BoundingBox(Vec3Int(0, 0, 0), arrayShape(0), arrayShape(1), arrayShape(2))
val inside = requested.intersection(boundingBox)
val chunkSize = Vec3Int.fromList(arrayChunkSize.toList).getOrElse(Vec3Int(0, 0, 0))
inside match {
case Some(inside) => {
val aligned = (inside - boundingBox.topLeft).div(chunkSize) * chunkSize + boundingBox.topLeft
aligned
.range(chunkSize)
.flatMap(chunkOffset => BoundingBox.fromOffsetAndShape(chunkOffset, chunkSize).intersection(boundingBox))
}
case _ => List()
}
}*/
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import java.nio.ByteBuffer
import java.util
import java.util.zip.{Deflater, DeflaterOutputStream, Inflater, InflaterInputStream}
import javax.imageio.ImageIO
import javax.imageio.ImageIO.{createImageInputStream, createImageOutputStream}
import javax.imageio.ImageIO.{createImageInputStream}
import javax.imageio.stream.ImageInputStream

sealed trait CompressionSetting
Expand Down Expand Up @@ -278,7 +278,7 @@ class JpegCompressor() extends Compressor {
val raster = bi.getRaster
val dbb: DataBufferByte = raster.getDataBuffer.asInstanceOf[DataBufferByte]
val width = raster.getWidth
val data = dbb.getData.grouped(width).toList.transpose
val data = dbb.getData.grouped(width).toList//.transpose
os.write(data.flatten.toArray)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class DatasetArray(relativePath: DatasetPath,
ChunkReader.create(store, header)

// cache currently limited to 1 GB per array
private lazy val chunkContentsCache: Cache[String, MultiArray] = {
protected lazy val chunkContentsCache: Cache[String, MultiArray] = {
val maxSizeBytes = 1000L * 1000 * 1000
val maxEntries = maxSizeBytes / header.bytesPerChunk
AlfuCache(maxEntries.toInt)
Expand Down Expand Up @@ -78,7 +78,8 @@ class DatasetArray(relativePath: DatasetPath,
offsetInChunk = computeOffsetInChunk(chunkIndex, offset)
sourceChunkInCOrder: MultiArray = MultiArrayUtils.axisOrderXYZView(sourceChunk,
axisOrder,
flip = header.order != ArrayOrder.C)
header.order != ArrayOrder.C,
header.shiftAxisOrderRight)
_ = MultiArrayUtils.copyRange(offsetInChunk, sourceChunkInCOrder, targetInCOrder)
} yield ()
}
Expand All @@ -88,12 +89,13 @@ class DatasetArray(relativePath: DatasetPath,
}
}

private def getSourceChunkDataWithCache(chunkIndex: Array[Int]): Future[MultiArray] = {
protected def getSourceChunkDataWithCache(chunkIndex: Array[Int]): Future[MultiArray] = {
val chunkFilename = getChunkFilename(chunkIndex)
val chunkFilePath = relativePath.resolve(chunkFilename)
val storeKey = chunkFilePath.storeKey
val chunkShape = header.chunkSizeAtIndex(chunkIndex)

chunkContentsCache.getOrLoad(storeKey, chunkReader.read)
chunkContentsCache.getOrLoad(storeKey, key => chunkReader.read(key, chunkShape))
}

protected def getChunkFilename(chunkIndex: Array[Int]): String =
Expand All @@ -118,9 +120,9 @@ class DatasetArray(relativePath: DatasetPath,
private def isZeroOffset(offset: Array[Int]): Boolean =
util.Arrays.equals(offset, new Array[Int](offset.length))

private def computeOffsetInChunk(chunkIndex: Array[Int], globalOffset: Array[Int]): Array[Int] =
protected def computeOffsetInChunk(chunkIndex: Array[Int], globalOffset: Array[Int]): Array[Int] =
chunkIndex.indices.map { dim =>
globalOffset(dim) - (chunkIndex(dim) * axisOrder.permuteIndicesReverse(header.chunkSize)(dim))
globalOffset(dim) - (chunkIndex(dim) * header.chunkSize(dim))
}.toArray

override def toString: String =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,19 @@ import java.nio.ByteOrder

trait DatasetHeader {
def datasetShape: Array[Int] // shape of the entire array

def chunkSize: Array[Int] // shape of each chunk

def dimension_separator: DimensionSeparator

def dataType: String

def fill_value: Either[String, Number]

def order: ArrayOrder

def resolvedDataType: ArrayDataType

def compressorImpl: Compressor

lazy val byteOrder: ByteOrder = ByteOrder.BIG_ENDIAN
Expand All @@ -40,4 +46,8 @@ trait DatasetHeader {
Some(BoundingBox(Vec3Int.zeros, datasetShape(axisOrder.x), datasetShape(axisOrder.y), datasetShape(axisOrder.z)))

lazy val rank: Int = datasetShape.length

def shiftAxisOrderRight: Boolean = false

def chunkSizeAtIndex(chunkIndex: Array[Int]): Array[Int] = chunkSize
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.scalableminds.webknossos.datastore.datareaders

import com.typesafe.scalalogging.LazyLogging
import net.liftweb.util.Helpers.tryo

import java.io.FileNotFoundException
import java.nio.file.{FileSystem, Files, Path}

class FileSystemStore(val internalRoot: Path) {
Expand All @@ -12,7 +14,8 @@ class FileSystemStore(val internalRoot: Path) {
}

class GoogleCloudFileSystemStore(override val internalRoot: Path, fs: FileSystem)
extends FileSystemStore(internalRoot) {
extends FileSystemStore(internalRoot)
with LazyLogging {

private def normalizedInternalRoot = {
def prefix = internalRoot.getParent.toString // This part uses "/"
Expand All @@ -23,6 +26,17 @@ class GoogleCloudFileSystemStore(override val internalRoot: Path, fs: FileSystem

override def readBytes(key: String): Option[Array[Byte]] = {
val path = s"$normalizedInternalRoot%2F$key?alt=media"
tryo(Files.readAllBytes(fs.getPath(path))).toOption
try {
Some(Files.readAllBytes(fs.getPath(path)))
} catch {
case e: FileNotFoundException => {
logger.info(s"Could not read data at ${path}")
None
}
case _ => {
logger.info(s"Could not read data at ${path}")
None
}
}
}
}
Loading

0 comments on commit 2d0e09b

Please sign in to comment.