Skip to content

Commit e001ca1

Browse files
committed
Merge branch 'master' into realpaths
2 parents e7772b2 + 3b946f8 commit e001ca1

File tree

97 files changed

+2243
-1108
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

97 files changed

+2243
-1108
lines changed

CHANGELOG.unreleased.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,18 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
1111
[Commits](https://github.com/scalableminds/webknossos/compare/25.01.0...HEAD)
1212

1313
### Added
14+
- It is now possible to start a split-merger evaluation when starting a neuron inference. [#8221](https://github.com/scalableminds/webknossos/pull/8221)
15+
- Added the possibility to configure a rotation for a dataset, which can be toggled off and on when viewing and annotating data. [#8159](https://github.com/scalableminds/webknossos/pull/8159)
1416
- When using the “Restore older Version” feature, there are no longer separate tabs for the different annotation layers. Only one linear annotation history is now used, and if you revert to an older version, all layers are reverted. If layers were added/deleted since then, that is also reverted. This also means that proofreading annotations can now be reverted to older versions as well. The description text of annotations is now versioned as well. [#7917](https://github.com/scalableminds/webknossos/pull/7917)
1517
- Added the possibility to use the "merger mode" even when the user has annotated volume data in the current layer (as long as no other mapping is active). [#8335](https://github.com/scalableminds/webknossos/pull/8335)
18+
- Measurement tools are now accessible when viewing datasets outside of an annotation. [#8334](https://github.com/scalableminds/webknossos/pull/8334)
1619

1720
### Changed
1821

1922
### Fixed
20-
23+
- Fixed a bug that lead to trees being dropped when merging to trees together. [#8359](https://github.com/scalableminds/webknossos/pull/8359)
24+
- Fixed that the onboarding screen incorrectly appeared when a certain request failed. [#8356](https://github.com/scalableminds/webknossos/pull/8356)
25+
- Fixed the segment registering in coarser mags for non-mag-aligned bounding boxes. [#8364](https://github.com/scalableminds/webknossos/pull/8364)
2126

2227
### Removed
2328
- Removed the feature to downsample existing volume annotations. All new volume annotations had a whole mag stack since [#4755](https://github.com/scalableminds/webknossos/pull/4755) (four years ago). [#7917](https://github.com/scalableminds/webknossos/pull/7917)

MIGRATIONS.released.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ User-facing changes are documented in the [changelog](CHANGELOG.released.md).
1010
[Commits](https://github.com/scalableminds/webknossos/compare/24.12.0...25.01.0)
1111
- Removed support for HTTP API versions 3 and 4. [#8075](https://github.com/scalableminds/webknossos/pull/8075)
1212
- New FossilDB version `0.1.33` (docker image `scalableminds/fossildb:master__504`) is required.
13-
- Datastore config options `datastore.baseFolder` and `localFolderWhitelist` to `datastore.baseDirectory` and `localDirectoryWhitelist` respectively, to avoid confusion with the dashboard folders. [#8292](https://github.com/scalableminds/webknossos/pull/8292)
13+
- Datastore config options `datastore.baseFolder` and `datastore.localFolderWhitelist` have been renamed to `datastore.baseDirectory` and `datastore.localDirectoryWhitelist` respectively, to avoid confusion with the dashboard folders. [#8292](https://github.com/scalableminds/webknossos/pull/8292)
1414

1515
### Postgres Evolutions:
1616
- [124-decouple-dataset-directory-from-name.sql](conf/evolutions/124-decouple-dataset-directory-from-name.sql)

app/controllers/AiModelController.scala

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -86,23 +86,21 @@ class AiModelController @Inject()(
8686
extends Controller
8787
with FoxImplicits {
8888

89-
def readAiModelInfo(aiModelId: String): Action[AnyContent] = sil.SecuredAction.async { implicit request =>
89+
def readAiModelInfo(aiModelId: ObjectId): Action[AnyContent] = sil.SecuredAction.async { implicit request =>
9090
{
9191
for {
9292
_ <- userService.assertIsSuperUser(request.identity)
93-
aiModelIdValidated <- ObjectId.fromString(aiModelId)
94-
aiModel <- aiModelDAO.findOne(aiModelIdValidated) ?~> "aiModel.notFound" ~> NOT_FOUND
93+
aiModel <- aiModelDAO.findOne(aiModelId) ?~> "aiModel.notFound" ~> NOT_FOUND
9594
jsResult <- aiModelService.publicWrites(aiModel)
9695
} yield Ok(jsResult)
9796
}
9897
}
9998

100-
def readAiInferenceInfo(aiInferenceId: String): Action[AnyContent] = sil.SecuredAction.async { implicit request =>
99+
def readAiInferenceInfo(aiInferenceId: ObjectId): Action[AnyContent] = sil.SecuredAction.async { implicit request =>
101100
{
102101
for {
103102
_ <- userService.assertIsSuperUser(request.identity)
104-
aiInferenceIdValidated <- ObjectId.fromString(aiInferenceId)
105-
aiInference <- aiInferenceDAO.findOne(aiInferenceIdValidated) ?~> "aiInference.notFound" ~> NOT_FOUND
103+
aiInference <- aiInferenceDAO.findOne(aiInferenceId) ?~> "aiInference.notFound" ~> NOT_FOUND
106104
jsResult <- aiInferenceService.publicWrites(aiInference, request.identity)
107105
} yield Ok(jsResult)
108106
}
@@ -213,15 +211,14 @@ class AiModelController @Inject()(
213211
} yield Ok(newAiModelJs)
214212
}
215213

216-
def updateAiModelInfo(aiModelId: String): Action[UpdateAiModelParameters] =
214+
def updateAiModelInfo(aiModelId: ObjectId): Action[UpdateAiModelParameters] =
217215
sil.SecuredAction.async(validateJson[UpdateAiModelParameters]) { implicit request =>
218216
for {
219217
_ <- userService.assertIsSuperUser(request.identity)
220-
aiModelIdValidated <- ObjectId.fromString(aiModelId)
221-
aiModel <- aiModelDAO.findOne(aiModelIdValidated) ?~> "aiModel.notFound" ~> NOT_FOUND
218+
aiModel <- aiModelDAO.findOne(aiModelId) ?~> "aiModel.notFound" ~> NOT_FOUND
222219
_ <- aiModelDAO.updateOne(
223220
aiModel.copy(name = request.body.name, comment = request.body.comment, modified = Instant.now))
224-
updatedAiModel <- aiModelDAO.findOne(aiModelIdValidated) ?~> "aiModel.notFound" ~> NOT_FOUND
221+
updatedAiModel <- aiModelDAO.findOne(aiModelId) ?~> "aiModel.notFound" ~> NOT_FOUND
225222
jsResult <- aiModelService.publicWrites(updatedAiModel)
226223
} yield Ok(jsResult)
227224
}
@@ -248,15 +245,14 @@ class AiModelController @Inject()(
248245
} yield Ok
249246
}
250247

251-
def deleteAiModel(aiModelId: String): Action[AnyContent] =
248+
def deleteAiModel(aiModelId: ObjectId): Action[AnyContent] =
252249
sil.SecuredAction.async { implicit request =>
253250
for {
254251
_ <- userService.assertIsSuperUser(request.identity)
255-
aiModelIdValidated <- ObjectId.fromString(aiModelId)
256-
referencesCount <- aiInferenceDAO.countForModel(aiModelIdValidated)
252+
referencesCount <- aiInferenceDAO.countForModel(aiModelId)
257253
_ <- bool2Fox(referencesCount == 0) ?~> "aiModel.delete.referencedByInferences"
258-
_ <- aiModelDAO.findOne(aiModelIdValidated) ?~> "aiModel.notFound" ~> NOT_FOUND
259-
_ <- aiModelDAO.deleteOne(aiModelIdValidated)
254+
_ <- aiModelDAO.findOne(aiModelId) ?~> "aiModel.notFound" ~> NOT_FOUND
255+
_ <- aiModelDAO.deleteOne(aiModelId)
260256
} yield Ok
261257
}
262258

app/controllers/AnnotationController.scala

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class AnnotationController @Inject()(
6767
def info( // Type of the annotation, one of Task, Explorational, CompoundTask, CompoundProject, CompoundTaskType
6868
typ: String,
6969
// For Task and Explorational annotations, id is an annotation id. For CompoundTask, id is a task id. For CompoundProject, id is a project id. For CompoundTaskType, id is a task type id
70-
id: String,
70+
id: ObjectId,
7171
// Timestamp in milliseconds (time at which the request is sent)
7272
timestamp: Option[Long]): Action[AnyContent] = sil.UserAwareAction.async { implicit request =>
7373
log() {
@@ -99,7 +99,7 @@ class AnnotationController @Inject()(
9999
}
100100
}
101101

102-
def infoWithoutType(id: String,
102+
def infoWithoutType(id: ObjectId,
103103
// Timestamp in milliseconds (time at which the request is sent)
104104
timestamp: Option[Long]): Action[AnyContent] = sil.UserAwareAction.async { implicit request =>
105105
log() {
@@ -111,7 +111,7 @@ class AnnotationController @Inject()(
111111
}
112112
}
113113

114-
def merge(typ: String, id: String, mergedTyp: String, mergedId: String): Action[AnyContent] =
114+
def merge(typ: String, id: ObjectId, mergedTyp: String, mergedId: ObjectId): Action[AnyContent] =
115115
sil.SecuredAction.async { implicit request =>
116116
for {
117117
annotationA <- provider.provideAnnotation(typ, id, request.identity) ?~> "annotation.notFound" ~> NOT_FOUND
@@ -124,15 +124,15 @@ class AnnotationController @Inject()(
124124
} yield JsonOk(js, Messages("annotation.merge.success"))
125125
}
126126

127-
def mergeWithoutType(id: String, mergedTyp: String, mergedId: String): Action[AnyContent] =
127+
def mergeWithoutType(id: ObjectId, mergedTyp: String, mergedId: ObjectId): Action[AnyContent] =
128128
sil.SecuredAction.async { implicit request =>
129129
for {
130130
annotation <- provider.provideAnnotation(id, request.identity) ?~> "annotation.notFound" ~> NOT_FOUND
131131
result <- merge(annotation.typ.toString, id, mergedTyp, mergedId)(request)
132132
} yield result
133133
}
134134

135-
def reset(typ: String, id: String): Action[AnyContent] = sil.SecuredAction.async { implicit request =>
135+
def reset(typ: String, id: ObjectId): Action[AnyContent] = sil.SecuredAction.async { implicit request =>
136136
for {
137137
annotation <- provider.provideAnnotation(typ, id, request.identity) ?~> "annotation.notFound" ~> NOT_FOUND
138138
_ <- Fox.assertTrue(userService.isTeamManagerOrAdminOf(request.identity, annotation._team))
@@ -142,7 +142,7 @@ class AnnotationController @Inject()(
142142
} yield JsonOk(json, Messages("annotation.reset.success"))
143143
}
144144

145-
def reopen(typ: String, id: String): Action[AnyContent] = sil.SecuredAction.async { implicit request =>
145+
def reopen(typ: String, id: ObjectId): Action[AnyContent] = sil.SecuredAction.async { implicit request =>
146146
def isReopenAllowed(user: User, annotation: Annotation) =
147147
for {
148148
isAdminOrTeamManager <- userService.isTeamManagerOrAdminOf(user, annotation._team)
@@ -162,8 +162,8 @@ class AnnotationController @Inject()(
162162
} yield JsonOk(json, Messages("annotation.reopened"))
163163
}
164164

165-
def editLockedState(typ: String, id: String, isLockedByOwner: Boolean): Action[AnyContent] = sil.SecuredAction.async {
166-
implicit request =>
165+
def editLockedState(typ: String, id: ObjectId, isLockedByOwner: Boolean): Action[AnyContent] =
166+
sil.SecuredAction.async { implicit request =>
167167
for {
168168
annotation <- provider.provideAnnotation(typ, id, request.identity)
169169
_ <- bool2Fox(annotation._user == request.identity._id) ?~> "annotation.isLockedByOwner.notAllowed"
@@ -174,13 +174,12 @@ class AnnotationController @Inject()(
174174
updatedAnnotation <- provider.provideAnnotation(typ, id, request.identity) ~> NOT_FOUND
175175
json <- annotationService.publicWrites(updatedAnnotation, Some(request.identity)) ?~> "annotation.write.failed"
176176
} yield JsonOk(json, Messages("annotation.isLockedByOwner.success"))
177-
}
177+
}
178178

179-
def createExplorational(datasetId: String): Action[List[AnnotationLayerParameters]] =
179+
def createExplorational(datasetId: ObjectId): Action[List[AnnotationLayerParameters]] =
180180
sil.SecuredAction.async(validateJson[List[AnnotationLayerParameters]]) { implicit request =>
181181
for {
182-
datasetIdValidated <- ObjectId.fromString(datasetId)
183-
dataset <- datasetDAO.findOne(datasetIdValidated) ?~> Messages("dataset.notFound", datasetIdValidated) ~> NOT_FOUND
182+
dataset <- datasetDAO.findOne(datasetId) ?~> Messages("dataset.notFound", datasetId) ~> NOT_FOUND
184183
annotation <- annotationService.createExplorationalFor(
185184
request.identity,
186185
dataset._id,
@@ -192,12 +191,11 @@ class AnnotationController @Inject()(
192191
} yield JsonOk(json)
193192
}
194193

195-
def getSandbox(datasetId: String, typ: String, sharingToken: Option[String]): Action[AnyContent] =
194+
def getSandbox(datasetId: ObjectId, typ: String, sharingToken: Option[String]): Action[AnyContent] =
196195
sil.UserAwareAction.async { implicit request =>
197196
val ctx = URLSharing.fallbackTokenAccessContext(sharingToken) // users with dataset sharing token may also get a sandbox annotation
198197
for {
199-
datasetIdValidated <- ObjectId.fromString(datasetId)
200-
dataset <- datasetDAO.findOne(datasetIdValidated)(ctx) ?~> Messages("dataset.notFound", datasetIdValidated) ~> NOT_FOUND
198+
dataset <- datasetDAO.findOne(datasetId)(ctx) ?~> Messages("dataset.notFound", datasetId) ~> NOT_FOUND
201199
tracingType <- TracingType.fromString(typ).toFox
202200
_ <- bool2Fox(tracingType == TracingType.skeleton) ?~> "annotation.sandbox.skeletonOnly"
203201
annotation = Annotation(
@@ -216,7 +214,7 @@ class AnnotationController @Inject()(
216214
} yield JsonOk(json)
217215
}
218216

219-
private def finishAnnotation(typ: String, id: String, issuingUser: User, timestamp: Instant)(
217+
private def finishAnnotation(typ: String, id: ObjectId, issuingUser: User, timestamp: Instant)(
220218
implicit ctx: DBAccessContext): Fox[(Annotation, String)] =
221219
for {
222220
annotation <- provider.provideAnnotation(typ, id, issuingUser) ~> NOT_FOUND
@@ -226,7 +224,7 @@ class AnnotationController @Inject()(
226224
_ <- timeSpanService.logUserInteractionIfTheyArePotentialContributor(timestamp, issuingUser, annotation) // log time on tracing end
227225
} yield (updated, message)
228226

229-
def finish(typ: String, id: String, timestamp: Long): Action[AnyContent] = sil.SecuredAction.async {
227+
def finish(typ: String, id: ObjectId, timestamp: Long): Action[AnyContent] = sil.SecuredAction.async {
230228
implicit request =>
231229
log() {
232230
for {
@@ -242,7 +240,10 @@ class AnnotationController @Inject()(
242240
log() {
243241
withJsonAs[JsArray](request.body \ "annotations") { annotationIds =>
244242
val results = Fox.serialSequence(annotationIds.value.toList) { jsValue =>
245-
jsValue.asOpt[String].toFox.flatMap(id => finishAnnotation(typ, id, request.identity, Instant(timestamp)))
243+
jsValue
244+
.asOpt[String]
245+
.toFox
246+
.flatMap(id => finishAnnotation(typ, ObjectId(id), request.identity, Instant(timestamp)))
246247
}
247248

248249
results.map { _ =>
@@ -252,7 +253,7 @@ class AnnotationController @Inject()(
252253
}
253254
}
254255

255-
def editAnnotation(typ: String, id: String): Action[JsValue] = sil.SecuredAction.async(parse.json) {
256+
def editAnnotation(typ: String, id: ObjectId): Action[JsValue] = sil.SecuredAction.async(parse.json) {
256257
implicit request =>
257258
for {
258259
annotation <- provider.provideAnnotation(typ, id, request.identity) ~> NOT_FOUND
@@ -276,7 +277,7 @@ class AnnotationController @Inject()(
276277
} yield JsonOk(Messages("annotation.edit.success"))
277278
}
278279

279-
def editAnnotationLayer(typ: String, id: String, tracingId: String): Action[JsValue] =
280+
def editAnnotationLayer(typ: String, id: ObjectId, tracingId: String): Action[JsValue] =
280281
sil.SecuredAction.async(parse.json) { implicit request =>
281282
for {
282283
annotation <- provider.provideAnnotation(typ, id, request.identity) ~> NOT_FOUND
@@ -287,19 +288,18 @@ class AnnotationController @Inject()(
287288
} yield JsonOk(Messages("annotation.edit.success"))
288289
}
289290

290-
def annotationsForTask(taskId: String): Action[AnyContent] =
291+
def annotationsForTask(taskId: ObjectId): Action[AnyContent] =
291292
sil.SecuredAction.async { implicit request =>
292293
for {
293-
taskIdValidated <- ObjectId.fromString(taskId)
294-
task <- taskDAO.findOne(taskIdValidated) ?~> "task.notFound" ~> NOT_FOUND
294+
task <- taskDAO.findOne(taskId) ?~> "task.notFound" ~> NOT_FOUND
295295
project <- projectDAO.findOne(task._project)
296296
_ <- Fox.assertTrue(userService.isTeamManagerOrAdminOf(request.identity, project._team))
297297
annotations <- annotationService.annotationsFor(task._id) ?~> "task.annotation.failed"
298298
jsons <- Fox.serialSequence(annotations)(a => annotationService.publicWrites(a, Some(request.identity)))
299299
} yield Ok(JsArray(jsons.flatten))
300300
}
301301

302-
def cancel(typ: String, id: String): Action[AnyContent] = sil.SecuredAction.async { implicit request =>
302+
def cancel(typ: String, id: ObjectId): Action[AnyContent] = sil.SecuredAction.async { implicit request =>
303303
def tryToCancel(annotation: Annotation) =
304304
annotation match {
305305
case t if t.typ == AnnotationType.Task =>
@@ -319,14 +319,14 @@ class AnnotationController @Inject()(
319319
} yield result
320320
}
321321

322-
def cancelWithoutType(id: String): Action[AnyContent] = sil.SecuredAction.async { implicit request =>
322+
def cancelWithoutType(id: ObjectId): Action[AnyContent] = sil.SecuredAction.async { implicit request =>
323323
for {
324324
annotation <- provider.provideAnnotation(id, request.identity) ~> NOT_FOUND
325325
result <- cancel(annotation.typ.toString, id)(request)
326326
} yield result
327327
}
328328

329-
def transfer(typ: String, id: String): Action[JsValue] = sil.SecuredAction.async(parse.json) { implicit request =>
329+
def transfer(typ: String, id: ObjectId): Action[JsValue] = sil.SecuredAction.async(parse.json) { implicit request =>
330330
for {
331331
restrictions <- provider.restrictionsFor(typ, id) ?~> "restrictions.notFound" ~> NOT_FOUND
332332
_ <- restrictions.allowFinish(request.identity) ?~> "notAllowed" ~> FORBIDDEN
@@ -337,10 +337,10 @@ class AnnotationController @Inject()(
337337
} yield JsonOk(json)
338338
}
339339

340-
def duplicate(typ: String, id: String): Action[AnyContent] = sil.SecuredAction.async { implicit request =>
340+
def duplicate(typ: String, id: ObjectId): Action[AnyContent] = sil.SecuredAction.async { implicit request =>
341341
for {
342342
annotation <- provider.provideAnnotation(typ, id, request.identity) ~> NOT_FOUND
343-
newAnnotation <- duplicateAnnotation(annotation, request.identity)
343+
newAnnotation <- duplicateAnnotation(annotation, request.identity) ?~> "annotation.duplicate.failed"
344344
restrictions <- provider.restrictionsFor(typ, id) ?~> "restrictions.notFound"
345345
json <- annotationService
346346
.publicWrites(newAnnotation, Some(request.identity), Some(restrictions)) ?~> "annotation.write.failed"
@@ -376,7 +376,7 @@ class AnnotationController @Inject()(
376376

377377
}
378378

379-
def getSharedTeams(typ: String, id: String): Action[AnyContent] = sil.SecuredAction.async { implicit request =>
379+
def getSharedTeams(typ: String, id: ObjectId): Action[AnyContent] = sil.SecuredAction.async { implicit request =>
380380
for {
381381
annotation <- provider.provideAnnotation(typ, id, request.identity)
382382
_ <- bool2Fox(annotation._user == request.identity._id) ?~> "notAllowed" ~> FORBIDDEN
@@ -385,7 +385,7 @@ class AnnotationController @Inject()(
385385
} yield Ok(Json.toJson(json))
386386
}
387387

388-
def updateSharedTeams(typ: String, id: String): Action[JsValue] = sil.SecuredAction.async(parse.json) {
388+
def updateSharedTeams(typ: String, id: ObjectId): Action[JsValue] = sil.SecuredAction.async(parse.json) {
389389
implicit request =>
390390
withJsonBodyAs[List[String]] { teams =>
391391
for {
@@ -399,7 +399,7 @@ class AnnotationController @Inject()(
399399
}
400400
}
401401

402-
def updateOthersMayEdit(typ: String, id: String, othersMayEdit: Boolean): Action[AnyContent] =
402+
def updateOthersMayEdit(typ: String, id: ObjectId, othersMayEdit: Boolean): Action[AnyContent] =
403403
sil.SecuredAction.async { implicit request =>
404404
for {
405405
annotation <- provider.provideAnnotation(typ, id, request.identity)
@@ -438,14 +438,13 @@ class AnnotationController @Inject()(
438438
_ <- annotationDAO.insertOne(clonedAnnotation)
439439
} yield clonedAnnotation
440440

441-
def tryAcquiringAnnotationMutex(id: String): Action[AnyContent] =
441+
def tryAcquiringAnnotationMutex(id: ObjectId): Action[AnyContent] =
442442
sil.SecuredAction.async { implicit request =>
443443
logTime(slackNotificationService.noticeSlowRequest, durationThreshold = 1 second) {
444444
for {
445-
idValidated <- ObjectId.fromString(id)
446445
annotation <- provider.provideAnnotation(id, request.identity) ~> NOT_FOUND
447446
_ <- bool2Fox(annotation.othersMayEdit) ?~> "notAllowed" ~> FORBIDDEN
448-
restrictions <- provider.restrictionsFor(AnnotationIdentifier(annotation.typ, idValidated)) ?~> "restrictions.notFound" ~> NOT_FOUND
447+
restrictions <- provider.restrictionsFor(AnnotationIdentifier(annotation.typ, id)) ?~> "restrictions.notFound" ~> NOT_FOUND
449448
_ <- restrictions.allowUpdate(request.identity) ?~> "notAllowed" ~> FORBIDDEN
450449
mutexResult <- annotationMutexService.tryAcquiringAnnotationMutex(annotation._id, request.identity._id) ?~> "annotation.mutex.failed"
451450
resultJson <- annotationMutexService.publicWrites(mutexResult)

0 commit comments

Comments
 (0)