Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add UI to globalize incomplete floodfill(s) #5905

Merged
merged 21 commits into from
Jan 26, 2022
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
57a6f33
some tag related tweaks
philippotto Dec 13, 2021
ec30401
add button for globalizing floodfill
philippotto Dec 13, 2021
01041ff
fix newDataSetName
philippotto Dec 13, 2021
a4697f6
Pass user auth token to globalizeFloodfills job
fm3 Dec 14, 2021
f69116a
Merge branch 'globalize-floodfill' of github.com:scalableminds/webkno…
fm3 Dec 14, 2021
575d1a3
Merge branch 'master' into globalize-floodfill
fm3 Dec 14, 2021
c5aa03d
fix orga access after master merge
fm3 Dec 14, 2021
9c39a5e
Merge branch 'master' of github.com:scalableminds/webknossos into glo…
philippotto Jan 11, 2022
abb06c5
let user choose name for new dataset with globalized floodfills
philippotto Jan 11, 2022
971072e
Merge branch 'master' of github.com:scalableminds/webknossos into glo…
philippotto Jan 21, 2022
3b577a5
don't hardcode segmentation layer name
philippotto Jan 21, 2022
03ad4d7
right-align globalize button
philippotto Jan 21, 2022
ef302ce
Merge branch 'master' of github.com:scalableminds/webknossos into glo…
philippotto Jan 26, 2022
b7f6571
Update frontend/javascripts/oxalis/view/right-border-tabs/bounding_bo…
philippotto Jan 26, 2022
7f5cb8e
Update frontend/javascripts/oxalis/view/right-border-tabs/bounding_bo…
philippotto Jan 26, 2022
62726d8
incorporate feedback
philippotto Jan 26, 2022
7e99efb
Merge branch 'globalize-floodfill' of github.com:scalableminds/webkno…
philippotto Jan 26, 2022
1204ddd
disable jobs again
philippotto Jan 26, 2022
30dba2e
update changelog
philippotto Jan 26, 2022
402e9f1
format
philippotto Jan 26, 2022
694f70a
Merge branch 'master' into globalize-floodfill
philippotto Jan 26, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 8 additions & 23 deletions app/controllers/AuthenticationController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import play.api.data.Forms.{email, _}
import play.api.data.validation.Constraints._
import play.api.i18n.Messages
import play.api.libs.json._
import play.api.mvc.{Action, AnyContent, PlayBodyParsers, _}
import play.api.mvc.{Action, AnyContent, PlayBodyParsers}
import utils.{ObjectId, WkConf}

import scala.concurrent.{ExecutionContext, Future}
Expand Down Expand Up @@ -384,32 +384,17 @@ class AuthenticationController @Inject()(
}

def getToken: Action[AnyContent] = sil.SecuredAction.async { implicit request =>
val futureOfFuture: Future[Future[Result]] =
combinedAuthenticatorService.findByLoginInfo(request.identity.loginInfo).map {
case Some(token) => Future.successful(Ok(Json.obj("token" -> token.id)))
case _ =>
combinedAuthenticatorService.createToken(request.identity.loginInfo).map { newToken =>
Ok(Json.obj("token" -> newToken.id))
}
}
for {
resultFuture <- futureOfFuture
result <- resultFuture
} yield result
token <- combinedAuthenticatorService.findOrCreateToken(request.identity.loginInfo)
} yield Ok(Json.obj("token" -> token.id))
}

def deleteToken(): Action[AnyContent] = sil.SecuredAction.async { implicit request =>
val futureOfFuture: Future[Future[Result]] =
combinedAuthenticatorService.findByLoginInfo(request.identity.loginInfo).map {
case Some(token) =>
combinedAuthenticatorService.discard(token, Ok(Json.obj("messages" -> Messages("auth.tokenDeleted"))))
case _ => Future.successful(Ok)
}

for {
resultFuture <- futureOfFuture
result <- resultFuture
} yield result
combinedAuthenticatorService.findTokenByLoginInfo(request.identity.loginInfo).flatMap {
case Some(token) =>
combinedAuthenticatorService.discard(token, Ok(Json.obj("messages" -> Messages("auth.tokenDeleted"))))
case _ => Future.successful(Ok)
}
}

def logout: Action[AnyContent] = sil.UserAwareAction.async { implicit request =>
Expand Down
39 changes: 38 additions & 1 deletion app/controllers/JobsController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import javax.inject.Inject
import models.binary.DataSetDAO
import models.job.{JobDAO, JobService, JobState, WorkerDAO, WorkerService}
import models.organization.OrganizationDAO
import oxalis.security.WkEnv
import oxalis.security.{WkEnv, WkSilhouetteEnvironment}
import oxalis.telemetry.SlackNotificationService
import play.api.i18n.Messages
import play.api.libs.json._
Expand All @@ -25,6 +25,7 @@ class JobsController @Inject()(jobDAO: JobDAO,
workerService: WorkerService,
workerDAO: WorkerDAO,
wkconf: WkConf,
wkSilhouetteEnvironment: WkSilhouetteEnvironment,
slackNotificationService: SlackNotificationService,
organizationDAO: OrganizationDAO)(implicit ec: ExecutionContext)
extends Controller {
Expand Down Expand Up @@ -149,6 +150,42 @@ class JobsController @Inject()(jobDAO: JobDAO,
}
}

def runGlobalizeFloodfills(
organizationName: String,
dataSetName: String,
newDataSetName: Option[String],
layerName: Option[String],
annotationId: Option[String],
annotationType: Option[String],
): Action[AnyContent] =
sil.SecuredAction.async { implicit request =>
log(Some(slackNotificationService.noticeFailedJobRequest)) {
for {
organization <- organizationDAO.findOneByName(organizationName)(GlobalAccessContext) ?~> Messages(
"organization.notFound",
organizationName)
_ <- bool2Fox(request.identity._organization == organization._id) ?~> "job.globalizeFloodfill.notAllowed.organization" ~> FORBIDDEN
userAuthToken <- wkSilhouetteEnvironment.combinedAuthenticatorService.findOrCreateToken(
request.identity.loginInfo)
dataSet <- dataSetDAO.findOneByNameAndOrganization(dataSetName, organization._id) ?~> Messages(
"dataSet.notFound",
dataSetName) ~> NOT_FOUND
command = "globalize_floodfills"
commandArgs = Json.obj(
"organization_name" -> organizationName,
"dataset_name" -> dataSetName,
"new_dataset_name" -> newDataSetName,
"layer_name" -> layerName,
"annotation_id" -> annotationId,
"annotation_type" -> annotationType,
"user_auth_token" -> userAuthToken.id,
)
job <- jobService.submitJob(command, commandArgs, request.identity, dataSet._dataStore) ?~> "job.couldNotRunGlobalizeFloodfills"
js <- jobService.publicWrites(job)
} yield Ok(js)
}
}

def runExportTiffJob(organizationName: String,
dataSetName: String,
bbox: String,
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 @@ -319,7 +319,7 @@ class JobService @Inject()(wkConf: WkConf,
Json.obj(
"id" -> job._id.id,
"command" -> job.command,
"commandArgs" -> (job.commandArgs - "webknossos_token"),
"commandArgs" -> (job.commandArgs - "webknossos_token" - "user_auth_token"),
"state" -> job.state,
"manualState" -> job.manualState,
"latestRunId" -> job.latestRunId,
Expand Down
9 changes: 8 additions & 1 deletion app/oxalis/security/CombinedAuthenticatorService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ case class CombinedAuthenticatorService(cookieSettings: CookieAuthenticatorSetti
tokenAuthenticator.map(CombinedAuthenticator(_))
}

def findOrCreateToken(loginInfo: LoginInfo): Future[CombinedAuthenticator] =
findTokenByLoginInfo(loginInfo).flatMap {
case Some(token) => Future.successful(token)
case _ =>
createToken(loginInfo)
}

override def retrieve[B](implicit request: ExtractableRequest[B]): Future[Option[CombinedAuthenticator]] =
for {
optionCookie <- cookieAuthenticatorService.retrieve(request)
Expand All @@ -73,7 +80,7 @@ case class CombinedAuthenticatorService(cookieSettings: CookieAuthenticatorSetti
}

// only called in token case
def findByLoginInfo(loginInfo: LoginInfo): Future[Option[CombinedAuthenticator]] =
def findTokenByLoginInfo(loginInfo: LoginInfo): Future[Option[CombinedAuthenticator]] =
tokenDao.findOneByLoginInfo(loginInfo, TokenType.Authentication).map(opt => opt.map(CombinedAuthenticator(_)))

// only called in the cookie case
Expand Down
2 changes: 1 addition & 1 deletion conf/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ features {
taskReopenAllowedInSeconds = 30
allowDeleteDatasets = true
# to enable jobs for local development, use "yarn enable-jobs" to also activate it in the database
jobsEnabled = false
jobsEnabled = true
philippotto marked this conversation as resolved.
Show resolved Hide resolved
# For new users, the dashboard will show a banner which encourages the user to check out the following dataset.
# If isDemoInstance == true, `/createExplorative/hybrid/true` is appended to the URL so that a new tracing is opened.
# If isDemoInstance == false, `/view` is appended to the URL so that it's opened in view mode (since the user might not
Expand Down
2 changes: 2 additions & 0 deletions conf/messages
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ job.couldNotRunCubing = Failed to start WKW conversion job.
job.couldNotRunTiffExport = Failed to start Tiff export job.
job.couldNotRunComputeMeshFile = Failed to start mesh file computation job.
job.couldNotRunNucleiInferral = Failed to start nuclei inferral job.
job.couldNotRunGlobalizeFloodfills = Failed to start job for globalizing floodfills.
job.disabled = Long-running jobs are not enabled for this webKnossos instance.
jobs.worker.notFound = Could not find this worker in the database.
job.export.fileNotFound = Exported file not found. The link may be expired.
Expand All @@ -346,6 +347,7 @@ job.export.tiff.edgeLengthExceeded = An edge length of the selected bounding box
job.export.notAllowed.organization = Currently tiff export is only allowed for datasets of your own organization.
job.inferNuclei.notAllowed.organization = Currently nuclei inferral is only allowed for datasets of your own organization.
job.meshFile.notAllowed.organization = Calculating mesh files is only allowed for datasets of your own organization.
job.globalizeFloodfill.notAllowed.organization = Globalizing floodfills is only allowed for datasets of your own organization.

agglomerateSkeleton.failed=Could not generate agglomerate skeleton.

Expand Down
1 change: 1 addition & 0 deletions conf/webknossos.latest.routes
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ GET /jobs/run/convertToWkw/:organizationName/:dataSetName c
GET /jobs/run/computeMeshFile/:organizationName/:dataSetName controllers.JobsController.runComputeMeshFileJob(organizationName: String, dataSetName: String, layerName: String, mag: String, agglomerateView: Option[String])
GET /jobs/run/exportTiff/:organizationName/:dataSetName controllers.JobsController.runExportTiffJob(organizationName: String, dataSetName: String, bbox: String, layerName: Option[String], tracingId: Option[String], tracingVersion: Option[String], annotationId: Option[String], annotationType: Option[String], hideUnmappedIds: Option[Boolean], mappingName: Option[String], mappingType: Option[String])
GET /jobs/run/inferNuclei/:organizationName/:dataSetName controllers.JobsController.runInferNucleiJob(organizationName: String, dataSetName: String, layerName: Option[String])
GET /jobs/run/globalizeFloodfills/:organizationName/:dataSetName controllers.JobsController.runGlobalizeFloodfills(organizationName: String, dataSetName: String, newDataSetName: Option[String], layerName: Option[String], annotationId: Option[String], annotationType: Option[String])
GET /jobs/:id controllers.JobsController.get(id: String)
PATCH /jobs/:id/cancel controllers.JobsController.cancel(id: String)
POST /jobs/:id/status controllers.WKRemoteWorkerController.updateJobStatus(key: String, id: String)
13 changes: 13 additions & 0 deletions frontend/javascripts/admin/admin_rest_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,19 @@ export function startNucleiInferralJob(
);
}

export function startGlobalizeFloodfillsJob(
organizationName: string,
datasetName: string,
newDataSetName: string,
layerName: string,
annotationId: string,
annotationType: APIAnnotationType,
): Promise<APIJob> {
return Request.receiveJSON(
`/api/jobs/run/globalizeFloodfills/${organizationName}/${datasetName}?newDataSetName=${newDataSetName}&layerName=${layerName}&annotationId=${annotationId}&annotationType=${annotationType}`,
);
}

export function getDatasetDatasource(
dataset: APIMaybeUnimportedDataset,
): Promise<APIDataSourceWithMessages> {
Expand Down
9 changes: 8 additions & 1 deletion frontend/javascripts/admin/dataset/dataset_components.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,19 @@ export function CardContainer({
}
}

export function DatasetNameFormItem({ activeUser }: { activeUser: ?APIUser }) {
export function DatasetNameFormItem({
activeUser,
initialName,
}: {
activeUser: ?APIUser,
initialName?: string,
}) {
return (
<FormItem
name="name"
label="Dataset Name"
hasFeedback
initialValue={initialName}
rules={[
{ required: true, message: messages["dataset.import.required.name"] },
{ min: 3 },
Expand Down
29 changes: 17 additions & 12 deletions frontend/javascripts/dashboard/advanced_dataset/dataset_table.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,35 @@
// @flow

import { Table, Tag } from "antd";
import {
CheckCircleOutlined,
CloseCircleOutlined,
PlusOutlined,
WarningOutlined,
} from "@ant-design/icons";
import { Link } from "react-router-dom";
import { Table, Tag, Tooltip } from "antd";
import * as React from "react";
import { CheckCircleOutlined, CloseCircleOutlined, PlusOutlined } from "@ant-design/icons";
import _ from "lodash";
import { Link } from "react-router-dom";
import dice from "dice-coefficient";
import update from "immutability-helper";

import EditableTextIcon from "oxalis/view/components/editable_text_icon";
import type {
APITeam,
APIMaybeUnimportedDataset,
APIDatasetId,
APIDataset,
} from "types/api_flow_types";
import { stringToColor, formatScale } from "libs/format_utils";
import type { DatasetFilteringMode } from "dashboard/dataset_view";
import DatasetAccessListView from "dashboard/advanced_dataset/dataset_access_list_view";
import DatasetActionView from "dashboard/advanced_dataset/dataset_action_view";
import FormattedDate from "components/formatted_date";
import { getDatasetExtentAsString } from "oxalis/model/accessors/dataset_accessor";
import { stringToColor, formatScale } from "libs/format_utils";
import { trackAction } from "oxalis/model/helpers/analytics";
import CategorizationLabel from "oxalis/view/components/categorization_label";
import DatasetAccessListView from "dashboard/advanced_dataset/dataset_access_list_view";
import DatasetActionView from "dashboard/advanced_dataset/dataset_action_view";
import EditableTextIcon from "oxalis/view/components/editable_text_icon";
import FixedExpandableTable from "components/fixed_expandable_table";
import FormattedDate from "components/formatted_date";
import * as Utils from "libs/utils";
import CategorizationLabel from "oxalis/view/components/categorization_label";

const { Column } = Table;

Expand Down Expand Up @@ -255,9 +260,9 @@ class DatasetTable extends React.PureComponent<Props, State> {
/>
</div>
) : (
<div style={{ color: "@disabled-color" }}>
Tags not available for inactive datasets.
</div>
<Tooltip title="No tags available for inactive datasets">
<WarningOutlined style={{ color: "@disabled-color" }} />
</Tooltip>
)
}
/>
Expand Down
5 changes: 2 additions & 3 deletions frontend/javascripts/oxalis/model/sagas/volumetracing_saga.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import {
getSegmentsForLayer,
isVolumeAnnotationDisallowedForZoom,
} from "oxalis/model/accessors/volumetracing_accessor";
import { getBBoxNameForPartialFloodfill } from "oxalis/view/right-border-tabs/bounding_box_tab";
import {
getPosition,
getFlooredPosition,
Expand Down Expand Up @@ -638,9 +639,7 @@ export function* floodFill(): Saga<void> {
yield* put(
addUserBoundingBoxAction({
boundingBox: coveredBoundingBox,
name: `Limits of flood-fill (source_id=${oldSegmentIdAtSeed}, target_id=${activeCellId}, seed=${seedPosition.join(
",",
)}, timestamp=${new Date().getTime()})`,
name: getBBoxNameForPartialFloodfill(oldSegmentIdAtSeed, activeCellId, seedPosition),
color: Utils.getRandomColor(),
isVisible: true,
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@ type FilterProps = {|
export default function CategorizationLabel({ tag, kind, onClick, onClose, closable }: LabelProps) {
return (
<Tooltip title={`Click to only show ${kind} with this tag.`}>
<Tag color={stringToColor(tag)} onClick={onClick} onClose={onClose} closable={closable}>
<Tag
color={stringToColor(tag)}
onClick={onClick}
onClose={onClose}
closable={closable}
style={{ cursor: "pointer" }}
>
{tag}
</Tag>
</Tooltip>
Expand Down
Loading