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

Adds OME-TIFF export #6838

Merged
merged 18 commits into from
Feb 24, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
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
25 changes: 11 additions & 14 deletions app/controllers/JobsController.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
package controllers

import java.util.Date

import com.mohiva.play.silhouette.api.Silhouette
import com.scalableminds.util.accesscontext.GlobalAccessContext
import com.scalableminds.util.tools.Fox
import javax.inject.Inject
import models.binary.DataSetDAO
import models.job.{JobDAO, JobService, JobState, WorkerDAO, WorkerService}
import models.job._
import models.organization.OrganizationDAO
import oxalis.security.{WkEnv, WkSilhouetteEnvironment}
import oxalis.telemetry.SlackNotificationService
Expand All @@ -16,6 +13,8 @@ import play.api.libs.json._
import play.api.mvc.{Action, AnyContent}
import utils.{ObjectId, WkConf}

import java.util.Date
import javax.inject.Inject
import scala.concurrent.ExecutionContext

class JobsController @Inject()(jobDAO: JobDAO,
Expand Down Expand Up @@ -226,12 +225,10 @@ class JobsController @Inject()(jobDAO: JobDAO,
dataSetName: String,
bbox: String,
layerName: Option[String],
mag: Option[String],
annotationLayerName: Option[String],
annotationId: Option[String],
annotationType: Option[String],
hideUnmappedIds: Option[Boolean],
mappingName: Option[String],
mappingType: Option[String]): Action[AnyContent] =
asOmeTiff: Boolean): Action[AnyContent] =
sil.SecuredAction.async { implicit request =>
log(Some(slackNotificationService.noticeFailedJobRequest)) {
for {
Expand All @@ -242,20 +239,20 @@ class JobsController @Inject()(jobDAO: JobDAO,
userAuthToken <- wkSilhouetteEnvironment.combinedAuthenticatorService.findOrCreateToken(
request.identity.loginInfo)
command = "export_tiff"
exportFileName = s"${formatDateForFilename(new Date())}__${dataSetName}__${annotationLayerName.map(_ => "volume").getOrElse(layerName.getOrElse(""))}.zip"
exportFileName = if (asOmeTiff)
s"${formatDateForFilename(new Date())}__${dataSetName}__${annotationLayerName.map(_ => "volume").getOrElse(layerName.getOrElse(""))}.ome.tif"
else
s"${formatDateForFilename(new Date())}__${dataSetName}__${annotationLayerName.map(_ => "volume").getOrElse(layerName.getOrElse(""))}.zip"
commandArgs = Json.obj(
"organization_name" -> organizationName,
"dataset_name" -> dataSetName,
"bbox" -> bbox,
"export_file_name" -> exportFileName,
"layer_name" -> layerName,
"mag" -> mag,
"user_auth_token" -> userAuthToken.id,
"annotation_layer_name" -> annotationLayerName,
"annotation_id" -> annotationId,
"annotation_type" -> annotationType,
"mapping_name" -> mappingName,
"mapping_type" -> mappingType,
"hide_unmapped_ids" -> hideUnmappedIds
"annotation_id" -> annotationId
)
job <- jobService.submitJob(command, commandArgs, request.identity, dataSet._dataStore) ?~> "job.couldNotRunTiffExport"
js <- jobService.publicWrites(job)
Expand Down
2 changes: 1 addition & 1 deletion conf/webknossos.latest.routes
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ GET /jobs
GET /jobs/status controllers.JobsController.status
POST /jobs/run/convertToWkw/:organizationName/:dataSetName controllers.JobsController.runConvertToWkwJob(organizationName: String, dataSetName: String, scale: String)
POST /jobs/run/computeMeshFile/:organizationName/:dataSetName controllers.JobsController.runComputeMeshFileJob(organizationName: String, dataSetName: String, layerName: String, mag: String, agglomerateView: Option[String])
POST /jobs/run/exportTiff/:organizationName/:dataSetName controllers.JobsController.runExportTiffJob(organizationName: String, dataSetName: String, bbox: String, layerName: Option[String], annotationLayerName: Option[String], annotationId: Option[String], annotationType: Option[String], hideUnmappedIds: Option[Boolean], mappingName: Option[String], mappingType: Option[String])
POST /jobs/run/exportTiff/:organizationName/:dataSetName controllers.JobsController.runExportTiffJob(organizationName: String, dataSetName: String, bbox: String, layerName: Option[String], mag: Option[String], annotationLayerName: Option[String], annotationId: Option[String], asOmeTiff: Boolean)
POST /jobs/run/inferNuclei/:organizationName/:dataSetName controllers.JobsController.runInferNucleiJob(organizationName: String, dataSetName: String, layerName: String, newDatasetName: String)
POST /jobs/run/inferNeurons/:organizationName/:dataSetName controllers.JobsController.runInferNeuronsJob(organizationName: String, dataSetName: String, layerName: String, bbox: String, newDatasetName: String)
POST /jobs/run/globalizeFloodfills/:organizationName/:dataSetName controllers.JobsController.runGlobalizeFloodfills(organizationName: String, dataSetName: String, fallbackLayerName: String, annotationId: String, annotationType: String, newDatasetName: String, volumeLayerName: Option[String])
Expand Down
55 changes: 39 additions & 16 deletions frontend/javascripts/admin/admin_rest_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,29 @@ export async function getJobs(): Promise<APIJob[]> {
);
}

export async function getJob(jobId: string): Promise<APIJob> {
const job = await Request.receiveJSON(`/api/jobs/${jobId}`);
return {
id: job.id,
type: job.command,
datasetName: job.commandArgs.dataset_name,
organizationName: job.commandArgs.organization_name,
layerName: job.commandArgs.layer_name || job.commandArgs.volume_layer_name,
annotationLayerName: job.commandArgs.annotation_layer_name,
boundingBox: job.commandArgs.bbox,
exportFileName: job.commandArgs.export_file_name,
tracingId: job.commandArgs.volume_tracing_id,
annotationId: job.commandArgs.annotation_id,
annotationType: job.commandArgs.annotation_type,
mergeSegments: job.commandArgs.merge_segments,
state: adaptJobState(job.command, job.state, job.manualState),
manualState: job.manualState,
result: job.returnValue,
resultLink: job.resultLink,
createdAt: job.created,
};
}

function adaptJobState(
command: string,
celeryState: APIJobCeleryState,
Expand Down Expand Up @@ -1141,26 +1164,26 @@ export async function startExportTiffJob(
organizationName: string,
bbox: Vector6,
layerName: string | null | undefined,
mag: string | null | undefined,
annotationId: string | null | undefined,
annotationType: APIAnnotationType | null | undefined,
annotationLayerName: string | null | undefined,
mappingName: string | null | undefined,
normanrz marked this conversation as resolved.
Show resolved Hide resolved
mappingType: string | null | undefined,
hideUnmappedIds: boolean | null | undefined,
asOmeTiff: boolean,
): Promise<APIJob> {
const layerNameSuffix = layerName != null ? `&layerName=${layerName}` : "";
const annotationIdSuffix = annotationId != null ? `&annotationId=${annotationId}` : "";
const annotationTypeSuffix = annotationType != null ? `&annotationType=${annotationType}` : "";
const annotationLayerNameSuffix =
annotationLayerName != null ? `&annotationLayerName=${annotationLayerName}` : "";
const mappingNameSuffix = mappingName != null ? `&mappingName=${mappingName}` : "";
const mappingTypeSuffix = mappingType != null ? `&mappingType=${mappingType}` : "";
const hideUnmappedIdsSuffix =
hideUnmappedIds != null ? `&hideUnmappedIds=${hideUnmappedIds.toString()}` : "";
const params = new URLSearchParams({ bbox: bbox.join(","), asOmeTiff: asOmeTiff.toString() });
if (layerName != null) {
params.append("layerName", layerName);
}
if (mag != null) {
params.append("mag", mag);
}
if (annotationId != null) {
params.append("annotationId", annotationId);
}
if (annotationLayerName != null) {
params.append("annotationLayerName", annotationLayerName);
}
return Request.receiveJSON(
`/api/jobs/run/exportTiff/${organizationName}/${datasetName}?bbox=${bbox.join(
",",
)}${layerNameSuffix}${annotationIdSuffix}${annotationTypeSuffix}${annotationLayerNameSuffix}${mappingNameSuffix}${mappingTypeSuffix}${hideUnmappedIdsSuffix}`,
normanrz marked this conversation as resolved.
Show resolved Hide resolved
`/api/jobs/run/exportTiff/${organizationName}/${datasetName}?${params}`,
{
method: "POST",
},
Expand Down
122 changes: 57 additions & 65 deletions frontend/javascripts/admin/job/job_list_view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,6 @@ class JobListView extends React.PureComponent<Props, State> {
};

renderState = (__: any, job: APIJob) => {
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
const { tooltip, icon } = TOOLTIP_MESSAGES_AND_ICONS[job.state];

const jobStateNormalized = _.capitalize(job.state.toLowerCase());
Expand All @@ -299,75 +298,68 @@ class JobListView extends React.PureComponent<Props, State> {
render() {
return (
<div className="container">
<div className="pull-right">
<Search
style={{
width: 200,
}}
onPressEnter={this.handleSearch}
onChange={this.handleSearch}
value={this.state.searchQuery}
/>
</div>
<h3>Jobs</h3>
<div
className="clearfix"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only got rid of this div in this file

style={{
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ marginTag: number; }' is not assignable to... Remove this comment to see the full error message
marginTag: 20,
margin: "20px 0px",
}}
>
<div className="pull-right">
<Search
style={{
width: 200,
}}
onPressEnter={this.handleSearch}
onChange={this.handleSearch}
value={this.state.searchQuery}
/>
</div>
<h3>Jobs</h3>
<div
className="clearfix"
/>

<Spin spinning={this.state.isLoading} size="large">
<Table
dataSource={Utils.filterWithSearchQueryAND(
this.state.jobs,
["datasetName"],
this.state.searchQuery,
)}
rowKey="id"
pagination={{
defaultPageSize: 50,
}}
style={{
margin: "20px 0px",
marginTop: 30,
marginBottom: 30,
}}
/>

<Spin spinning={this.state.isLoading} size="large">
<Table
dataSource={Utils.filterWithSearchQueryAND(
this.state.jobs,
["datasetName"],
this.state.searchQuery,
)}
rowKey="id"
pagination={{
defaultPageSize: 50,
}}
style={{
marginTop: 30,
marginBottom: 30,
}}
>
<Column
title="Job Id"
dataIndex="id"
key="id"
sorter={Utils.localeCompareBy(typeHint, (job) => job.id)}
/>
<Column title="Description" key="datasetName" render={this.renderDescription} />
<Column
title="Created at"
key="createdAt"
render={(job) => <FormattedDate timestamp={job.createdAt} />}
sorter={Utils.compareBy(typeHint, (job) => job.createdAt)}
/>
<Column
title="State"
key="state"
render={this.renderState}
sorter={Utils.localeCompareBy(typeHint, (job) => job.state)}
/>
<Column
title="Action"
key="actions"
fixed="right"
width={150}
render={this.renderActions}
/>
</Table>
</Spin>
</div>
>
<Column
title="Job Id"
dataIndex="id"
key="id"
sorter={Utils.localeCompareBy(typeHint, (job) => job.id)}
/>
<Column title="Description" key="datasetName" render={this.renderDescription} />
<Column
title="Created at"
key="createdAt"
render={(job) => <FormattedDate timestamp={job.createdAt} />}
sorter={Utils.compareBy(typeHint, (job) => job.createdAt)}
/>
<Column
title="State"
key="state"
render={this.renderState}
sorter={Utils.localeCompareBy(typeHint, (job) => job.state)}
/>
<Column
title="Action"
key="actions"
fixed="right"
width={150}
render={this.renderActions}
/>
</Table>
</Spin>
</div>
);
}
Expand Down
1 change: 0 additions & 1 deletion frontend/javascripts/dashboard/dataset_view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,6 @@ function NewJobsAlert({ jobs }: { jobs: APIJob[] }) {
}}
>
{newJobs.slice(0, MAX_JOBS_TO_DISPLAY).map((job) => {
// @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
const { tooltip, icon } = TOOLTIP_MESSAGES_AND_ICONS[job.state];
return (
<Row key={job.id} gutter={16}>
Expand Down
4 changes: 4 additions & 0 deletions frontend/javascripts/libs/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,10 @@ export function computeArrayFromBoundingBox(bb: BoundingBoxType): Vector6 {
];
}

export function computeShapeFromBoundingBox(bb: BoundingBoxType): Vector3 {
return [bb.max[0] - bb.min[0], bb.max[1] - bb.min[1], bb.max[2] - bb.min[2]];
}

export function aggregateBoundingBox(boundingBoxes: Array<BoundingBoxObject>): BoundingBoxType {
if (boundingBoxes.length === 0) {
return {
Expand Down
4 changes: 2 additions & 2 deletions frontend/javascripts/messages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -331,9 +331,9 @@ instead. Only enable this option if you understand its effect. All layers will n
"Updating the sharing options for the annotation failed. Please retry or see the error message in the console.",
"annotation.download": "The following annotation data is available for download immediately.",
"annotation.export":
"Exporting this annotation as TIFF images will trigger a background job to prepare data for download. This may take a while depending on the size of your dataset as well as bounding box and layer selection. You can monitor the progress and start the download from the ",
"Export this annotation as TIFF image(s). This may take a few moments depending on the size of your configured export.",
"annotation.export_no_worker":
"This WEBKNOSSOS instance is not configured to run TIFF export jobs on a dedicated background worker. To learn more about this feature please contact us at ",
"This WEBKNOSSOS instance is not configured to run export jobs. To learn more about this feature please contact us at ",
"annotation.python_do_not_share":
"These snippets are pre-configured and contain your personal access token and annotation meta data. Do not share this information with anyone you do not trust!",
"annotation.register_for_token": "Please log in to get an access token for the script below.",
Expand Down
Loading