Skip to content

Commit

Permalink
Change parameter precedence when creating task from base (#6249)
Browse files Browse the repository at this point in the history
* Change parameter precedence when creating task from base

* pass new arguments to duplicate call in tracingstore

* changelog
  • Loading branch information
fm3 authored Jun 10, 2022
1 parent a5b46b4 commit 03e565b
Show file tree
Hide file tree
Showing 12 changed files with 135 additions and 66 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Added a warning for when the resolution in the XY viewport on z=1-downsampled da

### Changed
- For the api routes that return annotation info objects, the user field was renamed to owner. User still exists as an alias, but will be removed in a future release. [#6250](https://github.com/scalableminds/webknossos/pull/6250)
- When creating a task from a base annotation, the starting position/rotation and bounding box as specified during task creation are now used and overwrite the ones from the original base annotation. [#6249](https://github.com/scalableminds/webknossos/pull/6249)


### Fixed
Expand Down
22 changes: 17 additions & 5 deletions app/models/annotation/WKRemoteTracingStoreClient.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package models.annotation

import java.io.File

import com.scalableminds.util.geometry.BoundingBox
import com.scalableminds.util.geometry.{BoundingBox, Vec3Double, Vec3Int}
import com.scalableminds.util.io.ZipIO
import com.scalableminds.util.tools.Fox
import com.scalableminds.util.tools.Fox.bool2Fox
Expand Down Expand Up @@ -78,26 +78,38 @@ class WKRemoteTracingStoreClient(tracingStore: TracingStore, dataSet: DataSet, r

def duplicateSkeletonTracing(skeletonTracingId: String,
versionString: Option[String] = None,
isFromTask: Boolean = false): Fox[String] = {
isFromTask: Boolean = false,
editPosition: Option[Vec3Int] = None,
editRotation: Option[Vec3Double] = None,
boundingBox: Option[BoundingBox] = None): Fox[String] = {
logger.debug("Called to duplicate SkeletonTracing." + baseInfo)
rpc(s"${tracingStore.url}/tracings/skeleton/$skeletonTracingId/duplicate").withLongTimeout
.addQueryString("token" -> RpcTokenHolder.webKnossosToken)
.addQueryStringOptional("version", versionString)
.addQueryStringOptional("editPosition", editPosition.map(_.toUriLiteral))
.addQueryStringOptional("editRotation", editRotation.map(_.toUriLiteral))
.addQueryStringOptional("boundingBox", boundingBox.map(_.toLiteral))
.addQueryString("fromTask" -> isFromTask.toString)
.postWithJsonResponse[String]
}

def duplicateVolumeTracing(volumeTracingId: String,
fromTask: Boolean = false,
isFromTask: Boolean = false,
dataSetBoundingBox: Option[BoundingBox] = None,
resolutionRestrictions: ResolutionRestrictions = ResolutionRestrictions.empty,
downsample: Boolean = false): Fox[String] = {
downsample: Boolean = false,
editPosition: Option[Vec3Int] = None,
editRotation: Option[Vec3Double] = None,
boundingBox: Option[BoundingBox] = None): Fox[String] = {
logger.debug("Called to duplicate VolumeTracing." + baseInfo)
rpc(s"${tracingStore.url}/tracings/volume/$volumeTracingId/duplicate").withLongTimeout
.addQueryString("token" -> RpcTokenHolder.webKnossosToken)
.addQueryString("fromTask" -> fromTask.toString)
.addQueryString("fromTask" -> isFromTask.toString)
.addQueryStringOptional("minResolution", resolutionRestrictions.minStr)
.addQueryStringOptional("maxResolution", resolutionRestrictions.maxStr)
.addQueryStringOptional("editPosition", editPosition.map(_.toUriLiteral))
.addQueryStringOptional("editRotation", editRotation.map(_.toUriLiteral))
.addQueryStringOptional("boundingBox", boundingBox.map(_.toLiteral))
.addQueryString("downsample" -> downsample.toString)
.postJsonWithJsonResponse[Option[BoundingBox], String](dataSetBoundingBox)
}
Expand Down
2 changes: 1 addition & 1 deletion app/models/job/Job.scala
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ class JobService @Inject()(wkConf: WkConf,

def assertTiffExportBoundingBoxLimits(bbox: String): Fox[Unit] =
for {
boundingBox <- BoundingBox.createFrom(bbox).toFox ?~> "job.export.tiff.invalidBoundingBox"
boundingBox <- BoundingBox.fromLiteral(bbox).toFox ?~> "job.export.tiff.invalidBoundingBox"
_ <- bool2Fox(boundingBox.volume <= wkConf.Features.exportTiffMaxVolumeMVx * 1024 * 1024) ?~> "job.export.tiff.volumeExceeded"
_ <- bool2Fox(boundingBox.dimensions.maxDim <= wkConf.Features.exportTiffMaxEdgeLengthVx) ?~> "job.export.tiff.edgeLengthExceeded"
} yield ()
Expand Down
7 changes: 6 additions & 1 deletion app/models/task/TaskCreationService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,12 @@ class TaskCreationService @Inject()(taskTypeService: TaskTypeService,
for {
baseSkeletonTracingIdOpt <- baseAnnotation.skeletonTracingId
newTracingId <- baseSkeletonTracingIdOpt
.map(id => tracingStoreClient.duplicateSkeletonTracing(id))
.map(
id =>
tracingStoreClient.duplicateSkeletonTracing(id,
editPosition = Some(params.editPosition),
editRotation = Some(params.editRotation),
boundingBox = params.boundingBox))
.getOrElse(
tracingStoreClient.saveSkeletonTracing(
annotationService.createSkeletonTracingBase(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.scalableminds.util.geometry

import net.liftweb.common.{Box, Empty, Full}
import net.liftweb.common.Full

case class BoundingBox(topLeft: Vec3Int, width: Int, height: Int, depth: Int) {

Expand Down Expand Up @@ -41,20 +41,21 @@ case class BoundingBox(topLeft: Vec3Int, width: Int, height: Int, depth: Int) {
def dimensions: Vec3Int =
Vec3Int(width, height, depth)

def toLiteral: String = f"${topLeft.x},${topLeft.y},${topLeft.z},$width,$height,$depth"
}

object BoundingBox {

import play.api.libs.json._

private val formRx = "\\s*([0-9]+),\\s*([0-9]+),\\s*([0-9]+)\\s*,\\s*([0-9]+),\\s*([0-9]+),\\s*([0-9]+)\\s*".r
private val literalPattern = "\\s*([0-9]+),\\s*([0-9]+),\\s*([0-9]+)\\s*,\\s*([0-9]+),\\s*([0-9]+),\\s*([0-9]+)\\s*".r

def empty: BoundingBox =
BoundingBox(Vec3Int(0, 0, 0), 0, 0, 0)

def createFrom(s: String): Box[BoundingBox] =
def fromLiteral(s: String): Option[BoundingBox] =
s match {
case formRx(minX, minY, minZ, width, height, depth) =>
case literalPattern(minX, minY, minZ, width, height, depth) =>
try {
Full(
BoundingBox(
Expand All @@ -64,12 +65,18 @@ object BoundingBox {
Integer.parseInt(depth)
))
} catch {
case _: NumberFormatException => Empty
case _: NumberFormatException => None
}
case _ =>
Empty
None
}

def fromSQL(ints: List[Int]): Option[BoundingBox] =
if (ints.length == 6)
Some(BoundingBox(Vec3Int(ints(0), ints(1), ints(2)), ints(3), ints(4), ints(5)))
else
None

def combine(bbs: List[BoundingBox]): BoundingBox =
bbs match {
case head :: tail =>
Expand All @@ -78,27 +85,5 @@ object BoundingBox {
BoundingBox(Vec3Int(0, 0, 0), 0, 0, 0)
}

def createFrom(bbox: List[List[Int]]): Box[BoundingBox] =
if (bbox.size < 3 || bbox(0).size < 2 || bbox(1).size < 2 || bbox(2).size < 2)
Empty
else
Full(
BoundingBox(Vec3Int(bbox(0)(0), bbox(1)(0), bbox(2)(0)),
bbox(0)(1) - bbox(0)(0),
bbox(1)(1) - bbox(1)(0),
bbox(2)(1) - bbox(2)(0)))

def createFrom(topLeft: Vec3Int, bottomRight: Vec3Int): Box[BoundingBox] =
if (topLeft <= bottomRight)
Full(BoundingBox(topLeft, bottomRight.x - topLeft.x, bottomRight.y - topLeft.y, bottomRight.z - topLeft.z))
else
Empty

def fromSQL(ints: List[Int]): Option[BoundingBox] =
if (ints.length == 6)
Some(BoundingBox(Vec3Int(ints(0), ints(1), ints(2)), ints(3), ints(4), ints(5)))
else
None

implicit val boundingBoxFormat: OFormat[BoundingBox] = Json.format[BoundingBox]
implicit val jsonFormat: OFormat[BoundingBox] = Json.format[BoundingBox]
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ case class Vec3Double(x: Double, y: Double, z: Double) {
def isStrictlyPositive: Boolean = x > 0 && y > 0 && z > 0

override def toString = s"($x, $y, $z)"

def toUriLiteral: String = s"$x,$y,$z"
}

object Vec3Double {
Expand All @@ -88,6 +90,13 @@ object Vec3Double {
def fromList(l: List[Double]): Option[Vec3Double] =
fromArray(l.toArray)

def fromUriLiteral(s: String): Option[Vec3Double] =
try {
fromArray(s.trim.split(",").map(java.lang.Double.parseDouble))
} catch {
case _: NumberFormatException => None
}

implicit object Vector3DReads extends Format[Vec3Double] {
def reads(json: JsValue): JsResult[Vec3Double] = json match {
case JsArray(ts) if ts.size == 3 =>
Expand Down
38 changes: 26 additions & 12 deletions util/src/main/scala/com/scalableminds/util/geometry/Vec3Int.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,22 @@ case class Vec3Int(x: Int, y: Int, z: Int) {
def toMagLiteral(allowScalar: Boolean = false): String =
if (allowScalar && isIsotropic) s"$x" else s"$x-$y-$z"

def toList = List(x, y, z)
def toUriLiteral: String = s"$x,$y,$z"

def move(dx: Int, dy: Int, dz: Int) =
def toList: List[Int] = List(x, y, z)

def move(dx: Int, dy: Int, dz: Int): Vec3Int =
Vec3Int(x + dx, y + dy, z + dz)

def move(other: Vec3Int): Vec3Int =
move(other.x, other.y, other.z)

def negate = Vec3Int(-x, -y, -z)
def negate: Vec3Int = Vec3Int(-x, -y, -z)

def to(bottomRight: Vec3Int) =
def to(bottomRight: Vec3Int): Seq[Vec3Int] =
range(bottomRight, _ to _)

def until(bottomRight: Vec3Int) =
def until(bottomRight: Vec3Int): Seq[Vec3Int] =
range(bottomRight, _ until _)

def maxDim: Int = Math.max(Math.max(x, y), z)
Expand All @@ -52,28 +54,40 @@ case class Vec3Int(x: Int, y: Int, z: Int) {

object Vec3Int {
private val magLiteralRegex = """(\d+)-(\d+)-(\d+)""".r
private val uriLiteralRegex = """(\d+),(\d+),(\d+)""".r

def fromMagLiteral(s: String, allowScalar: Boolean = false): Option[Vec3Int] =
s.toIntOpt match {
case Some(scalar) if allowScalar => Some(Vec3Int.full(scalar))
case _ =>
s match {
case magLiteralRegex(x, y, z) =>
Some(Vec3Int(Integer.parseInt(x), Integer.parseInt(y), Integer.parseInt(z)))
try {
Some(Vec3Int(Integer.parseInt(x), Integer.parseInt(y), Integer.parseInt(z)))
} catch {
case _: NumberFormatException => None
}
case _ =>
None
}
}

def fromArray[T <% Int](array: Array[T]) =
if (array.size >= 3)
Some(Vec3Int(array(0), array(1), array(2)))
def fromUriLiteral(s: String): Option[Vec3Int] = s match {
case uriLiteralRegex(x, y, z) =>
try {
Some(Vec3Int(Integer.parseInt(x), Integer.parseInt(y), Integer.parseInt(z)))
} catch {
case _: NumberFormatException => None
}
case _ => None
}

def fromList(l: List[Int]): Option[Vec3Int] =
if (l.length >= 3)
Some(Vec3Int(l.head, l(1), l(2)))
else
None

def fromList(l: List[Int]) =
fromArray(l.toArray)

def full(i: Int): Vec3Int = Vec3Int(i, i, i)

implicit object Vec3IntReads extends Reads[Vec3Int] {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.scalableminds.webknossos.tracingstore.controllers

import com.google.inject.Inject
import com.scalableminds.util.geometry.{BoundingBox, Vec3Double, Vec3Int}
import com.scalableminds.util.tools.Fox
import com.scalableminds.webknossos.datastore.SkeletonTracing.{SkeletonTracing, SkeletonTracingOpt, SkeletonTracings}
import com.scalableminds.webknossos.datastore.services.UserAccessRequest
import com.scalableminds.webknossos.tracingstore.slacknotification.TSSlackNotificationService
import com.scalableminds.webknossos.tracingstore.tracings.skeleton._
import com.scalableminds.webknossos.tracingstore.{TracingStoreAccessTokenService, TSRemoteWebKnossosClient}
import com.scalableminds.webknossos.tracingstore.{TSRemoteWebKnossosClient, TracingStoreAccessTokenService}
import play.api.i18n.Messages
import play.api.libs.json.Json
import play.api.mvc.{Action, AnyContent, PlayBodyParsers}
Expand Down Expand Up @@ -48,13 +50,23 @@ class SkeletonTracingController @Inject()(val tracingService: SkeletonTracingSer
def duplicate(token: Option[String],
tracingId: String,
version: Option[Long],
fromTask: Option[Boolean]): Action[AnyContent] =
fromTask: Option[Boolean],
editPosition: Option[String],
editRotation: Option[String],
boundingBox: Option[String]): Action[AnyContent] =
Action.async { implicit request =>
log() {
accessTokenService.validateAccess(UserAccessRequest.webknossos, token) {
for {
tracing <- tracingService.find(tracingId, version, applyUpdates = true) ?~> Messages("tracing.notFound")
newId <- tracingService.duplicate(tracing, fromTask.getOrElse(false))
editPositionParsed <- Fox.runOptional(editPosition)(Vec3Int.fromUriLiteral)
editRotationParsed <- Fox.runOptional(editRotation)(Vec3Double.fromUriLiteral)
boundingBoxParsed <- Fox.runOptional(boundingBox)(BoundingBox.fromLiteral)
newId <- tracingService.duplicate(tracing,
fromTask.getOrElse(false),
editPositionParsed,
editRotationParsed,
boundingBoxParsed)
} yield {
Ok(Json.toJson(newId))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
package com.scalableminds.webknossos.tracingstore.controllers

import akka.http.caching.LfuCache
import akka.http.caching.scaladsl.{Cache, CachingSettings}

import java.io.File
import java.nio.{ByteBuffer, ByteOrder}

import akka.http.caching.LfuCache
import akka.http.caching.scaladsl.{Cache, CachingSettings}
import akka.stream.scaladsl.Source
import com.google.inject.Inject
import com.scalableminds.util.geometry.{BoundingBox, Vec3Double, Vec3Int}
import com.scalableminds.util.tools.ExtendedTypes.ExtendedString
import com.scalableminds.util.tools.Fox
import com.scalableminds.webknossos.datastore.models.datasource.{DataLayer, DataSourceId, ElementClass}
import com.scalableminds.webknossos.datastore.models.{WebKnossosDataRequest, WebKnossosIsosurfaceRequest}
import com.scalableminds.webknossos.datastore.services.UserAccessRequest
import com.scalableminds.webknossos.datastore.VolumeTracing.{VolumeTracing, VolumeTracingOpt, VolumeTracings}
import com.scalableminds.webknossos.datastore.dataformats.zarr.ZarrCoordinatesParser
import com.scalableminds.webknossos.datastore.helpers.ProtoGeometryImplicits
import com.scalableminds.webknossos.datastore.jzarr.{ArrayOrder, OmeNgffHeader, ZarrHeader}
import com.scalableminds.webknossos.datastore.models.datasource.{DataLayer, ElementClass}
import com.scalableminds.webknossos.datastore.models.{WebKnossosDataRequest, WebKnossosIsosurfaceRequest}
import com.scalableminds.webknossos.datastore.rpc.RPC
import com.scalableminds.webknossos.datastore.services.UserAccessRequest
import com.scalableminds.webknossos.tracingstore.slacknotification.TSSlackNotificationService
import com.scalableminds.webknossos.tracingstore.tracings.volume.{ResolutionRestrictions, VolumeTracingService}
import com.scalableminds.webknossos.tracingstore.{
Expand Down Expand Up @@ -159,19 +159,28 @@ class VolumeTracingController @Inject()(
fromTask: Option[Boolean],
minResolution: Option[Int],
maxResolution: Option[Int],
downsample: Option[Boolean]): Action[AnyContent] = Action.async { implicit request =>
downsample: Option[Boolean],
editPosition: Option[String],
editRotation: Option[String],
boundingBox: Option[String]): Action[AnyContent] = Action.async { implicit request =>
log() {
logTime(slackNotificationService.noticeSlowRequest) {
accessTokenService.validateAccess(UserAccessRequest.webknossos, token) {
for {
tracing <- tracingService.find(tracingId) ?~> Messages("tracing.notFound")
dataSetBoundingBox = request.body.asJson.flatMap(_.validateOpt[BoundingBox].asOpt.flatten)
resolutionRestrictions = ResolutionRestrictions(minResolution, maxResolution)
editPositionParsed <- Fox.runOptional(editPosition)(Vec3Int.fromUriLiteral)
editRotationParsed <- Fox.runOptional(editRotation)(Vec3Double.fromUriLiteral)
boundingBoxParsed <- Fox.runOptional(boundingBox)(BoundingBox.fromLiteral)
(newId, newTracing) <- tracingService.duplicate(tracingId,
tracing,
fromTask.getOrElse(false),
dataSetBoundingBox,
resolutionRestrictions)
resolutionRestrictions,
editPositionParsed,
editRotationParsed,
boundingBoxParsed)
_ <- Fox.runIfOptionTrue(downsample)(tracingService.downsample(newId, newTracing))
} yield Ok(Json.toJson(newId))
}
Expand Down
Loading

0 comments on commit 03e565b

Please sign in to comment.