From c3f98bdb8c342d27cdb6cd45810811d68a342027 Mon Sep 17 00:00:00 2001 From: Youri K Date: Fri, 12 Apr 2019 11:53:02 +0200 Subject: [PATCH 01/10] [WIP] first version of histogram route #3975 --- .../controllers/BinaryDataController.scala | 16 +++++++ .../datastore/services/FindDataService.scala | 42 +++++++++++++++++++ ....scalableminds.webknossos.datastore.routes | 1 + 3 files changed, 59 insertions(+) diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala index c78a587d22b..b42b16027e6 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala @@ -402,6 +402,22 @@ class BinaryDataController @Inject()( } } + def createHistogram(organizationName: String, dataSetName: String, dataLayerName: String) = Action.async { + implicit request => + accessTokenService + .validateAccess(UserAccessRequest.readDataSources(DataSourceId(dataSetName, organizationName))) { + AllowRemoteOrigin { + for { + (dataSource, dataLayer) <- getDataSourceAndDataLayer(organizationName, dataSetName, dataLayerName) + histogramAndCount <- findDataService.createHistogram(dataSource, dataLayer) + } yield + Ok( + Json.obj("histogram" -> histogramAndCount._1, + "count" -> histogramAndCount._2)) + } + } + } + private def getDataSourceAndDataLayer(organizationName: String, dataSetName: String, dataLayerName: String)( implicit m: MessagesProvider): Fox[(DataSource, DataLayer)] = for { diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala index f30855dcfe6..183194ea34f 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala @@ -204,4 +204,46 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp dataLayer.resolutions.minBy(_.maxDim)) } yield meanAndStdDev } + + def createHistogram(dataSource: DataSource, dataLayer: DataLayer) = { + + def getDataFor(position: Point3D, resolution: Point3D): Fox[Array[Byte]] = { + val request = DataRequest( + new VoxelPosition(position.x, position.y, position.z, resolution), + DataLayer.bucketLength, + DataLayer.bucketLength, + DataLayer.bucketLength + ) + binaryDataService.handleDataRequest( + DataServiceDataRequest(dataSource, dataLayer, None, request.cuboid(dataLayer), request.settings)) + } + + def concatenateBuckets(buckets: Seq[Array[Byte]]): Array[Byte] = + buckets.foldLeft(Array[Byte]()) { (acc, i) => + { + acc ++ i + } + } + + def createHistogram(data: Array[Int], width: Int = 256) = { + val counts = Array.ofDim[Long](width) + data.foreach(el => counts(el + 128) = counts(el + 128) + 1) + counts + } + + def histogramForPositions(positions: List[Point3D], resolution: Point3D) = + for { + dataBucketWise: Seq[Array[Byte]] <- Fox.serialCombined(positions)(pos => getDataFor(pos, resolution)) + dataConcatenated = concatenateBuckets(dataBucketWise) + + // TODO data conversion + histogramValues = createHistogram(dataConcatenated.map(_.toInt)) + } yield (histogramValues, dataConcatenated.length) + + for { + _ <- bool2Fox(dataLayer.resolutions.nonEmpty) + histogramAndCount <- histogramForPositions(createPositions(dataLayer, 2).distinct, + dataLayer.resolutions.minBy(_.maxDim)) + } yield histogramAndCount + } } diff --git a/webknossos-datastore/conf/com.scalableminds.webknossos.datastore.routes b/webknossos-datastore/conf/com.scalableminds.webknossos.datastore.routes index 655bda4794b..ee901fcd6f9 100644 --- a/webknossos-datastore/conf/com.scalableminds.webknossos.datastore.routes +++ b/webknossos-datastore/conf/com.scalableminds.webknossos.datastore.routes @@ -15,6 +15,7 @@ GET /datasets/:organizationName/:dataSetName/layers/:dataLayerName/thumb GET /datasets/:organizationName/:dataSetName/layers/:dataLayerName/thumbnail.jpg @com.scalableminds.webknossos.datastore.controllers.BinaryDataController.requestImageThumbnailJpeg(organizationName: String, dataSetName: String, dataLayerName: String, width: Int, height: Int, centerX: Option[Int], centerY: Option[Int], centerZ: Option[Int], zoom: Option[Double]) GET /datasets/:organizationName/:dataSetName/layers/:dataLayerName/findData @com.scalableminds.webknossos.datastore.controllers.BinaryDataController.findData(organizationName: String, dataSetName: String, dataLayerName: String) GET /datasets/:organizationName/:dataSetName/layers/:dataLayerName/colorStatistics @com.scalableminds.webknossos.datastore.controllers.BinaryDataController.colorStatistics(organizationName: String, dataSetName: String, dataLayerName: String) +GET /datasets/:organizationName/:dataSetName/layers/:dataLayerName/histogram @com.scalableminds.webknossos.datastore.controllers.BinaryDataController.createHistogram(organizationName: String, dataSetName: String, dataLayerName: String) # Knossos compatibale routes GET /datasets/:organizationName/:dataSetName/layers/:dataLayerName/mag:resolution/x:x/y:y/z:z/bucket.raw @com.scalableminds.webknossos.datastore.controllers.BinaryDataController.requestViaKnossos(organizationName: String, dataSetName: String, dataLayerName: String, resolution: Int, x: Int, y: Int, z: Int, cubeSize: Int) From 84f0eebe47908f5eacfaa32978309cee6c8e01ff Mon Sep 17 00:00:00 2001 From: Youri K Date: Fri, 12 Apr 2019 13:03:48 +0200 Subject: [PATCH 02/10] lint backend --- .../datastore/controllers/BinaryDataController.scala | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala index b42b16027e6..23a04dd0a78 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala @@ -410,10 +410,7 @@ class BinaryDataController @Inject()( for { (dataSource, dataLayer) <- getDataSourceAndDataLayer(organizationName, dataSetName, dataLayerName) histogramAndCount <- findDataService.createHistogram(dataSource, dataLayer) - } yield - Ok( - Json.obj("histogram" -> histogramAndCount._1, - "count" -> histogramAndCount._2)) + } yield Ok(Json.obj("histogram" -> histogramAndCount._1, "count" -> histogramAndCount._2)) } } } From 5019f073f4a2dbc8a18dd3bada1c521c1fde2395 Mon Sep 17 00:00:00 2001 From: Youri K Date: Thu, 18 Apr 2019 16:10:23 +0200 Subject: [PATCH 03/10] implement data conversion --- .../datastore/services/FindDataService.scala | 191 +++++++++--------- 1 file changed, 90 insertions(+), 101 deletions(-) diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala index 183194ea34f..0f3ea283dcc 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala @@ -17,13 +17,6 @@ import scala.reflect.ClassTag class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(implicit ec: ExecutionContext) extends FoxImplicits { val binaryDataService: BinaryDataService = dataServicesHolder.binaryDataService - var i = 0 - - def findPositionWithData(dataSource: DataSource, dataLayer: DataLayer)( - implicit m: MessagesProvider): Fox[Option[(Point3D, Point3D)]] = - for { - positionAndResolutionOpt <- checkAllPositionsForData(dataSource, dataLayer) - } yield positionAndResolutionOpt private def convertData(data: Array[Byte], elementClass: ElementClass.Value): Array[_ >: Byte with Short with Int with Long] = @@ -48,6 +41,77 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp dstArray } + private def getDataFor(dataSource: DataSource, + dataLayer: DataLayer, + position: Point3D, + resolution: Point3D): Fox[Array[Byte]] = { + val request = DataRequest( + new VoxelPosition(position.x, position.y, position.z, resolution), + DataLayer.bucketLength, + DataLayer.bucketLength, + DataLayer.bucketLength + ) + binaryDataService.handleDataRequest( + DataServiceDataRequest(dataSource, dataLayer, None, request.cuboid(dataLayer), request.settings)) + } + + private def concatenateBuckets(buckets: Seq[Array[Byte]]): Array[Byte] = + buckets.foldLeft(Array[Byte]()) { (acc, i) => + { + acc ++ i + } + } + + private def getConcatenatedDataFor(dataSource: DataSource, + dataLayer: DataLayer, + positions: List[Point3D], + resolution: Point3D) = + for { + dataBucketWise: Seq[Array[Byte]] <- Fox.serialCombined(positions)(pos => + getDataFor(dataSource, dataLayer, pos, resolution)) + dataConcatenated = concatenateBuckets(dataBucketWise) + } yield dataConcatenated + + private def createPositions(dataLayer: DataLayer, iterationCount: Int = 4) = { + + def positionCreationIter(remainingRuns: List[Int], currentPositions: List[Point3D]): List[Point3D] = { + + def createPositionsFromExponent(exponent: Int) = { + val power = math.pow(2, exponent).toInt + val spaceBetweenWidth = dataLayer.boundingBox.width / power + val spaceBetweenHeight = dataLayer.boundingBox.height / power + val spaceBetweenDepth = dataLayer.boundingBox.depth / power + val topLeft = dataLayer.boundingBox.topLeft + + if (spaceBetweenWidth < DataLayer.bucketLength && spaceBetweenHeight < DataLayer.bucketLength && spaceBetweenDepth < DataLayer.bucketLength) { + None + } else { + Some( + (for { + z <- 1 until power + y <- 1 until power + x <- 1 until power + } yield + Point3D(topLeft.x + x * spaceBetweenWidth, + topLeft.y + y * spaceBetweenHeight, + topLeft.z + z * spaceBetweenDepth)).toList + ) + } + } + + remainingRuns match { + case List() => currentPositions + case head :: tail => + createPositionsFromExponent(head) match { + case Some(values) => positionCreationIter(tail, currentPositions ::: values) + case None => currentPositions + } + } + } + + positionCreationIter((1 to iterationCount).toList, List[Point3D]()) + } + private def checkAllPositionsForData(dataSource: DataSource, dataLayer: DataLayer): Fox[Option[(Point3D, Point3D)]] = { @@ -75,19 +139,11 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp } } - def checkIfPositionHasData(position: Point3D, resolution: Point3D) = { - val request = DataRequest( - new VoxelPosition(position.x, position.y, position.z, resolution), - DataLayer.bucketLength, - DataLayer.bucketLength, - DataLayer.bucketLength - ) + def checkIfPositionHasData(position: Point3D, resolution: Point3D) = for { - data <- binaryDataService.handleDataRequest( - DataServiceDataRequest(dataSource, dataLayer, None, request.cuboid(dataLayer), request.settings)) + data <- getDataFor(dataSource, dataLayer, position, resolution) if data.nonEmpty && data.exists(_ != 0) } yield position.move(getExactDataOffset(data)) - } def resolutionIter(positions: List[Point3D], remainingResolutions: List[Point3D]): Fox[Option[(Point3D, Point3D)]] = remainingResolutions match { @@ -105,67 +161,15 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp resolutionIter(createPositions(dataLayer).distinct, dataLayer.resolutions.sortBy(_.maxDim)) } - private def createPositions(dataLayer: DataLayer, iterationCount: Int = 4) = { - - def positionCreationIter(remainingRuns: List[Int], currentPositions: List[Point3D]): List[Point3D] = { - - def createPositionsFromExponent(exponent: Int) = { - val power = math.pow(2, exponent).toInt - val spaceBetweenWidth = dataLayer.boundingBox.width / power - val spaceBetweenHeight = dataLayer.boundingBox.height / power - val spaceBetweenDepth = dataLayer.boundingBox.depth / power - val topLeft = dataLayer.boundingBox.topLeft - - if (spaceBetweenWidth < DataLayer.bucketLength && spaceBetweenHeight < DataLayer.bucketLength && spaceBetweenDepth < DataLayer.bucketLength) { - None - } else { - Some( - (for { - z <- 1 until power - y <- 1 until power - x <- 1 until power - } yield - Point3D(topLeft.x + x * spaceBetweenWidth, - topLeft.y + y * spaceBetweenHeight, - topLeft.z + z * spaceBetweenDepth)).toList - ) - } - } - - remainingRuns match { - case List() => currentPositions - case head :: tail => - createPositionsFromExponent(head) match { - case Some(values) => positionCreationIter(tail, currentPositions ::: values) - case None => currentPositions - } - } - } - - positionCreationIter((1 to iterationCount).toList, List[Point3D]()) - } + def findPositionWithData(dataSource: DataSource, dataLayer: DataLayer)( + implicit m: MessagesProvider): Fox[Option[(Point3D, Point3D)]] = + for { + positionAndResolutionOpt <- checkAllPositionsForData(dataSource, dataLayer) + } yield positionAndResolutionOpt def meanAndStdDev(dataSource: DataSource, dataLayer: DataLayer)( implicit m: MessagesProvider): Fox[(Double, Double)] = { - def getDataFor(position: Point3D, resolution: Point3D): Fox[Array[Byte]] = { - val request = DataRequest( - new VoxelPosition(position.x, position.y, position.z, resolution), - DataLayer.bucketLength, - DataLayer.bucketLength, - DataLayer.bucketLength - ) - binaryDataService.handleDataRequest( - DataServiceDataRequest(dataSource, dataLayer, None, request.cuboid(dataLayer), request.settings)) - } - - def concatenateBuckets(buckets: Seq[Array[Byte]]): Array[Byte] = - buckets.foldLeft(Array[Byte]()) { (acc, i) => - { - acc ++ i - } - } - def convertNonZeroDataToDouble(data: Array[Byte], elementClass: ElementClass.Value): Array[Double] = elementClass match { case ElementClass.uint8 => @@ -192,8 +196,7 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp def meanAndStdDevForPositions(positions: List[Point3D], resolution: Point3D)( implicit m: MessagesProvider): Fox[(Double, Double)] = for { - dataBucketWise: Seq[Array[Byte]] <- Fox.serialCombined(positions)(pos => getDataFor(pos, resolution)) - dataConcatenated = concatenateBuckets(dataBucketWise) + dataConcatenated <- getConcatenatedDataFor(dataSource, dataLayer, positions, resolution) dataAsDoubles = convertNonZeroDataToDouble(dataConcatenated, dataLayer.elementClass) _ <- Fox.bool2Fox(dataAsDoubles.nonEmpty) ?~> "dataSet.sampledOnlyBlack" } yield (Math.mean(dataAsDoubles), Math.stdDev(dataAsDoubles)) @@ -206,39 +209,25 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp } def createHistogram(dataSource: DataSource, dataLayer: DataLayer) = { - - def getDataFor(position: Point3D, resolution: Point3D): Fox[Array[Byte]] = { - val request = DataRequest( - new VoxelPosition(position.x, position.y, position.z, resolution), - DataLayer.bucketLength, - DataLayer.bucketLength, - DataLayer.bucketLength - ) - binaryDataService.handleDataRequest( - DataServiceDataRequest(dataSource, dataLayer, None, request.cuboid(dataLayer), request.settings)) - } - - def concatenateBuckets(buckets: Seq[Array[Byte]]): Array[Byte] = - buckets.foldLeft(Array[Byte]()) { (acc, i) => - { - acc ++ i - } - } - - def createHistogram(data: Array[Int], width: Int = 256) = { + def createHistogram(data: Array[_ >: Byte with Short with Int with Long], width: Int = 256) = { val counts = Array.ofDim[Long](width) - data.foreach(el => counts(el + 128) = counts(el + 128) + 1) + data match { + case byteData: Array[Byte] => byteData.foreach(el => counts(el + 128) += 1) + case shortData: Array[Short] => shortData.foreach(el => counts(el / math.pow(2, 8).toInt + 128) += 1) + case intData: Array[Int] => intData.foreach(el => counts(el / math.pow(2, 24).toInt + 128) += 1) + case longData: Array[Long] => longData.foreach(el => counts((el / math.pow(2, 56).toInt + 128).toInt) += 1) + } counts } def histogramForPositions(positions: List[Point3D], resolution: Point3D) = for { - dataBucketWise: Seq[Array[Byte]] <- Fox.serialCombined(positions)(pos => getDataFor(pos, resolution)) - dataConcatenated = concatenateBuckets(dataBucketWise) + dataConcatenated <- getConcatenatedDataFor(dataSource, dataLayer, positions, resolution) + convertedData = convertData(dataConcatenated, dataLayer.elementClass) // TODO data conversion - histogramValues = createHistogram(dataConcatenated.map(_.toInt)) - } yield (histogramValues, dataConcatenated.length) + histogramValues = createHistogram(convertedData) + } yield (histogramValues, convertedData.length) for { _ <- bool2Fox(dataLayer.resolutions.nonEmpty) From 546a9b508d51f3bcd068efb1f90a4bd5e7244038 Mon Sep 17 00:00:00 2001 From: Youri K Date: Thu, 18 Apr 2019 18:15:49 +0200 Subject: [PATCH 04/10] show all layers in json and use dynamic histogram bucket width --- .../controllers/BinaryDataController.scala | 26 ++++++++------ .../datastore/services/FindDataService.scala | 34 ++++++++++++++----- ....scalableminds.webknossos.datastore.routes | 2 +- 3 files changed, 42 insertions(+), 20 deletions(-) diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala index 23a04dd0a78..03132eb650d 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala @@ -402,17 +402,21 @@ class BinaryDataController @Inject()( } } - def createHistogram(organizationName: String, dataSetName: String, dataLayerName: String) = Action.async { - implicit request => - accessTokenService - .validateAccess(UserAccessRequest.readDataSources(DataSourceId(dataSetName, organizationName))) { - AllowRemoteOrigin { - for { - (dataSource, dataLayer) <- getDataSourceAndDataLayer(organizationName, dataSetName, dataLayerName) - histogramAndCount <- findDataService.createHistogram(dataSource, dataLayer) - } yield Ok(Json.obj("histogram" -> histogramAndCount._1, "count" -> histogramAndCount._2)) - } - } + def createHistogram(organizationName: String, dataSetName: String) = Action.async { implicit request => + accessTokenService.validateAccess(UserAccessRequest.readDataSources(DataSourceId(dataSetName, organizationName))) { + AllowRemoteOrigin { + for { + dataSource <- dataSourceRepository.findUsable(DataSourceId(dataSetName, organizationName)).toFox ?~> Messages( + "dataSource.notFound") ~> 404 + histograms <- Fox.serialCombined(dataSource.dataLayers)(layer => + findDataService.createHistogram(dataSource, layer)) + jsList = dataSource.dataLayers + .map(layer => layer.name) + .zip(histograms) + .map(tuple => Json.obj(tuple._1 -> Json.obj("histogram" -> tuple._2._1, "count" -> tuple._2._2))) + } yield Ok(Json.toJson(jsList)) + } + } } private def getDataSourceAndDataLayer(organizationName: String, dataSetName: String, dataLayerName: String)( diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala index 0f3ea283dcc..4d73dd57c60 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala @@ -211,11 +211,32 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp def createHistogram(dataSource: DataSource, dataLayer: DataLayer) = { def createHistogram(data: Array[_ >: Byte with Short with Int with Long], width: Int = 256) = { val counts = Array.ofDim[Long](width) + var min = Long.MaxValue + var max = Long.MinValue data match { - case byteData: Array[Byte] => byteData.foreach(el => counts(el + 128) += 1) - case shortData: Array[Short] => shortData.foreach(el => counts(el / math.pow(2, 8).toInt + 128) += 1) - case intData: Array[Int] => intData.foreach(el => counts(el / math.pow(2, 24).toInt + 128) += 1) - case longData: Array[Long] => longData.foreach(el => counts((el / math.pow(2, 56).toInt + 128).toInt) += 1) + case byteData: Array[Byte] => + byteData.foreach { el => + min = math.min(el, min); max = math.max(el, max) + } + case shortData: Array[Short] => + shortData.foreach { el => + min = math.min(el, min); max = math.max(el, max) + } + case intData: Array[Int] => + intData.foreach { el => + min = math.min(el, min); max = math.max(el, max) + } + case longData: Array[Long] => + longData.foreach { el => + min = math.min(el, min); max = math.max(el, max) + } + } + val bucketWidth = math.ceil((max - min + 1) / width.toDouble).toInt + data match { + case byteData: Array[Byte] => byteData.foreach(el => counts(((el - min) / bucketWidth).toInt) += 1) + case shortData: Array[Short] => shortData.foreach(el => counts(((el - min) / bucketWidth).toInt) += 1) + case intData: Array[Int] => intData.foreach(el => counts(((el - min) / bucketWidth).toInt) += 1) + case longData: Array[Long] => longData.foreach(el => counts(((el - min) / bucketWidth).toInt) += 1) } counts } @@ -224,10 +245,7 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp for { dataConcatenated <- getConcatenatedDataFor(dataSource, dataLayer, positions, resolution) convertedData = convertData(dataConcatenated, dataLayer.elementClass) - - // TODO data conversion - histogramValues = createHistogram(convertedData) - } yield (histogramValues, convertedData.length) + } yield (createHistogram(convertedData), convertedData.length) for { _ <- bool2Fox(dataLayer.resolutions.nonEmpty) diff --git a/webknossos-datastore/conf/com.scalableminds.webknossos.datastore.routes b/webknossos-datastore/conf/com.scalableminds.webknossos.datastore.routes index ee901fcd6f9..decc48afa45 100644 --- a/webknossos-datastore/conf/com.scalableminds.webknossos.datastore.routes +++ b/webknossos-datastore/conf/com.scalableminds.webknossos.datastore.routes @@ -15,7 +15,7 @@ GET /datasets/:organizationName/:dataSetName/layers/:dataLayerName/thumb GET /datasets/:organizationName/:dataSetName/layers/:dataLayerName/thumbnail.jpg @com.scalableminds.webknossos.datastore.controllers.BinaryDataController.requestImageThumbnailJpeg(organizationName: String, dataSetName: String, dataLayerName: String, width: Int, height: Int, centerX: Option[Int], centerY: Option[Int], centerZ: Option[Int], zoom: Option[Double]) GET /datasets/:organizationName/:dataSetName/layers/:dataLayerName/findData @com.scalableminds.webknossos.datastore.controllers.BinaryDataController.findData(organizationName: String, dataSetName: String, dataLayerName: String) GET /datasets/:organizationName/:dataSetName/layers/:dataLayerName/colorStatistics @com.scalableminds.webknossos.datastore.controllers.BinaryDataController.colorStatistics(organizationName: String, dataSetName: String, dataLayerName: String) -GET /datasets/:organizationName/:dataSetName/layers/:dataLayerName/histogram @com.scalableminds.webknossos.datastore.controllers.BinaryDataController.createHistogram(organizationName: String, dataSetName: String, dataLayerName: String) +GET /datasets/:organizationName/:dataSetName/histogram @com.scalableminds.webknossos.datastore.controllers.BinaryDataController.createHistogram(organizationName: String, dataSetName: String) # Knossos compatibale routes GET /datasets/:organizationName/:dataSetName/layers/:dataLayerName/mag:resolution/x:x/y:y/z:z/bucket.raw @com.scalableminds.webknossos.datastore.controllers.BinaryDataController.requestViaKnossos(organizationName: String, dataSetName: String, dataLayerName: String, resolution: Int, x: Int, y: Int, z: Int, cubeSize: Int) From ce7c68d9dd72b727e53dcd526bbc02339016b372 Mon Sep 17 00:00:00 2001 From: Youri K Date: Thu, 25 Apr 2019 11:16:44 +0200 Subject: [PATCH 05/10] implement pr feedback --- .../controllers/BinaryDataController.scala | 26 ++++++++----------- .../datastore/services/FindDataService.scala | 26 ++++++++++++++++--- ....scalableminds.webknossos.datastore.routes | 2 +- 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala index 03132eb650d..deea72b54ab 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala @@ -402,21 +402,17 @@ class BinaryDataController @Inject()( } } - def createHistogram(organizationName: String, dataSetName: String) = Action.async { implicit request => - accessTokenService.validateAccess(UserAccessRequest.readDataSources(DataSourceId(dataSetName, organizationName))) { - AllowRemoteOrigin { - for { - dataSource <- dataSourceRepository.findUsable(DataSourceId(dataSetName, organizationName)).toFox ?~> Messages( - "dataSource.notFound") ~> 404 - histograms <- Fox.serialCombined(dataSource.dataLayers)(layer => - findDataService.createHistogram(dataSource, layer)) - jsList = dataSource.dataLayers - .map(layer => layer.name) - .zip(histograms) - .map(tuple => Json.obj(tuple._1 -> Json.obj("histogram" -> tuple._2._1, "count" -> tuple._2._2))) - } yield Ok(Json.toJson(jsList)) - } - } + def createHistogram(organizationName: String, dataSetName: String, dataLayerName: String) = Action.async { + implicit request => + accessTokenService + .validateAccess(UserAccessRequest.readDataSources(DataSourceId(dataSetName, organizationName))) { + AllowRemoteOrigin { + for { + (dataSource, dataLayer) <- getDataSourceAndDataLayer(organizationName, dataSetName, dataLayerName) + (histogram, count) <- findDataService.createHistogram(dataSource, dataLayer) + } yield Ok(Json.obj("histogram" -> histogram, "count" -> count)) + } + } } private def getDataSourceAndDataLayer(organizationName: String, dataSetName: String, dataLayerName: String)( diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala index 4d73dd57c60..e6fa7bc743a 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala @@ -26,6 +26,9 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp case ElementClass.uint16 => convertDataImpl[Short, ShortBuffer](data, DataTypeFunctors[Short, ShortBuffer](_.asShortBuffer, _.get(_), _.toShort)) + case ElementClass.uint24 => + convertDataImpl[Byte, ByteBuffer](combineBytes(data), + DataTypeFunctors[Byte, ByteBuffer](identity, _.get(_), _.toByte)) case ElementClass.uint32 => convertDataImpl[Int, IntBuffer](data, DataTypeFunctors[Int, IntBuffer](_.asIntBuffer, _.get(_), _.toInt)) case ElementClass.uint64 => @@ -41,6 +44,18 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp dstArray } + private def combineBytes(data: Array[Byte], numBytes: Int = 3) = { + val result = Array.ofDim[Byte](data.length / numBytes) + for (i <- data.indices by numBytes) { + var sum = 0 + for (j <- 0 until numBytes) { + sum += data(i + j) + } + result(i / numBytes) = (sum / numBytes).toByte + } + result + } + private def getDataFor(dataSource: DataSource, dataLayer: DataLayer, position: Point3D, @@ -233,10 +248,13 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp } val bucketWidth = math.ceil((max - min + 1) / width.toDouble).toInt data match { - case byteData: Array[Byte] => byteData.foreach(el => counts(((el - min) / bucketWidth).toInt) += 1) - case shortData: Array[Short] => shortData.foreach(el => counts(((el - min) / bucketWidth).toInt) += 1) - case intData: Array[Int] => intData.foreach(el => counts(((el - min) / bucketWidth).toInt) += 1) - case longData: Array[Long] => longData.foreach(el => counts(((el - min) / bucketWidth).toInt) += 1) + case byteData: Array[Byte] => + byteData.filter(_ != 0).foreach(el => counts(((el - min) / bucketWidth).toInt) += 1) + case shortData: Array[Short] => + shortData.filter(_ != 0).foreach(el => counts(((el - min) / bucketWidth).toInt) += 1) + case intData: Array[Int] => intData.filter(_ != 0).foreach(el => counts(((el - min) / bucketWidth).toInt) += 1) + case longData: Array[Long] => + longData.filter(_ != 0).foreach(el => counts(((el - min) / bucketWidth).toInt) += 1) } counts } diff --git a/webknossos-datastore/conf/com.scalableminds.webknossos.datastore.routes b/webknossos-datastore/conf/com.scalableminds.webknossos.datastore.routes index decc48afa45..ee901fcd6f9 100644 --- a/webknossos-datastore/conf/com.scalableminds.webknossos.datastore.routes +++ b/webknossos-datastore/conf/com.scalableminds.webknossos.datastore.routes @@ -15,7 +15,7 @@ GET /datasets/:organizationName/:dataSetName/layers/:dataLayerName/thumb GET /datasets/:organizationName/:dataSetName/layers/:dataLayerName/thumbnail.jpg @com.scalableminds.webknossos.datastore.controllers.BinaryDataController.requestImageThumbnailJpeg(organizationName: String, dataSetName: String, dataLayerName: String, width: Int, height: Int, centerX: Option[Int], centerY: Option[Int], centerZ: Option[Int], zoom: Option[Double]) GET /datasets/:organizationName/:dataSetName/layers/:dataLayerName/findData @com.scalableminds.webknossos.datastore.controllers.BinaryDataController.findData(organizationName: String, dataSetName: String, dataLayerName: String) GET /datasets/:organizationName/:dataSetName/layers/:dataLayerName/colorStatistics @com.scalableminds.webknossos.datastore.controllers.BinaryDataController.colorStatistics(organizationName: String, dataSetName: String, dataLayerName: String) -GET /datasets/:organizationName/:dataSetName/histogram @com.scalableminds.webknossos.datastore.controllers.BinaryDataController.createHistogram(organizationName: String, dataSetName: String) +GET /datasets/:organizationName/:dataSetName/layers/:dataLayerName/histogram @com.scalableminds.webknossos.datastore.controllers.BinaryDataController.createHistogram(organizationName: String, dataSetName: String, dataLayerName: String) # Knossos compatibale routes GET /datasets/:organizationName/:dataSetName/layers/:dataLayerName/mag:resolution/x:x/y:y/z:z/bucket.raw @com.scalableminds.webknossos.datastore.controllers.BinaryDataController.requestViaKnossos(organizationName: String, dataSetName: String, dataLayerName: String, resolution: Int, x: Int, y: Int, z: Int, cubeSize: Int) From 5e2f41841ceb6ec03f50ee6fb40a134a865dbe47 Mon Sep 17 00:00:00 2001 From: Youri K Date: Thu, 25 Apr 2019 14:37:52 +0200 Subject: [PATCH 06/10] implement pr feedback 2 --- .../datastore/services/FindDataService.scala | 95 +++++++------------ 1 file changed, 36 insertions(+), 59 deletions(-) diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala index 4fca0a72188..e7708364532 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala @@ -9,7 +9,8 @@ import com.scalableminds.webknossos.datastore.models.datasource.{DataLayer, Data import com.scalableminds.webknossos.datastore.models.requests.DataServiceDataRequest import com.scalableminds.util.tools.Math import net.liftweb.common.Full -import play.api.i18n.{Messages, MessagesProvider} +import play.api.i18n.MessagesProvider +import spire.math._ import scala.concurrent.ExecutionContext import scala.reflect.ClassTag @@ -19,20 +20,31 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp val binaryDataService: BinaryDataService = dataServicesHolder.binaryDataService private def convertData(data: Array[Byte], - elementClass: ElementClass.Value): Array[_ >: Byte with Short with Int with Long] = + elementClass: ElementClass.Value, + filterZeroes: Boolean = false): Array[_ >: UByte with UShort with UInt with ULong] = elementClass match { case ElementClass.uint8 => convertDataImpl[Byte, ByteBuffer](data, DataTypeFunctors[Byte, ByteBuffer](identity, _.get(_), _.toByte)) + .map(UByte(_)) + .filter(filterZeroes && _ != UByte(0)) case ElementClass.uint16 => convertDataImpl[Short, ShortBuffer](data, DataTypeFunctors[Short, ShortBuffer](_.asShortBuffer, _.get(_), _.toShort)) + .map(UShort(_)) + .filter(filterZeroes && _ != UShort(0)) case ElementClass.uint24 => convertDataImpl[Byte, ByteBuffer](combineBytes(data), DataTypeFunctors[Byte, ByteBuffer](identity, _.get(_), _.toByte)) + .map(UByte(_)) + .filter(filterZeroes && _ != UByte(0)) case ElementClass.uint32 => convertDataImpl[Int, IntBuffer](data, DataTypeFunctors[Int, IntBuffer](_.asIntBuffer, _.get(_), _.toInt)) + .map(UInt(_)) + .filter(filterZeroes && _ != UInt(0)) case ElementClass.uint64 => convertDataImpl[Long, LongBuffer](data, DataTypeFunctors[Long, LongBuffer](_.asLongBuffer, _.get(_), identity)) + .map(ULong(_)) + .filter(filterZeroes && _ != ULong(0)) } private def convertDataImpl[T: ClassTag, B <: Buffer](data: Array[Byte], @@ -189,26 +201,11 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp implicit m: MessagesProvider): Fox[(Double, Double)] = { def convertNonZeroDataToDouble(data: Array[Byte], elementClass: ElementClass.Value): Array[Double] = - elementClass match { - case ElementClass.uint8 => - convertDataImpl[Byte, ByteBuffer](data, DataTypeFunctors[Byte, ByteBuffer](identity, _.get(_), _.toByte)) - .filter(_ != 0) - .map(spire.math.UByte(_).toDouble) - case ElementClass.uint16 => - convertDataImpl[Short, ShortBuffer](data, - DataTypeFunctors[Short, ShortBuffer]( - _.asShortBuffer, - _.get(_), - _.toShort)).filter(_ != 0).map(spire.math.UShort(_).toDouble) - case ElementClass.uint32 => - convertDataImpl[Int, IntBuffer](data, DataTypeFunctors[Int, IntBuffer](_.asIntBuffer, _.get(_), _.toInt)) - .filter(_ != 0) - .map(spire.math.UInt(_).toDouble) - case ElementClass.uint64 => - convertDataImpl[Long, LongBuffer](data, - DataTypeFunctors[Long, LongBuffer](_.asLongBuffer, _.get(_), identity)) - .filter(_ != 0) - .map(spire.math.ULong(_).toDouble) + convertData(data, elementClass, filterZeroes = true) match { + case d: Array[UByte] => d.map(_.toDouble) + case d: Array[UShort] => d.map(_.toDouble) + case d: Array[UInt] => d.map(_.toDouble) + case d: Array[ULong] => d.map(_.toDouble) } def meanAndStdDevForPositions(positions: List[Point3D], resolution: Point3D)( @@ -227,37 +224,18 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp } def createHistogram(dataSource: DataSource, dataLayer: DataLayer) = { - def createHistogram(data: Array[_ >: Byte with Short with Int with Long], width: Int = 256) = { - val counts = Array.ofDim[Long](width) - var min = Long.MaxValue - var max = Long.MinValue - data match { - case byteData: Array[Byte] => - byteData.foreach { el => - min = math.min(el, min); max = math.max(el, max) - } - case shortData: Array[Short] => - shortData.foreach { el => - min = math.min(el, min); max = math.max(el, max) - } - case intData: Array[Int] => - intData.foreach { el => - min = math.min(el, min); max = math.max(el, max) - } - case longData: Array[Long] => - longData.foreach { el => - min = math.min(el, min); max = math.max(el, max) - } - } - val bucketWidth = math.ceil((max - min + 1) / width.toDouble).toInt + + def calculateHistogramValues(data: Array[_ >: UByte with UShort with UInt with ULong], width: UInt = UInt(256)) = { + val counts = Array.ofDim[Long](width.toInt) data match { - case byteData: Array[Byte] => - byteData.filter(_ != 0).foreach(el => counts(((el - min) / bucketWidth).toInt) += 1) - case shortData: Array[Short] => - shortData.filter(_ != 0).foreach(el => counts(((el - min) / bucketWidth).toInt) += 1) - case intData: Array[Int] => intData.filter(_ != 0).foreach(el => counts(((el - min) / bucketWidth).toInt) += 1) - case longData: Array[Long] => - longData.filter(_ != 0).foreach(el => counts(((el - min) / bucketWidth).toInt) += 1) + case byteData: Array[UByte] => + byteData.foreach(el => counts((el / (UByte.MaxValue / UByte(width.toInt - 1))).toInt) += 1) + case shortData: Array[UShort] => + shortData.foreach(el => counts((el / (UShort.MaxValue / UShort(width.toInt - 1))).toInt) += 1) + case intData: Array[UInt] => + intData.foreach(el => counts((el / (UInt.MaxValue / (width - UInt(1)))).toInt) += 1) + case longData: Array[ULong] => + longData.foreach(el => counts((el / (ULong.MaxValue / ULong(width.toInt - 1))).toInt) += 1) } counts } @@ -265,13 +243,12 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp def histogramForPositions(positions: List[Point3D], resolution: Point3D) = for { dataConcatenated <- getConcatenatedDataFor(dataSource, dataLayer, positions, resolution) - convertedData = convertData(dataConcatenated, dataLayer.elementClass) - } yield (createHistogram(convertedData), convertedData.length) + convertedData = convertData(dataConcatenated, dataLayer.elementClass, filterZeroes = true) + } yield (calculateHistogramValues(convertedData), convertedData.length) - for { - _ <- bool2Fox(dataLayer.resolutions.nonEmpty) - histogramAndCount <- histogramForPositions(createPositions(dataLayer, 2).distinct, - dataLayer.resolutions.minBy(_.maxDim)) - } yield histogramAndCount + if (dataLayer.resolutions.nonEmpty) + histogramForPositions(createPositions(dataLayer, 2).distinct, dataLayer.resolutions.minBy(_.maxDim)) + else + Fox.empty } } From 11d0a8366a54181de5f279c26f61770896ed3db6 Mon Sep 17 00:00:00 2001 From: Youri K Date: Thu, 25 Apr 2019 14:54:14 +0200 Subject: [PATCH 07/10] remove unnecessary complexity by removing configurable histogram width --- .../datastore/services/FindDataService.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala index e7708364532..d85b5d7356b 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala @@ -225,17 +225,17 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp def createHistogram(dataSource: DataSource, dataLayer: DataLayer) = { - def calculateHistogramValues(data: Array[_ >: UByte with UShort with UInt with ULong], width: UInt = UInt(256)) = { - val counts = Array.ofDim[Long](width.toInt) + def calculateHistogramValues(data: Array[_ >: UByte with UShort with UInt with ULong]) = { + val counts = Array.ofDim[Long](256) data match { case byteData: Array[UByte] => - byteData.foreach(el => counts((el / (UByte.MaxValue / UByte(width.toInt - 1))).toInt) += 1) + byteData.foreach(el => counts(el.toInt) += 1) case shortData: Array[UShort] => - shortData.foreach(el => counts((el / (UShort.MaxValue / UShort(width.toInt - 1))).toInt) += 1) + shortData.foreach(el => counts((el / UShort(256)).toInt) += 1) case intData: Array[UInt] => - intData.foreach(el => counts((el / (UInt.MaxValue / (width - UInt(1)))).toInt) += 1) + intData.foreach(el => counts((el / UInt(16777216)).toInt) += 1) case longData: Array[ULong] => - longData.foreach(el => counts((el / (ULong.MaxValue / ULong(width.toInt - 1))).toInt) += 1) + longData.foreach(el => counts((el / ULong(math.pow(2, 56).toLong)).toInt) += 1) } counts } From ccaf5b39150873e638aacab1bc6b65615858f0c5 Mon Sep 17 00:00:00 2001 From: Youri K Date: Thu, 2 May 2019 17:53:09 +0200 Subject: [PATCH 08/10] add error messages --- .../webknossos/datastore/services/FindDataService.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala index d85b5d7356b..606039d2c39 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala @@ -242,13 +242,13 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp def histogramForPositions(positions: List[Point3D], resolution: Point3D) = for { - dataConcatenated <- getConcatenatedDataFor(dataSource, dataLayer, positions, resolution) + dataConcatenated <- getConcatenatedDataFor(dataSource, dataLayer, positions, resolution) ?~> "getting data failed" convertedData = convertData(dataConcatenated, dataLayer.elementClass, filterZeroes = true) } yield (calculateHistogramValues(convertedData), convertedData.length) if (dataLayer.resolutions.nonEmpty) histogramForPositions(createPositions(dataLayer, 2).distinct, dataLayer.resolutions.minBy(_.maxDim)) else - Fox.empty + Fox.empty ?~> "empty resolutions" } } From 8802d7e349fbb37b07fb2c93f1f43e99b33d40fc Mon Sep 17 00:00:00 2001 From: Youri K Date: Thu, 9 May 2019 15:17:58 +0200 Subject: [PATCH 09/10] add float support --- .../datastore/services/FindDataService.scala | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala index 606039d2c39..c4edd618dae 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala @@ -19,9 +19,10 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp extends FoxImplicits { val binaryDataService: BinaryDataService = dataServicesHolder.binaryDataService - private def convertData(data: Array[Byte], - elementClass: ElementClass.Value, - filterZeroes: Boolean = false): Array[_ >: UByte with UShort with UInt with ULong] = + private def convertData( + data: Array[Byte], + elementClass: ElementClass.Value, + filterZeroes: Boolean = false): Array[_ >: UByte with UShort with UInt with ULong with Float] = elementClass match { case ElementClass.uint8 => convertDataImpl[Byte, ByteBuffer](data, DataTypeFunctors[Byte, ByteBuffer](identity, _.get(_), _.toByte)) @@ -45,6 +46,10 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp convertDataImpl[Long, LongBuffer](data, DataTypeFunctors[Long, LongBuffer](_.asLongBuffer, _.get(_), identity)) .map(ULong(_)) .filter(filterZeroes && _ != ULong(0)) + case ElementClass.float => + convertDataImpl[Float, FloatBuffer]( + data, + DataTypeFunctors[Float, FloatBuffer](_.asFloatBuffer(), _.get(_), _.toFloat)).filter(filterZeroes && _ != 0f) } private def convertDataImpl[T: ClassTag, B <: Buffer](data: Array[Byte], @@ -206,6 +211,7 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp case d: Array[UShort] => d.map(_.toDouble) case d: Array[UInt] => d.map(_.toDouble) case d: Array[ULong] => d.map(_.toDouble) + case d: Array[Float] => d.map(_.toDouble) } def meanAndStdDevForPositions(positions: List[Point3D], resolution: Point3D)( @@ -225,7 +231,7 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp def createHistogram(dataSource: DataSource, dataLayer: DataLayer) = { - def calculateHistogramValues(data: Array[_ >: UByte with UShort with UInt with ULong]) = { + def calculateHistogramValues(data: Array[_ >: UByte with UShort with UInt with ULong with Float]) = { val counts = Array.ofDim[Long](256) data match { case byteData: Array[UByte] => @@ -236,6 +242,8 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp intData.foreach(el => counts((el / UInt(16777216)).toInt) += 1) case longData: Array[ULong] => longData.foreach(el => counts((el / ULong(math.pow(2, 56).toLong)).toInt) += 1) + case floatData: Array[Float] => + floatData.foreach(el => counts(Math.clamp(Math.roundDown(el * 255), 0, 255) += 1)) } counts } From c90d4f8654e63659a778778283f85f8765e918f3 Mon Sep 17 00:00:00 2001 From: Youri K Date: Thu, 9 May 2019 15:56:07 +0200 Subject: [PATCH 10/10] fix bracket --- .../webknossos/datastore/services/FindDataService.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala index c4edd618dae..9adf745261f 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala @@ -243,7 +243,7 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp case longData: Array[ULong] => longData.foreach(el => counts((el / ULong(math.pow(2, 56).toLong)).toInt) += 1) case floatData: Array[Float] => - floatData.foreach(el => counts(Math.clamp(Math.roundDown(el * 255), 0, 255) += 1)) + floatData.foreach(el => counts(Math.clamp(Math.roundDown(el * 255), 0, 255)) += 1) } counts }