Skip to content

Commit 0b21b58

Browse files
committed
Merge branch 'master' into upload-mags-attachments
2 parents 2f4d28d + 0b031be commit 0b21b58

File tree

301 files changed

+8845
-4861
lines changed

Some content is hidden

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

301 files changed

+8845
-4861
lines changed

.gitignore

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ conf/application.conf-e
3636
dev-db/
3737
docker-compose.override.yml
3838
fossildb-dev/
39-
!frontend/javascripts/test/snapshots/type-check/.gitkeep
40-
!frontend/javascripts/test/snapshots/debug-htmls/.gitkeep
41-
frontend/javascripts/test/snapshots/debug-htmls/*
42-
frontend/javascripts/test/snapshots/type-check/*
39+
frontend/javascripts/test/snapshots/debug_htmls/*
40+
frontend/javascripts/test/snapshots/type_check/*
41+
!frontend/javascripts/test/snapshots/type_check/.gitkeep
42+
!frontend/javascripts/test/snapshots/debug_htmls/.gitkeep
4343
logs/
4444
project/boot/
4545
project/plugins/project/
@@ -121,4 +121,4 @@ tools/debugging/version-visualizer/data/*
121121
tools/**/.yarn/*
122122
public/index.html
123123

124-
tsconfig.tsbuildinfo
124+
tsconfig.tsbuildinfo

app/controllers/AiModelController.scala

Lines changed: 82 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import com.scalableminds.util.objectid.ObjectId
1717
import javax.inject.Inject
1818
import scala.concurrent.ExecutionContext
1919
import com.scalableminds.util.time.Instant
20+
import com.scalableminds.webknossos.datastore.rpc.RPC
2021
import com.scalableminds.webknossos.datastore.helpers.UPath
2122
import models.aimodels.AiModelCategory.AiModelCategory
2223
import models.organization.{OrganizationDAO, OrganizationService}
@@ -52,17 +53,21 @@ object RunInstanceModelTrainingParameters {
5253
implicit val jsonFormat: OFormat[RunInstanceModelTrainingParameters] = Json.format[RunInstanceModelTrainingParameters]
5354
}
5455

55-
case class RunInferenceParameters(annotationId: Option[ObjectId],
56-
aiModelId: ObjectId,
57-
datasetDirectoryName: String,
58-
organizationId: String,
56+
case class RunInferenceParameters(datasetId: ObjectId,
57+
aiModelId: Option[ObjectId],
5958
colorLayerName: String,
60-
boundingBox: String,
61-
newDatasetName: String,
59+
boundingBox: String, // Always in mag1
60+
annotationId: Option[ObjectId],
6261
maskAnnotationLayerName: Option[String],
62+
newDatasetName: String,
6363
workflowYaml: Option[String],
6464
invertColorLayer: Option[Boolean],
65-
seedGeneratorDistanceThreshold: Option[Double])
65+
seedGeneratorDistanceThreshold: Option[Double],
66+
doSplitMergerEvaluation: Boolean = false,
67+
evalUseSparseTracing: Option[Boolean],
68+
evalMaxEdgeLength: Option[Double],
69+
evalSparseTubeThresholdNm: Option[Double],
70+
evalMinMergerPathLengthNm: Option[Double])
6671

6772
object RunInferenceParameters {
6873
implicit val jsonFormat: OFormat[RunInferenceParameters] = Json.format[RunInferenceParameters]
@@ -101,24 +106,32 @@ class AiModelController @Inject()(
101106
jobService: JobService,
102107
datasetDAO: DatasetDAO,
103108
dataStoreDAO: DataStoreDAO,
109+
rpc: RPC,
104110
uploadToPathsService: UploadToPathsService)(implicit ec: ExecutionContext, bodyParsers: PlayBodyParsers)
105111
extends Controller
106112
with FoxImplicits {
107113

108114
def readAiModelInfo(aiModelId: ObjectId): Action[AnyContent] = sil.SecuredAction.async { implicit request =>
109-
{
110-
for {
111-
_ <- userService.assertIsSuperUser(request.identity)
112-
aiModel <- aiModelDAO.findOne(aiModelId) ?~> "aiModel.notFound" ~> NOT_FOUND
113-
jsResult <- aiModelService.publicWrites(aiModel, request.identity)
114-
} yield Ok(jsResult)
115-
}
115+
for {
116+
_ <- organizationService.assertIsSuperUserOrOrganizationHasAiPlan(request.identity)
117+
aiModel <- aiModelDAO.findOne(aiModelId) ?~> "aiModel.notFound" ~> NOT_FOUND
118+
jsResult <- aiModelService.publicWrites(aiModel, request.identity)
119+
} yield Ok(jsResult)
120+
}
121+
122+
def aiModelVoxelSize(aiModelId: ObjectId): Action[AnyContent] = sil.SecuredAction.async { implicit request =>
123+
for {
124+
_ <- organizationService.assertIsSuperUserOrOrganizationHasAiPlan(request.identity)
125+
aiModel <- aiModelDAO.findOne(aiModelId) ?~> "aiModel.notFound" ~> NOT_FOUND
126+
dataStore <- dataStoreDAO.findOneByName(aiModel._dataStore)
127+
voxelSize <- aiModelService.findModelVoxelSize(Some(aiModel), usePretrainedNeuronModel = false, dataStore)
128+
} yield Ok(Json.toJson(voxelSize))
116129
}
117130

118131
def readAiInferenceInfo(aiInferenceId: ObjectId): Action[AnyContent] = sil.SecuredAction.async { implicit request =>
119132
{
120133
for {
121-
_ <- userService.assertIsSuperUser(request.identity)
134+
_ <- organizationService.assertIsSuperUserOrOrganizationHasAiPlan(request.identity)
122135
aiInference <- aiInferenceDAO.findOne(aiInferenceId) ?~> "aiInference.notFound" ~> NOT_FOUND
123136
jsResult <- aiInferenceService.publicWrites(aiInference, request.identity)
124137
} yield Ok(jsResult)
@@ -137,7 +150,7 @@ class AiModelController @Inject()(
137150
def listAiInferences: Action[AnyContent] = sil.SecuredAction.async { implicit request =>
138151
{
139152
for {
140-
_ <- userService.assertIsSuperUser(request.identity)
153+
_ <- organizationService.assertIsSuperUserOrOrganizationHasAiPlan(request.identity)
141154
aiInferences <- aiInferenceDAO.findAll
142155
jsResults <- Fox.serialCombined(aiInferences)(inference =>
143156
aiInferenceService.publicWrites(inference, request.identity))
@@ -260,36 +273,45 @@ class AiModelController @Inject()(
260273
} yield Ok(newAiModelJs)
261274
}
262275

263-
def runCustomInstanceModelInference: Action[RunInferenceParameters] =
276+
// If no model is selected, the pretrained *nuclei model* is used
277+
def runInstanceModelInference: Action[RunInferenceParameters] =
264278
sil.SecuredAction.async(validateJson[RunInferenceParameters]) { implicit request =>
265279
for {
266-
organization <- organizationDAO.findOne(request.body.organizationId)(GlobalAccessContext) ?~> Messages(
267-
"organization.notFound",
268-
request.body.organizationId)
269-
_ <- Fox.fromBool(request.identity._organization == organization._id) ?~> "job.runInference.notAllowed.organization" ~> FORBIDDEN
270-
dataset <- datasetDAO.findOneByDirectoryNameAndOrganization(request.body.datasetDirectoryName, organization._id)
280+
dataset <- datasetDAO.findOne(request.body.datasetId)
281+
_ <- Fox.fromBool(request.identity._organization == dataset._organization) ?~> "job.runInference.notAllowed.organization" ~> FORBIDDEN
282+
aiModelOpt <- Fox.runOptional(request.body.aiModelId)(aiModelDAO.findOne) ?~> "aiModel.notFound"
283+
_ <- Fox.runOptional(aiModelOpt) { aiModel =>
284+
Fox.fromBool(aiModel._dataStore == dataset._dataStore) ?~> "aiModel.dataStoreMismatch"
285+
}
286+
(dataSource, layer) <- datasetService.getDataSourceAndLayerFor(dataset, request.body.colorLayerName)
271287
dataStore <- dataStoreDAO.findOneByName(dataset._dataStore) ?~> "dataStore.notFound"
272-
aiModel <- aiModelDAO.findOne(request.body.aiModelId) ?~> "aiModel.notFound"
273288
_ <- datasetService.assertValidDatasetName(request.body.newDatasetName)
274289
jobCommand = JobCommand.infer_instances
275-
boundingBox <- BoundingBox.fromLiteral(request.body.boundingBox).toFox
290+
mag1BoundingBox <- BoundingBox.fromLiteral(request.body.boundingBox).toFox
276291
commandArgs = Json.obj(
277292
"dataset_id" -> dataset._id,
278-
"organization_id" -> organization._id,
293+
"organization_id" -> dataset._organization,
279294
"dataset_name" -> dataset.name,
280295
"layer_name" -> request.body.colorLayerName,
281-
"bbox" -> boundingBox.toLiteral,
296+
"bbox" -> mag1BoundingBox.toLiteral,
282297
"model_id" -> request.body.aiModelId,
283-
"model_organization_id" -> aiModel._organization,
284-
"dataset_directory_name" -> request.body.datasetDirectoryName,
298+
"model_organization_id" -> aiModelOpt.map(_._organization),
299+
"dataset_directory_name" -> dataset.directoryName,
285300
"new_dataset_name" -> request.body.newDatasetName,
286301
"custom_workflow_provided_by_user" -> request.body.workflowYaml,
287-
"seed_generator_distance_threshold" -> request.body.seedGeneratorDistanceThreshold
302+
"invert_color_layer" -> request.body.invertColorLayer,
303+
"seed_generator_distance_threshold" -> request.body.seedGeneratorDistanceThreshold,
288304
)
289305
creditTransactionComment = s"AI custom instance segmentation with model ${request.body.aiModelId} for dataset ${dataset.name}"
306+
targetMagBoundingBox <- aiModelService.inferenceBBoxToTargetMag(mag1BoundingBox,
307+
layer,
308+
dataSource.scale,
309+
aiModelOpt,
310+
usePretrainedNeuronModel = false,
311+
dataStore)
290312
newInferenceJob <- jobService.submitPaidJob(jobCommand,
291313
commandArgs,
292-
boundingBox,
314+
targetMagBoundingBox,
293315
creditTransactionComment,
294316
request.identity,
295317
dataStore.name) ?~> "job.couldNotRunInferWithModel"
@@ -299,7 +321,7 @@ class AiModelController @Inject()(
299321
_aiModel = request.body.aiModelId,
300322
_newDataset = None,
301323
_annotation = request.body.annotationId,
302-
boundingBox = boundingBox,
324+
boundingBox = mag1BoundingBox,
303325
_inferenceJob = newInferenceJob._id,
304326
newSegmentationLayerName = "segmentation",
305327
maskAnnotationLayerName = request.body.maskAnnotationLayerName
@@ -309,36 +331,48 @@ class AiModelController @Inject()(
309331
} yield Ok(newAiModelJs)
310332
}
311333

312-
def runCustomNeuronInference: Action[RunInferenceParameters] =
334+
def runNeuronModelInference: Action[RunInferenceParameters] =
313335
sil.SecuredAction.async(validateJson[RunInferenceParameters]) { implicit request =>
314336
for {
315-
organization <- organizationDAO.findOne(request.body.organizationId)(GlobalAccessContext) ?~> Messages(
316-
"organization.notFound",
317-
request.body.organizationId)
318-
_ <- Fox.fromBool(request.identity._organization == organization._id) ?~> "job.runInference.notAllowed.organization" ~> FORBIDDEN
319-
dataset <- datasetDAO.findOneByDirectoryNameAndOrganization(request.body.datasetDirectoryName, organization._id)
337+
dataset <- datasetDAO.findOne(request.body.datasetId)
338+
_ <- Fox.fromBool(request.identity._organization == dataset._organization) ?~> "job.runInference.notAllowed.organization" ~> FORBIDDEN
339+
aiModelOpt <- Fox.runOptional(request.body.aiModelId)(aiModelDAO.findOne) ?~> "aiModel.notFound"
340+
_ <- Fox.runOptional(aiModelOpt) { aiModel =>
341+
Fox.fromBool(aiModel._dataStore == dataset._dataStore) ?~> "aiModel.dataStoreMismatch"
342+
}
343+
(dataSource, layer) <- datasetService.getDataSourceAndLayerFor(dataset, request.body.colorLayerName)
320344
dataStore <- dataStoreDAO.findOneByName(dataset._dataStore) ?~> "dataStore.notFound"
321-
aiModel <- aiModelDAO.findOne(request.body.aiModelId) ?~> "aiModel.notFound"
322345
_ <- datasetService.assertValidDatasetName(request.body.newDatasetName)
323346
jobCommand = JobCommand.infer_neurons
324-
boundingBox <- BoundingBox.fromLiteral(request.body.boundingBox).toFox
347+
mag1BoundingBox <- BoundingBox.fromLiteral(request.body.boundingBox).toFox
348+
targetMagBoundingBox <- aiModelService.inferenceBBoxToTargetMag(mag1BoundingBox,
349+
layer,
350+
dataSource.scale,
351+
aiModelOpt,
352+
usePretrainedNeuronModel = aiModelOpt.isEmpty,
353+
dataStore)
325354
commandArgs = Json.obj(
326355
"dataset_id" -> dataset._id,
327-
"organization_id" -> organization._id,
356+
"organization_id" -> dataset._organization,
328357
"dataset_name" -> dataset.name,
329358
"layer_name" -> request.body.colorLayerName,
330-
"bbox" -> boundingBox.toLiteral,
359+
"bbox" -> mag1BoundingBox.toLiteral,
331360
"model_id" -> request.body.aiModelId,
332-
"model_organization_id" -> aiModel._organization,
333-
"dataset_directory_name" -> request.body.datasetDirectoryName,
361+
"model_organization_id" -> aiModelOpt.map(_._organization),
362+
"dataset_directory_name" -> dataset.directoryName,
334363
"new_dataset_name" -> request.body.newDatasetName,
335364
"custom_workflow_provided_by_user" -> request.body.workflowYaml,
336-
"invert_color_layer" -> request.body.invertColorLayer
365+
"invert_color_layer" -> request.body.invertColorLayer,
366+
"do_split_merger_evaluation" -> request.body.doSplitMergerEvaluation,
367+
"eval_use_sparse_tracing" -> request.body.evalUseSparseTracing,
368+
"eval_max_edge_length" -> request.body.evalMaxEdgeLength,
369+
"eval_sparse_tube_threshold_nm" -> request.body.evalSparseTubeThresholdNm,
370+
"eval_min_merger_path_length_nm" -> request.body.evalMinMergerPathLengthNm,
337371
)
338372
creditTransactionComment = s"AI custom neuron segmentation with model ${request.body.aiModelId} for dataset ${dataset.name}"
339373
newInferenceJob <- jobService.submitPaidJob(jobCommand,
340374
commandArgs,
341-
boundingBox,
375+
targetMagBoundingBox,
342376
creditTransactionComment,
343377
request.identity,
344378
dataStore.name) ?~> "job.couldNotRunInferWithModel"
@@ -348,7 +382,7 @@ class AiModelController @Inject()(
348382
_aiModel = request.body.aiModelId,
349383
_newDataset = None,
350384
_annotation = request.body.annotationId,
351-
boundingBox = boundingBox,
385+
boundingBox = mag1BoundingBox,
352386
_inferenceJob = newInferenceJob._id,
353387
newSegmentationLayerName = "segmentation",
354388
maskAnnotationLayerName = request.body.maskAnnotationLayerName
@@ -368,7 +402,7 @@ class AiModelController @Inject()(
368402
} else sharedOrganizationIds
369403
}
370404
for {
371-
_ <- userService.assertIsSuperUser(request.identity)
405+
_ <- organizationService.assertIsSuperUserOrOrganizationHasAiPlan(request.identity)
372406
aiModel <- aiModelDAO.findOne(aiModelId) ?~> "aiModel.notFound" ~> NOT_FOUND
373407
_ <- Fox.fromBool(aiModel._organization == request.identity._organization) ?~> "aiModel.notOwned"
374408
_ <- aiModelDAO.updateOne(aiModel.copy(name = request.body.name,
@@ -462,7 +496,7 @@ class AiModelController @Inject()(
462496
def deleteAiModel(aiModelId: ObjectId): Action[AnyContent] =
463497
sil.SecuredAction.async { implicit request =>
464498
for {
465-
_ <- userService.assertIsSuperUser(request.identity)
499+
_ <- organizationService.assertIsSuperUserOrOrganizationHasAiPlan(request.identity)
466500
referencesCount <- aiInferenceDAO.countForModel(aiModelId)
467501
_ <- Fox.fromBool(referencesCount == 0) ?~> "aiModel.delete.referencedByInferences"
468502
_ <- aiModelDAO.findOne(aiModelId) ?~> "aiModel.notFound" ~> NOT_FOUND

app/controllers/AnnotationIOController.scala

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ class AnnotationIOController @Inject()(
6464
datasetService: DatasetService,
6565
userService: UserService,
6666
taskDAO: TaskDAO,
67+
multiUserDAO: MultiUserDAO,
6768
taskTypeDAO: TaskTypeDAO,
6869
tracingStoreService: TracingStoreService,
6970
tempFileService: WkTempFileService,
@@ -453,6 +454,7 @@ class AnnotationIOController @Inject()(
453454
tracingStoreClient.getSkeletonTracing(annotation._id, _, version))
454455
annotationProto <- tracingStoreClient.getAnnotationProto(annotation._id, version)
455456
annotationOwner <- userService.findOneCached(annotation._user)(GlobalAccessContext)
457+
ownerMultiUser <- multiUserDAO.findOne(annotationOwner._multiUser)(GlobalAccessContext)
456458
taskOpt <- Fox.runOptional(annotation._task)(taskDAO.findOne)
457459
nmlStream = nmlWriter.toNmlStream(
458460
"temp",
@@ -465,7 +467,8 @@ class AnnotationIOController @Inject()(
465467
conf.Http.uri,
466468
dataset.name,
467469
dataset._id,
468-
annotationOwner,
470+
annotation._user,
471+
ownerMultiUser.fullName,
469472
taskOpt,
470473
skipVolumeData,
471474
volumeDataZipFormat,
@@ -497,6 +500,7 @@ class AnnotationIOController @Inject()(
497500
tracingStoreClient.getSkeletonTracing(annotation._id, skeletonAnnotationLayer, version)
498501
} ?~> "annotation.download.fetchSkeletonLayer.failed"
499502
annotationOwner <- userService.findOneCached(annotation._user)(GlobalAccessContext) ?~> "annotation.download.findUser.failed"
503+
ownerMultiUser <- multiUserDAO.findOne(annotationOwner._multiUser)(GlobalAccessContext)
500504
taskOpt <- Fox.runOptional(annotation._task)(taskDAO.findOne(_)(GlobalAccessContext)) ?~> "task.notFound"
501505
annotationProto <- tracingStoreClient.getAnnotationProto(annotation._id, version)
502506
nmlStream = nmlWriter.toNmlStream(
@@ -510,7 +514,8 @@ class AnnotationIOController @Inject()(
510514
conf.Http.uri,
511515
dataset.name,
512516
dataset._id,
513-
annotationOwner,
517+
annotation._user,
518+
ownerMultiUser.fullName,
514519
taskOpt,
515520
skipVolumeData,
516521
volumeDataZipFormat,

app/controllers/Application.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import com.scalableminds.util.tools.{Fox, FoxImplicits}
44
import com.typesafe.config.ConfigRenderOptions
55
import mail.{DefaultMails, Send}
66
import models.organization.OrganizationDAO
7-
import models.user.UserService
7+
import models.user.MultiUserDAO
88
import org.apache.pekko.actor.ActorSystem
99
import play.api.libs.json.Json
1010
import play.api.mvc.{Action, AnyContent, Result}
@@ -17,7 +17,7 @@ import javax.inject.Inject
1717
import scala.concurrent.ExecutionContext
1818

1919
class Application @Inject()(actorSystem: ActorSystem,
20-
userService: UserService,
20+
multiUserDAO: MultiUserDAO,
2121
buildInfoService: BuildInfoService,
2222
organizationDAO: OrganizationDAO,
2323
conf: WkConf,
@@ -58,8 +58,8 @@ class Application @Inject()(actorSystem: ActorSystem,
5858
def helpEmail(message: String, currentUrl: String): Action[AnyContent] = sil.SecuredAction.async { implicit request =>
5959
for {
6060
organization <- organizationDAO.findOne(request.identity._organization)
61-
userEmail <- userService.emailFor(request.identity)
62-
_ = Mailer ! Send(defaultMails.helpMail(request.identity, userEmail, organization.name, message, currentUrl))
61+
multiUser <- multiUserDAO.findOne(request.identity._multiUser)
62+
_ = Mailer ! Send(defaultMails.helpMail(multiUser, organization.name, message, currentUrl))
6363
} yield Ok
6464
}
6565

0 commit comments

Comments
 (0)