From c4be79f0252e8ac4a5e14362432a78ab0c5414ac Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Wed, 2 Oct 2024 11:15:00 +0200 Subject: [PATCH 01/37] start to replace term resolution --- docs/data_formats.md | 4 ++-- docs/neuroglancer_precomputed.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/data_formats.md b/docs/data_formats.md index c90ed65843e..b0bffb4f511 100644 --- a/docs/data_formats.md +++ b/docs/data_formats.md @@ -41,8 +41,8 @@ A WEBKNOSSOS dataset can contain several `color` and `segmentation` layers which ### Magnification Steps and Downsampling -To enable zooming within huge datasets in WEBKNOSSOS, dataset layers usually contain multiple magnification steps (also called mipmaps or image pyramids or resolutions). -`1` is the magnification step with the finest resolution, i.e. the original data. +To enable zooming within huge datasets in WEBKNOSSOS, dataset layers usually contain multiple magnification steps (also called mags, mipmaps, image pyramids or resolutions). +`1` is the magnification step with the finest magnification, i.e. the original data. `2` is downsampled by a factor of two in all dimensions and therefore only is an eighth of the file size of the original data. Downsampling is done in power-of-two steps: `1, 2, 4, 8, 16, 32, 64, …` diff --git a/docs/neuroglancer_precomputed.md b/docs/neuroglancer_precomputed.md index b73a82c8c36..fb5c579b566 100644 --- a/docs/neuroglancer_precomputed.md +++ b/docs/neuroglancer_precomputed.md @@ -28,7 +28,7 @@ WEBKNOSSOS expects the following file structure for Neuroglancer Precomputed dat ``` my_dataset.precomputed # One root folder per dataset ├─ info # Dataset [metadata in JSON format](https://github.com/google/neuroglancer/blob/master/src/neuroglancer/datasource/precomputed/volume.md#info-json-file-specification) -├─ scale_1 # One subdirectory with the same name as each scale/magnification "key" value specified in the info file. Each subdirectory contains a chunked representation of the data for a single resolution. +├─ scale_1 # One subdirectory with the same name as each scale/magnification "key" value specified in the info file. Each subdirectory contains a chunked representation of the data for a single mag. │  ├─ │  └─ ... ├─ ... From 751d4f9220ea8b8c1692c2874e905397b50063e8 Mon Sep 17 00:00:00 2001 From: Florian M Date: Tue, 8 Oct 2024 11:12:44 +0200 Subject: [PATCH 02/37] rename resolution to mag in backend where possible --- app/controllers/AnnotationController.scala | 4 +- app/controllers/AnnotationIOController.scala | 2 +- app/controllers/LegacyApiController.scala | 4 +- app/controllers/TaskTypeController.scala | 2 +- app/models/annotation/AnnotationService.scala | 35 +++-- .../annotation/AnnotationSettings.scala | 4 +- .../WKRemoteTracingStoreClient.scala | 14 +- app/models/annotation/nml/NmlParser.scala | 19 +-- app/models/annotation/nml/NmlWriter.scala | 2 +- app/models/dataset/Dataset.scala | 33 +++-- app/models/task/TaskCreationService.scala | 18 +-- app/models/task/TaskType.scala | 12 +- conf/messages | 8 +- .../SkeletonUpdateActionsUnitTestSuite.scala | 6 +- .../controllers/BinaryDataController.scala | 14 +- .../controllers/ZarrStreamingController.scala | 8 +- .../datastore/dataformats/MagLocator.scala | 4 +- .../dataformats/layers/N5DataLayers.scala | 2 +- .../layers/PrecomputedDataLayers.scala | 2 +- .../dataformats/layers/WKWDataLayers.scala | 6 +- .../dataformats/layers/Zarr3DataLayers.scala | 2 +- .../dataformats/layers/ZarrDataLayers.scala | 2 +- .../dataformats/wkw/WKWDataFormatHelper.scala | 4 +- .../helpers/SkeletonElementDefaults.scala | 4 +- .../models/datasource/DataLayer.scala | 12 +- .../services/BinaryDataService.scala | 2 +- .../datastore/services/FindDataService.scala | 36 ++--- .../services/uploading/UploadService.scala | 10 +- ....scalableminds.webknossos.datastore.routes | 2 +- .../proto/SkeletonTracing.proto | 2 +- .../proto/VolumeTracing.proto | 2 +- .../controllers/VolumeTracingController.scala | 28 ++-- ...VolumeTracingZarrStreamingController.scala | 20 +-- .../EditableMappingLayer.scala | 2 +- .../EditableMappingService.scala | 2 +- .../updating/SkeletonUpdateActions.scala | 4 +- .../tracings/volume/MergedVolume.scala | 14 +- .../tracings/volume/TSFullMeshService.scala | 2 +- .../tracings/volume/VolumeDataZipHelper.scala | 8 +- .../volume/VolumeTracingBucketHelper.scala | 37 +++-- .../volume/VolumeTracingDownsampling.scala | 35 +++-- .../tracings/volume/VolumeTracingLayer.scala | 10 +- .../volume/VolumeTracingService.scala | 132 +++++++++--------- .../volume/Zarr3BucketStreamSink.scala | 2 +- ...alableminds.webknossos.tracingstore.routes | 4 +- 45 files changed, 275 insertions(+), 302 deletions(-) diff --git a/app/controllers/AnnotationController.scala b/app/controllers/AnnotationController.scala index 34c5efa5cd4..9279822fa10 100755 --- a/app/controllers/AnnotationController.scala +++ b/app/controllers/AnnotationController.scala @@ -14,7 +14,7 @@ import com.scalableminds.webknossos.datastore.models.annotation.{ } import com.scalableminds.webknossos.datastore.models.datasource.AdditionalAxis import com.scalableminds.webknossos.datastore.rpc.RPC -import com.scalableminds.webknossos.tracingstore.tracings.volume.ResolutionRestrictions +import com.scalableminds.webknossos.tracingstore.tracings.volume.MagRestrictions import com.scalableminds.webknossos.tracingstore.tracings.{TracingIds, TracingType} import mail.{MailchimpClient, MailchimpTag} import models.analytics.{AnalyticsService, CreateAnnotationEvent, OpenAnnotationEvent} @@ -44,7 +44,7 @@ case class AnnotationLayerParameters(typ: AnnotationLayerType, fallbackLayerName: Option[String], autoFallbackLayer: Boolean = false, mappingName: Option[String] = None, - resolutionRestrictions: Option[ResolutionRestrictions], + magRestrictions: Option[MagRestrictions], name: Option[String], additionalAxes: Option[Seq[AdditionalAxis]]) object AnnotationLayerParameters { diff --git a/app/controllers/AnnotationIOController.scala b/app/controllers/AnnotationIOController.scala index a3e2bdf490c..3fffaa23121 100755 --- a/app/controllers/AnnotationIOController.scala +++ b/app/controllers/AnnotationIOController.scala @@ -335,7 +335,7 @@ class AnnotationIOController @Inject()( largestSegmentId = annotationService.combineLargestSegmentIdsByPrecedence(volumeTracing.largestSegmentId, fallbackLayerOpt.map(_.largestSegmentId)), - resolutions = VolumeTracingDownsampling.magsForVolumeTracing(dataSource, fallbackLayerOpt).map(vec3IntToProto), + mags = VolumeTracingDownsampling.magsForVolumeTracing(dataSource, fallbackLayerOpt).map(vec3IntToProto), hasSegmentIndex = Some(tracingCanHaveSegmentIndex) ) } diff --git a/app/controllers/LegacyApiController.scala b/app/controllers/LegacyApiController.scala index 0bcf377cce6..418e8285bac 100644 --- a/app/controllers/LegacyApiController.scala +++ b/app/controllers/LegacyApiController.scala @@ -5,7 +5,7 @@ import play.silhouette.api.actions.SecuredRequest import com.scalableminds.util.tools.Fox import com.scalableminds.webknossos.datastore.models.VoxelSize import com.scalableminds.webknossos.datastore.models.annotation.{AnnotationLayer, AnnotationLayerType} -import com.scalableminds.webknossos.tracingstore.tracings.volume.ResolutionRestrictions +import com.scalableminds.webknossos.tracingstore.tracings.volume.MagRestrictions import models.dataset.DatasetService import models.organization.OrganizationDAO @@ -24,7 +24,7 @@ import scala.concurrent.ExecutionContext case class LegacyCreateExplorationalParameters(typ: String, fallbackLayerName: Option[String], - resolutionRestrictions: Option[ResolutionRestrictions]) + resolutionRestrictions: Option[MagRestrictions]) object LegacyCreateExplorationalParameters { implicit val jsonFormat: OFormat[LegacyCreateExplorationalParameters] = Json.format[LegacyCreateExplorationalParameters] diff --git a/app/controllers/TaskTypeController.scala b/app/controllers/TaskTypeController.scala index 86d29c15253..1f2e2831d3e 100755 --- a/app/controllers/TaskTypeController.scala +++ b/app/controllers/TaskTypeController.scala @@ -69,7 +69,7 @@ class TaskTypeController @Inject()(taskTypeDAO: TaskTypeDAO, taskTypeIdValidated <- ObjectId.fromString(taskTypeId) ?~> "taskType.id.invalid" taskType <- taskTypeDAO.findOne(taskTypeIdValidated) ?~> "taskType.notFound" ~> NOT_FOUND _ <- bool2Fox(taskTypeFromForm.tracingType == taskType.tracingType) ?~> "taskType.tracingTypeImmutable" - _ <- bool2Fox(taskTypeFromForm.settings.resolutionRestrictions == taskType.settings.resolutionRestrictions) ?~> "taskType.resolutionRestrictionsImmutable" + _ <- bool2Fox(taskTypeFromForm.settings.magRestrictions == taskType.settings.magRestrictions) ?~> "taskType.magRestrictionsImmutable" updatedTaskType = taskTypeFromForm.copy(_id = taskType._id) _ <- Fox.assertTrue(userService.isTeamManagerOrAdminOf(request.identity, taskType._team)) ?~> "notAllowed" ~> FORBIDDEN _ <- Fox diff --git a/app/models/annotation/AnnotationService.scala b/app/models/annotation/AnnotationService.scala index f3b56e813d3..05020dc622e 100755 --- a/app/models/annotation/AnnotationService.scala +++ b/app/models/annotation/AnnotationService.scala @@ -35,7 +35,7 @@ import com.scalableminds.webknossos.datastore.rpc.RPC import com.scalableminds.webknossos.tracingstore.tracings._ import com.scalableminds.webknossos.tracingstore.tracings.volume.VolumeDataZipFormat.VolumeDataZipFormat import com.scalableminds.webknossos.tracingstore.tracings.volume.{ - ResolutionRestrictions, + MagRestrictions, VolumeDataZipFormat, VolumeTracingDefaults, VolumeTracingDownsampling @@ -144,15 +144,15 @@ class AnnotationService @Inject()( boundingBox: Option[BoundingBox] = None, startPosition: Option[Vec3Int] = None, startRotation: Option[Vec3Double] = None, - resolutionRestrictions: ResolutionRestrictions, + magRestrictions: MagRestrictions, mappingName: Option[String] ): Fox[VolumeTracing] = { - val resolutions = VolumeTracingDownsampling.magsForVolumeTracing(dataSource, fallbackLayer) - val resolutionsRestricted = resolutionRestrictions.filterAllowed(resolutions) + val mags = VolumeTracingDownsampling.magsForVolumeTracing(dataSource, fallbackLayer) + val magsRestricted = magRestrictions.filterAllowed(mags) val additionalAxes = fallbackLayer.map(_.additionalAxes).getOrElse(dataSource.additionalAxesUnion) for { - _ <- bool2Fox(resolutionsRestricted.nonEmpty) ?~> "annotation.volume.resolutionRestrictionsTooTight" + _ <- bool2Fox(magsRestricted.nonEmpty) ?~> "annotation.volume.magRestrictionsTooTight" remoteDatastoreClient = new WKRemoteDataStoreClient(datasetDataStore, rpc) fallbackLayerHasSegmentIndex <- fallbackLayer match { case Some(layer) => @@ -175,7 +175,7 @@ class AnnotationService @Inject()( VolumeTracingDefaults.zoomLevel, organizationId = Some(datasetOrganizationId), mappingName = mappingName, - resolutions = resolutionsRestricted.map(vec3IntToProto), + mags = magsRestricted.map(vec3IntToProto), hasSegmentIndex = Some(fallbackLayer.isEmpty || fallbackLayerHasSegmentIndex), additionalAxes = AdditionalAxis.toProto(additionalAxes) ) @@ -286,8 +286,7 @@ class AnnotationService @Inject()( datasetOrganizationId, dataStore, fallbackLayer, - resolutionRestrictions = - annotationLayerParameters.resolutionRestrictions.getOrElse(ResolutionRestrictions.empty), + magRestrictions = annotationLayerParameters.magRestrictions.getOrElse(MagRestrictions.empty), mappingName = annotationLayerParameters.mappingName ) volumeTracingAdapted = oldPrecedenceLayerProperties.map { p => @@ -417,7 +416,7 @@ class AnnotationService @Inject()( usedFallbackLayerName, autoFallbackLayer = false, None, - Some(ResolutionRestrictions.empty), + Some(MagRestrictions.empty), Some(AnnotationLayer.defaultNameForType(newAnnotationLayerType)), None ) @@ -551,14 +550,14 @@ class AnnotationService @Inject()( ) } - def createVolumeTracingBase(datasetName: String, - organizationId: String, - boundingBox: Option[BoundingBox], - startPosition: Vec3Int, - startRotation: Vec3Double, - volumeShowFallbackLayer: Boolean, - resolutionRestrictions: ResolutionRestrictions)(implicit ctx: DBAccessContext, - m: MessagesProvider): Fox[VolumeTracing] = + def createVolumeTracingBase( + datasetName: String, + organizationId: String, + boundingBox: Option[BoundingBox], + startPosition: Vec3Int, + startRotation: Vec3Double, + volumeShowFallbackLayer: Boolean, + magRestrictions: MagRestrictions)(implicit ctx: DBAccessContext, m: MessagesProvider): Fox[VolumeTracing] = for { organization <- organizationDAO.findOne(organizationId) dataset <- datasetDAO.findOneByNameAndOrganization(datasetName, organizationId) ?~> Messages("dataset.notFound", @@ -583,7 +582,7 @@ class AnnotationService @Inject()( }, startPosition = Some(startPosition), startRotation = Some(startRotation), - resolutionRestrictions = resolutionRestrictions, + magRestrictions = magRestrictions, mappingName = None ) } yield volumeTracing diff --git a/app/models/annotation/AnnotationSettings.scala b/app/models/annotation/AnnotationSettings.scala index b34cca4ac0e..7a7ca8fbf2d 100755 --- a/app/models/annotation/AnnotationSettings.scala +++ b/app/models/annotation/AnnotationSettings.scala @@ -3,7 +3,7 @@ package models.annotation import com.scalableminds.util.enumeration.ExtendedEnumeration import com.scalableminds.webknossos.tracingstore.tracings.TracingType import com.scalableminds.webknossos.tracingstore.tracings.TracingType.TracingType -import com.scalableminds.webknossos.tracingstore.tracings.volume.ResolutionRestrictions +import com.scalableminds.webknossos.tracingstore.tracings.volume.MagRestrictions import play.api.libs.json._ object TracingMode extends ExtendedEnumeration { @@ -18,7 +18,7 @@ case class AnnotationSettings( somaClickingAllowed: Boolean = true, volumeInterpolationAllowed: Boolean = true, mergerMode: Boolean = false, - resolutionRestrictions: ResolutionRestrictions = ResolutionRestrictions.empty + magRestrictions: MagRestrictions = MagRestrictions.empty ) object AnnotationSettings { diff --git a/app/models/annotation/WKRemoteTracingStoreClient.scala b/app/models/annotation/WKRemoteTracingStoreClient.scala index 95f2c054f02..4502ae0c0fd 100644 --- a/app/models/annotation/WKRemoteTracingStoreClient.scala +++ b/app/models/annotation/WKRemoteTracingStoreClient.scala @@ -17,7 +17,7 @@ import com.scalableminds.webknossos.datastore.models.annotation.{ import com.scalableminds.webknossos.datastore.models.datasource.DataSourceLike import com.scalableminds.webknossos.datastore.rpc.RPC import com.scalableminds.webknossos.tracingstore.tracings.TracingSelector -import com.scalableminds.webknossos.tracingstore.tracings.volume.ResolutionRestrictions +import com.scalableminds.webknossos.tracingstore.tracings.volume.MagRestrictions import com.scalableminds.webknossos.tracingstore.tracings.volume.VolumeDataZipFormat.VolumeDataZipFormat import com.typesafe.scalalogging.LazyLogging import controllers.RpcTokenHolder @@ -100,7 +100,7 @@ class WKRemoteTracingStoreClient( def duplicateVolumeTracing(volumeTracingId: String, isFromTask: Boolean = false, datasetBoundingBox: Option[BoundingBox] = None, - resolutionRestrictions: ResolutionRestrictions = ResolutionRestrictions.empty, + magRestrictions: MagRestrictions = MagRestrictions.empty, downsample: Boolean = false, editPosition: Option[Vec3Int] = None, editRotation: Option[Vec3Double] = None, @@ -109,8 +109,8 @@ class WKRemoteTracingStoreClient( rpc(s"${tracingStore.url}/tracings/volume/$volumeTracingId/duplicate").withLongTimeout .addQueryString("token" -> RpcTokenHolder.webknossosToken) .addQueryString("fromTask" -> isFromTask.toString) - .addQueryStringOptional("minResolution", resolutionRestrictions.minStr) - .addQueryStringOptional("maxResolution", resolutionRestrictions.maxStr) + .addQueryStringOptional("minMag", magRestrictions.minStr) + .addQueryStringOptional("maxMag", magRestrictions.maxStr) .addQueryStringOptional("editPosition", editPosition.map(_.toUriLiteral)) .addQueryStringOptional("editRotation", editRotation.map(_.toUriLiteral)) .addQueryStringOptional("boundingBox", boundingBox.map(_.toLiteral)) @@ -173,7 +173,7 @@ class WKRemoteTracingStoreClient( def saveVolumeTracing(tracing: VolumeTracing, initialData: Option[File] = None, - resolutionRestrictions: ResolutionRestrictions = ResolutionRestrictions.empty, + magRestrictions: MagRestrictions = MagRestrictions.empty, dataSource: Option[DataSourceLike] = None): Fox[String] = { logger.debug("Called to create VolumeTracing." + baseInfo) for { @@ -185,8 +185,8 @@ class WKRemoteTracingStoreClient( case Some(file) => rpc(s"${tracingStore.url}/tracings/volume/$tracingId/initialData").withLongTimeout .addQueryString("token" -> RpcTokenHolder.webknossosToken) - .addQueryStringOptional("minResolution", resolutionRestrictions.minStr) - .addQueryStringOptional("maxResolution", resolutionRestrictions.maxStr) + .addQueryStringOptional("minMag", magRestrictions.minStr) + .addQueryStringOptional("maxMag", magRestrictions.maxStr) .post(file) case _ => Fox.successful(()) diff --git a/app/models/annotation/nml/NmlParser.scala b/app/models/annotation/nml/NmlParser.scala index b3567fd2079..47061403075 100755 --- a/app/models/annotation/nml/NmlParser.scala +++ b/app/models/annotation/nml/NmlParser.scala @@ -33,7 +33,7 @@ object NmlParser extends LazyLogging with ProtoGeometryImplicits with ColorGener private val DEFAULT_TIME = 0L private val DEFAULT_VIEWPORT = 0 - private val DEFAULT_RESOLUTION = 0 + private val DEFAULT_MAG = 0 private val DEFAULT_BITDEPTH = 0 private val DEFAULT_DESCRIPTION = "" private val DEFAULT_INTERPOLATION = false @@ -519,8 +519,8 @@ object NmlParser extends LazyLogging with ProtoGeometryImplicits with ColorGener private def parseViewport(node: XMLNode) = getSingleAttribute(node, "inVp").toIntOpt.getOrElse(DEFAULT_VIEWPORT) - private def parseResolution(node: XMLNode) = - getSingleAttribute(node, "inMag").toIntOpt.getOrElse(DEFAULT_RESOLUTION) + private def parseMag(node: XMLNode) = + getSingleAttribute(node, "inMag").toIntOpt.getOrElse(DEFAULT_MAG) private def parseBitDepth(node: XMLNode) = getSingleAttribute(node, "bitDepth").toIntOpt.getOrElse(DEFAULT_BITDEPTH) @@ -540,21 +540,12 @@ object NmlParser extends LazyLogging with ProtoGeometryImplicits with ColorGener position <- parseVec3Int(node) ?~ Messages("nml.node.attribute.invalid", "position", id) } yield { val viewport = parseViewport(node) - val resolution = parseResolution(node) + val mag = parseMag(node) val timestamp = parseTimestamp(node) val bitDepth = parseBitDepth(node) val interpolation = parseInterpolation(node) val rotation = parseRotationForNode(node).getOrElse(NodeDefaults.rotation) - Node(id, - position, - rotation, - radius, - viewport, - resolution, - bitDepth, - interpolation, - timestamp, - additionalCoordinates) + Node(id, position, rotation, radius, viewport, mag, bitDepth, interpolation, timestamp, additionalCoordinates) } } diff --git a/app/models/annotation/nml/NmlWriter.scala b/app/models/annotation/nml/NmlWriter.scala index 4a667dd5f57..ef8ddd14437 100644 --- a/app/models/annotation/nml/NmlWriter.scala +++ b/app/models/annotation/nml/NmlWriter.scala @@ -359,7 +359,7 @@ class NmlWriter @Inject()(implicit ec: ExecutionContext) extends FoxImplicits { writer.writeAttribute("rotY", n.rotation.y.toString) writer.writeAttribute("rotZ", n.rotation.z.toString) writer.writeAttribute("inVp", n.viewport.toString) - writer.writeAttribute("inMag", n.resolution.toString) + writer.writeAttribute("inMag", n.mag.toString) writer.writeAttribute("bitDepth", n.bitDepth.toString) writer.writeAttribute("interpolation", n.interpolation.toString) writer.writeAttribute("time", n.createdTimestamp.toString) diff --git a/app/models/dataset/Dataset.scala b/app/models/dataset/Dataset.scala index 7eb6d4f704d..fbf0cf0668d 100755 --- a/app/models/dataset/Dataset.scala +++ b/app/models/dataset/Dataset.scala @@ -606,7 +606,7 @@ class DatasetDAO @Inject()(sqlClient: SqlClient, datasetLayerDAO: DatasetLayerDA if (existingDatasetIds.isEmpty) q"TRUE" else q"_id NOT IN ${SqlToken.tupleFromList(existingDatasetIds)}" val statusNotAlreadyInactive = q"status NOT IN ${SqlToken.tupleFromList(inactiveStatusList)}" - val deleteResolutionsQuery = + val deleteMagsQuery = q"""DELETE FROM webknossos.dataset_resolutions WHERE _dataset IN ( SELECT _id @@ -629,12 +629,12 @@ class DatasetDAO @Inject()(sqlClient: SqlClient, datasetLayerDAO: DatasetLayerDA AND $inclusionPredicate AND $statusNotAlreadyInactive""".asUpdate for { - _ <- run(DBIO.sequence(List(deleteResolutionsQuery, deleteLayersQuery, setToUnusableQuery)).transactionally) + _ <- run(DBIO.sequence(List(deleteMagsQuery, deleteLayersQuery, setToUnusableQuery)).transactionally) } yield () } def deleteDataset(datasetId: ObjectId, onlyMarkAsDeleted: Boolean = false): Fox[Unit] = { - val deleteResolutionsQuery = + val deleteMagsQuery = q"DELETE FROM webknossos.dataset_resolutions WHERE _dataset = $datasetId".asUpdate val deleteCoordinateTransformsQuery = q"DELETE FROM webknossos.dataset_layer_coordinateTransformations WHERE _dataset = $datasetId".asUpdate @@ -653,7 +653,7 @@ class DatasetDAO @Inject()(sqlClient: SqlClient, datasetLayerDAO: DatasetLayerDA _ <- run( DBIO .sequence( - List(deleteResolutionsQuery, + List(deleteMagsQuery, deleteAdditionalAxesQuery, deleteLayersQuery, deleteAllowedTeamsQuery, @@ -664,14 +664,13 @@ class DatasetDAO @Inject()(sqlClient: SqlClient, datasetLayerDAO: DatasetLayerDA } } -class DatasetResolutionsDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext) - extends SimpleSQLDAO(sqlClient) { +class DatasetMagsDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext) extends SimpleSQLDAO(sqlClient) { private def parseRow(row: DatasetResolutionsRow): Fox[Vec3Int] = for { - resolution <- Vec3Int.fromList(parseArrayLiteral(row.resolution).map(_.toInt)) ?~> "could not parse resolution" - } yield resolution + mag <- Vec3Int.fromList(parseArrayLiteral(row.resolution).map(_.toInt)) ?~> "could not parse resolution" + } yield mag - def findDataResolutionForLayer(datasetId: ObjectId, dataLayerName: String): Fox[List[Vec3Int]] = + def findMagForLayer(datasetId: ObjectId, dataLayerName: String): Fox[List[Vec3Int]] = for { rows <- run( DatasetResolutions.filter(r => r._Dataset === datasetId.id && r.datalayername === dataLayerName).result) @@ -679,13 +678,13 @@ class DatasetResolutionsDAO @Inject()(sqlClient: SqlClient)(implicit ec: Executi rowsParsed <- Fox.combined(rows.map(parseRow)) ?~> "could not parse resolution row" } yield rowsParsed - def updateResolutions(datasetId: ObjectId, dataLayersOpt: Option[List[DataLayer]]): Fox[Unit] = { + def updateMags(datasetId: ObjectId, dataLayersOpt: Option[List[DataLayer]]): Fox[Unit] = { val clearQuery = q"DELETE FROM webknossos.dataset_resolutions WHERE _dataset = $datasetId".asUpdate val insertQueries = dataLayersOpt.getOrElse(List.empty).flatMap { layer: DataLayer => - layer.resolutions.map { resolution: Vec3Int => + layer.resolutions.map { mag: Vec3Int => { q"""INSERT INTO webknossos.dataset_resolutions(_dataset, dataLayerName, resolution) - VALUES($datasetId, ${layer.name}, $resolution)""".asUpdate + VALUES($datasetId, ${layer.name}, $mag)""".asUpdate } } } @@ -696,7 +695,7 @@ class DatasetResolutionsDAO @Inject()(sqlClient: SqlClient)(implicit ec: Executi class DatasetLayerDAO @Inject()( sqlClient: SqlClient, - datasetResolutionsDAO: DatasetResolutionsDAO, + datasetMagsDAO: DatasetMagsDAO, datasetCoordinateTransformationsDAO: DatasetCoordinateTransformationsDAO, datasetLayerAdditionalAxesDAO: DatasetLayerAdditionalAxesDAO)(implicit ec: ExecutionContext) extends SimpleSQLDAO(sqlClient) { @@ -708,7 +707,7 @@ class DatasetLayerDAO @Inject()( .fromSQL(parseArrayLiteral(row.boundingbox).map(_.toInt)) .toFox ?~> "Could not parse boundingbox" elementClass <- ElementClass.fromString(row.elementclass).toFox ?~> "Could not parse Layer ElementClass" - resolutions <- datasetResolutionsDAO.findDataResolutionForLayer(datasetId, row.name) ?~> "Could not find resolution for layer" + mags <- datasetMagsDAO.findMagForLayer(datasetId, row.name) ?~> "Could not find resolution for layer" defaultViewConfigurationOpt <- Fox.runOptional(row.defaultviewconfiguration)( JsonHelper.parseAndValidateJson[LayerViewConfiguration](_)) adminViewConfigurationOpt <- Fox.runOptional(row.adminviewconfiguration)( @@ -727,7 +726,7 @@ class DatasetLayerDAO @Inject()( row.name, category, boundingBox, - resolutions.sortBy(_.maxDim), + mags.sortBy(_.maxDim), elementClass, row.largestsegmentid, mappingsAsSet.flatMap(m => if (m.isEmpty) None else Some(m)), @@ -742,7 +741,7 @@ class DatasetLayerDAO @Inject()( row.name, category, boundingBox, - resolutions.sortBy(_.maxDim), + mags.sortBy(_.maxDim), elementClass, defaultViewConfigurationOpt, adminViewConfigurationOpt, @@ -812,7 +811,7 @@ class DatasetLayerDAO @Inject()( for { _ <- run(DBIO.sequence(queries)) - _ <- datasetResolutionsDAO.updateResolutions(datasetId, source.toUsable.map(_.dataLayers)) + _ <- datasetMagsDAO.updateMags(datasetId, source.toUsable.map(_.dataLayers)) _ <- datasetCoordinateTransformationsDAO.updateCoordinateTransformations(datasetId, source.toUsable.map(_.dataLayers)) _ <- datasetLayerAdditionalAxesDAO.updateAdditionalAxes(datasetId, source.toUsable.map(_.dataLayers)) diff --git a/app/models/task/TaskCreationService.scala b/app/models/task/TaskCreationService.scala index 1cf52a573d3..865316eef8b 100644 --- a/app/models/task/TaskCreationService.scala +++ b/app/models/task/TaskCreationService.scala @@ -8,7 +8,7 @@ import com.scalableminds.webknossos.datastore.SkeletonTracing.{SkeletonTracing, import com.scalableminds.webknossos.datastore.VolumeTracing.VolumeTracing import com.scalableminds.webknossos.datastore.helpers.ProtoGeometryImplicits import com.scalableminds.webknossos.tracingstore.tracings.TracingType -import com.scalableminds.webknossos.tracingstore.tracings.volume.ResolutionRestrictions +import com.scalableminds.webknossos.tracingstore.tracings.volume.MagRestrictions import javax.inject.Inject import models.annotation.nml.NmlResults.TracingBoxContainer @@ -86,7 +86,7 @@ class TaskCreationService @Inject()(taskTypeService: TaskTypeService, taskParameters, tracingStoreClient, organizationId, - taskType.settings.resolutionRestrictions).map(Some(_)) + taskType.settings.magRestrictions).map(Some(_)) else Fox.successful(None) } yield BaseAnnotation(baseAnnotationIdValidated.id, newSkeletonId, newVolumeId) @@ -146,11 +146,11 @@ class TaskCreationService @Inject()(taskTypeService: TaskTypeService, params: TaskParameters, tracingStoreClient: WKRemoteTracingStoreClient, organizationId: String, - resolutionRestrictions: ResolutionRestrictions)(implicit ctx: DBAccessContext, m: MessagesProvider): Fox[String] = + magRestrictions: MagRestrictions)(implicit ctx: DBAccessContext, m: MessagesProvider): Fox[String] = for { volumeTracingOpt <- baseAnnotation.volumeTracingId newVolumeTracingId <- volumeTracingOpt - .map(id => tracingStoreClient.duplicateVolumeTracing(id, resolutionRestrictions = resolutionRestrictions)) + .map(id => tracingStoreClient.duplicateVolumeTracing(id, magRestrictions = magRestrictions)) .getOrElse( annotationService .createVolumeTracingBase( @@ -160,9 +160,9 @@ class TaskCreationService @Inject()(taskTypeService: TaskTypeService, params.editPosition, params.editRotation, volumeShowFallbackLayer = false, - resolutionRestrictions = resolutionRestrictions + magRestrictions = magRestrictions ) - .flatMap(tracingStoreClient.saveVolumeTracing(_, resolutionRestrictions = resolutionRestrictions))) + .flatMap(tracingStoreClient.saveVolumeTracing(_, magRestrictions = magRestrictions))) } yield newVolumeTracingId // Used in create (without files). If base annotations were used, this does nothing. @@ -201,7 +201,7 @@ class TaskCreationService @Inject()(taskTypeService: TaskTypeService, params.editPosition, params.editRotation, volumeShowFallbackLayer = false, - resolutionRestrictions = taskType.settings.resolutionRestrictions + magRestrictions = taskType.settings.magRestrictions ) .map(v => Some((v, None))) } else Fox.successful(None) @@ -333,7 +333,7 @@ class TaskCreationService @Inject()(taskTypeService: TaskTypeService, params.editPosition, params.editRotation, volumeShowFallbackLayer = false, - resolutionRestrictions = taskType.settings.resolutionRestrictions + magRestrictions = taskType.settings.magRestrictions ) .map(v => (v, None))) @@ -485,7 +485,7 @@ class TaskCreationService @Inject()(taskTypeService: TaskTypeService, taskTypeIdValidated <- ObjectId.fromString(params.taskTypeId) ?~> "taskType.id.invalid" taskType <- taskTypeDAO.findOne(taskTypeIdValidated) ?~> "taskType.notFound" saveResult <- tracingStoreClient - .saveVolumeTracing(tracing, initialFile, resolutionRestrictions = taskType.settings.resolutionRestrictions) + .saveVolumeTracing(tracing, initialFile, magRestrictions = taskType.settings.magRestrictions) .map(Some(_)) } yield saveResult case f: Failure => box2Fox(f) diff --git a/app/models/task/TaskType.scala b/app/models/task/TaskType.scala index 06903e8566f..6532cbbc3fc 100755 --- a/app/models/task/TaskType.scala +++ b/app/models/task/TaskType.scala @@ -6,7 +6,7 @@ import com.scalableminds.util.tools.Fox import com.scalableminds.webknossos.schema.Tables._ import com.scalableminds.webknossos.tracingstore.tracings.TracingType import com.scalableminds.webknossos.tracingstore.tracings.TracingType.TracingType -import com.scalableminds.webknossos.tracingstore.tracings.volume.ResolutionRestrictions +import com.scalableminds.webknossos.tracingstore.tracings.volume.MagRestrictions import models.annotation.{AnnotationSettings, TracingMode} import models.team.TeamDAO import play.api.libs.json._ @@ -95,7 +95,7 @@ class TaskTypeDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext) r.settingsSomaclickingallowed, r.settingsVolumeinterpolationallowed, r.settingsMergermode, - ResolutionRestrictions(r.settingsResolutionrestrictionsMin, r.settingsResolutionrestrictionsMax) + MagRestrictions(r.settingsResolutionrestrictionsMin, r.settingsResolutionrestrictionsMax) ), r.recommendedconfiguration.map(Json.parse), tracingType, @@ -151,8 +151,8 @@ class TaskTypeDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext) ${t.settings.somaClickingAllowed}, ${t.settings.volumeInterpolationAllowed}, ${t.settings.mergerMode}, - ${t.settings.resolutionRestrictions.min}, - ${t.settings.resolutionRestrictions.max}, + ${t.settings.magRestrictions.min}, + ${t.settings.magRestrictions.max}, ${t.recommendedConfiguration.map(Json.toJson(_))}, ${t.tracingType}, ${t.created}, ${t.isDeleted}) @@ -174,8 +174,8 @@ class TaskTypeDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext) settings_somaClickingAllowed = ${t.settings.somaClickingAllowed}, settings_volumeInterpolationAllowed = ${t.settings.volumeInterpolationAllowed}, settings_mergerMode = ${t.settings.mergerMode}, - settings_resolutionRestrictions_min = ${t.settings.resolutionRestrictions.min}, - settings_resolutionRestrictions_max = ${t.settings.resolutionRestrictions.max}, + settings_resolutionRestrictions_min = ${t.settings.magRestrictions.min}, + settings_resolutionRestrictions_max = ${t.settings.magRestrictions.max}, recommendedConfiguration = ${t.recommendedConfiguration.map(Json.toJson(_))}, isDeleted = ${t.isDeleted} WHERE _id = ${t._id}""".asUpdate) diff --git a/conf/messages b/conf/messages index 9bf059ca955..44eff4e660d 100644 --- a/conf/messages +++ b/conf/messages @@ -94,7 +94,7 @@ dataset.import.fileAccessDenied=Cannot create organization folder. Please make s dataset.type.invalid=External data set of type “{0}” is not supported dataset.list.failed=Failed to retrieve list of data sets. dataset.list.writesFailed=Failed to write json for dataset {0} -dataset.noResolutions=Data layer does not contain resolutions +dataset.noMags=Data layer does not contain mags dataset.sampledOnlyBlack=Sampled data positions contained only black data dataset.noData=Could not get data for the requested dataset dataset.initialTeams.teamsNotEmpty=Dataset already has allowed teams @@ -185,8 +185,8 @@ annotation.create.failed=Failed to create annotation. annotation.create.forbidden=You do not have permission to create annotations for this dataset. annotation.createTracings.failed=Failed to set up annotation layers annotation.volume.invalidLargestSegmentId=Cannot create tasks with fallback segmentation layers that do not have a valid largest segment id. -annotation.volume.resolutionRestrictionsTooTight=Task type resolution restrictions are too tight, resulting annotation has no resolutions. -annotation.volume.resolutionsDoNotMatch=Could not merge volume annotations, as their resolutions differ. Please ensure each annotation has the same set of resolutions. +annotation.volume.magRestrictionsTooTight=Task type mag restrictions are too tight, resulting annotation has no magnifications. +annotation.volume.magssDoNotMatch=Could not merge volume annotations, as their magnifications differ. Please ensure each annotation has the same set of mags. annotation.volume.largestSegmentIdExceedsRange=The largest segment id {0} specified for the annotation layer exceeds the range of its data type {1} annotation.notFound=Annotation could not be found annotation.notFound.considerLoggingIn=Annotation could not be found. If the annotation is not public, you need to log in to see it. @@ -293,7 +293,7 @@ taskType.deleteFailure=Task type “{0}” deletion failed taskType.noAnnotations=We could not find finished annotations for this task type taskType.id.invalid=The provided task type id is invalid. taskType.tracingTypeImmutable=Annotation types of task types are immutable. Consider creating a new task type. -taskType.resolutionRestrictionsImmutable=Resolution restrictions of task types are immutable. Consider creating a new task type. +taskType.magRestrictionsImmutable=Mag restrictions of task types are immutable. Consider creating a new task type. taskType.mismatch=The annotation type of the task type is {0}, but found a {1} annotation as base. Consider choosing another task type or correct the NMLs. script.notFound=Script could not be found diff --git a/test/backend/SkeletonUpdateActionsUnitTestSuite.scala b/test/backend/SkeletonUpdateActionsUnitTestSuite.scala index 8d7a23ac771..8d12fe3331d 100644 --- a/test/backend/SkeletonUpdateActionsUnitTestSuite.scala +++ b/test/backend/SkeletonUpdateActionsUnitTestSuite.scala @@ -156,7 +156,7 @@ class SkeletonUpdateActionsUnitTestSuite extends PlaySpec { Option(Vec3Double(newNode.rotation.x, newNode.rotation.y, newNode.rotation.z)), Option(newNode.radius), Option(newNode.viewport), - Option(newNode.resolution), + Option(newNode.mag), Option(newNode.bitDepth), Option(newNode.interpolation), treeId = 1, @@ -181,7 +181,7 @@ class SkeletonUpdateActionsUnitTestSuite extends PlaySpec { Option(Vec3Double(newNode.rotation.x, newNode.rotation.y, newNode.rotation.z)), Option(newNode.radius), Option(newNode.viewport), - Option(newNode.resolution), + Option(newNode.mag), Option(newNode.bitDepth), Option(newNode.interpolation), treeId = 1, @@ -206,7 +206,7 @@ class SkeletonUpdateActionsUnitTestSuite extends PlaySpec { Option(Vec3Double(newNode.rotation.x, newNode.rotation.y, newNode.rotation.z)), Option(newNode.radius), Option(newNode.viewport), - Option(newNode.resolution), + Option(newNode.mag), Option(newNode.bitDepth), Option(newNode.interpolation), treeId = 1, diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala index bac34609baf..7b25237b40d 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala @@ -139,7 +139,7 @@ class BinaryDataController @Inject()( organizationId: String, datasetName: String, dataLayerName: String, - resolution: Int, + mag: Int, x: Int, y: Int, z: Int, @@ -151,10 +151,7 @@ class BinaryDataController @Inject()( datasetName, dataLayerName) ~> NOT_FOUND request = DataRequest( - VoxelPosition(x * cubeSize * resolution, - y * cubeSize * resolution, - z * cubeSize * resolution, - Vec3Int(resolution, resolution, resolution)), + VoxelPosition(x * cubeSize * mag, y * cubeSize * mag, z * cubeSize * mag, Vec3Int(mag, mag, mag)), cubeSize, cubeSize, cubeSize @@ -296,11 +293,8 @@ class BinaryDataController @Inject()( (dataSource, dataLayer) <- dataSourceRepository.getDataSourceAndDataLayer(organizationId, datasetName, dataLayerName) ~> NOT_FOUND - positionAndResolutionOpt <- findDataService.findPositionWithData(dataSource, dataLayer) - } yield - Ok( - Json.obj("position" -> positionAndResolutionOpt.map(_._1), - "resolution" -> positionAndResolutionOpt.map(_._2))) + positionAndMagOpt <- findDataService.findPositionWithData(dataSource, dataLayer) + } yield Ok(Json.obj("position" -> positionAndMagOpt.map(_._1), "mag" -> positionAndMagOpt.map(_._2))) } } diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/ZarrStreamingController.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/ZarrStreamingController.scala index 2c94c3386e6..40ba103615d 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/ZarrStreamingController.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/ZarrStreamingController.scala @@ -281,7 +281,7 @@ class ZarrStreamingController @Inject()( coordinates, reorderedAdditionalAxes) ?~> "zarr.invalidChunkCoordinates" ~> NOT_FOUND magParsed <- Vec3Int.fromMagLiteral(mag, allowScalar = true) ?~> Messages("dataLayer.invalidMag", mag) ~> NOT_FOUND - _ <- bool2Fox(dataLayer.containsResolution(magParsed)) ?~> Messages("dataLayer.wrongMag", dataLayerName, mag) ~> NOT_FOUND + _ <- bool2Fox(dataLayer.containsMag(magParsed)) ?~> Messages("dataLayer.wrongMag", dataLayerName, mag) ~> NOT_FOUND cubeSize = DataLayer.bucketLength request = DataServiceDataRequest( dataSource, @@ -319,7 +319,7 @@ class ZarrStreamingController @Inject()( (_, dataLayer) <- dataSourceRepository.getDataSourceAndDataLayer(organizationId, datasetName, dataLayerName) ?~> Messages( "dataSource.notFound") ~> NOT_FOUND magParsed <- Vec3Int.fromMagLiteral(mag, allowScalar = true) ?~> Messages("dataLayer.invalidMag", mag) ~> NOT_FOUND - _ <- bool2Fox(dataLayer.containsResolution(magParsed)) ?~> Messages("dataLayer.wrongMag", dataLayerName, mag) ~> NOT_FOUND + _ <- bool2Fox(dataLayer.containsMag(magParsed)) ?~> Messages("dataLayer.wrongMag", dataLayerName, mag) ~> NOT_FOUND zarrHeader = ZarrHeader.fromLayer(dataLayer, magParsed) } yield Ok(Json.toJson(zarrHeader)) @@ -341,7 +341,7 @@ class ZarrStreamingController @Inject()( (_, dataLayer) <- dataSourceRepository.getDataSourceAndDataLayer(organizationId, datasetName, dataLayerName) ?~> Messages( "dataSource.notFound") ~> NOT_FOUND magParsed <- Vec3Int.fromMagLiteral(mag, allowScalar = true) ?~> Messages("dataLayer.invalidMag", mag) ~> NOT_FOUND - _ <- bool2Fox(dataLayer.containsResolution(magParsed)) ?~> Messages("dataLayer.wrongMag", dataLayerName, mag) ~> NOT_FOUND + _ <- bool2Fox(dataLayer.containsMag(magParsed)) ?~> Messages("dataLayer.wrongMag", dataLayerName, mag) ~> NOT_FOUND zarrHeader = Zarr3ArrayHeader.fromDataLayer(dataLayer) } yield Ok(Json.toJson(zarrHeader)) @@ -417,7 +417,7 @@ class ZarrStreamingController @Inject()( for { (_, dataLayer) <- dataSourceRepository.getDataSourceAndDataLayer(organizationId, datasetName, dataLayerName) ~> NOT_FOUND magParsed <- Vec3Int.fromMagLiteral(mag, allowScalar = true) ?~> Messages("dataLayer.invalidMag", mag) ~> NOT_FOUND - _ <- bool2Fox(dataLayer.containsResolution(magParsed)) ?~> Messages("dataLayer.wrongMag", dataLayerName, mag) ~> NOT_FOUND + _ <- bool2Fox(dataLayer.containsMag(magParsed)) ?~> Messages("dataLayer.wrongMag", dataLayerName, mag) ~> NOT_FOUND additionalEntries = if (zarrVersion == 2) List(ZarrHeader.FILENAME_DOT_ZARRAY) else List(Zarr3ArrayHeader.FILENAME_ZARR_JSON) } yield diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/MagLocator.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/MagLocator.scala index c97d25d2d5e..0ad0df3a22d 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/MagLocator.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/MagLocator.scala @@ -2,7 +2,7 @@ package com.scalableminds.webknossos.datastore.dataformats import com.scalableminds.util.geometry.Vec3Int import com.scalableminds.webknossos.datastore.datareaders.AxisOrder -import com.scalableminds.webknossos.datastore.models.datasource.ResolutionFormatHelper +import com.scalableminds.webknossos.datastore.models.datasource.MagFormatHelper import com.scalableminds.webknossos.datastore.storage.LegacyDataVaultCredential import play.api.libs.json.{Json, OFormat} @@ -13,6 +13,6 @@ case class MagLocator(mag: Vec3Int, channelIndex: Option[Int] = None, credentialId: Option[String] = None) -object MagLocator extends ResolutionFormatHelper { +object MagLocator extends MagFormatHelper { implicit val jsonFormat: OFormat[MagLocator] = Json.format[MagLocator] } diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/layers/N5DataLayers.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/layers/N5DataLayers.scala index ba9d7db10e7..005b9556bd6 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/layers/N5DataLayers.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/layers/N5DataLayers.scala @@ -20,7 +20,7 @@ trait N5Layer extends DataLayerWithMagLocators { def resolutions: List[Vec3Int] = mags.map(_.mag) - def lengthOfUnderlyingCubes(resolution: Vec3Int): Int = Int.MaxValue // Prevents the wkw-shard-specific handle caching + def lengthOfUnderlyingCubes(mag: Vec3Int): Int = Int.MaxValue // Prevents the wkw-shard-specific handle caching def numChannels: Option[Int] = Some(if (elementClass == ElementClass.uint24) 3 else 1) } diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/layers/PrecomputedDataLayers.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/layers/PrecomputedDataLayers.scala index bb8e7ccda48..2f76460b780 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/layers/PrecomputedDataLayers.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/layers/PrecomputedDataLayers.scala @@ -20,7 +20,7 @@ trait PrecomputedLayer extends DataLayerWithMagLocators { def resolutions: List[Vec3Int] = mags.map(_.mag) - def lengthOfUnderlyingCubes(resolution: Vec3Int): Int = Int.MaxValue // Prevents the wkw-shard-specific handle caching + def lengthOfUnderlyingCubes(mag: Vec3Int): Int = Int.MaxValue // Prevents the wkw-shard-specific handle caching def numChannels: Option[Int] = Some(if (elementClass == ElementClass.uint24) 3 else 1) } diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/layers/WKWDataLayers.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/layers/WKWDataLayers.scala index ee117e5f234..09cb5564e02 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/layers/WKWDataLayers.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/layers/WKWDataLayers.scala @@ -11,7 +11,7 @@ import ucar.ma2.{Array => MultiArray} case class WKWResolution(resolution: Vec3Int, cubeLength: Int) -object WKWResolution extends ResolutionFormatHelper { +object WKWResolution extends MagFormatHelper { implicit val jsonFormat: OFormat[WKWResolution] = Json.format[WKWResolution] } @@ -30,8 +30,8 @@ trait WKWLayer extends DataLayer { def resolutions: List[Vec3Int] = wkwResolutions.map(_.resolution) - def lengthOfUnderlyingCubes(resolution: Vec3Int): Int = - wkwResolutions.find(_.resolution == resolution).map(_.cubeLength).getOrElse(0) + def lengthOfUnderlyingCubes(mag: Vec3Int): Int = + wkwResolutions.find(_.resolution == mag).map(_.cubeLength).getOrElse(0) } diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/layers/Zarr3DataLayers.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/layers/Zarr3DataLayers.scala index 54a343879d4..819fa8082e5 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/layers/Zarr3DataLayers.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/layers/Zarr3DataLayers.scala @@ -20,7 +20,7 @@ trait Zarr3Layer extends DataLayerWithMagLocators { def resolutions: List[Vec3Int] = mags.map(_.mag) - def lengthOfUnderlyingCubes(resolution: Vec3Int): Int = Int.MaxValue // Prevents the wkw-shard-specific handle caching + def lengthOfUnderlyingCubes(mag: Vec3Int): Int = Int.MaxValue // Prevents the wkw-shard-specific handle caching def numChannels: Option[Int] = Some(if (elementClass == ElementClass.uint24) 3 else 1) diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/layers/ZarrDataLayers.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/layers/ZarrDataLayers.scala index 17810f5efb3..6348f8592f0 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/layers/ZarrDataLayers.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/layers/ZarrDataLayers.scala @@ -18,7 +18,7 @@ trait ZarrLayer extends DataLayerWithMagLocators { def resolutions: List[Vec3Int] = mags.map(_.mag) - def lengthOfUnderlyingCubes(resolution: Vec3Int): Int = Int.MaxValue // Prevents the wkw-shard-specific handle caching + def lengthOfUnderlyingCubes(mag: Vec3Int): Int = Int.MaxValue // Prevents the wkw-shard-specific handle caching def numChannels: Option[Int] = Some(if (elementClass == ElementClass.uint24) 3 else 1) diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/wkw/WKWDataFormatHelper.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/wkw/WKWDataFormatHelper.scala index 4449dda46d9..98e10cd876d 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/wkw/WKWDataFormatHelper.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/wkw/WKWDataFormatHelper.scala @@ -20,8 +20,8 @@ trait WKWDataFormatHelper { protected def parseWKWFilePath(path: String): Option[BucketPosition] = { val CubeRx = s"(|.*/)(\\d+|\\d+-\\d+-\\d+)/z(\\d+)/y(\\d+)/x(\\d+).$dataFileExtension".r path match { - case CubeRx(_, resolutionStr, z, y, x) => - Vec3Int.fromMagLiteral(resolutionStr, allowScalar = true).map { mag => + case CubeRx(_, magStr, z, y, x) => + Vec3Int.fromMagLiteral(magStr, allowScalar = true).map { mag => BucketPosition(x.toInt * mag.x * DataLayer.bucketLength, y.toInt * mag.y * DataLayer.bucketLength, z.toInt * mag.z * DataLayer.bucketLength, diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/helpers/SkeletonElementDefaults.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/helpers/SkeletonElementDefaults.scala index 2f6602d498b..714ebd7e725 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/helpers/SkeletonElementDefaults.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/helpers/SkeletonElementDefaults.scala @@ -34,11 +34,11 @@ object NodeDefaults extends ProtoGeometryImplicits { val position: Vec3Int = Vec3Int.zeros val radius: Float = 1.0f val viewport: Int = 1 - val resolution: Int = 1 + val mag: Int = 1 val bitDepth: Int = 0 val interpolation: Boolean = false def createdTimestamp: Long = System.currentTimeMillis() def createInstance: Node = - Node(id, position, rotation, radius, viewport, resolution, bitDepth, interpolation, createdTimestamp) + Node(id, position, rotation, radius, viewport, mag, bitDepth, interpolation, createdTimestamp) } diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/models/datasource/DataLayer.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/models/datasource/DataLayer.scala index 030c33ee148..5390c371da0 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/models/datasource/DataLayer.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/models/datasource/DataLayer.scala @@ -195,7 +195,7 @@ trait DataLayer extends DataLayerLike { /** * Defines the length of the underlying cubes making up the layer. This is the maximal size that can be loaded from a single file. */ - def lengthOfUnderlyingCubes(resolution: Vec3Int): Int + def lengthOfUnderlyingCubes(mag: Vec3Int): Int def bucketProvider(remoteSourceDescriptorServiceOpt: Option[RemoteSourceDescriptorService], dataSourceId: DataSourceId, @@ -203,7 +203,7 @@ trait DataLayer extends DataLayerLike { def bucketProviderCacheKey: String = this.name - def containsResolution(resolution: Vec3Int): Boolean = resolutions.contains(resolution) + def containsMag(mag: Vec3Int): Boolean = resolutions.contains(mag) def doesContainBucket(bucket: BucketPosition): Boolean = boundingBox.intersects(bucket.toMag1BoundingBox) @@ -411,15 +411,15 @@ object AbstractSegmentationLayer { implicit val jsonFormat: OFormat[AbstractSegmentationLayer] = Json.format[AbstractSegmentationLayer] } -trait ResolutionFormatHelper { +trait MagFormatHelper { - implicit object resolutionFormat extends Format[Vec3Int] { + implicit object magFormat extends Format[Vec3Int] { override def reads(json: JsValue): JsResult[Vec3Int] = json.validate[Int].map(result => Vec3Int(result, result, result)).orElse(Vec3Int.Vec3IntReads.reads(json)) - override def writes(resolution: Vec3Int): JsValue = - Vec3Int.Vec3IntWrites.writes(resolution) + override def writes(mag: Vec3Int): JsValue = + Vec3Int.Vec3IntWrites.writes(mag) } } diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/BinaryDataService.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/BinaryDataService.scala index f82f1a9233b..fa255676d2b 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/BinaryDataService.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/BinaryDataService.scala @@ -83,7 +83,7 @@ class BinaryDataService(val dataBaseDir: Path, } private def handleBucketRequest(request: DataServiceDataRequest, bucket: BucketPosition): Fox[Array[Byte]] = - if (request.dataLayer.doesContainBucket(bucket) && request.dataLayer.containsResolution(bucket.mag)) { + if (request.dataLayer.doesContainBucket(bucket) && request.dataLayer.containsMag(bucket.mag)) { val readInstruction = DataReadInstruction(dataBaseDir, request.dataSource, request.dataLayer, bucket, request.settings.version) // dataSource is null and unused for volume tracings. Insert dummy DataSourceId (also unused in that case) diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala index eb1e326ab00..aa712788421 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/FindDataService.scala @@ -25,9 +25,9 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp private def getDataFor(dataSource: DataSource, dataLayer: DataLayer, position: Vec3Int, - resolution: Vec3Int): Fox[Array[Byte]] = { + mag: Vec3Int): Fox[Array[Byte]] = { val request = DataRequest( - VoxelPosition(position.x, position.y, position.z, resolution), + VoxelPosition(position.x, position.y, position.z, mag), DataLayer.bucketLength, DataLayer.bucketLength, DataLayer.bucketLength @@ -46,10 +46,10 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp private def getConcatenatedDataFor(dataSource: DataSource, dataLayer: DataLayer, positions: List[Vec3Int], - resolution: Vec3Int) = + mag: Vec3Int) = for { dataBucketWise: Seq[Array[Byte]] <- Fox - .sequenceOfFulls(positions.map(getDataFor(dataSource, dataLayer, _, resolution))) + .sequenceOfFulls(positions.map(getDataFor(dataSource, dataLayer, _, mag))) .toFox _ <- bool2Fox(dataBucketWise.nonEmpty) ?~> "dataset.noData" dataConcatenated = concatenateBuckets(dataBucketWise) @@ -100,24 +100,24 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp private def checkAllPositionsForData(dataSource: DataSource, dataLayer: DataLayer): Fox[Option[(Vec3Int, Vec3Int)]] = { - def searchPositionIter(positions: List[Vec3Int], resolution: Vec3Int): Fox[Option[Vec3Int]] = + def searchPositionIter(positions: List[Vec3Int], mag: Vec3Int): Fox[Option[Vec3Int]] = positions match { case List() => Fox.successful(None) case head :: tail => - checkIfPositionHasData(head, resolution).futureBox.flatMap { + checkIfPositionHasData(head, mag).futureBox.flatMap { case Full(pos) => Fox.successful(Some(pos)) - case _ => searchPositionIter(tail, resolution) + case _ => searchPositionIter(tail, mag) } } - def checkIfPositionHasData(position: Vec3Int, resolution: Vec3Int) = + def checkIfPositionHasData(position: Vec3Int, mag: Vec3Int) = for { - data <- getDataFor(dataSource, dataLayer, position, resolution) + data <- getDataFor(dataSource, dataLayer, position, mag) position <- getPositionOfNonZeroData(data, position, dataLayer.bytesPerElement) } yield position - def resolutionIter(positions: List[Vec3Int], remainingResolutions: List[Vec3Int]): Fox[Option[(Vec3Int, Vec3Int)]] = - remainingResolutions match { + def magIter(positions: List[Vec3Int], remainingMags: List[Vec3Int]): Fox[Option[(Vec3Int, Vec3Int)]] = + remainingMags match { case List() => Fox.successful(None) case head :: tail => (for { @@ -125,17 +125,17 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp } yield foundPosition match { case Some(position) => Fox.successful(Some((position, head))) - case None => resolutionIter(positions, tail) + case None => magIter(positions, tail) }).flatten } - resolutionIter(createPositions(dataLayer).distinct, dataLayer.resolutions.sortBy(_.maxDim)) + magIter(createPositions(dataLayer).distinct, dataLayer.resolutions.sortBy(_.maxDim)) } def findPositionWithData(dataSource: DataSource, dataLayer: DataLayer): Fox[Option[(Vec3Int, Vec3Int)]] = for { - positionAndResolutionOpt <- checkAllPositionsForData(dataSource, dataLayer) - } yield positionAndResolutionOpt + positionAndMagOpt <- checkAllPositionsForData(dataSource, dataLayer) + } yield positionAndMagOpt def createHistogram(dataSource: DataSource, dataLayer: DataLayer): Fox[List[Histogram]] = { @@ -182,9 +182,9 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp List(Histogram(counts, data.length, extrema._1, extrema._2)) } - def histogramForPositions(positions: List[Vec3Int], resolution: Vec3Int) = + def histogramForPositions(positions: List[Vec3Int], mag: Vec3Int) = for { - dataConcatenated <- getConcatenatedDataFor(dataSource, dataLayer, positions, resolution) ?~> "dataset.noData" + dataConcatenated <- getConcatenatedDataFor(dataSource, dataLayer, positions, mag) ?~> "dataset.noData" isUint24 = dataLayer.elementClass == ElementClass.uint24 convertedData = toUnsigned(filterZeroes(convertData(dataConcatenated, dataLayer.elementClass), skip = isUint24)) } yield calculateHistogramValues(convertedData, dataLayer.bytesPerElement, isUint24) @@ -192,6 +192,6 @@ class FindDataService @Inject()(dataServicesHolder: BinaryDataServiceHolder)(imp if (dataLayer.resolutions.nonEmpty) histogramForPositions(createPositions(dataLayer, 2).distinct, dataLayer.resolutions.minBy(_.maxDim)) else - Fox.empty ?~> "dataset.noResolutions" + Fox.empty ?~> "dataset.noMags" } } diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/uploading/UploadService.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/uploading/UploadService.scala index 8949a462d1f..96242243433 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/uploading/UploadService.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/uploading/UploadService.scala @@ -320,7 +320,7 @@ class UploadService @Inject()(dataSourceRepository: DataSourceRepository, case UploadedDataSourceType.ZARR_MULTILAYER | UploadedDataSourceType.NEUROGLANCER_MULTILAYER | UploadedDataSourceType.N5_MULTILAYER => tryExploringMultipleLayers(unpackToDir, dataSourceId, uploadedDataSourceType) - case UploadedDataSourceType.WKW => addLayerAndResolutionDirIfMissing(unpackToDir).toFox + case UploadedDataSourceType.WKW => addLayerAndMagDirIfMissing(unpackToDir).toFox } _ <- datasetSymlinkService.addSymlinksToOtherDatasetLayers(unpackToDir, layersToLink.getOrElse(List.empty)) _ <- addLinkedLayersToDataSourceProperties(unpackToDir, dataSourceId.team, layersToLink.getOrElse(List.empty)) @@ -331,8 +331,7 @@ class UploadService @Inject()(dataSourceRepository: DataSourceRepository, dataSourceId: DataSourceId, typ: UploadedDataSourceType.Value): Fox[Unit] = for { - _ <- Fox.runIf(typ == UploadedDataSourceType.ZARR)( - addLayerAndResolutionDirIfMissing(path, FILENAME_DOT_ZARRAY).toFox) + _ <- Fox.runIf(typ == UploadedDataSourceType.ZARR)(addLayerAndMagDirIfMissing(path, FILENAME_DOT_ZARRAY).toFox) explored <- exploreLocalLayerService.exploreLocal(path, dataSourceId) _ <- exploreLocalLayerService.writeLocalDatasourceProperties(explored, path) } yield () @@ -350,7 +349,7 @@ class UploadService @Inject()(dataSourceRepository: DataSourceRepository, layerDirs .map(layerDir => for { - _ <- addLayerAndResolutionDirIfMissing(layerDir).toFox + _ <- addLayerAndMagDirIfMissing(layerDir).toFox explored: DataSourceWithMagLocators <- exploreLocalLayerService .exploreLocal(path, dataSourceId, layerDir.getFileName.toString) } yield explored) @@ -522,8 +521,7 @@ class UploadService @Inject()(dataSourceRepository: DataSourceRepository, layerDirs = potentialLayers.filter(p => looksLikeZarrArray(p, maxDepth = 2).isDefined) } yield layerDirs - private def addLayerAndResolutionDirIfMissing(dataSourceDir: Path, - headerFile: String = FILENAME_HEADER_WKW): Box[Unit] = + private def addLayerAndMagDirIfMissing(dataSourceDir: Path, headerFile: String = FILENAME_HEADER_WKW): Box[Unit] = if (Files.exists(dataSourceDir)) { for { listing: Seq[Path] <- PathUtils.listFilesRecursive(dataSourceDir, diff --git a/webknossos-datastore/conf/com.scalableminds.webknossos.datastore.routes b/webknossos-datastore/conf/com.scalableminds.webknossos.datastore.routes index a4dca523cde..17ebff8ea57 100644 --- a/webknossos-datastore/conf/com.scalableminds.webknossos.datastore.routes +++ b/webknossos-datastore/conf/com.scalableminds.webknossos.datastore.routes @@ -13,7 +13,7 @@ GET /datasets/:organizationId/:datasetName/layers/:dataLayerName/findD GET /datasets/:organizationId/:datasetName/layers/:dataLayerName/histogram @com.scalableminds.webknossos.datastore.controllers.BinaryDataController.histogram(token: Option[String], organizationId: String, datasetName: String, dataLayerName: String) # Knossos compatible routes -GET /datasets/:organizationId/:datasetName/layers/:dataLayerName/mag:resolution/x:x/y:y/z:z/bucket.raw @com.scalableminds.webknossos.datastore.controllers.BinaryDataController.requestViaKnossos(token: Option[String], organizationId: String, datasetName: String, dataLayerName: String, resolution: Int, x: Int, y: Int, z: Int, cubeSize: Int) +GET /datasets/:organizationId/:datasetName/layers/:dataLayerName/mag:mag/x:x/y:y/z:z/bucket.raw @com.scalableminds.webknossos.datastore.controllers.BinaryDataController.requestViaKnossos(token: Option[String], organizationId: String, datasetName: String, dataLayerName: String, mag: Int, x: Int, y: Int, z: Int, cubeSize: Int) # Zarr2 compatible routes GET /zarr/:organizationId/:datasetName @com.scalableminds.webknossos.datastore.controllers.ZarrStreamingController.requestDataSourceFolderContents(token: Option[String], organizationId: String, datasetName: String, zarrVersion: Int = 2) diff --git a/webknossos-datastore/proto/SkeletonTracing.proto b/webknossos-datastore/proto/SkeletonTracing.proto index 7f9ebc91e38..ef2ce5dc507 100644 --- a/webknossos-datastore/proto/SkeletonTracing.proto +++ b/webknossos-datastore/proto/SkeletonTracing.proto @@ -11,7 +11,7 @@ message Node { required Vec3DoubleProto rotation = 3; required float radius = 4; required int32 viewport = 5; - required int32 resolution = 6; + required int32 mag = 6; required int32 bitDepth = 7; required bool interpolation = 8; required int64 createdTimestamp = 9; diff --git a/webknossos-datastore/proto/VolumeTracing.proto b/webknossos-datastore/proto/VolumeTracing.proto index ee2651eea33..0f06affd9ad 100644 --- a/webknossos-datastore/proto/VolumeTracing.proto +++ b/webknossos-datastore/proto/VolumeTracing.proto @@ -39,7 +39,7 @@ message VolumeTracing { optional BoundingBoxProto userBoundingBox = 12; repeated NamedBoundingBoxProto userBoundingBoxes = 13; optional string organizationId = 14; // used when parsing and handling nmls, not used in tracing store anymore, do not rely on correct values - repeated Vec3IntProto resolutions = 15; + repeated Vec3IntProto mags = 15; repeated Segment segments = 16; optional string mappingName = 17; // either a mapping present in the fallback layer, or an editable mapping on the tracingstore optional bool hasEditableMapping = 18; // the selected mapping is an editable mapping diff --git a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/controllers/VolumeTracingController.scala b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/controllers/VolumeTracingController.scala index 67f9c7c3275..cc9090c79ed 100644 --- a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/controllers/VolumeTracingController.scala +++ b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/controllers/VolumeTracingController.scala @@ -36,7 +36,7 @@ import com.scalableminds.webknossos.tracingstore.tracings.editablemapping.{ } import com.scalableminds.webknossos.tracingstore.tracings.volume.{ MergedVolumeStats, - ResolutionRestrictions, + MagRestrictions, TSFullMeshService, UpdateMappingNameAction, VolumeDataZipFormat, @@ -91,8 +91,8 @@ class VolumeTracingController @Inject()( def initialData(token: Option[String], tracingId: String, - minResolution: Option[Int], - maxResolution: Option[Int]): Action[AnyContent] = + minMag: Option[Int], + maxMag: Option[Int]): Action[AnyContent] = Action.async { implicit request => log() { logTime(slackNotificationService.noticeSlowRequest) { @@ -100,11 +100,9 @@ class VolumeTracingController @Inject()( for { initialData <- request.body.asRaw.map(_.asFile) ?~> Messages("zipFile.notFound") tracing <- tracingService.find(tracingId) ?~> Messages("tracing.notFound") - resolutionRestrictions = ResolutionRestrictions(minResolution, maxResolution) - resolutions <- tracingService - .initializeWithData(tracingId, tracing, initialData, resolutionRestrictions, token) - .toFox - _ <- tracingService.updateResolutionList(tracingId, tracing, resolutions) + magRestrictions = MagRestrictions(minMag, maxMag) + mags <- tracingService.initializeWithData(tracingId, tracing, initialData, magRestrictions, token).toFox + _ <- tracingService.updateMagList(tracingId, tracing, mags) } yield Ok(Json.toJson(tracingId)) } } @@ -138,8 +136,8 @@ class VolumeTracingController @Inject()( for { initialData <- request.body.asRaw.map(_.asFile) ?~> Messages("zipFile.notFound") tracing <- tracingService.find(tracingId) ?~> Messages("tracing.notFound") - resolutions <- tracingService.initializeWithDataMultiple(tracingId, tracing, initialData, token).toFox - _ <- tracingService.updateResolutionList(tracingId, tracing, resolutions) + mags <- tracingService.initializeWithDataMultiple(tracingId, tracing, initialData, token).toFox + _ <- tracingService.updateMagList(tracingId, tracing, mags) } yield Ok(Json.toJson(tracingId)) } } @@ -196,8 +194,8 @@ class VolumeTracingController @Inject()( def duplicate(token: Option[String], tracingId: String, fromTask: Option[Boolean], - minResolution: Option[Int], - maxResolution: Option[Int], + minMag: Option[Int], + maxMag: Option[Int], downsample: Option[Boolean], editPosition: Option[String], editRotation: Option[String], @@ -210,7 +208,7 @@ class VolumeTracingController @Inject()( tracing <- tracingService.find(tracingId) ?~> Messages("tracing.notFound") _ = logger.info(s"Duplicating volume tracing $tracingId...") datasetBoundingBox = request.body.asJson.flatMap(_.validateOpt[BoundingBox].asOpt.flatten) - resolutionRestrictions = ResolutionRestrictions(minResolution, maxResolution) + magRestrictions = MagRestrictions(minMag, maxMag) editPositionParsed <- Fox.runOptional(editPosition)(Vec3Int.fromUriLiteral) editRotationParsed <- Fox.runOptional(editRotation)(Vec3Double.fromUriLiteral) boundingBoxParsed <- Fox.runOptional(boundingBox)(BoundingBox.fromLiteral) @@ -223,7 +221,7 @@ class VolumeTracingController @Inject()( tracing, fromTask.getOrElse(false), datasetBoundingBox, - resolutionRestrictions, + magRestrictions, editPositionParsed, editRotationParsed, boundingBoxParsed, @@ -336,7 +334,7 @@ class VolumeTracingController @Inject()( for { positionOpt <- tracingService.findData(tracingId) } yield { - Ok(Json.obj("position" -> positionOpt, "resolution" -> positionOpt.map(_ => Vec3Int.ones))) + Ok(Json.obj("position" -> positionOpt, "mag" -> positionOpt.map(_ => Vec3Int.ones))) } } } diff --git a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/controllers/VolumeTracingZarrStreamingController.scala b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/controllers/VolumeTracingZarrStreamingController.scala index 0466da5c31c..ffef816f23a 100644 --- a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/controllers/VolumeTracingZarrStreamingController.scala +++ b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/controllers/VolumeTracingZarrStreamingController.scala @@ -61,7 +61,7 @@ class VolumeTracingZarrStreamingController @Inject()( accessTokenService.validateAccess(UserAccessRequest.readTracing(tracingId), urlOrHeaderToken(token, request)) { for { tracing <- tracingService.find(tracingId) ?~> Messages("tracing.notFound") ~> NOT_FOUND - existingMags = tracing.resolutions.map(vec3IntFromProto) + existingMags = tracing.mags.map(vec3IntFromProto) additionalFiles = if (zarrVersion == 2) List(NgffMetadata.FILENAME_DOT_ZATTRS, NgffGroupHeader.FILENAME_DOT_ZGROUP) else List(Zarr3ArrayHeader.FILENAME_ZARR_JSON) @@ -80,7 +80,7 @@ class VolumeTracingZarrStreamingController @Inject()( accessTokenService.validateAccess(UserAccessRequest.readTracing(tracingId), urlOrHeaderToken(token, request)) { for { tracing <- tracingService.find(tracingId) ?~> Messages("tracing.notFound") ~> NOT_FOUND - existingMags = tracing.resolutions.map(vec3IntFromProto(_).toMagLiteral(allowScalar = true)) + existingMags = tracing.mags.map(vec3IntFromProto(_).toMagLiteral(allowScalar = true)) additionalFiles = if (zarrVersion == 2) List(NgffMetadata.FILENAME_DOT_ZATTRS, NgffGroupHeader.FILENAME_DOT_ZGROUP) else List(Zarr3ArrayHeader.FILENAME_ZARR_JSON) @@ -97,7 +97,7 @@ class VolumeTracingZarrStreamingController @Inject()( for { tracing <- tracingService.find(tracingId) ?~> Messages("tracing.notFound") ~> NOT_FOUND - existingMags = tracing.resolutions.map(vec3IntFromProto) + existingMags = tracing.mags.map(vec3IntFromProto) magParsed <- Vec3Int.fromMagLiteral(mag, allowScalar = true) ?~> Messages("dataLayer.invalidMag", mag) ~> NOT_FOUND _ <- bool2Fox(existingMags.contains(magParsed)) ?~> Messages("tracing.wrongMag", tracingId, mag) ~> NOT_FOUND files = if (zarrVersion == 2) List(".zarray") else List(Zarr3ArrayHeader.FILENAME_ZARR_JSON) @@ -120,7 +120,7 @@ class VolumeTracingZarrStreamingController @Inject()( for { tracing <- tracingService.find(tracingId) ?~> Messages("tracing.notFound") ~> NOT_FOUND - existingMags = tracing.resolutions.map(vec3IntFromProto) + existingMags = tracing.mags.map(vec3IntFromProto) magParsed <- Vec3Int.fromMagLiteral(mag, allowScalar = true) ?~> Messages("dataLayer.invalidMag", mag) ~> NOT_FOUND _ <- bool2Fox(existingMags.contains(magParsed)) ?~> Messages("tracing.wrongMag", tracingId, mag) ~> NOT_FOUND files = if (zarrVersion == 2) List(".zarray") else List(Zarr3ArrayHeader.FILENAME_ZARR_JSON) @@ -134,7 +134,7 @@ class VolumeTracingZarrStreamingController @Inject()( for { tracing <- tracingService.find(tracingId) ?~> Messages("tracing.notFound") ~> NOT_FOUND - existingMags = tracing.resolutions.map(vec3IntFromProto) + existingMags = tracing.mags.map(vec3IntFromProto) magParsed <- Vec3Int .fromMagLiteral(mag, allowScalar = true) ?~> Messages("dataLayer.invalidMag", mag) ~> NOT_FOUND _ <- bool2Fox(existingMags.contains(magParsed)) ?~> Messages("tracing.wrongMag", tracingId, mag) ~> NOT_FOUND @@ -170,7 +170,7 @@ class VolumeTracingZarrStreamingController @Inject()( for { tracing <- tracingService.find(tracingId) ?~> Messages("tracing.notFound") ~> NOT_FOUND - existingMags = tracing.resolutions.map(vec3IntFromProto) + existingMags = tracing.mags.map(vec3IntFromProto) magParsed <- Vec3Int .fromMagLiteral(mag, allowScalar = true) ?~> Messages("dataLayer.invalidMag", mag) ~> NOT_FOUND _ <- bool2Fox(existingMags.contains(magParsed)) ?~> Messages("tracing.wrongMag", tracingId, mag) ~> NOT_FOUND @@ -230,7 +230,7 @@ class VolumeTracingZarrStreamingController @Inject()( for { tracing <- tracingService.find(tracingId) ?~> Messages("tracing.notFound") ~> NOT_FOUND - existingMags = tracing.resolutions.map(vec3IntFromProto) + existingMags = tracing.mags.map(vec3IntFromProto) dataSource <- remoteWebknossosClient.getDataSourceForTracing(tracingId) ~> NOT_FOUND omeNgffHeader = NgffMetadata.fromNameVoxelSizeAndMags(tracingId, dataSourceVoxelSize = dataSource.scale, @@ -247,7 +247,7 @@ class VolumeTracingZarrStreamingController @Inject()( for { tracing <- tracingService.find(tracingId) ?~> Messages("tracing.notFound") ~> NOT_FOUND - existingMags = tracing.resolutions.map(vec3IntFromProto) + existingMags = tracing.mags.map(vec3IntFromProto) dataSource <- remoteWebknossosClient.getDataSourceForTracing(tracingId) ~> NOT_FOUND omeNgffHeader = NgffMetadataV0_5.fromNameVoxelSizeAndMags(tracingId, dataSourceVoxelSize = dataSource.scale, @@ -272,7 +272,7 @@ class VolumeTracingZarrStreamingController @Inject()( largestSegmentId = tracing.largestSegmentId, boundingBox = tracing.boundingBox, elementClass = tracing.elementClass, - mags = tracing.resolutions.toList.map(x => MagLocator(x, None, None, Some(AxisOrder.cxyz), None, None)), + mags = tracing.mags.toList.map(x => MagLocator(x, None, None, Some(AxisOrder.cxyz), None, None)), mappings = None, numChannels = Some(if (tracing.elementClass.isuint24) 3 else 1), dataFormat = if (zarrVersion == 2) DataFormat.zarr else DataFormat.zarr3 @@ -288,7 +288,7 @@ class VolumeTracingZarrStreamingController @Inject()( for { tracing <- tracingService.find(tracingId) ?~> Messages("tracing.notFound") ~> NOT_FOUND - existingMags = tracing.resolutions.map(vec3IntFromProto) + existingMags = tracing.mags.map(vec3IntFromProto) magParsed <- Vec3Int.fromMagLiteral(mag, allowScalar = true) ?~> Messages("dataLayer.invalidMag", mag) ~> NOT_FOUND _ <- bool2Fox(existingMags.contains(magParsed)) ?~> Messages("tracing.wrongMag", tracingId, mag) ~> NOT_FOUND diff --git a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/editablemapping/EditableMappingLayer.scala b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/editablemapping/EditableMappingLayer.scala index 47965cee726..96d44509897 100644 --- a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/editablemapping/EditableMappingLayer.scala +++ b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/editablemapping/EditableMappingLayer.scala @@ -83,7 +83,7 @@ case class EditableMappingLayer(name: String, override def coordinateTransformations: Option[List[CoordinateTransformation]] = None - override def lengthOfUnderlyingCubes(resolution: Vec3Int): Int = DataLayer.bucketLength + override def lengthOfUnderlyingCubes(mag: Vec3Int): Int = DataLayer.bucketLength override def bucketProvider(remoteSourceDescriptorServiceOpt: Option[RemoteSourceDescriptorService], dataSourceId: DataSourceId, diff --git a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/editablemapping/EditableMappingService.scala b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/editablemapping/EditableMappingService.scala index cd83ccee97e..0f9f857ca61 100644 --- a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/editablemapping/EditableMappingService.scala +++ b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/editablemapping/EditableMappingService.scala @@ -561,7 +561,7 @@ class EditableMappingService @Inject()( EditableMappingLayer( mappingName, tracing.boundingBox, - resolutions = tracing.resolutions.map(vec3IntFromProto).toList, + resolutions = tracing.mags.map(vec3IntFromProto).toList, largestSegmentId = Some(0L), elementClass = tracing.elementClass, userToken, diff --git a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/skeleton/updating/SkeletonUpdateActions.scala b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/skeleton/updating/SkeletonUpdateActions.scala index 46dc8bad0f0..c34e5ae8c74 100644 --- a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/skeleton/updating/SkeletonUpdateActions.scala +++ b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/skeleton/updating/SkeletonUpdateActions.scala @@ -233,7 +233,7 @@ case class CreateNodeSkeletonAction(id: Int, rotationOrDefault, radius getOrElse NodeDefaults.radius, viewport getOrElse NodeDefaults.viewport, - resolution getOrElse NodeDefaults.resolution, + resolution getOrElse NodeDefaults.mag, bitDepth getOrElse NodeDefaults.bitDepth, interpolation getOrElse NodeDefaults.interpolation, createdTimestamp = timestamp, @@ -278,7 +278,7 @@ case class UpdateNodeSkeletonAction(id: Int, rotationOrDefault, radius getOrElse NodeDefaults.radius, viewport getOrElse NodeDefaults.viewport, - resolution getOrElse NodeDefaults.resolution, + resolution getOrElse NodeDefaults.mag, bitDepth getOrElse NodeDefaults.bitDepth, interpolation getOrElse NodeDefaults.interpolation, createdTimestamp = timestamp, diff --git a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/MergedVolume.scala b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/MergedVolume.scala index 800380e388b..ac4b4507fed 100644 --- a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/MergedVolume.scala +++ b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/MergedVolume.scala @@ -14,7 +14,7 @@ import scala.concurrent.ExecutionContext case class MergedVolumeStats( largestSegmentId: Long, - sortedResolutionList: Option[List[Vec3IntProto]], // None means do not touch the resolution list + sortedMagsList: Option[List[Vec3IntProto]], // None means do not touch the mag list labelMaps: List[Map[Long, Long]], createdSegmentIndex: Boolean ) @@ -49,11 +49,11 @@ class MergedVolume(elementClass: ElementClassProto, initialLargestSegmentId: Lon } def addLabelSetFromBucketStream(bucketStream: Iterator[(BucketPosition, Array[Byte])], - allowedResolutions: Set[Vec3Int]): Unit = { + allowedMags: Set[Vec3Int]): Unit = { val labelSet: mutable.Set[UnsignedInteger] = scala.collection.mutable.Set() bucketStream.foreach { case (bucketPosition, data) => - if (allowedResolutions.contains(bucketPosition.mag)) { + if (allowedMags.contains(bucketPosition.mag)) { val dataTyped = UnsignedIntegerArray.fromByteArray(data, elementClass) val nonZeroData: Array[UnsignedInteger] = UnsignedIntegerArray.filterNonZero(dataTyped) labelSet ++= nonZeroData @@ -86,10 +86,10 @@ class MergedVolume(elementClass: ElementClassProto, initialLargestSegmentId: Lon def addFromBucketStream(sourceVolumeIndex: Int, bucketStream: Iterator[(BucketPosition, Array[Byte])], - allowedResolutions: Option[Set[Vec3Int]] = None): Unit = + allowedMags: Option[Set[Vec3Int]] = None): Unit = bucketStream.foreach { case (bucketPosition, bytes) => - if (!isAllZero(bytes) && allowedResolutions.forall(_.contains(bucketPosition.mag))) { + if (!isAllZero(bytes) && allowedMags.forall(_.contains(bucketPosition.mag))) { add(sourceVolumeIndex, bucketPosition, bytes) } } @@ -136,7 +136,7 @@ class MergedVolume(elementClass: ElementClassProto, initialLargestSegmentId: Lon } } yield () - def presentResolutions: Set[Vec3Int] = + def presentMags: Set[Vec3Int] = mergedVolume.map { case (bucketPosition: BucketPosition, _) => bucketPosition.mag }.toSet @@ -144,7 +144,7 @@ class MergedVolume(elementClass: ElementClassProto, initialLargestSegmentId: Lon def stats(createdSegmentIndex: Boolean): MergedVolumeStats = MergedVolumeStats( largestSegmentId.toLong, - Some(presentResolutions.toList.sortBy(_.maxDim).map(vec3IntToProto)), + Some(presentMags.toList.sortBy(_.maxDim).map(vec3IntToProto)), labelMapsToLongMaps, createdSegmentIndex ) diff --git a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/TSFullMeshService.scala b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/TSFullMeshService.scala index 8ccfcfc7033..3b3c99db295 100644 --- a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/TSFullMeshService.scala +++ b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/TSFullMeshService.scala @@ -64,7 +64,7 @@ class TSFullMeshService @Inject()(volumeTracingService: VolumeTracingService, fullMeshRequest: FullMeshRequest)(implicit ec: ExecutionContext): Fox[Array[Byte]] = for { mag <- fullMeshRequest.mag.toFox ?~> "mag.neededForAdHoc" - _ <- bool2Fox(tracing.resolutions.contains(vec3IntToProto(mag))) ?~> "mag.notPresentInTracing" + _ <- bool2Fox(tracing.mags.contains(vec3IntToProto(mag))) ?~> "mag.notPresentInTracing" before = Instant.now voxelSize <- remoteDatastoreClient.voxelSizeForTracingWithCache(tracingId, token) ?~> "voxelSize.failedToFetch" verticesForChunks <- if (tracing.hasSegmentIndex.getOrElse(false)) diff --git a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeDataZipHelper.scala b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeDataZipHelper.scala index ce74001a669..5b9fb21fad4 100644 --- a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeDataZipHelper.scala +++ b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeDataZipHelper.scala @@ -130,15 +130,15 @@ trait VolumeDataZipHelper } } - protected def resolutionSetFromZipfile(zipFile: File): Set[Vec3Int] = { - val resolutionSet = new mutable.HashSet[Vec3Int]() + protected def magSetFromZipfile(zipFile: File): Set[Vec3Int] = { + val magSet = new mutable.HashSet[Vec3Int]() ZipIO.withUnziped(zipFile) { case (fileName, _) => getMagFromWkwOrZarrHeaderFilePath(fileName.toString).map { mag: Vec3Int => - resolutionSet.add(mag) + magSet.add(mag) } } - resolutionSet.toSet + magSet.toSet } private def getMagFromWkwOrZarrHeaderFilePath(path: String): Option[Vec3Int] = { diff --git a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingBucketHelper.scala b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingBucketHelper.scala index 97d64b826db..14832ee36de 100644 --- a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingBucketHelper.scala +++ b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingBucketHelper.scala @@ -106,8 +106,8 @@ trait BucketKeys extends MortonEncoding with WKWDataFormatHelper with LazyLoggin private def parseBucketKeyXYZ(key: String) = { val keyRx = "([0-9a-z-]+)/(\\d+|\\d+-\\d+-\\d+)/-?\\d+-\\[(\\d+),(\\d+),(\\d+)]".r key match { - case keyRx(name, resolutionStr, xStr, yStr, zStr) => - getBucketPosition(xStr, yStr, zStr, resolutionStr, None).map(bucketPosition => (name, bucketPosition)) + case keyRx(name, magStr, xStr, yStr, zStr) => + getBucketPosition(xStr, yStr, zStr, magStr, None).map(bucketPosition => (name, bucketPosition)) case _ => None } @@ -122,7 +122,7 @@ trait BucketKeys extends MortonEncoding with WKWDataFormatHelper with LazyLoggin matchOpt match { case Some(aMatch) => val name = aMatch.group(1) - val resolutionStr = aMatch.group(2) + val magStr = aMatch.group(2) val xStr = aMatch.group(additionalAxes.length + 3) val yStr = aMatch.group(additionalAxes.length + 4) val zStr = aMatch.group(additionalAxes.length + 5) @@ -134,7 +134,7 @@ trait BucketKeys extends MortonEncoding with WKWDataFormatHelper with LazyLoggin AdditionalCoordinate(additionalAxesIndexSorted(groupIndexAndAxisIndex._2).name, aMatch.group(groupIndexAndAxisIndex._1).toInt)) - getBucketPosition(xStr, yStr, zStr, resolutionStr, Some(additionalCoordinates)).map(bucketPosition => + getBucketPosition(xStr, yStr, zStr, magStr, Some(additionalCoordinates)).map(bucketPosition => (name, bucketPosition)) case _ => @@ -145,23 +145,20 @@ trait BucketKeys extends MortonEncoding with WKWDataFormatHelper with LazyLoggin private def getBucketPosition(xStr: String, yStr: String, zStr: String, - resolutionStr: String, + magStr: String, additionalCoordinates: Option[Seq[AdditionalCoordinate]]): Option[BucketPosition] = { - val resolutionOpt = Vec3Int.fromMagLiteral(resolutionStr, allowScalar = true) - resolutionOpt match { - case Some(resolution) => - val x = xStr.toInt - val y = yStr.toInt - val z = zStr.toInt - val bucket = BucketPosition( - x * resolution.x * DataLayer.bucketLength, - y * resolution.y * DataLayer.bucketLength, - z * resolution.z * DataLayer.bucketLength, - resolution, - additionalCoordinates - ) - Some(bucket) - case _ => None + val magnOpt = Vec3Int.fromMagLiteral(magStr, allowScalar = true) + magnOpt.map { mag => + val x = xStr.toInt + val y = yStr.toInt + val z = zStr.toInt + BucketPosition( + x * mag.x * DataLayer.bucketLength, + y * mag.y * DataLayer.bucketLength, + z * mag.z * DataLayer.bucketLength, + mag, + additionalCoordinates + ) } } diff --git a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingDownsampling.scala b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingDownsampling.scala index b5c9eec46c2..48903b2ffb4 100644 --- a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingDownsampling.scala +++ b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingDownsampling.scala @@ -85,7 +85,7 @@ trait VolumeTracingDownsampling val bucketVolume = 32 * 32 * 32 for { _ <- bool2Fox(tracing.version == 0L) ?~> "Tracing has already been edited." - _ <- bool2Fox(tracing.resolutions.nonEmpty) ?~> "Cannot downsample tracing with no resolution list" + _ <- bool2Fox(tracing.mags.nonEmpty) ?~> "Cannot downsample tracing with no mag list" sourceMag = getSourceMag(tracing) magsToCreate <- getMagsToCreate(tracing, oldTracingId) elementClass = elementClassFromProto(tracing.elementClass) @@ -254,7 +254,7 @@ trait VolumeTracingDownsampling items.groupBy(i => i).view.mapValues(_.size).maxBy(_._2)._1 private def getSourceMag(tracing: VolumeTracing): Vec3Int = - tracing.resolutions.minBy(_.maxDim) + tracing.mags.minBy(_.maxDim) private def getMagsToCreate(tracing: VolumeTracing, oldTracingId: String): Fox[List[Vec3Int]] = for { @@ -269,32 +269,31 @@ trait VolumeTracingDownsampling magsForTracing = VolumeTracingDownsampling.magsForVolumeTracingByLayerName(dataSource, tracing.fallbackLayer) } yield magsForTracing.sortBy(_.maxDim) - protected def restrictMagList(tracing: VolumeTracing, - resolutionRestrictions: ResolutionRestrictions): VolumeTracing = { - val tracingResolutions = - resolveLegacyResolutionList(tracing.resolutions) - val allowedResolutions = resolutionRestrictions.filterAllowed(tracingResolutions.map(vec3IntFromProto)) - tracing.withResolutions(allowedResolutions.map(vec3IntToProto)) + protected def restrictMagList(tracing: VolumeTracing, magRestrictions: MagRestrictions): VolumeTracing = { + val tracingMags = + resolveLegacyMagList(tracing.mags) + val allowedMags = magRestrictions.filterAllowed(tracingMags.map(vec3IntFromProto)) + tracing.withMags(allowedMags.map(vec3IntToProto)) } - protected def resolveLegacyResolutionList(resolutions: Seq[ProtoPoint3D]): Seq[ProtoPoint3D] = - if (resolutions.isEmpty) Seq(ProtoPoint3D(1, 1, 1)) else resolutions + protected def resolveLegacyMagList(mags: Seq[ProtoPoint3D]): Seq[ProtoPoint3D] = + if (mags.isEmpty) Seq(ProtoPoint3D(1, 1, 1)) else mags } -object ResolutionRestrictions { - def empty: ResolutionRestrictions = ResolutionRestrictions(None, None) - implicit val jsonFormat: Format[ResolutionRestrictions] = Json.format[ResolutionRestrictions] +object MagRestrictions { + def empty: MagRestrictions = MagRestrictions(None, None) + implicit val jsonFormat: Format[MagRestrictions] = Json.format[MagRestrictions] } -case class ResolutionRestrictions( +case class MagRestrictions( min: Option[Int], max: Option[Int] ) { - def filterAllowed(resolutions: Seq[Vec3Int]): Seq[Vec3Int] = - resolutions.filter(isAllowed) + def filterAllowed(mags: Seq[Vec3Int]): Seq[Vec3Int] = + mags.filter(isAllowed) - def isAllowed(resolution: Vec3Int): Boolean = - min.getOrElse(0) <= resolution.maxDim && max.getOrElse(Int.MaxValue) >= resolution.maxDim + def isAllowed(mag: Vec3Int): Boolean = + min.getOrElse(0) <= mag.maxDim && max.getOrElse(Int.MaxValue) >= mag.maxDim def isForbidden(resolution: Vec3Int): Boolean = !isAllowed(resolution) diff --git a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingLayer.scala b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingLayer.scala index f8d620e1405..534ca0ada77 100644 --- a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingLayer.scala +++ b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingLayer.scala @@ -93,11 +93,11 @@ case class VolumeTracingLayer( override val coordinateTransformations: Option[List[CoordinateTransformation]] = None override val mags: List[MagLocator] = List.empty // MagLocators do not apply for annotation layers - private lazy val volumeResolutions: List[Vec3Int] = tracing.resolutions.map(vec3IntFromProto).toList + private lazy val volumeMags: List[Vec3Int] = tracing.mags.map(vec3IntFromProto).toList override def bucketProviderCacheKey: String = s"$name-withFallbackData=$includeFallbackDataIfAvailable" - def lengthOfUnderlyingCubes(resolution: Vec3Int): Int = DataLayer.bucketLength + def lengthOfUnderlyingCubes(mag: Vec3Int): Int = DataLayer.bucketLength val dataFormat: DataFormat.Value = DataFormat.tracing @@ -115,9 +115,9 @@ case class VolumeTracingLayer( def bucketProvider: AbstractVolumeTracingBucketProvider = volumeBucketProvider override val resolutions: List[Vec3Int] = - if (volumeResolutions.nonEmpty) volumeResolutions else List(Vec3Int.ones) + if (volumeMags.nonEmpty) volumeMags else List(Vec3Int.ones) - override def containsResolution(resolution: Vec3Int) = - true // allow requesting buckets of all resolutions. database takes care of missing. + override def containsMag(mag: Vec3Int) = + true // allow requesting buckets of all mags. database takes care of missing. } diff --git a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala index f8fef19f3b8..77dca95a5b2 100644 --- a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala +++ b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala @@ -236,9 +236,9 @@ class VolumeTracingService @Inject()( possibleAdditionalCoordinates.toList } mappingName <- baseMappingName(volumeTracing) - _ <- Fox.serialCombined(volumeTracing.resolutions.toList)(resolution => + _ <- Fox.serialCombined(volumeTracing.mags.toList)(magProto => Fox.serialCombined(additionalCoordinateList)(additionalCoordinates => { - val mag = vec3IntFromProto(resolution) + val mag = vec3IntFromProto(magProto) for { fallbackLayer <- getFallbackLayer(tracingId) bucketPositionsRaw <- volumeSegmentIndexService.getSegmentToBucketIndexWithEmptyFallbackWithoutBuffer( @@ -284,8 +284,8 @@ class VolumeTracingService @Inject()( } yield volumeTracing private def assertMagIsValid(tracing: VolumeTracing, mag: Vec3Int): Fox[Unit] = - if (tracing.resolutions.nonEmpty) { - bool2Fox(tracing.resolutions.exists(r => vec3IntFromProto(r) == mag)) + if (tracing.mags.nonEmpty) { + bool2Fox(tracing.mags.exists(r => vec3IntFromProto(r) == mag)) } else { // old volume tracings do not have a mag list, no assert possible. Check compatibility by asserting isotropic mag bool2Fox(mag.isIsotropic) } @@ -358,26 +358,26 @@ class VolumeTracingService @Inject()( if (tracing.version != 0L) Failure("Tracing has already been edited.") else { - val resolutionSets = new mutable.HashSet[Set[Vec3Int]]() + val magSets = new mutable.HashSet[Set[Vec3Int]]() for { _ <- withZipsFromMultiZipAsync(initialData) { (_, dataZip) => for { _ <- Fox.successful(()) - resolutionSet = resolutionSetFromZipfile(dataZip) - _ = if (resolutionSet.nonEmpty) resolutionSets.add(resolutionSet) + magSet = magSetFromZipfile(dataZip) + _ = if (magSet.nonEmpty) magSets.add(magSet) } yield () } mappingName <- baseMappingName(tracing) - resolutions <- - // if none of the tracings contained any volume data do not save buckets, use full resolution list, as already initialized on wk-side - if (resolutionSets.isEmpty) - Fox.successful(tracing.resolutions.map(vec3IntFromProto).toSet) + mags <- + // if none of the tracings contained any volume data do not save buckets, use full mag list, as already initialized on wk-side + if (magSets.isEmpty) + Fox.successful(tracing.mags.map(vec3IntFromProto).toSet) else { - val resolutionsDoMatch = resolutionSets.headOption.forall { head => - resolutionSets.forall(_ == head) + val magsDoMatch = magSets.headOption.forall { head => + magSets.forall(_ == head) } - if (!resolutionsDoMatch) - Fox.failure("annotation.volume.resolutionsDoNotMatch") + if (!magsDoMatch) + Fox.failure("annotation.volume.magsDoNotMatch") else { val mergedVolume = new MergedVolume(tracing.elementClass) for { @@ -415,22 +415,22 @@ class VolumeTracingService @Inject()( } yield () } _ <- segmentIndexBuffer.flush() - } yield mergedVolume.presentResolutions + } yield mergedVolume.presentMags } } - } yield resolutions + } yield mags } def initializeWithData(tracingId: String, tracing: VolumeTracing, initialData: File, - resolutionRestrictions: ResolutionRestrictions, + magRestrictions: MagRestrictions, userToken: Option[String]): Fox[Set[Vec3Int]] = if (tracing.version != 0L) { Failure("Tracing has already been edited.") } else { val dataLayer = volumeTracingLayer(tracingId, tracing) - val savedResolutions = new mutable.HashSet[Vec3Int]() + val savedMags = new mutable.HashSet[Vec3Int]() for { fallbackLayer <- getFallbackLayer(tracingId) mappingName <- baseMappingName(tracing) @@ -444,10 +444,10 @@ class VolumeTracingService @Inject()( userToken ) _ <- withBucketsFromZip(initialData) { (bucketPosition, bytes) => - if (resolutionRestrictions.isForbidden(bucketPosition.mag)) { + if (magRestrictions.isForbidden(bucketPosition.mag)) { Fox.successful(()) } else { - savedResolutions.add(bucketPosition.mag) + savedMags.add(bucketPosition.mag) for { _ <- saveBucket(dataLayer, bucketPosition, bytes, tracing.version) _ <- Fox.runIfOptionTrue(tracing.hasSegmentIndex)( @@ -463,10 +463,10 @@ class VolumeTracingService @Inject()( } ?~> "failed to import volume data from zipfile" _ <- segmentIndexBuffer.flush() } yield { - if (savedResolutions.isEmpty) { - resolutionSetFromZipfile(initialData) + if (savedMags.isEmpty) { + magSetFromZipfile(initialData) } else { - savedResolutions.toSet + savedMags.toSet } } } @@ -490,11 +490,11 @@ class VolumeTracingService @Inject()( case VolumeDataZipFormat.wkw => new WKWBucketStreamSink(dataLayer, tracing.fallbackLayer.nonEmpty)( dataLayer.bucketProvider.bucketStream(Some(tracing.version)), - tracing.resolutions.map(mag => vec3IntFromProto(mag))) + tracing.mags.map(mag => vec3IntFromProto(mag))) case VolumeDataZipFormat.zarr3 => new Zarr3BucketStreamSink(dataLayer, tracing.fallbackLayer.nonEmpty)( dataLayer.bucketProvider.bucketStream(Some(tracing.version)), - tracing.resolutions.map(mag => vec3IntFromProto(mag)), + tracing.mags.map(mag => vec3IntFromProto(mag)), voxelSize) } @@ -529,28 +529,28 @@ class VolumeTracingService @Inject()( sourceTracing: VolumeTracing, fromTask: Boolean, datasetBoundingBox: Option[BoundingBox], - resolutionRestrictions: ResolutionRestrictions, + magRestrictions: MagRestrictions, editPosition: Option[Vec3Int], editRotation: Option[Vec3Double], boundingBox: Option[BoundingBox], mappingName: Option[String], userToken: Option[String]): Fox[(String, VolumeTracing)] = { val tracingWithBB = addBoundingBoxFromTaskIfRequired(sourceTracing, fromTask, datasetBoundingBox) - val tracingWithResolutionRestrictions = restrictMagList(tracingWithBB, resolutionRestrictions) + val tracingWithMagRestrictions = restrictMagList(tracingWithBB, magRestrictions) for { fallbackLayer <- getFallbackLayer(tracingId) hasSegmentIndex <- VolumeSegmentIndexService.canHaveSegmentIndex(remoteDatastoreClient, fallbackLayer, userToken) - newTracing = tracingWithResolutionRestrictions.copy( + newTracing = tracingWithMagRestrictions.copy( createdTimestamp = System.currentTimeMillis(), - editPosition = editPosition.map(vec3IntToProto).getOrElse(tracingWithResolutionRestrictions.editPosition), - editRotation = editRotation.map(vec3DoubleToProto).getOrElse(tracingWithResolutionRestrictions.editRotation), - boundingBox = boundingBoxOptToProto(boundingBox).getOrElse(tracingWithResolutionRestrictions.boundingBox), - mappingName = mappingName.orElse(tracingWithResolutionRestrictions.mappingName), + editPosition = editPosition.map(vec3IntToProto).getOrElse(tracingWithMagRestrictions.editPosition), + editRotation = editRotation.map(vec3DoubleToProto).getOrElse(tracingWithMagRestrictions.editRotation), + boundingBox = boundingBoxOptToProto(boundingBox).getOrElse(tracingWithMagRestrictions.boundingBox), + mappingName = mappingName.orElse(tracingWithMagRestrictions.mappingName), version = 0, // Adding segment index on duplication if the volume tracing allows it. This will be used in duplicateData hasSegmentIndex = Some(hasSegmentIndex) ) - _ <- bool2Fox(newTracing.resolutions.nonEmpty) ?~> "resolutionRestrictions.tooTight" + _ <- bool2Fox(newTracing.mags.nonEmpty) ?~> "magRestrictions.tooTight" newId <- save(newTracing, None, newTracing.version) _ <- duplicateData(tracingId, sourceTracing, newId, newTracing, userToken) } yield (newId, newTracing) @@ -595,7 +595,7 @@ class VolumeTracingService @Inject()( mappingName <- baseMappingName(sourceTracing) _ <- Fox.serialCombined(buckets) { case (bucketPosition, bucketData) => - if (destinationTracing.resolutions.contains(vec3IntToProto(bucketPosition.mag))) { + if (destinationTracing.mags.contains(vec3IntToProto(bucketPosition.mag))) { for { _ <- saveBucket(destinationDataLayer, bucketPosition, bucketData, destinationTracing.version) _ <- Fox.runIfOptionTrue(destinationTracing.hasSegmentIndex)( @@ -647,14 +647,14 @@ class VolumeTracingService @Inject()( } yield Json.toJson(updateActionGroupsJs) } - def updateResolutionList(tracingId: String, - tracing: VolumeTracing, - resolutions: Set[Vec3Int], - toCache: Boolean = false): Fox[String] = + def updateMagList(tracingId: String, + tracing: VolumeTracing, + mags: Set[Vec3Int], + toCache: Boolean = false): Fox[String] = for { _ <- bool2Fox(tracing.version == 0L) ?~> "Tracing has already been edited." - _ <- bool2Fox(resolutions.nonEmpty) ?~> "Resolution restrictions result in zero resolutions" - id <- save(tracing.copy(resolutions = resolutions.toList.sortBy(_.maxDim).map(vec3IntToProto)), + _ <- bool2Fox(mags.nonEmpty) ?~> "Mag restrictions result in zero mags" + id <- save(tracing.copy(mags = mags.toList.sortBy(_.maxDim).map(vec3IntToProto)), Some(tracingId), tracing.version, toCache) @@ -665,13 +665,13 @@ class VolumeTracingService @Inject()( tracing: VolumeTracing, userToken: Option[String]): Fox[Unit] = for { - resultingResolutions <- downsampleWithLayer(tracingId, - oldTracingId, - tracing, - volumeTracingLayer(tracingId, tracing), - this, - userToken) - _ <- updateResolutionList(tracingId, tracing, resultingResolutions.toSet) + resultingMags <- downsampleWithLayer(tracingId, + oldTracingId, + tracing, + volumeTracingLayer(tracingId, tracing), + this, + userToken) + _ <- updateMagList(tracingId, tracing, resultingMags.toSet) } yield () def volumeBucketsAreEmpty(tracingId: String): Boolean = @@ -800,16 +800,16 @@ class VolumeTracingService @Inject()( userToken: Option[String])(implicit mp: MessagesProvider): Fox[MergedVolumeStats] = { val elementClass = tracings.headOption.map(_.elementClass).getOrElse(elementClassToProto(ElementClass.uint8)) - val resolutionSets = new mutable.HashSet[Set[Vec3Int]]() + val magSets = new mutable.HashSet[Set[Vec3Int]]() tracingSelectors.zip(tracings).foreach { case (selector, tracing) => - val resolutionSet = new mutable.HashSet[Vec3Int]() + val magSet = new mutable.HashSet[Vec3Int]() bucketStreamFromSelector(selector, tracing).foreach { case (bucketPosition, _) => - resolutionSet.add(bucketPosition.mag) + magSet.add(bucketPosition.mag) } - if (resolutionSet.nonEmpty) { // empty tracings should have no impact in this check - resolutionSets.add(resolutionSet.toSet) + if (magSet.nonEmpty) { // empty tracings should have no impact in this check + magSets.add(magSet.toSet) } } @@ -818,12 +818,12 @@ class VolumeTracingService @Inject()( logger.info( s"Merging ${tracings.length} volume tracings into new $newId. CreateSegmentIndex = $shouldCreateSegmentIndex") - // If none of the tracings contained any volume data. Do not save buckets, do not touch resolution list - if (resolutionSets.isEmpty) + // If none of the tracings contained any volume data. Do not save buckets, do not touch mag list + if (magSets.isEmpty) Fox.successful(MergedVolumeStats.empty(shouldCreateSegmentIndex)) else { - val resolutionsIntersection: Set[Vec3Int] = resolutionSets.headOption.map { head => - resolutionSets.foldLeft(head) { (acc, element) => + val magsIntersection: Set[Vec3Int] = magSets.headOption.map { head => + magSets.foldLeft(head) { (acc, element) => acc.intersect(element) } }.getOrElse(Set.empty) @@ -833,13 +833,13 @@ class VolumeTracingService @Inject()( tracingSelectors.zip(tracings).foreach { case (selector, tracing) => val bucketStream = bucketStreamFromSelector(selector, tracing) - mergedVolume.addLabelSetFromBucketStream(bucketStream, resolutionsIntersection) + mergedVolume.addLabelSetFromBucketStream(bucketStream, magsIntersection) } tracingSelectors.zip(tracings).zipWithIndex.foreach { case ((selector, tracing), sourceVolumeIndex) => val bucketStream = bucketStreamFromSelector(selector, tracing) - mergedVolume.addFromBucketStream(sourceVolumeIndex, bucketStream, Some(resolutionsIntersection)) + mergedVolume.addFromBucketStream(sourceVolumeIndex, bucketStream, Some(magsIntersection)) } for { _ <- bool2Fox(ElementClass.largestSegmentIdIsInRange(mergedVolume.largestSegmentId.toLong, elementClass)) ?~> Messages( @@ -939,14 +939,12 @@ class VolumeTracingService @Inject()( if (currentVersion != tracing.version) Fox.failure("version.mismatch") else { - val resolutionSet = resolutionSetFromZipfile(zipFile) - val resolutionsDoMatch = - resolutionSet.isEmpty || resolutionSet == resolveLegacyResolutionList(tracing.resolutions) - .map(vec3IntFromProto) - .toSet - - if (!resolutionsDoMatch) - Fox.failure("annotation.volume.resolutionsDoNotMatch") + val magSet = magSetFromZipfile(zipFile) + val magsDoMatch = + magSet.isEmpty || magSet == resolveLegacyMagList(tracing.mags).map(vec3IntFromProto).toSet + + if (!magsDoMatch) + Fox.failure("annotation.volume.magssDoNotMatch") else { val volumeLayer = volumeTracingLayer(tracingId, tracing) for { diff --git a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/Zarr3BucketStreamSink.scala b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/Zarr3BucketStreamSink.scala index f2baa1f9c80..139df91a988 100644 --- a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/Zarr3BucketStreamSink.scala +++ b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/Zarr3BucketStreamSink.scala @@ -68,7 +68,7 @@ class Zarr3BucketStreamSink(val layer: VolumeTracingLayer, tracingHasFallbackLay } private def createVolumeDataSource(voxelSize: Option[VoxelSize]): GenericDataSource[DataLayer] = { - val magLocators = layer.tracing.resolutions.map { mag => + val magLocators = layer.tracing.mags.map { mag => MagLocator(mag = vec3IntToProto(mag), axisOrder = Some(AxisOrder.cAdditionalxyz(rank))) } GenericDataSource( diff --git a/webknossos-tracingstore/conf/com.scalableminds.webknossos.tracingstore.routes b/webknossos-tracingstore/conf/com.scalableminds.webknossos.tracingstore.routes index fae27dfb475..d1384d8aa4b 100644 --- a/webknossos-tracingstore/conf/com.scalableminds.webknossos.tracingstore.routes +++ b/webknossos-tracingstore/conf/com.scalableminds.webknossos.tracingstore.routes @@ -7,14 +7,14 @@ GET /health @com.scalablemin # Volume tracings POST /volume/save @com.scalableminds.webknossos.tracingstore.controllers.VolumeTracingController.save(token: Option[String]) -POST /volume/:tracingId/initialData @com.scalableminds.webknossos.tracingstore.controllers.VolumeTracingController.initialData(token: Option[String], tracingId: String, minResolution: Option[Int], maxResolution: Option[Int]) +POST /volume/:tracingId/initialData @com.scalableminds.webknossos.tracingstore.controllers.VolumeTracingController.initialData(token: Option[String], tracingId: String, minMag: Option[Int], maxMag: Option[Int]) POST /volume/:tracingId/initialDataMultiple @com.scalableminds.webknossos.tracingstore.controllers.VolumeTracingController.initialDataMultiple(token: Option[String], tracingId: String) GET /volume/:tracingId @com.scalableminds.webknossos.tracingstore.controllers.VolumeTracingController.get(token: Option[String], tracingId: String, version: Option[Long]) GET /volume/:tracingId/newestVersion @com.scalableminds.webknossos.tracingstore.controllers.VolumeTracingController.newestVersion(token: Option[String], tracingId: String) POST /volume/:tracingId/update @com.scalableminds.webknossos.tracingstore.controllers.VolumeTracingController.update(token: Option[String], tracingId: String) GET /volume/:tracingId/allDataZip @com.scalableminds.webknossos.tracingstore.controllers.VolumeTracingController.allDataZip(token: Option[String], tracingId: String, volumeDataZipFormat: String, version: Option[Long], voxelSize: Option[String], voxelSizeUnit: Option[String]) POST /volume/:tracingId/data @com.scalableminds.webknossos.tracingstore.controllers.VolumeTracingController.data(token: Option[String], tracingId: String) -POST /volume/:tracingId/duplicate @com.scalableminds.webknossos.tracingstore.controllers.VolumeTracingController.duplicate(token: Option[String], tracingId: String, fromTask: Option[Boolean], minResolution: Option[Int], maxResolution: Option[Int], downsample: Option[Boolean], editPosition: Option[String], editRotation: Option[String], boundingBox: Option[String]) +POST /volume/:tracingId/duplicate @com.scalableminds.webknossos.tracingstore.controllers.VolumeTracingController.duplicate(token: Option[String], tracingId: String, fromTask: Option[Boolean], minMag: Option[Int], maxMag: Option[Int], downsample: Option[Boolean], editPosition: Option[String], editRotation: Option[String], boundingBox: Option[String]) GET /volume/:tracingId/updateActionLog @com.scalableminds.webknossos.tracingstore.controllers.VolumeTracingController.updateActionLog(token: Option[String], tracingId: String, newestVersion: Option[Long], oldestVersion: Option[Long]) POST /volume/:tracingId/adHocMesh @com.scalableminds.webknossos.tracingstore.controllers.VolumeTracingController.requestAdHocMesh(token: Option[String], tracingId: String) POST /volume/:tracingId/fullMesh.stl @com.scalableminds.webknossos.tracingstore.controllers.VolumeTracingController.loadFullMeshStl(token: Option[String], tracingId: String) From 58e75ddec2d1c8bbbad757bed41ffdad8b18d115 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Fri, 11 Oct 2024 09:20:57 +0200 Subject: [PATCH 03/37] rename resolution restrictions --- frontend/javascripts/admin/admin_rest_api.ts | 6 +++--- .../admin/tasktype/task_type_create_view.tsx | 9 +++------ frontend/javascripts/messages.tsx | 2 +- .../oxalis/model/accessors/flycam_accessor.ts | 4 ++-- frontend/javascripts/oxalis/model_initialization.ts | 2 +- .../javascripts/oxalis/view/jobs/train_ai_model.tsx | 2 +- frontend/javascripts/router.tsx | 6 +++--- .../test/fixtures/skeletontracing_server_objects.ts | 2 +- .../test/fixtures/tasktracing_server_objects.ts | 4 ++-- .../javascripts/test/fixtures/volumetracing_object.ts | 2 +- .../test/fixtures/volumetracing_server_objects.ts | 4 ++-- frontend/javascripts/types/api_flow_types.ts | 10 +++++----- 12 files changed, 25 insertions(+), 28 deletions(-) diff --git a/frontend/javascripts/admin/admin_rest_api.ts b/frontend/javascripts/admin/admin_rest_api.ts index ff90597a51e..79163d19885 100644 --- a/frontend/javascripts/admin/admin_rest_api.ts +++ b/frontend/javascripts/admin/admin_rest_api.ts @@ -28,7 +28,7 @@ import type { APIProjectUpdater, APIProjectWithStatus, APIPublication, - APIResolutionRestrictions, + APIMagRestrictions, APIScript, APIScriptCreator, APIScriptUpdater, @@ -665,7 +665,7 @@ type AnnotationLayerCreateDescriptor = { autoFallbackLayer?: boolean; fallbackLayerName?: string | null | undefined; mappingName?: string | null | undefined; - resolutionRestrictions?: APIResolutionRestrictions | null | undefined; + resolutionRestrictions?: APIMagRestrictions | null | undefined; }; export function addAnnotationLayer( @@ -795,7 +795,7 @@ export function createExplorational( autoFallbackLayer: boolean, fallbackLayerName?: string | null | undefined, mappingName?: string | null | undefined, - resolutionRestrictions?: APIResolutionRestrictions | null | undefined, + resolutionRestrictions?: APIMagRestrictions | null | undefined, options: RequestOptions = {}, ): Promise { const url = `/api/datasets/${datasetId.owningOrganization}/${datasetId.name}/createExplorational`; diff --git a/frontend/javascripts/admin/tasktype/task_type_create_view.tsx b/frontend/javascripts/admin/tasktype/task_type_create_view.tsx index 13bcc5ebb69..555d47e410f 100644 --- a/frontend/javascripts/admin/tasktype/task_type_create_view.tsx +++ b/frontend/javascripts/admin/tasktype/task_type_create_view.tsx @@ -10,7 +10,7 @@ import { type TracingType, TracingTypeEnum, type APIAllowedMode, - type APIResolutionRestrictions, + type APIMagRestrictions, type APITeam, } from "types/api_flow_types"; import { @@ -48,7 +48,7 @@ type FormValues = { mergerMode: boolean; preferredMode?: APIAllowedMode; allowedModes: APIAllowedMode[]; - resolutionRestrictions: APIResolutionRestrictions; + resolutionRestrictions: APIMagRestrictions; }; recommendedConfiguration: string | undefined; }; @@ -130,10 +130,7 @@ function TaskTypeCreateView({ taskTypeId, history }: Props) { ); } - if ( - taskType?.settings.resolutionRestrictions.min || - taskType?.settings.resolutionRestrictions.max - ) + if (taskType?.settings.magRestrictions.min || taskType?.settings.magRestrictions.max) form.setFieldValue(["isResolutionRestricted"], true); } diff --git a/frontend/javascripts/messages.tsx b/frontend/javascripts/messages.tsx index 75695efab5d..b0f8eae1260 100644 --- a/frontend/javascripts/messages.tsx +++ b/frontend/javascripts/messages.tsx @@ -368,7 +368,7 @@ instead. Only enable this option if you understand its effect. All layers will n "dataset.is_scratch": "This dataset location is marked as 'scratch' and meant for testing only. Please move this dataset to a permanent storage location and reimport it.", "dataset.resolution_mismatch": - "This dataset contains multiple layers which differ in their magnification. Please convert the layers to make their resolutions match. Otherwise, rendering errors cannot be avoided.", + "This dataset contains multiple layers which differ in their magnification. Please convert the layers to make their magnifications match. Otherwise, rendering errors cannot be avoided.", "dataset.z1_downsampling_hint": "The currently rendered quality is not optimal due to the available magnifications and the viewport arrangement. To improve the quality try to increase the size of the XY viewport (e.g. by maximizing it).", "annotation.finish": "Are you sure you want to permanently finish this annotation?", diff --git a/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts b/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts index d86ac7592f5..a6aab45500a 100644 --- a/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts +++ b/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts @@ -433,7 +433,7 @@ export function getValidTaskZoomRange( baseDatasetViewConfiguration.zoom.minimum, Number.POSITIVE_INFINITY, ] as Vector2; - const { resolutionRestrictions } = state.tracing.restrictions; + const { magRestrictions: resolutionRestrictions } = state.tracing.restrictions; // We use the first color layer as a heuristic to check the validity of the zoom range, // as we don't know to which layer a restriction is meant to be applied. // If the layers don't have any transforms, the layer choice doesn't matter, anyway. @@ -463,7 +463,7 @@ export function getValidTaskZoomRange( } export function isMagRestrictionViolated(state: OxalisState): boolean { - const { resolutionRestrictions } = state.tracing.restrictions; + const { magRestrictions: resolutionRestrictions } = state.tracing.restrictions; // We use the first color layer as a heuristic to check the validity of the zoom range, // as we don't know to which layer a restriction is meant to be applied. // If the layers don't have any transforms, the layer choice doesn't matter, anyway. diff --git a/frontend/javascripts/oxalis/model_initialization.ts b/frontend/javascripts/oxalis/model_initialization.ts index 5ff7619bfab..811e6ba3c9b 100644 --- a/frontend/javascripts/oxalis/model_initialization.ts +++ b/frontend/javascripts/oxalis/model_initialization.ts @@ -503,7 +503,7 @@ function getMergedDataLayersFromDatasetAndVolumeTracings( const fallbackLayer = fallbackLayerIndex > -1 ? originalLayers[fallbackLayerIndex] : null; const boundingBox = getDatasetBoundingBox(dataset).asServerBoundingBox(); - const resolutions = tracing.resolutions || []; + const resolutions = tracing.mags || []; const tracingHasResolutionList = resolutions.length > 0; // Legacy tracings don't have the `tracing.resolutions` property // since they were created before WK started to maintain multiple resolution diff --git a/frontend/javascripts/oxalis/view/jobs/train_ai_model.tsx b/frontend/javascripts/oxalis/view/jobs/train_ai_model.tsx index 6dada26125f..0cad279373c 100644 --- a/frontend/javascripts/oxalis/view/jobs/train_ai_model.tsx +++ b/frontend/javascripts/oxalis/view/jobs/train_ai_model.tsx @@ -511,7 +511,7 @@ function AnnotationsCsvInput({ annotation, dataset, volumeTracings, - volumeTracingResolutions: volumeServerTracings.map(({ resolutions }) => + volumeTracingResolutions: volumeServerTracings.map(({ mags: resolutions }) => resolutions ? resolutions.map(Utils.point3ToVector3) : ([[1, 1, 1]] as Vector3[]), ), userBoundingBoxes: userBoundingBoxes || [], diff --git a/frontend/javascripts/router.tsx b/frontend/javascripts/router.tsx index 403732675b0..1323cd75837 100644 --- a/frontend/javascripts/router.tsx +++ b/frontend/javascripts/router.tsx @@ -57,7 +57,7 @@ import { type ContextRouter, Link, type RouteProps } from "react-router-dom"; import { Redirect, Route, Router, Switch } from "react-router-dom"; import { APICompoundTypeEnum, - type APIResolutionRestrictions, + type APIMagRestrictions, type APIUser, TracingTypeEnum, } from "types/api_flow_types"; @@ -647,13 +647,13 @@ class ReactRouter extends React.Component { coalesce(TracingTypeEnum, match.params.type) || TracingTypeEnum.skeleton; const getParams = Utils.getUrlParamsObjectFromString(location.search); const { autoFallbackLayer, fallbackLayerName } = getParams; - const resolutionRestrictions: APIResolutionRestrictions = {}; + const resolutionRestrictions: APIMagRestrictions = {}; if (getParams.minRes !== undefined) { resolutionRestrictions.min = Number.parseInt(getParams.minRes); if (!_.isNumber(resolutionRestrictions.min)) { - throw new Error("Invalid minRes parameter"); + throw new Error("Invalid minRes parameter"); // TODO_c maybe change get params } } diff --git a/frontend/javascripts/test/fixtures/skeletontracing_server_objects.ts b/frontend/javascripts/test/fixtures/skeletontracing_server_objects.ts index 3bf873d4665..55a2c1fa71d 100644 --- a/frontend/javascripts/test/fixtures/skeletontracing_server_objects.ts +++ b/frontend/javascripts/test/fixtures/skeletontracing_server_objects.ts @@ -201,7 +201,7 @@ export const annotation: APIAnnotation = { somaClickingAllowed: true, volumeInterpolationAllowed: false, mergerMode: false, - resolutionRestrictions: {}, + magRestrictions: {}, }, tags: ["ROI2017_wkw", "skeleton"], tracingTime: 0, diff --git a/frontend/javascripts/test/fixtures/tasktracing_server_objects.ts b/frontend/javascripts/test/fixtures/tasktracing_server_objects.ts index ac821f43780..8da34fd0361 100644 --- a/frontend/javascripts/test/fixtures/tasktracing_server_objects.ts +++ b/frontend/javascripts/test/fixtures/tasktracing_server_objects.ts @@ -86,7 +86,7 @@ export const annotation: APIAnnotation = { somaClickingAllowed: true, volumeInterpolationAllowed: false, mergerMode: false, - resolutionRestrictions: {}, + magRestrictions: {}, }, recommendedConfiguration: null, tracingType: "skeleton", @@ -144,7 +144,7 @@ export const annotation: APIAnnotation = { somaClickingAllowed: true, volumeInterpolationAllowed: false, mergerMode: false, - resolutionRestrictions: {}, + magRestrictions: {}, }, tracingTime: null, tags: ["ROI2017_wkw", "skeleton"], diff --git a/frontend/javascripts/test/fixtures/volumetracing_object.ts b/frontend/javascripts/test/fixtures/volumetracing_object.ts index f9d7c902e58..e516a787cfa 100644 --- a/frontend/javascripts/test/fixtures/volumetracing_object.ts +++ b/frontend/javascripts/test/fixtures/volumetracing_object.ts @@ -38,7 +38,7 @@ export const initialState = update(defaultState, { allowFinish: true, allowAccess: true, allowDownload: true, - resolutionRestrictions: { + magRestrictions: { // @ts-expect-error ts-migrate(2322) FIXME: Type 'null' is not assignable to type 'number | un... Remove this comment to see the full error message min: null, // @ts-expect-error ts-migrate(2322) FIXME: Type 'null' is not assignable to type 'number | un... Remove this comment to see the full error message diff --git a/frontend/javascripts/test/fixtures/volumetracing_server_objects.ts b/frontend/javascripts/test/fixtures/volumetracing_server_objects.ts index d13a74939d3..6915f6168e3 100644 --- a/frontend/javascripts/test/fixtures/volumetracing_server_objects.ts +++ b/frontend/javascripts/test/fixtures/volumetracing_server_objects.ts @@ -33,7 +33,7 @@ export const tracing: ServerVolumeTracing = { largestSegmentId: 21890, version: 0, zoomLevel: 0, - resolutions: [ + mags: [ { x: 1, y: 1, @@ -110,7 +110,7 @@ export const annotation: APIAnnotation = { somaClickingAllowed: true, volumeInterpolationAllowed: false, mergerMode: false, - resolutionRestrictions: {}, + magRestrictions: {}, }, tags: ["ROI2017_wkw", "volume"], tracingTime: 0, diff --git a/frontend/javascripts/types/api_flow_types.ts b/frontend/javascripts/types/api_flow_types.ts index 2ede117844c..69ed8cddcb7 100644 --- a/frontend/javascripts/types/api_flow_types.ts +++ b/frontend/javascripts/types/api_flow_types.ts @@ -350,7 +350,7 @@ export type APIRestrictions = { readonly allowSave?: boolean; }; export type APIAllowedMode = "orthogonal" | "oblique" | "flight"; -export type APIResolutionRestrictions = { +export type APIMagRestrictions = { min?: number; max?: number; }; @@ -361,7 +361,7 @@ export type APISettings = { readonly somaClickingAllowed: boolean; readonly volumeInterpolationAllowed: boolean; readonly mergerMode: boolean; - readonly resolutionRestrictions: APIResolutionRestrictions; + readonly magRestrictions: APIMagRestrictions; }; export enum APIAnnotationTypeEnum { Explorational = "Explorational", @@ -856,11 +856,11 @@ export type ServerVolumeTracing = ServerTracingBase & { segments: Array; segmentGroups: Array | null | undefined; largestSegmentId: number; - // `resolutions` will be undefined for legacy annotations - // which were created before the multi-resolution capabilities + // `mags` will be undefined for legacy annotations + // which were created before the multi-magnification capabilities // were added to volume tracings. Also see: // https://github.com/scalableminds/webknossos/pull/4755 - resolutions?: Array; + mags?: Array; mappingName?: string | null | undefined; hasEditableMapping?: boolean; mappingIsLocked?: boolean; From 9b32b5fb050a8e9353708451d267fc71ca183430 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Sat, 12 Oct 2024 11:01:13 +0200 Subject: [PATCH 04/37] rename resolution info to mag info --- .../admin/tasktype/task_type_create_view.tsx | 16 +- .../admin/voxelytics/ai_model_list_view.tsx | 4 +- .../create_explorative_modal.tsx | 24 +- frontend/javascripts/oxalis/api/api_latest.ts | 16 +- frontend/javascripts/oxalis/default_state.ts | 2 +- .../materials/plane_material_factory.ts | 8 +- .../model/accessors/dataset_accessor.ts | 24 +- .../oxalis/model/accessors/flycam_accessor.ts | 8 +- .../model/accessors/volumetracing_accessor.ts | 16 +- .../bucket_data_handling/bounding_box.ts | 6 +- .../model/bucket_data_handling/bucket.ts | 6 +- .../model/bucket_data_handling/data_cube.ts | 20 +- .../layer_rendering_manager.ts | 4 +- .../prefetch_strategy_arbitrary.ts | 4 +- .../prefetch_strategy_plane.ts | 6 +- .../bucket_data_handling/wkstore_adapter.ts | 8 +- .../oxalis/model/helpers/mag_info.ts | 257 +++++++++++++++++ .../model/helpers/position_converter.ts | 15 +- .../oxalis/model/helpers/resolution_info.ts | 261 ------------------ .../oxalis/model/sagas/dataset_saga.ts | 4 +- .../oxalis/model/sagas/mesh_saga.ts | 18 +- .../oxalis/model/sagas/min_cut_saga.ts | 9 +- .../oxalis/model/sagas/prefetch_saga.ts | 4 +- .../oxalis/model/sagas/proofread_saga.ts | 4 +- .../sagas/quick_select_heuristic_saga.ts | 2 +- .../oxalis/model/sagas/save_saga.ts | 4 +- .../oxalis/model/sagas/volume/helpers.ts | 10 +- .../sagas/volume/volume_interpolation_saga.ts | 2 +- .../view/action-bar/download_modal_view.tsx | 4 +- .../view/action-bar/starting_job_modals.tsx | 6 +- .../javascripts/oxalis/view/context_menu.tsx | 2 +- .../oxalis/view/jobs/train_ai_model.tsx | 2 +- .../left-border-tabs/layer_settings_tab.tsx | 2 +- .../modals/add_volume_layer_modal.tsx | 4 +- .../segments_tab/segment_statistics_modal.tsx | 2 +- .../segments_tab/segments_view.tsx | 10 +- .../oxalis/view/viewport_status_indicator.tsx | 2 +- .../backend-snapshot-tests/tasktypes.e2e.ts | 2 +- .../test/model/binary/cube.spec.ts | 4 +- .../binary/layers/wkstore_adapter.spec.ts | 4 +- .../test/model/model_resolutions.spec.ts | 7 +- .../volume_annotation_sampling.spec.ts | 4 +- 42 files changed, 402 insertions(+), 415 deletions(-) create mode 100644 frontend/javascripts/oxalis/model/helpers/mag_info.ts delete mode 100644 frontend/javascripts/oxalis/model/helpers/resolution_info.ts diff --git a/frontend/javascripts/admin/tasktype/task_type_create_view.tsx b/frontend/javascripts/admin/tasktype/task_type_create_view.tsx index 555d47e410f..45ca475cb24 100644 --- a/frontend/javascripts/admin/tasktype/task_type_create_view.tsx +++ b/frontend/javascripts/admin/tasktype/task_type_create_view.tsx @@ -48,7 +48,7 @@ type FormValues = { mergerMode: boolean; preferredMode?: APIAllowedMode; allowedModes: APIAllowedMode[]; - resolutionRestrictions: APIMagRestrictions; + magRestrictions: APIMagRestrictions; }; recommendedConfiguration: string | undefined; }; @@ -58,7 +58,7 @@ function isValidMagnification(_rule: RuleObject, value: number | undefined) { return Promise.resolve(); } else { return Promise.reject( - new Error("The resolution must be stated as a power of two (e.g., 1 or 2 or 4 or 8 ...)"), + new Error("The magnification must be stated as a power of two (e.g., 1 or 2 or 4 or 8 ...)"), ); } } @@ -68,7 +68,7 @@ function isMinimumMagnifactionLargerThenMaxRule(value: number | undefined, maxMa return Promise.resolve(); } return Promise.reject( - new Error("The minimum resolution needs to be smaller then the maximum mag."), + new Error("The minimum magnification needs to be smaller then the maximum mag."), ); } function isMaximumMagnificationSmallerThenMinRule(value: number | undefined, minMag: number) { @@ -76,7 +76,7 @@ function isMaximumMagnificationSmallerThenMinRule(value: number | undefined, min return Promise.resolve(); } return Promise.reject( - new Error("The maximum resolution needs to be larger then the minimum mag."), + new Error("The maximum magnification needs to be larger then the minimum mag."), ); } @@ -150,8 +150,8 @@ function TaskTypeCreateView({ taskTypeId, history }: Props) { // FormItems which are not rendered/hidden are not serialized by onFinish // add them manually - if (!settings.resolutionRestrictions) { - settings.resolutionRestrictions = { min: undefined, max: undefined }; + if (!settings.magRestrictions) { + settings.magRestrictions = { min: undefined, max: undefined }; } const newTaskType: Omit = { @@ -418,9 +418,9 @@ function TaskTypeCreateView({ taskTypeId, history }: Props) { }} > - Restrict Resolutions{" "} + Restrict Magnifications{" "} diff --git a/frontend/javascripts/admin/voxelytics/ai_model_list_view.tsx b/frontend/javascripts/admin/voxelytics/ai_model_list_view.tsx index 1bfa431b025..8bae25a83c3 100644 --- a/frontend/javascripts/admin/voxelytics/ai_model_list_view.tsx +++ b/frontend/javascripts/admin/voxelytics/ai_model_list_view.tsx @@ -134,10 +134,10 @@ function TrainNewAiJobModal({ onClose }: { onClose: () => void }) { ); const resolutions = volumeTracingResolutions[volumeTracingIndex] || ([[1, 1, 1]] as Vector3[]); - return getResolutionInfo(resolutions).getFinestResolution(); + return getResolutionInfo(resolutions).getFinestMag(); } else { const segmentationLayer = getSegmentationLayerByName(dataset, layerName); - return getResolutionInfo(segmentationLayer.resolutions).getFinestResolution(); + return getResolutionInfo(segmentationLayer.resolutions).getFinestMag(); } }; diff --git a/frontend/javascripts/dashboard/advanced_dataset/create_explorative_modal.tsx b/frontend/javascripts/dashboard/advanced_dataset/create_explorative_modal.tsx index 5e64975c724..852b86b6945 100644 --- a/frontend/javascripts/dashboard/advanced_dataset/create_explorative_modal.tsx +++ b/frontend/javascripts/dashboard/advanced_dataset/create_explorative_modal.tsx @@ -12,14 +12,14 @@ import { } from "oxalis/model/accessors/dataset_accessor"; import { getDataset } from "admin/admin_rest_api"; import { useFetch } from "libs/react_helpers"; -import type { ResolutionInfo } from "oxalis/model/helpers/resolution_info"; +import type { MagInfo } from "oxalis/model/helpers/mag_info"; type Props = { datasetId: APIDatasetId; onClose: () => void; }; type RestrictResolutionSliderProps = { - resolutionInfo: ResolutionInfo; + resolutionInfo: MagInfo; selectedSegmentationLayer: APISegmentationLayer | null; resolutionIndices: number[]; setResolutionIndices: (userIndices: number[]) => void; @@ -89,15 +89,15 @@ export function RestrictResolutionSlider({ resolutionIndices, setResolutionIndices, }: RestrictResolutionSliderProps) { - let highestResolutionIndex = resolutionInfo.getCoarsestResolutionIndex(); - let lowestResolutionIndex = resolutionInfo.getFinestResolutionIndex(); + let highestResolutionIndex = resolutionInfo.getCoarsestMagIndex(); + let lowestResolutionIndex = resolutionInfo.getFinestMagIndex(); if (selectedSegmentationLayer != null) { const datasetFallbackLayerResolutionInfo = getResolutionInfo( selectedSegmentationLayer.resolutions, ); - highestResolutionIndex = datasetFallbackLayerResolutionInfo.getCoarsestResolutionIndex(); - lowestResolutionIndex = datasetFallbackLayerResolutionInfo.getFinestResolutionIndex(); + highestResolutionIndex = datasetFallbackLayerResolutionInfo.getCoarsestMagIndex(); + lowestResolutionIndex = datasetFallbackLayerResolutionInfo.getFinestMagIndex(); } const highResolutionIndex = Math.min(highestResolutionIndex, resolutionIndices[1]); @@ -137,7 +137,7 @@ export function RestrictResolutionSlider({ marginRight: 20, }} > - {resolutionInfo.getResolutionByIndexOrThrow(lowResolutionIndex).join("-")} + {resolutionInfo.getMagByIndexOrThrow(lowResolutionIndex).join("-")} - {resolutionInfo.getResolutionByIndexOrThrow(highResolutionIndex).join("-")} + {resolutionInfo.getMagByIndexOrThrow(highResolutionIndex).join("-")} @@ -198,8 +198,8 @@ function CreateExplorativeModal({ datasetId, onClose }: Props) { selectedSegmentationLayer == null ? getSomeResolutionInfoForDataset(dataset) : getResolutionInfo(selectedSegmentationLayer.resolutions); - const highestResolutionIndex = resolutionInfo.getCoarsestResolutionIndex(); - const lowestResolutionIndex = resolutionInfo.getFinestResolutionIndex(); + const highestResolutionIndex = resolutionInfo.getCoarsestMagIndex(); + const lowestResolutionIndex = resolutionInfo.getFinestMagIndex(); const highResolutionIndex = Math.min(highestResolutionIndex, userDefinedResolutionIndices[1]); const lowResolutionIndex = Math.max(lowestResolutionIndex, userDefinedResolutionIndices[0]); @@ -245,9 +245,9 @@ function CreateExplorativeModal({ datasetId, onClose }: Props) { to={`/datasets/${dataset.owningOrganization}/${ dataset.name }/createExplorative/${annotationType}/?minRes=${Math.max( - ...resolutionInfo.getResolutionByIndexOrThrow(lowResolutionIndex), + ...resolutionInfo.getMagByIndexOrThrow(lowResolutionIndex), )}&maxRes=${Math.max( - ...resolutionInfo.getResolutionByIndexOrThrow(highResolutionIndex), + ...resolutionInfo.getMagByIndexOrThrow(highResolutionIndex), )}${fallbackLayerGetParameter}`} title="Create new annotation with selected properties" > diff --git a/frontend/javascripts/oxalis/api/api_latest.ts b/frontend/javascripts/oxalis/api/api_latest.ts index 404d508f660..bcc99c4a5d5 100644 --- a/frontend/javascripts/oxalis/api/api_latest.ts +++ b/frontend/javascripts/oxalis/api/api_latest.ts @@ -175,7 +175,7 @@ import messages from "messages"; import window, { location } from "libs/window"; import { coalesce } from "libs/utils"; import { setLayerTransformsAction } from "oxalis/model/actions/dataset_actions"; -import { ResolutionInfo } from "oxalis/model/helpers/resolution_info"; +import { MagInfo } from "oxalis/model/helpers/mag_info"; import type { AdditionalCoordinate } from "types/api_flow_types"; import { getMaximumGroupId } from "oxalis/model/reducers/skeletontracing_reducer_helpers"; import { @@ -677,7 +677,7 @@ class TracingApi { if (existingMagIndex == null) { throw new Error("The index of the current mag could not be found."); } - const currentMag = resolutionInfo.getResolutionByIndex(existingMagIndex); + const currentMag = resolutionInfo.getMagByIndex(existingMagIndex); if (currentMag == null) { throw new Error("No mag could be found."); } @@ -1843,7 +1843,7 @@ class DataApi { } else { const layer = getLayerByName(Store.getState().dataset, layerName); const resolutionInfo = getResolutionInfo(layer.resolutions); - zoomStep = resolutionInfo.getFinestResolutionIndex(); + zoomStep = resolutionInfo.getFinestMagIndex(); } const cube = this.model.getCubeByLayerName(layerName); @@ -1905,10 +1905,10 @@ class DataApi { if (_zoomStep != null) { zoomStep = _zoomStep; } else { - zoomStep = resolutionInfo.getFinestResolutionIndex(); + zoomStep = resolutionInfo.getFinestMagIndex(); } - const resolutions = resolutionInfo.getDenseResolutions(); + const resolutions = resolutionInfo.getDenseMags(); const bucketAddresses = this.getBucketAddressesInCuboid( mag1Bbox, resolutions, @@ -1960,7 +1960,7 @@ class DataApi { viewport, ); - const resolution = resolutionInfo.getResolutionByIndexOrThrow(zoomStep); + const resolution = resolutionInfo.getMagByIndexOrThrow(zoomStep); const resolutionUVX = dimensions.transDim(resolution, viewport); const widthInVoxel = Math.ceil(halfViewportExtentU / resolutionUVX[0]); const heightInVoxel = Math.ceil(halfViewportExtentV / resolutionUVX[1]); @@ -1998,7 +1998,7 @@ class DataApi { ); const topLeft = (bucketAddress: BucketAddress) => - bucketPositionToGlobalAddress(bucketAddress, new ResolutionInfo(resolutions)); + bucketPositionToGlobalAddress(bucketAddress, new MagInfo(resolutions)); const nextBucketInDim = (bucket: BucketAddress, dim: 0 | 1 | 2) => { const copy = bucket.slice() as BucketAddress; @@ -2107,7 +2107,7 @@ class DataApi { ): string { const { dataset } = Store.getState(); const resolutionInfo = getResolutionInfo(getLayerByName(dataset, layerName, true).resolutions); - resolution = resolution || resolutionInfo.getFinestResolution(); + resolution = resolution || resolutionInfo.getFinestMag(); const magString = resolution.join("-"); return ( diff --git a/frontend/javascripts/oxalis/default_state.ts b/frontend/javascripts/oxalis/default_state.ts index 298aa359a17..11e4e68e2ed 100644 --- a/frontend/javascripts/oxalis/default_state.ts +++ b/frontend/javascripts/oxalis/default_state.ts @@ -36,7 +36,7 @@ const initialAnnotationInfo = { mergerMode: false, volumeInterpolationAllowed: false, allowedModes: ["orthogonal", "oblique", "flight"] as APIAllowedMode[], - resolutionRestrictions: {}, + magRestrictions: {}, }, visibility: "Internal" as APIAnnotationVisibility, tags: [], diff --git a/frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts b/frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts index 58bc4049eea..4c1d7574dfb 100644 --- a/frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts +++ b/frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts @@ -489,9 +489,7 @@ class PlaneMaterialFactory { // to determine a representative mag. const suitableMagIndex = resolutionInfo.getIndexOrClosestHigherIndex(activeMagIndex); const suitableMag = - suitableMagIndex != null - ? resolutionInfo.getResolutionByIndex(suitableMagIndex) - : null; + suitableMagIndex != null ? resolutionInfo.getMagByIndex(suitableMagIndex) : null; const hasTransform = !_.isEqual( getTransformsForLayer( @@ -569,7 +567,7 @@ class PlaneMaterialFactory { (storeState) => getResolutionInfoByLayer(storeState.dataset), (resolutionInfosByLayer) => { const allDenseResolutions = Object.values(resolutionInfosByLayer).map((resInfo) => - resInfo.getDenseResolutions(), + resInfo.getDenseMags(), ); const flatResolutions = _.flattenDeep(allDenseResolutions); this.uniforms.allResolutions = { @@ -1115,7 +1113,7 @@ class PlaneMaterialFactory { getTotalResolutionCount(): number { const storeState = Store.getState(); const allDenseResolutions = Object.values(getResolutionInfoByLayer(storeState.dataset)).map( - (resInfo) => resInfo.getDenseResolutions(), + (resInfo) => resInfo.getDenseMags(), ); const flatResolutions = _.flatten(allDenseResolutions); return flatResolutions.length; diff --git a/frontend/javascripts/oxalis/model/accessors/dataset_accessor.ts b/frontend/javascripts/oxalis/model/accessors/dataset_accessor.ts index 3c6742cf6cf..4d7a3813294 100644 --- a/frontend/javascripts/oxalis/model/accessors/dataset_accessor.ts +++ b/frontend/javascripts/oxalis/model/accessors/dataset_accessor.ts @@ -33,7 +33,7 @@ import messages from "messages"; import type { DataLayer } from "types/schemas/datasource.types"; import BoundingBox from "../bucket_data_handling/bounding_box"; import { M4x4, type Matrix4x4, V3 } from "libs/mjs"; -import { convertToDenseResolution, ResolutionInfo } from "../helpers/resolution_info"; +import { convertToDenseMag, MagInfo } from "../helpers/mag_info"; import MultiKeyMap from "libs/multi_key_map"; import { chainTransforms, @@ -44,16 +44,16 @@ import { transformPointUnscaled, } from "../helpers/transformation_helpers"; -function _getResolutionInfo(resolutions: Array): ResolutionInfo { - return new ResolutionInfo(resolutions); +function _getResolutionInfo(resolutions: Array): MagInfo { + return new MagInfo(resolutions); } // Don't use memoizeOne here, since we want to cache the resolutions for all layers // (which are not that many). export const getResolutionInfo = _.memoize(_getResolutionInfo); -function _getResolutionInfoByLayer(dataset: APIDataset): Record { - const infos: Record = {}; +function _getResolutionInfoByLayer(dataset: APIDataset): Record { + const infos: Record = {}; for (const layer of dataset.dataSource.dataLayers) { infos[layer.name] = getResolutionInfo(layer.resolutions); @@ -65,7 +65,7 @@ function _getResolutionInfoByLayer(dataset: APIDataset): Record => { @@ -106,20 +106,20 @@ export const getResolutionUnion = memoizeOne((dataset: APIDataset): Array - convertToDenseResolution(layer.resolutions), + convertToDenseMag(layer.resolutions), ); return _.maxBy(allLayerResolutions, (resolutions) => resolutions.length) || []; } -export const getSomeResolutionInfoForDataset = memoizeOne((dataset: APIDataset): ResolutionInfo => { +export const getSomeResolutionInfoForDataset = memoizeOne((dataset: APIDataset): MagInfo => { const resolutionUnion = getResolutionUnion(dataset); const areMagsDistinct = resolutionUnion.every((mags) => mags.length <= 1); if (areMagsDistinct) { - return new ResolutionInfo(resolutionUnion.map((mags) => mags[0])); + return new MagInfo(resolutionUnion.map((mags) => mags[0])); } else { - return new ResolutionInfo(getWidestResolutions(dataset)); + return new MagInfo(getWidestResolutions(dataset)); } }); @@ -143,11 +143,11 @@ export function getDataLayers(dataset: APIDataset): DataLayerType[] { return dataset.dataSource.dataLayers; } -function _getResolutionInfoOfVisibleSegmentationLayer(state: OxalisState): ResolutionInfo { +function _getResolutionInfoOfVisibleSegmentationLayer(state: OxalisState): MagInfo { const segmentationLayer = getVisibleSegmentationLayer(state); if (!segmentationLayer) { - return new ResolutionInfo([]); + return new MagInfo([]); } return getResolutionInfo(segmentationLayer.resolutions); diff --git a/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts b/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts index a6aab45500a..97caf204200 100644 --- a/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts +++ b/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts @@ -34,7 +34,7 @@ import { reuseInstanceOnEquality } from "./accessor_helpers"; import { baseDatasetViewConfiguration } from "types/schemas/dataset_view_configuration.schema"; import { MAX_ZOOM_STEP_DIFF } from "oxalis/model/bucket_data_handling/loading_strategy_logic"; import { getMatrixScale, rotateOnAxis } from "../reducers/flycam_reducer"; -import type { SmallerOrHigherInfo } from "../helpers/resolution_info"; +import type { SmallerOrHigherInfo } from "../helpers/mag_info"; import { getBaseVoxelInUnit } from "oxalis/model/scaleinfo"; import type { AdditionalCoordinate, VoxelSize } from "types/api_flow_types"; @@ -241,7 +241,7 @@ function getMaximumZoomForAllResolutionsFromStore( viewMode, state.datasetConfiguration.loadingStrategy, state.dataset.dataSource.scale.factor, - getResolutionInfo(layer.resolutions).getDenseResolutions(), + getResolutionInfo(layer.resolutions).getDenseMags(), getViewportRects(state), Math.min( state.temporaryConfiguration.gpuSetup.smallestCommonBucketCapacity, @@ -368,7 +368,7 @@ export function getCurrentResolution( if (existingMagIndex == null) { return null; } - return resolutionInfo.getResolutionByIndex(existingMagIndex); + return resolutionInfo.getMagByIndex(existingMagIndex); } function _getValidZoomRangeForUser(state: OxalisState): [number, number] { @@ -635,7 +635,7 @@ function _getActiveResolutionInfo(state: OxalisState) { const activeMagOfEnabledLayers = Object.fromEntries( enabledLayers.map((l) => [ l.name, - getResolutionInfo(l.resolutions).getResolutionByIndex(activeMagIndices[l.name]), + getResolutionInfo(l.resolutions).getMagByIndex(activeMagIndices[l.name]), ]), ); diff --git a/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts b/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts index 8e0feea16e0..d6071e37abe 100644 --- a/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts +++ b/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts @@ -49,7 +49,7 @@ import { reuseInstanceOnEquality } from "oxalis/model/accessors/accessor_helpers import { V3 } from "libs/mjs"; import { jsConvertCellIdToRGBA } from "oxalis/shaders/segmentation.glsl"; import { jsRgb2hsl } from "oxalis/shaders/utils.glsl"; -import { ResolutionInfo } from "../helpers/resolution_info"; +import { MagInfo } from "../helpers/mag_info"; import messages from "messages"; import { MISSING_GROUP_ID, @@ -179,11 +179,11 @@ export function getSegmentationLayerForTracing( return getSegmentationLayerByName(state.dataset, volumeTracing.tracingId); } -function _getResolutionInfoOfActiveSegmentationTracingLayer(state: OxalisState): ResolutionInfo { +function _getResolutionInfoOfActiveSegmentationTracingLayer(state: OxalisState): MagInfo { const volumeTracing = getActiveSegmentationTracing(state); if (!volumeTracing) { - return new ResolutionInfo([]); + return new MagInfo([]); } const segmentationLayer = getSegmentationLayerForTracing(state, volumeTracing); @@ -243,7 +243,7 @@ export function isVolumeAnnotationDisallowedForZoom(tool: AnnotationTool, state: } const volumeResolutions = getResolutionInfoOfActiveSegmentationTracingLayer(state); - const lowestExistingResolutionIndex = volumeResolutions.getFinestResolutionIndex(); + const lowestExistingResolutionIndex = volumeResolutions.getFinestMagIndex(); // The current resolution is too high for the tool // because too many voxels could be annotated at the same time. const isZoomStepTooHigh = @@ -256,11 +256,11 @@ const MAX_BRUSH_SIZE_FOR_MAG1 = 300; export function getMaximumBrushSize(state: OxalisState) { const volumeResolutions = getResolutionInfoOfActiveSegmentationTracingLayer(state); - if (volumeResolutions.resolutions.length === 0) { + if (volumeResolutions.mags.length === 0) { return MAX_BRUSH_SIZE_FOR_MAG1; } - const lowestExistingResolutionIndex = volumeResolutions.getFinestResolutionIndex(); + const lowestExistingResolutionIndex = volumeResolutions.getFinestMagIndex(); // For each leading magnification which does not exist, // we double the maximum brush size. return MAX_BRUSH_SIZE_FOR_MAG1 * 2 ** lowestExistingResolutionIndex; @@ -508,7 +508,7 @@ function _getRenderableResolutionForSegmentationTracing( if (resolutionInfo.hasIndex(requestedZoomStep)) { return { zoomStep: requestedZoomStep, - resolution: resolutionInfo.getResolutionByIndexOrThrow(requestedZoomStep), + resolution: resolutionInfo.getMagByIndexOrThrow(requestedZoomStep), }; } @@ -529,7 +529,7 @@ function _getRenderableResolutionForSegmentationTracing( if (resolutionInfo.hasIndex(fallbackZoomStep)) { return { zoomStep: fallbackZoomStep, - resolution: resolutionInfo.getResolutionByIndexOrThrow(fallbackZoomStep), + resolution: resolutionInfo.getMagByIndexOrThrow(fallbackZoomStep), }; } } diff --git a/frontend/javascripts/oxalis/model/bucket_data_handling/bounding_box.ts b/frontend/javascripts/oxalis/model/bucket_data_handling/bounding_box.ts index c3571cb9e5a..3a203dc86aa 100644 --- a/frontend/javascripts/oxalis/model/bucket_data_handling/bounding_box.ts +++ b/frontend/javascripts/oxalis/model/bucket_data_handling/bounding_box.ts @@ -3,7 +3,7 @@ import { V3 } from "libs/mjs"; import { map3, mod } from "libs/utils"; import type { BoundingBoxType, OrthoView, Vector2, Vector3, Vector4 } from "oxalis/constants"; import constants, { Vector3Indicies } from "oxalis/constants"; -import type { ResolutionInfo } from "../helpers/resolution_info"; +import type { MagInfo } from "../helpers/mag_info"; import Dimensions from "../dimensions"; class BoundingBox { @@ -51,12 +51,12 @@ class BoundingBox { }; }); - containsBucket([x, y, z, zoomStep]: Vector4, resolutionInfo: ResolutionInfo): boolean { + containsBucket([x, y, z, zoomStep]: Vector4, resolutionInfo: MagInfo): boolean { /* Checks whether a bucket is contained in the active bounding box. * If the passed resolutionInfo does not contain the passed zoomStep, this method * returns false. */ - const resolutionIndex = resolutionInfo.getResolutionByIndex(zoomStep); + const resolutionIndex = resolutionInfo.getMagByIndex(zoomStep); if (resolutionIndex == null) { return false; } diff --git a/frontend/javascripts/oxalis/model/bucket_data_handling/bucket.ts b/frontend/javascripts/oxalis/model/bucket_data_handling/bucket.ts index b290532f85b..0703ace532d 100644 --- a/frontend/javascripts/oxalis/model/bucket_data_handling/bucket.ts +++ b/frontend/javascripts/oxalis/model/bucket_data_handling/bucket.ts @@ -188,9 +188,7 @@ export class DataBucket { getBoundingBox(): BoundingBoxType { const min = bucketPositionToGlobalAddress(this.zoomedAddress, this.cube.resolutionInfo); - const bucketResolution = this.cube.resolutionInfo.getResolutionByIndexOrThrow( - this.zoomedAddress[3], - ); + const bucketResolution = this.cube.resolutionInfo.getMagByIndexOrThrow(this.zoomedAddress[3]); const max: Vector3 = [ min[0] + Constants.BUCKET_WIDTH * bucketResolution[0], min[1] + Constants.BUCKET_WIDTH * bucketResolution[1], @@ -711,7 +709,7 @@ export class DataBucket { this.visualizedMesh = window.addBucketMesh( bucketPositionToGlobalAddress(this.zoomedAddress, this.cube.resolutionInfo), this.zoomedAddress[3], - this.cube.resolutionInfo.getResolutionByIndex(this.zoomedAddress[3]), + this.cube.resolutionInfo.getMagByIndex(this.zoomedAddress[3]), colors[this.zoomedAddress[3]] || this.visualizationColor, ); } diff --git a/frontend/javascripts/oxalis/model/bucket_data_handling/data_cube.ts b/frontend/javascripts/oxalis/model/bucket_data_handling/data_cube.ts index 1c38abdbaf8..ac0f3bff585 100644 --- a/frontend/javascripts/oxalis/model/bucket_data_handling/data_cube.ts +++ b/frontend/javascripts/oxalis/model/bucket_data_handling/data_cube.ts @@ -33,7 +33,7 @@ import type { BucketAddress, } from "oxalis/constants"; import constants, { MappingStatusEnum } from "oxalis/constants"; -import type { ResolutionInfo } from "../helpers/resolution_info"; +import type { MagInfo } from "../helpers/mag_info"; import type { AdditionalCoordinate } from "types/api_flow_types"; const warnAboutTooManyAllocations = _.once(() => { @@ -82,7 +82,7 @@ class DataCube { temporalBucketManager: TemporalBucketManager; isSegmentation: boolean; elementClass: ElementClass; - resolutionInfo: ResolutionInfo; + resolutionInfo: MagInfo; layerName: string; emitter: Emitter; lastRequestForValueSet: number | null = null; @@ -106,7 +106,7 @@ class DataCube { constructor( layerBBox: BoundingBox, additionalAxes: AdditionalAxis[], - resolutionInfo: ResolutionInfo, + resolutionInfo: MagInfo, elementClass: ElementClass, isSegmentation: boolean, layerName: string, @@ -239,7 +239,7 @@ class DataCube { ): CubeEntry | null { const cubeKey = this.getCubeKey(zoomStep, coords); if (this.cubes[cubeKey] == null) { - const resolution = this.resolutionInfo.getResolutionByIndex(zoomStep); + const resolution = this.resolutionInfo.getMagByIndex(zoomStep); if (resolution == null) { return null; } @@ -433,7 +433,7 @@ class DataCube { // Please make use of a LabeledVoxelsMap instead. const promises = []; - for (const [resolutionIndex] of this.resolutionInfo.getResolutionsWithIndices()) { + for (const [resolutionIndex] of this.resolutionInfo.getMagsWithIndices()) { promises.push( this._labelVoxelInResolution_DEPRECATED( voxel, @@ -630,7 +630,7 @@ class DataCube { const currentLabeledVoxelMap = bucketsWithLabeledVoxelsMap.get(currentBucket.zoomedAddress) || new Map(); - const currentResolution = this.resolutionInfo.getResolutionByIndexOrThrow( + const currentResolution = this.resolutionInfo.getMagByIndexOrThrow( currentBucket.zoomedAddress[3], ); @@ -823,7 +823,7 @@ class DataCube { additionalCoordinates: AdditionalCoordinate[] | null, zoomStep: number, ): number { - const resolutions = this.resolutionInfo.getDenseResolutions(); + const resolutions = this.resolutionInfo.getDenseMags(); let usableZoomStep = zoomStep; while ( @@ -842,7 +842,7 @@ class DataCube { additionalCoordinates: AdditionalCoordinate[] | null, zoomStep: number, ): Promise { - const resolutions = this.resolutionInfo.getDenseResolutions(); + const resolutions = this.resolutionInfo.getDenseMags(); let usableZoomStep = zoomStep; while ( @@ -922,7 +922,7 @@ class DataCube { getVoxelOffset(voxel: Vector3, zoomStep: number = 0): Vector3 { // No `map` for performance reasons const voxelOffset: Vector3 = [0, 0, 0]; - const resolution = this.resolutionInfo.getResolutionByIndexOrThrow(zoomStep); + const resolution = this.resolutionInfo.getMagByIndexOrThrow(zoomStep); for (let i = 0; i < 3; i++) { voxelOffset[i] = Math.floor(voxel[i] / resolution[i]) % constants.BUCKET_WIDTH; @@ -944,7 +944,7 @@ class DataCube { // return the bucket a given voxel lies in return globalPositionToBucketPosition( position, - this.resolutionInfo.getDenseResolutions(), + this.resolutionInfo.getDenseMags(), zoomStep, additionalCoordinates, ); diff --git a/frontend/javascripts/oxalis/model/bucket_data_handling/layer_rendering_manager.ts b/frontend/javascripts/oxalis/model/bucket_data_handling/layer_rendering_manager.ts index 1ae2b349385..a33ae2ff9a6 100644 --- a/frontend/javascripts/oxalis/model/bucket_data_handling/layer_rendering_manager.ts +++ b/frontend/javascripts/oxalis/model/bucket_data_handling/layer_rendering_manager.ts @@ -188,7 +188,7 @@ export default class LayerRenderingManager { const { dataset, datasetConfiguration } = state; const layer = getLayerByName(dataset, this.name); const resolutionInfo = getResolutionInfo(layer.resolutions); - const maximumResolutionIndex = resolutionInfo.getCoarsestResolutionIndex(); + const maximumResolutionIndex = resolutionInfo.getCoarsestMagIndex(); if (logZoomStep > maximumResolutionIndex) { // Don't render anything if the zoomStep is too high @@ -196,7 +196,7 @@ export default class LayerRenderingManager { return; } - const resolutions = getResolutionInfo(layer.resolutions).getDenseResolutions(); + const resolutions = getResolutionInfo(layer.resolutions).getDenseMags(); const layerMatrix = invertAndTranspose( getTransformsForLayer(dataset, layer, datasetConfiguration.nativelyRenderedLayerName) .affineMatrix, diff --git a/frontend/javascripts/oxalis/model/bucket_data_handling/prefetch_strategy_arbitrary.ts b/frontend/javascripts/oxalis/model/bucket_data_handling/prefetch_strategy_arbitrary.ts index ccbe9fcb3a2..3e95a92078b 100644 --- a/frontend/javascripts/oxalis/model/bucket_data_handling/prefetch_strategy_arbitrary.ts +++ b/frontend/javascripts/oxalis/model/bucket_data_handling/prefetch_strategy_arbitrary.ts @@ -5,7 +5,7 @@ import { M4x4, V3 } from "libs/mjs"; import type { PullQueueItem } from "oxalis/model/bucket_data_handling/pullqueue"; import { globalPositionToBucketPosition } from "oxalis/model/helpers/position_converter"; import PolyhedronRasterizer from "oxalis/model/bucket_data_handling/polyhedron_rasterizer"; -import type { ResolutionInfo } from "../helpers/resolution_info"; +import type { MagInfo } from "../helpers/mag_info"; import type { AdditionalCoordinate } from "types/api_flow_types"; export class PrefetchStrategyArbitrary extends AbstractPrefetchStrategy { @@ -58,7 +58,7 @@ export class PrefetchStrategyArbitrary extends AbstractPrefetchStrategy { activeZoomStep: number, position: Vector3, resolutions: Array, - resolutionInfo: ResolutionInfo, + resolutionInfo: MagInfo, additionalCoordinates: AdditionalCoordinate[] | null, ): Array { const pullQueue: PullQueueItem[] = []; diff --git a/frontend/javascripts/oxalis/model/bucket_data_handling/prefetch_strategy_plane.ts b/frontend/javascripts/oxalis/model/bucket_data_handling/prefetch_strategy_plane.ts index b63f56ccb94..0585125bef4 100644 --- a/frontend/javascripts/oxalis/model/bucket_data_handling/prefetch_strategy_plane.ts +++ b/frontend/javascripts/oxalis/model/bucket_data_handling/prefetch_strategy_plane.ts @@ -8,7 +8,7 @@ import Dimensions from "oxalis/model/dimensions"; import type { OrthoView, OrthoViewMap, Vector3, Vector4 } from "oxalis/constants"; import constants, { OrthoViewValuesWithoutTDView } from "oxalis/constants"; import { getPriorityWeightForPrefetch } from "oxalis/model/bucket_data_handling/loading_strategy_logic"; -import type { ResolutionInfo } from "../helpers/resolution_info"; +import type { MagInfo } from "../helpers/mag_info"; import type { AdditionalCoordinate } from "types/api_flow_types"; const { MAX_ZOOM_STEP_DIFF_PREFETCH } = constants; @@ -88,7 +88,7 @@ export class PrefetchStrategy extends AbstractPrefetchStrategy { activePlane: OrthoView, areas: OrthoViewMap, resolutions: Vector3[], - resolutionInfo: ResolutionInfo, + resolutionInfo: MagInfo, additionalCoordinates: AdditionalCoordinate[] | null, ): Array { const zoomStep = resolutionInfo.getIndexOrClosestHigherIndex(currentZoomStep); @@ -99,7 +99,7 @@ export class PrefetchStrategy extends AbstractPrefetchStrategy { return []; } - const maxZoomStep = resolutionInfo.getCoarsestResolutionIndex(); + const maxZoomStep = resolutionInfo.getCoarsestMagIndex(); const zoomStepDiff = currentZoomStep - zoomStep; const queueItemsForCurrentZoomStep = this.prefetchImpl( cube, diff --git a/frontend/javascripts/oxalis/model/bucket_data_handling/wkstore_adapter.ts b/frontend/javascripts/oxalis/model/bucket_data_handling/wkstore_adapter.ts index 4db8385e75b..26623be1275 100644 --- a/frontend/javascripts/oxalis/model/bucket_data_handling/wkstore_adapter.ts +++ b/frontend/javascripts/oxalis/model/bucket_data_handling/wkstore_adapter.ts @@ -26,7 +26,7 @@ import type { BucketAddress, Vector3 } from "oxalis/constants"; import constants, { MappingStatusEnum } from "oxalis/constants"; import window from "libs/window"; import { getGlobalDataConnectionInfo } from "../data_connection_info"; -import type { ResolutionInfo } from "../helpers/resolution_info"; +import type { MagInfo } from "../helpers/mag_info"; import type { AdditionalCoordinate } from "types/api_flow_types"; import _ from "lodash"; @@ -60,7 +60,7 @@ type RequestBucketInfo = SendBucketInfo & { // object as expected by the server on bucket request const createRequestBucketInfo = ( zoomedAddress: BucketAddress, - resolutionInfo: ResolutionInfo, + resolutionInfo: MagInfo, fourBit: boolean, applyAgglomerate: string | null | undefined, version: number | null | undefined, @@ -81,12 +81,12 @@ const createRequestBucketInfo = ( function createSendBucketInfo( zoomedAddress: BucketAddress, - resolutionInfo: ResolutionInfo, + resolutionInfo: MagInfo, ): SendBucketInfo { return { position: bucketPositionToGlobalAddress(zoomedAddress, resolutionInfo), additionalCoordinates: zoomedAddress[4], - mag: resolutionInfo.getResolutionByIndexOrThrow(zoomedAddress[3]), + mag: resolutionInfo.getMagByIndexOrThrow(zoomedAddress[3]), cubeSize: constants.BUCKET_WIDTH, }; } diff --git a/frontend/javascripts/oxalis/model/helpers/mag_info.ts b/frontend/javascripts/oxalis/model/helpers/mag_info.ts new file mode 100644 index 00000000000..c766f2468b0 --- /dev/null +++ b/frontend/javascripts/oxalis/model/helpers/mag_info.ts @@ -0,0 +1,257 @@ +import { map3, maxValue, minValue } from "libs/utils"; +import _ from "lodash"; +import memoizeOne from "memoize-one"; +import type { Vector3 } from "oxalis/constants"; + +export type SmallerOrHigherInfo = { + smaller: boolean; + higher: boolean; +}; + +export class MagInfo { + readonly mags: ReadonlyArray; + readonly magnificationMap: ReadonlyMap; + + constructor(mags: Array) { + this.mags = mags; + this.magnificationMap = this._buildMagnificationMap(); + } + + _buildMagnificationMap() { + // Each resolution entry can be characterized by it's greatest resolution dimension. + // E.g., the resolution array [[1, 1, 1], [2, 2, 1], [4, 4, 2]] defines that + // a zoomstep of 2 corresponds to the resolution [2, 2, 1] (and not [4, 4, 2]). + // Therefore, the largest dim for each resolution has to be unique across all resolutions. + // This function creates a map which maps from powerOfTwo (2**index) to resolution. + // E.g. + // { + // 0: [1, 1, 1], + // 2: [2, 2, 1], + // 4: [4, 4, 2] + // } + const { mags } = this; + const magnificationMap = new Map(); + + if (mags.length !== _.uniq(mags.map(maxValue)).length) { + throw new Error("Max dimension in magnifications is not unique."); + } + + for (const resolution of mags) { + magnificationMap.set(maxValue(resolution), resolution); + } + return magnificationMap; + } + + getDenseMags = memoizeOne(() => convertToDenseMag(this.getMagList())); + + getMagList = memoizeOne(() => Array.from(this.magnificationMap.values())); + + getMagsWithIndices(): Array<[number, Vector3]> { + return _.sortBy( + Array.from(this.magnificationMap.entries()).map((entry) => { + const [powerOfTwo, resolution] = entry; + const resolutionIndex = Math.log2(powerOfTwo); + return [resolutionIndex, resolution]; + }), // Sort by resolutionIndex + (tuple) => tuple[0], + ); + } + + indexToPowerOf2(index: number): number { + return 2 ** index; + } + + hasIndex(index: number): boolean { + const powerOfTwo = this.indexToPowerOf2(index); + return this.magnificationMap.has(powerOfTwo); + } + + hasMag(magnification: Vector3): boolean { + return this.magnificationMap.has(Math.max(...magnification)); + } + + getMagByIndex(index: number): Vector3 | null | undefined { + const powerOfTwo = this.indexToPowerOf2(index); + return this.getMagByPowerOf2(powerOfTwo); + } + + getMagByIndexOrThrow(index: number): Vector3 { + const resolution = this.getMagByIndex(index); + + if (!resolution) { + throw new Error(`Magnification with index ${index} does not exist.`); + } + + return resolution; + } + + getIndexByMag(magnification: Vector3): number { + const index = Math.log2(Math.max(...magnification)); + + // Assert that the index exists and that the mag at that index + // equals the mag argument + const resolutionMaybe = this.getMagByIndex(index); + if (!_.isEqual(magnification, resolutionMaybe)) { + throw new Error( + `Magnification ${magnification} with index ${index} is not equal to existing magnification at that index: ${resolutionMaybe}.`, + ); + } + return index; + } + + getMagByIndexWithFallback(index: number, fallbackMagInfo: MagInfo | null | undefined): Vector3 { + let resolutionMaybe = this.getMagByIndex(index); + + if (resolutionMaybe) { + return resolutionMaybe; + } + + resolutionMaybe = fallbackMagInfo != null ? fallbackMagInfo.getMagByIndex(index) : null; + + if (resolutionMaybe) { + return resolutionMaybe; + } + + if (index === 0) { + // If the index is 0, only mag 1-1-1 can be meant. + return [1, 1, 1]; + } + + throw new Error(`Magnification could not be determined for index ${index}`); + } + + getMagByPowerOf2(powerOfTwo: number): Vector3 | null | undefined { + return this.magnificationMap.get(powerOfTwo); + } + + getCoarsestMagPowerOf2(): number { + return maxValue(Array.from(this.magnificationMap.keys())); + } + + getFinestMagPowerOf2(): number { + return minValue(Array.from(this.magnificationMap.keys())); + } + + getCoarsestMagIndex(): number { + return Math.log2(this.getCoarsestMagPowerOf2()); + } + + getFinestMagIndex(): number { + return Math.log2(this.getFinestMagPowerOf2()); + } + + getCoarsestMag(): Vector3 { + // @ts-ignore + return this.getMagByPowerOf2(this.getCoarsestMagPowerOf2()); + } + + getFinestMag(): Vector3 { + // @ts-ignore + return this.getMagByPowerOf2(this.getFinestMagPowerOf2()); + } + + getAllIndices(): Array { + return this.getMagsWithIndices().map((entry) => entry[0]); + } + + getClosestExistingIndex(index: number, errorMessage: string | null = null): number { + if (this.hasIndex(index)) { + return index; + } + + const indices = this.getAllIndices(); + const indicesWithDistances = indices.map((_index) => { + const distance = index - _index; + + if (distance >= 0) { + // The candidate _index is smaller than the requested index. + // Since webKnossos only supports rendering from higher mags, + // when a mag is missing, we want to prioritize "higher" mags + // when looking for a substitute. Therefore, we artificially + // downrank the smaller mag _index. + return [_index, distance + 0.5]; + } else { + return [_index, Math.abs(distance)]; + } + }); + + const bestIndexWithDistance = _.head(_.sortBy(indicesWithDistances, (entry) => entry[1])); + if (bestIndexWithDistance == null) { + throw new Error(errorMessage || "Couldn't find any magnification."); + } + + return bestIndexWithDistance[0]; + } + + getClosestExistingMag(magnification: Vector3): Vector3 { + const index = Math.log2(Math.max(...magnification)); + return this.getMagByIndex(this.getClosestExistingIndex(index)) as Vector3; + } + + hasSmallerAndOrHigherIndex(index: number): SmallerOrHigherInfo { + const indices = this.getAllIndices(); + const hasSmallOrHigher = { + smaller: false, + higher: false, + }; + + for (const currentIndex of indices) { + if (currentIndex < index) { + hasSmallOrHigher.smaller = true; + } else if (currentIndex > index) { + hasSmallOrHigher.higher = true; + } + } + + return hasSmallOrHigher; + } + + getIndexOrClosestHigherIndex(requestedIndex: number): number | null | undefined { + if (this.hasIndex(requestedIndex)) { + return requestedIndex; + } + + const indices = this.getMagsWithIndices().map((entry) => entry[0]); + + for (const index of indices) { + if (index > requestedIndex) { + // Return the first existing index which is higher than the requestedIndex + return index; + } + } + + return null; + } +} + +export function convertToDenseMag(magnifications: Array): Array { + // Each magnification entry can be characterized by it's greatest mag dimension. + // E.g., the mag array [[1, 1, 1], [2, 2, 1], [4, 4, 2]] defines that + // a log zoomstep of 2 corresponds to the mag [2, 2, 1] (and not [4, 4, 2]). + // Therefore, the largest dim for each mag has to be unique across all mags. + // This function returns an array of mags, for which each index will + // hold a mag with highest_dim === 2**index and where mags are monotonously increasing. + + if (magnifications.length !== _.uniq(magnifications.map(maxValue)).length) { + throw new Error("Max dimension in magnifications is not unique."); + } + + const maxResolution = Math.log2(maxValue(magnifications.map((v) => maxValue(v)))); + + const resolutionsLookUp = _.keyBy(magnifications, maxValue); + + const maxResPower = 2 ** maxResolution; + let lastResolution = [maxResPower, maxResPower, maxResPower]; + + return _.range(maxResolution, -1, -1) + .map((exp) => { + const resPower = 2 ** exp; + // If the magnification does not exist, use the component-wise minimum of the next-higher + // mag and an isotropic fallback mag. Otherwise for anisotropic mags, + // the dense mags wouldn't be monotonously increasing. + const fallback = map3((i) => Math.min(lastResolution[i], resPower), [0, 1, 2]); + lastResolution = resolutionsLookUp[resPower] || fallback; + return lastResolution as Vector3; + }) + .reverse(); +} diff --git a/frontend/javascripts/oxalis/model/helpers/position_converter.ts b/frontend/javascripts/oxalis/model/helpers/position_converter.ts index 89deb77b845..6e105972e5e 100644 --- a/frontend/javascripts/oxalis/model/helpers/position_converter.ts +++ b/frontend/javascripts/oxalis/model/helpers/position_converter.ts @@ -1,7 +1,7 @@ import type { Vector3, Vector4, BucketAddress } from "oxalis/constants"; import constants from "oxalis/constants"; import type { AdditionalCoordinate } from "types/api_flow_types"; -import type { ResolutionInfo } from "./resolution_info"; +import type { MagInfo } from "./mag_info"; export function globalPositionToBucketPosition( [x, y, z]: Vector3, @@ -69,10 +69,10 @@ export function upsampleResolution(resolutions: Array, resolutionIndex: } export function bucketPositionToGlobalAddress( bucketPosition: BucketAddress, - resolutionInfo: ResolutionInfo, + resolutionInfo: MagInfo, ): Vector3 { const [x, y, z, resolutionIndex, _additionalCoordinates] = bucketPosition; - const resolution = resolutionInfo.getResolutionByIndexOrThrow(resolutionIndex); + const resolution = resolutionInfo.getMagByIndexOrThrow(resolutionIndex); return [ x * constants.BUCKET_WIDTH * resolution[0], y * constants.BUCKET_WIDTH * resolution[1], @@ -128,14 +128,11 @@ export function zoomedAddressToAnotherZoomStep( */ export function zoomedAddressToAnotherZoomStepWithInfo( [x, y, z, resolutionIndex]: Vector4, - resolutionInfo: ResolutionInfo, + resolutionInfo: MagInfo, targetResolutionIndex: number, ): Vector4 { - const currentResolution = resolutionInfo.getResolutionByIndexWithFallback(resolutionIndex, null); - const targetResolution = resolutionInfo.getResolutionByIndexWithFallback( - targetResolutionIndex, - null, - ); + const currentResolution = resolutionInfo.getMagByIndexWithFallback(resolutionIndex, null); + const targetResolution = resolutionInfo.getMagByIndexWithFallback(targetResolutionIndex, null); const factors = getResolutionsFactors(currentResolution, targetResolution); return [ Math.floor(x * factors[0]), diff --git a/frontend/javascripts/oxalis/model/helpers/resolution_info.ts b/frontend/javascripts/oxalis/model/helpers/resolution_info.ts deleted file mode 100644 index 4307b1d026d..00000000000 --- a/frontend/javascripts/oxalis/model/helpers/resolution_info.ts +++ /dev/null @@ -1,261 +0,0 @@ -import { map3, maxValue, minValue } from "libs/utils"; -import _ from "lodash"; -import memoizeOne from "memoize-one"; -import type { Vector3 } from "oxalis/constants"; - -export type SmallerOrHigherInfo = { - smaller: boolean; - higher: boolean; -}; - -export class ResolutionInfo { - readonly resolutions: ReadonlyArray; - readonly resolutionMap: ReadonlyMap; - - constructor(resolutions: Array) { - this.resolutions = resolutions; - this.resolutionMap = this._buildResolutionMap(); - } - - _buildResolutionMap() { - // Each resolution entry can be characterized by it's greatest resolution dimension. - // E.g., the resolution array [[1, 1, 1], [2, 2, 1], [4, 4, 2]] defines that - // a zoomstep of 2 corresponds to the resolution [2, 2, 1] (and not [4, 4, 2]). - // Therefore, the largest dim for each resolution has to be unique across all resolutions. - // This function creates a map which maps from powerOfTwo (2**index) to resolution. - // E.g. - // { - // 0: [1, 1, 1], - // 2: [2, 2, 1], - // 4: [4, 4, 2] - // } - const { resolutions } = this; - const resolutionMap = new Map(); - - if (resolutions.length !== _.uniq(resolutions.map(maxValue)).length) { - throw new Error("Max dimension in resolutions is not unique."); - } - - for (const resolution of resolutions) { - resolutionMap.set(maxValue(resolution), resolution); - } - return resolutionMap; - } - - getDenseResolutions = memoizeOne(() => convertToDenseResolution(this.getResolutionList())); - - getResolutionList = memoizeOne(() => Array.from(this.resolutionMap.values())); - - getResolutionsWithIndices(): Array<[number, Vector3]> { - return _.sortBy( - Array.from(this.resolutionMap.entries()).map((entry) => { - const [powerOfTwo, resolution] = entry; - const resolutionIndex = Math.log2(powerOfTwo); - return [resolutionIndex, resolution]; - }), // Sort by resolutionIndex - (tuple) => tuple[0], - ); - } - - indexToPowerOf2(index: number): number { - return 2 ** index; - } - - hasIndex(index: number): boolean { - const powerOfTwo = this.indexToPowerOf2(index); - return this.resolutionMap.has(powerOfTwo); - } - - hasResolution(resolution: Vector3): boolean { - return this.resolutionMap.has(Math.max(...resolution)); - } - - getResolutionByIndex(index: number): Vector3 | null | undefined { - const powerOfTwo = this.indexToPowerOf2(index); - return this.getResolutionByPowerOf2(powerOfTwo); - } - - getResolutionByIndexOrThrow(index: number): Vector3 { - const resolution = this.getResolutionByIndex(index); - - if (!resolution) { - throw new Error(`Magnification with index ${index} does not exist.`); - } - - return resolution; - } - - getIndexByResolution(resolution: Vector3): number { - const index = Math.log2(Math.max(...resolution)); - - // Assert that the index exists and that the resolution at that index - // equals the resolution argument - const resolutionMaybe = this.getResolutionByIndex(index); - if (!_.isEqual(resolution, resolutionMaybe)) { - throw new Error( - `Magnification ${resolution} with index ${index} is not equal to existing magnification at that index: ${resolutionMaybe}.`, - ); - } - return index; - } - - getResolutionByIndexWithFallback( - index: number, - fallbackResolutionInfo: ResolutionInfo | null | undefined, - ): Vector3 { - let resolutionMaybe = this.getResolutionByIndex(index); - - if (resolutionMaybe) { - return resolutionMaybe; - } - - resolutionMaybe = - fallbackResolutionInfo != null ? fallbackResolutionInfo.getResolutionByIndex(index) : null; - - if (resolutionMaybe) { - return resolutionMaybe; - } - - if (index === 0) { - // If the index is 0, only mag 1-1-1 can be meant. - return [1, 1, 1]; - } - - throw new Error(`Magnification could not be determined for index ${index}`); - } - - getResolutionByPowerOf2(powerOfTwo: number): Vector3 | null | undefined { - return this.resolutionMap.get(powerOfTwo); - } - - getCoarsestResolutionPowerOf2(): number { - return maxValue(Array.from(this.resolutionMap.keys())); - } - - getFinestResolutionPowerOf2(): number { - return minValue(Array.from(this.resolutionMap.keys())); - } - - getCoarsestResolutionIndex(): number { - return Math.log2(this.getCoarsestResolutionPowerOf2()); - } - - getFinestResolutionIndex(): number { - return Math.log2(this.getFinestResolutionPowerOf2()); - } - - getCoarsestResolution(): Vector3 { - // @ts-ignore - return this.getResolutionByPowerOf2(this.getCoarsestResolutionPowerOf2()); - } - - getFinestResolution(): Vector3 { - // @ts-ignore - return this.getResolutionByPowerOf2(this.getFinestResolutionPowerOf2()); - } - - getAllIndices(): Array { - return this.getResolutionsWithIndices().map((entry) => entry[0]); - } - - getClosestExistingIndex(index: number, errorMessage: string | null = null): number { - if (this.hasIndex(index)) { - return index; - } - - const indices = this.getAllIndices(); - const indicesWithDistances = indices.map((_index) => { - const distance = index - _index; - - if (distance >= 0) { - // The candidate _index is smaller than the requested index. - // Since webKnossos only supports rendering from higher mags, - // when a mag is missing, we want to prioritize "higher" mags - // when looking for a substitute. Therefore, we artificially - // downrank the smaller mag _index. - return [_index, distance + 0.5]; - } else { - return [_index, Math.abs(distance)]; - } - }); - - const bestIndexWithDistance = _.head(_.sortBy(indicesWithDistances, (entry) => entry[1])); - if (bestIndexWithDistance == null) { - throw new Error(errorMessage || "Couldn't find any resolution."); - } - - return bestIndexWithDistance[0]; - } - - getClosestExistingResolution(resolution: Vector3): Vector3 { - const index = Math.log2(Math.max(...resolution)); - return this.getResolutionByIndex(this.getClosestExistingIndex(index)) as Vector3; - } - - hasSmallerAndOrHigherIndex(index: number): SmallerOrHigherInfo { - const indices = this.getAllIndices(); - const hasSmallOrHigher = { - smaller: false, - higher: false, - }; - - for (const currentIndex of indices) { - if (currentIndex < index) { - hasSmallOrHigher.smaller = true; - } else if (currentIndex > index) { - hasSmallOrHigher.higher = true; - } - } - - return hasSmallOrHigher; - } - - getIndexOrClosestHigherIndex(requestedIndex: number): number | null | undefined { - if (this.hasIndex(requestedIndex)) { - return requestedIndex; - } - - const indices = this.getResolutionsWithIndices().map((entry) => entry[0]); - - for (const index of indices) { - if (index > requestedIndex) { - // Return the first existing index which is higher than the requestedIndex - return index; - } - } - - return null; - } -} - -export function convertToDenseResolution(resolutions: Array): Array { - // Each resolution entry can be characterized by it's greatest resolution dimension. - // E.g., the resolution array [[1, 1, 1], [2, 2, 1], [4, 4, 2]] defines that - // a log zoomstep of 2 corresponds to the resolution [2, 2, 1] (and not [4, 4, 2]). - // Therefore, the largest dim for each resolution has to be unique across all resolutions. - // This function returns an array of resolutions, for which each index will - // hold a resolution with highest_dim === 2**index and where resolutions are monotonously increasing. - - if (resolutions.length !== _.uniq(resolutions.map(maxValue)).length) { - throw new Error("Max dimension in resolutions is not unique."); - } - - const maxResolution = Math.log2(maxValue(resolutions.map((v) => maxValue(v)))); - - const resolutionsLookUp = _.keyBy(resolutions, maxValue); - - const maxResPower = 2 ** maxResolution; - let lastResolution = [maxResPower, maxResPower, maxResPower]; - - return _.range(maxResolution, -1, -1) - .map((exp) => { - const resPower = 2 ** exp; - // If the resolution does not exist, use the component-wise minimum of the next-higher - // resolution and an isotropic fallback resolution. Otherwise for anisotropic resolutions, - // the dense resolutions wouldn't be monotonously increasing. - const fallback = map3((i) => Math.min(lastResolution[i], resPower), [0, 1, 2]); - lastResolution = resolutionsLookUp[resPower] || fallback; - return lastResolution as Vector3; - }) - .reverse(); -} diff --git a/frontend/javascripts/oxalis/model/sagas/dataset_saga.ts b/frontend/javascripts/oxalis/model/sagas/dataset_saga.ts index 31179a886d4..628314d3830 100644 --- a/frontend/javascripts/oxalis/model/sagas/dataset_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/dataset_saga.ts @@ -112,8 +112,8 @@ export function* watchZ1Downsampling(): Saga { break; } const resolutionInfo = getResolutionInfo(dataLayer.resolutions); - const bestExistingIndex = resolutionInfo.getFinestResolutionIndex(); - const currentIndex = resolutionInfo.getIndexByResolution(currentRes); + const bestExistingIndex = resolutionInfo.getFinestMagIndex(); + const currentIndex = resolutionInfo.getIndexByMag(currentRes); if (currentIndex <= bestExistingIndex) { // There's no better mag to render the current layer in. continue; diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index 772a23a763e..624e99028b1 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -75,7 +75,7 @@ import type { RemoveSegmentAction, UpdateSegmentAction, } from "../actions/volumetracing_actions"; -import type { ResolutionInfo } from "../helpers/resolution_info"; +import type { MagInfo } from "../helpers/mag_info"; import type { AdditionalCoordinate } from "types/api_flow_types"; import Zip from "libs/zipjs_wrapper"; import type { FlycamAction } from "../actions/flycam_actions"; @@ -155,7 +155,7 @@ function removeMapForSegment( adhocMeshesMapByLayer[additionalCoordinateKey][layerName].delete(segmentId); } -function getCubeSizeInMag1(zoomStep: number, resolutionInfo: ResolutionInfo): Vector3 { +function getCubeSizeInMag1(zoomStep: number, resolutionInfo: MagInfo): Vector3 { // Convert marchingCubeSizeInTargetMag to mag1 via zoomStep // Drop the last element of the Vector4; const [x, y, z] = zoomedAddressToAnotherZoomStepWithInfo( @@ -169,7 +169,7 @@ function getCubeSizeInMag1(zoomStep: number, resolutionInfo: ResolutionInfo): Ve function clipPositionToCubeBoundary( position: Vector3, zoomStep: number, - resolutionInfo: ResolutionInfo, + resolutionInfo: MagInfo, ): Vector3 { const cubeSizeInMag1 = getCubeSizeInMag1(zoomStep, resolutionInfo); const currentCube = V3.floor(V3.divide3(position, cubeSizeInMag1)); @@ -191,7 +191,7 @@ function getNeighborPosition( clippedPosition: Vector3, neighborId: number, zoomStep: number, - resolutionInfo: ResolutionInfo, + resolutionInfo: MagInfo, ): Vector3 { const neighborMultiplier = NEIGHBOR_LOOKUP[neighborId]; const cubeSizeInMag1 = getCubeSizeInMag1(zoomStep, resolutionInfo); @@ -238,7 +238,7 @@ function* getInfoForMeshLoading( meshExtraInfo: AdHocMeshInfo, ): Saga<{ zoomStep: number; - resolutionInfo: ResolutionInfo; + resolutionInfo: MagInfo; }> { const resolutionInfo = getResolutionInfo(layer.resolutions); const preferredZoomStep = @@ -322,7 +322,7 @@ function* loadFullAdHocMesh( additionalCoordinates: AdditionalCoordinate[] | undefined | null, zoomStep: number, meshExtraInfo: AdHocMeshInfo, - resolutionInfo: ResolutionInfo, + resolutionInfo: MagInfo, removeExistingMesh: boolean, ): Saga { let isInitialRequest = true; @@ -342,7 +342,7 @@ function* loadFullAdHocMesh( const cubeSize = marchingCubeSizeInTargetMag(); const tracingStoreHost = yield* select((state) => state.tracing.tracingStore.url); - const mag = resolutionInfo.getResolutionByIndexOrThrow(zoomStep); + const mag = resolutionInfo.getMagByIndexOrThrow(zoomStep); const volumeTracing = yield* select((state) => getActiveSegmentationTracing(state)); const visibleSegmentationLayer = yield* select((state) => getVisibleSegmentationLayer(state)); @@ -442,7 +442,7 @@ function* maybeLoadMeshChunk( clippedPosition: Vector3, zoomStep: number, meshExtraInfo: AdHocMeshInfo, - resolutionInfo: ResolutionInfo, + resolutionInfo: MagInfo, isInitialRequest: boolean, removeExistingMesh: boolean, useDataStore: boolean, @@ -471,7 +471,7 @@ function* maybeLoadMeshChunk( }`; const tracingStoreUrl = `${tracingStoreHost}/tracings/volume/${layer.name}`; - const mag = resolutionInfo.getResolutionByIndexOrThrow(zoomStep); + const mag = resolutionInfo.getMagByIndexOrThrow(zoomStep); if (isInitialRequest) { sendAnalyticsEvent("request_isosurface", { diff --git a/frontend/javascripts/oxalis/model/sagas/min_cut_saga.ts b/frontend/javascripts/oxalis/model/sagas/min_cut_saga.ts index d5483dcd63e..252400ae50f 100644 --- a/frontend/javascripts/oxalis/model/sagas/min_cut_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/min_cut_saga.ts @@ -21,7 +21,7 @@ import createProgressCallback from "libs/progress_callback"; import { api } from "oxalis/singletons"; import window from "libs/window"; import type { APISegmentationLayer } from "types/api_flow_types"; -import type { ResolutionInfo } from "../helpers/resolution_info"; +import type { MagInfo } from "../helpers/mag_info"; import type { AdditionalCoordinate } from "types/api_flow_types"; // By default, a new bounding box is created around @@ -62,9 +62,9 @@ const ALWAYS_IGNORE_FIRST_MAG_INITIALLY = true; function selectAppropriateResolutions( boundingBoxMag1: BoundingBox, - resolutionInfo: ResolutionInfo, + resolutionInfo: MagInfo, ): Array<[number, Vector3]> { - const resolutionsWithIndices = resolutionInfo.getResolutionsWithIndices(); + const resolutionsWithIndices = resolutionInfo.getMagsWithIndices(); const appropriateResolutions: Array<[number, Vector3]> = []; for (const [resolutionIndex, resolution] of resolutionsWithIndices) { @@ -332,8 +332,7 @@ function* performMinCut(action: Action): Saga { continue; } - const refiningResolution = - resolutionInfo.getResolutionByIndexOrThrow(refiningResolutionIndex); + const refiningResolution = resolutionInfo.getMagByIndexOrThrow(refiningResolutionIndex); console.group("Refining min-cut at", refiningResolution.join("-")); yield* call( progressCallback, diff --git a/frontend/javascripts/oxalis/model/sagas/prefetch_saga.ts b/frontend/javascripts/oxalis/model/sagas/prefetch_saga.ts index 985fbd090d8..016340c32a4 100644 --- a/frontend/javascripts/oxalis/model/sagas/prefetch_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/prefetch_saga.ts @@ -110,7 +110,7 @@ export function* prefetchForPlaneMode( const lastConnectionStats = getGlobalDataConnectionInfo().lastStats; const { lastPosition, lastDirection, lastZoomStep, lastBucketPickerTick } = previousProperties; const direction = getTraceDirection(position, lastPosition, lastDirection); - const resolutions = resolutionInfo.getDenseResolutions(); + const resolutions = resolutionInfo.getDenseMags(); const layerRenderingManager = yield* call( [Model, Model.getLayerRenderingManagerByName], layer.name, @@ -172,7 +172,7 @@ export function* prefetchForArbitraryMode( const zoomStep = yield* select((state) => getActiveMagIndexForLayer(state, layer.name)); const tracingTypes = yield* select(getTracingTypes); const resolutionInfo = getResolutionInfo(layer.resolutions); - const resolutions = resolutionInfo.getDenseResolutions(); + const resolutions = resolutionInfo.getDenseMags(); const layerRenderingManager = yield* call( [Model, Model.getLayerRenderingManagerByName], layer.name, diff --git a/frontend/javascripts/oxalis/model/sagas/proofread_saga.ts b/frontend/javascripts/oxalis/model/sagas/proofread_saga.ts index e70b43a7058..dc0ecc2c14c 100644 --- a/frontend/javascripts/oxalis/model/sagas/proofread_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/proofread_saga.ts @@ -1041,13 +1041,13 @@ function* prepareSplitOrMerge(isSkeletonProofreading: boolean): Saga => { const { additionalCoordinates } = Store.getState().flycam; diff --git a/frontend/javascripts/oxalis/model/sagas/quick_select_heuristic_saga.ts b/frontend/javascripts/oxalis/model/sagas/quick_select_heuristic_saga.ts index 9936ba855e4..e937a6b23c1 100644 --- a/frontend/javascripts/oxalis/model/sagas/quick_select_heuristic_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/quick_select_heuristic_saga.ts @@ -154,7 +154,7 @@ export function* prepareQuickSelect( requestedZoomStep, "The visible color layer and the active segmentation layer don't have any magnifications in common. Cannot select segment.", ); - const labeledResolution = resolutionInfo.getResolutionByIndexOrThrow(labeledZoomStep); + const labeledResolution = resolutionInfo.getMagByIndexOrThrow(labeledZoomStep); return { labeledZoomStep, diff --git a/frontend/javascripts/oxalis/model/sagas/save_saga.ts b/frontend/javascripts/oxalis/model/sagas/save_saga.ts index 8680f2ccae7..7301b806bbf 100644 --- a/frontend/javascripts/oxalis/model/sagas/save_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/save_saga.ts @@ -295,10 +295,10 @@ function* markBucketsAsNotDirty(saveQueue: Array, tracingId: str for (const updateAction of saveEntry.actions) { if (updateAction.name === "updateBucket") { const { position, mag, additionalCoordinates } = updateAction.value; - const resolutionIndex = segmentationResolutionInfo.getIndexByResolution(mag); + const resolutionIndex = segmentationResolutionInfo.getIndexByMag(mag); const zoomedBucketAddress = globalPositionToBucketPosition( position, - segmentationResolutionInfo.getDenseResolutions(), + segmentationResolutionInfo.getDenseMags(), resolutionIndex, additionalCoordinates, ); diff --git a/frontend/javascripts/oxalis/model/sagas/volume/helpers.ts b/frontend/javascripts/oxalis/model/sagas/volume/helpers.ts index 26cb1a4c958..7333db24e0e 100644 --- a/frontend/javascripts/oxalis/model/sagas/volume/helpers.ts +++ b/frontend/javascripts/oxalis/model/sagas/volume/helpers.ts @@ -26,7 +26,7 @@ import { enforceActiveVolumeTracing } from "oxalis/model/accessors/volumetracing import type { BoundingBoxObject, VolumeTracing } from "oxalis/store"; import { getFlooredPosition } from "oxalis/model/accessors/flycam_accessor"; import { zoomedPositionToZoomedAddress } from "oxalis/model/helpers/position_converter"; -import type { ResolutionInfo } from "oxalis/model/helpers/resolution_info"; +import type { MagInfo } from "oxalis/model/helpers/mag_info"; function* pairwise(arr: Array): Generator<[T, T], any, any> { for (let i = 0; i < arr.length - 1; i++) { @@ -79,7 +79,7 @@ export function applyLabeledVoxelMapToAllMissingResolutions( inputLabeledVoxelMap: LabeledVoxelsMap, labeledZoomStep: number, dimensionIndices: DimensionMap, - resolutionInfo: ResolutionInfo, + resolutionInfo: MagInfo, segmentationCube: DataCube, segmentId: number, thirdDimensionOfSlice: number, // this value is specified in global (mag1) coords @@ -111,8 +111,8 @@ export function applyLabeledVoxelMapToAllMissingResolutions( // should be downsampled) // `upsampleSequence` contains the current mag and all lower mags (to which // should be upsampled) - const labeledResolution = resolutionInfo.getResolutionByIndexOrThrow(labeledZoomStep); - const allResolutionsWithIndices = resolutionInfo.getResolutionsWithIndices(); + const labeledResolution = resolutionInfo.getMagByIndexOrThrow(labeledZoomStep); + const allResolutionsWithIndices = resolutionInfo.getMagsWithIndices(); const pivotIndex = allResolutionsWithIndices.findIndex(([index]) => index === labeledZoomStep); const downsampleSequence = allResolutionsWithIndices.slice(pivotIndex); const upsampleSequence = allResolutionsWithIndices.slice(0, pivotIndex + 1).reverse(); @@ -186,7 +186,7 @@ export function* labelWithVoxelBuffer2D( const currentLabeledVoxelMap: LabeledVoxelsMap = new Map(); const dimensionIndices = Dimensions.getIndices(viewport); const resolutionInfo = yield* call(getResolutionInfo, segmentationLayer.resolutions); - const labeledResolution = resolutionInfo.getResolutionByIndexOrThrow(labeledZoomStep); + const labeledResolution = resolutionInfo.getMagByIndexOrThrow(labeledZoomStep); const get3DCoordinateFromLocal2D = ([x, y]: Vector2) => voxelBuffer.get3DCoordinate([x + voxelBuffer.minCoord2d[0], y + voxelBuffer.minCoord2d[1]]); diff --git a/frontend/javascripts/oxalis/model/sagas/volume/volume_interpolation_saga.ts b/frontend/javascripts/oxalis/model/sagas/volume/volume_interpolation_saga.ts index f21282546d6..fb29d27210a 100644 --- a/frontend/javascripts/oxalis/model/sagas/volume/volume_interpolation_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/volume/volume_interpolation_saga.ts @@ -86,7 +86,7 @@ function _getInterpolationInfo(state: OxalisState, explanationPrefix: string) { const requestedZoomStep = getActiveMagIndexForLayer(state, segmentationLayer.name); const resolutionInfo = getResolutionInfo(segmentationLayer.resolutions); const labeledZoomStep = resolutionInfo.getClosestExistingIndex(requestedZoomStep); - const labeledResolution = resolutionInfo.getResolutionByIndexOrThrow(labeledZoomStep); + const labeledResolution = resolutionInfo.getMagByIndexOrThrow(labeledZoomStep); const previousCentroid = getLabelActionFromPreviousSlice( state, diff --git a/frontend/javascripts/oxalis/view/action-bar/download_modal_view.tsx b/frontend/javascripts/oxalis/view/action-bar/download_modal_view.tsx index 8d87f2abb4c..7e364fa5edd 100644 --- a/frontend/javascripts/oxalis/view/action-bar/download_modal_view.tsx +++ b/frontend/javascripts/oxalis/view/action-bar/download_modal_view.tsx @@ -323,8 +323,8 @@ function _DownloadModalView({ const [selectedBoundingBoxId, setSelectedBoundingBoxId] = useState( initialBoundingBoxId ?? userBoundingBoxes[0].id, ); - const [rawMag, setMag] = useState(selectedLayerResolutionInfo.getFinestResolution()); - const mag = selectedLayerResolutionInfo.getClosestExistingResolution(rawMag); + const [rawMag, setMag] = useState(selectedLayerResolutionInfo.getFinestMag()); + const mag = selectedLayerResolutionInfo.getClosestExistingMag(rawMag); const [exportFormat, setExportFormat] = useState(ExportFormat.OME_TIFF); const selectedBoundingBox = userBoundingBoxes.find( diff --git a/frontend/javascripts/oxalis/view/action-bar/starting_job_modals.tsx b/frontend/javascripts/oxalis/view/action-bar/starting_job_modals.tsx index 93b7b722410..9f84dbeffbb 100644 --- a/frontend/javascripts/oxalis/view/action-bar/starting_job_modals.tsx +++ b/frontend/javascripts/oxalis/view/action-bar/starting_job_modals.tsx @@ -46,7 +46,7 @@ import { } from "libs/utils"; import { getBaseSegmentationName } from "oxalis/view/right-border-tabs/segments_tab/segments_view_helper"; import { V3 } from "libs/mjs"; -import type { ResolutionInfo } from "oxalis/model/helpers/resolution_info"; +import type { MagInfo } from "oxalis/model/helpers/mag_info"; import { isBoundingBoxExportable } from "./download_modal_view"; import features from "features"; import { setAIJobModalStateAction } from "oxalis/model/actions/ui_actions"; @@ -256,12 +256,12 @@ export function MagSlider({ value, onChange, }: { - resolutionInfo: ResolutionInfo; + resolutionInfo: MagInfo; value: Vector3; onChange: (v: Vector3) => void; }) { // Use `getResolutionsWithIndices` because returns a sorted list - const allMags = resolutionInfo.getResolutionsWithIndices(); + const allMags = resolutionInfo.getMagsWithIndices(); return ( void const getMagForSegmentationLayer = async (_annotationId: string, layerName: string) => { const segmentationLayer = getSegmentationLayerByHumanReadableName(dataset, tracing, layerName); - return getResolutionInfo(segmentationLayer.resolutions).getFinestResolution(); + return getResolutionInfo(segmentationLayer.resolutions).getFinestMag(); }; const userBoundingBoxes = getSomeTracing(tracing).userBoundingBoxes; diff --git a/frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx b/frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx index 2113876dc4a..d34ce424c14 100644 --- a/frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx +++ b/frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx @@ -306,7 +306,7 @@ function LayerInfoIconWithTooltip({ const renderTooltipContent = useCallback(() => { const elementClass = getElementClass(dataset, layer.name); const resolutionInfo = getResolutionInfo(layer.resolutions); - const resolutions = resolutionInfo.getResolutionList(); + const resolutions = resolutionInfo.getMagList(); return (
Data Type: {elementClass}
diff --git a/frontend/javascripts/oxalis/view/left-border-tabs/modals/add_volume_layer_modal.tsx b/frontend/javascripts/oxalis/view/left-border-tabs/modals/add_volume_layer_modal.tsx index c2c45358c09..c8bf5c178d8 100644 --- a/frontend/javascripts/oxalis/view/left-border-tabs/modals/add_volume_layer_modal.tsx +++ b/frontend/javascripts/oxalis/view/left-border-tabs/modals/add_volume_layer_modal.tsx @@ -155,10 +155,10 @@ export default function AddVolumeLayerModal({ return; } const minResolutionAllowed = Math.max( - ...resolutionInfo.getResolutionByIndexOrThrow(resolutionIndices[0]), + ...resolutionInfo.getMagByIndexOrThrow(resolutionIndices[0]), ); const maxResolutionAllowed = Math.max( - ...resolutionInfo.getResolutionByIndexOrThrow(resolutionIndices[1]), + ...resolutionInfo.getMagByIndexOrThrow(resolutionIndices[1]), ); if (selectedSegmentationLayerName == null) { diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_statistics_modal.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_statistics_modal.tsx index d7ac006d461..ccbe94e8a52 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_statistics_modal.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_statistics_modal.tsx @@ -106,7 +106,7 @@ export function SegmentStatisticsModal({ }: Props) { const { dataset, tracing, temporaryConfiguration } = useSelector((state: OxalisState) => state); const magInfo = getResolutionInfo(visibleSegmentationLayer.resolutions); - const layersFinestResolution = magInfo.getFinestResolution(); + const layersFinestResolution = magInfo.getFinestMag(); const voxelSize = dataset.dataSource.scale; // Omit checking that all prerequisites for segment stats (such as a segment index) are // met right here because that should happen before opening the modal. diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx index 160ce9e9d2e..c0a5838aa4d 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx @@ -81,7 +81,7 @@ import { setSelectedSegmentsOrGroupAction, updateSegmentAction, } from "oxalis/model/actions/volumetracing_actions"; -import type { ResolutionInfo } from "oxalis/model/helpers/resolution_info"; +import type { MagInfo } from "oxalis/model/helpers/mag_info"; import { api } from "oxalis/singletons"; import type { ActiveMappingInfo, @@ -165,7 +165,7 @@ type StateProps = { activeCellId: number | null | undefined; preferredQualityForMeshPrecomputation: number; preferredQualityForMeshAdHocComputation: number; - resolutionInfoOfVisibleSegmentationLayer: ResolutionInfo; + resolutionInfoOfVisibleSegmentationLayer: MagInfo; }; const mapStateToProps = (state: OxalisState): StateProps => { @@ -835,7 +835,7 @@ class SegmentsView extends React.Component { defaultOrHigherIndex != null ? defaultOrHigherIndex : resolutionInfo.getClosestExistingIndex(preferredQualityForMeshPrecomputation); - const meshfileResolution = resolutionInfo.getResolutionByIndexWithFallback( + const meshfileResolution = resolutionInfo.getMagByIndexWithFallback( meshfileResolutionIndex, null, ); @@ -918,7 +918,7 @@ class SegmentsView extends React.Component { onChange={this.handleQualityChangeForAdHocGeneration} > {resolutionInfo - .getResolutionsWithIndices() + .getMagsWithIndices() .map(([log2Index, mag]: [number, Vector3], index: number) => (
setResolutionIndices(value)} + onChange={(value) => setMagIndices(value)} range step={1} min={lowestResolutionIndex} @@ -157,7 +155,7 @@ export function RestrictResolutionSlider({ textAlign: "right", }} > - {resolutionInfo.getMagByIndexOrThrow(highResolutionIndex).join("-")} + {magInfo.getMagByIndexOrThrow(highResolutionIndex).join("-")} @@ -196,8 +194,8 @@ function CreateExplorativeModal({ datasetId, onClose }: Props) { : ""; const resolutionInfo = selectedSegmentationLayer == null - ? getSomeResolutionInfoForDataset(dataset) - : getResolutionInfo(selectedSegmentationLayer.resolutions); + ? getSomeMagInfoForDataset(dataset) + : getMagInfo(selectedSegmentationLayer.resolutions); const highestResolutionIndex = resolutionInfo.getCoarsestMagIndex(); const lowestResolutionIndex = resolutionInfo.getFinestMagIndex(); @@ -205,11 +203,11 @@ function CreateExplorativeModal({ datasetId, onClose }: Props) { const lowResolutionIndex = Math.max(lowestResolutionIndex, userDefinedResolutionIndices[0]); const resolutionSlider = annotationType !== "skeleton" ? ( - ) : null; modalContent = ( diff --git a/frontend/javascripts/dashboard/dataset/dataset_settings_viewconfig_tab.tsx b/frontend/javascripts/dashboard/dataset/dataset_settings_viewconfig_tab.tsx index b1fe4b446cc..6ba1b9897b5 100644 --- a/frontend/javascripts/dashboard/dataset/dataset_settings_viewconfig_tab.tsx +++ b/frontend/javascripts/dashboard/dataset/dataset_settings_viewconfig_tab.tsx @@ -203,7 +203,7 @@ export default function DatasetSettingsViewConfigTab(props: { maximumVolume) { throw new Error( - `The volume of the bounding box exceeds ${maximumVolume} vx, please make it smaller. Currently, the bounding box has a volume of ${volume} vx in the active resolution (${currentMag.join("-")}).`, + `The volume of the bounding box exceeds ${maximumVolume} vx, please make it smaller. Currently, the bounding box has a volume of ${volume} vx in the active magnification (${currentMag.join("-")}).`, ); } else if (volume > maximumVolume / 8) { Toast.warning( @@ -1512,7 +1512,7 @@ class TracingApi { } /** - * Use this method to create a complete resolution pyramid by downsampling the lowest present mag (e.g., mag 1). + * Use this method to create a complete magnification pyramid by downsampling the lowest present mag (e.g., mag 1). This method will save the current changes and then reload the page after the downsampling has finished. This function can only be used for non-tasks. @@ -1815,9 +1815,9 @@ class DataApi { /** * Returns raw binary data for a given layer, position and zoom level. If the zoom - * level is not provided, the first resolution will be used. If this - * resolution does not exist, the next existing resolution will be used. - * If the zoom level is provided and points to a not existent resolution, + * level is not provided, the first magnification will be used. If this + * mag does not exist, the next existing mag will be used. + * If the zoom level is provided and points to a not existent mag, * 0 will be returned. * * @example // Return the greyscale value for a bucket @@ -1842,7 +1842,7 @@ class DataApi { zoomStep = _zoomStep; } else { const layer = getLayerByName(Store.getState().dataset, layerName); - const resolutionInfo = getResolutionInfo(layer.resolutions); + const resolutionInfo = getMagInfo(layer.resolutions); zoomStep = resolutionInfo.getFinestMagIndex(); } @@ -1899,7 +1899,7 @@ class DataApi { additionalCoordinates: AdditionalCoordinate[] | null = null, ) { const layer = getLayerByName(Store.getState().dataset, layerName); - const resolutionInfo = getResolutionInfo(layer.resolutions); + const resolutionInfo = getMagInfo(layer.resolutions); let zoomStep; if (_zoomStep != null) { @@ -1918,7 +1918,7 @@ class DataApi { if (bucketAddresses.length > 15000) { console.warn( - "More than 15000 buckets need to be requested for the given bounding box. Consider passing a smaller bounding box or using another resolution.", + "More than 15000 buckets need to be requested for the given bounding box. Consider passing a smaller bounding box or using another magnification.", ); } @@ -1945,7 +1945,7 @@ class DataApi { viewport, ); const layer = getLayerByName(state.dataset, layerName); - const resolutionInfo = getResolutionInfo(layer.resolutions); + const resolutionInfo = getMagInfo(layer.resolutions); if (maybeResolutionIndex == null) { maybeResolutionIndex = getActiveMagIndexForLayer(state, layerName); } @@ -1966,7 +1966,7 @@ class DataApi { const heightInVoxel = Math.ceil(halfViewportExtentV / resolutionUVX[1]); if (widthInVoxel * heightInVoxel > 1024 ** 2) { throw new Error( - "Requested data for viewport cannot be loaded, since the amount of data is too large for the available resolution. Please zoom in further or ensure that coarser magnifications are available.", + "Requested data for viewport cannot be loaded, since the amount of data is too large for the available magnification. Please zoom in further or ensure that coarser magnifications are available.", ); } @@ -1984,7 +1984,7 @@ class DataApi { getBucketAddressesInCuboid( bbox: BoundingBoxType, - resolutions: Array, + magnifications: Array, zoomStep: number, additionalCoordinates: AdditionalCoordinate[] | null, ): Array { @@ -1992,13 +1992,13 @@ class DataApi { const bottomRight = bbox.max; const minBucket = globalPositionToBucketPosition( bbox.min, - resolutions, + magnifications, zoomStep, additionalCoordinates, ); const topLeft = (bucketAddress: BucketAddress) => - bucketPositionToGlobalAddress(bucketAddress, new MagInfo(resolutions)); + bucketPositionToGlobalAddress(bucketAddress, new MagInfo(magnifications)); const nextBucketInDim = (bucket: BucketAddress, dim: 0 | 1 | 2) => { const copy = bucket.slice() as BucketAddress; @@ -2032,12 +2032,12 @@ class DataApi { buckets: Array, bbox: BoundingBoxType, elementClass: ElementClass, - resolutions: Array, + magnifications: Array, zoomStep: number, ): TypedArray { - const resolution = resolutions[zoomStep]; + const resolution = magnifications[zoomStep]; // All calculations in this method are in zoomStep-space, so in global coordinates which are divided - // by the resolution + // by the mag const topLeft = scaleGlobalPositionWithResolution(bbox.min, resolution); // Ceil the bounding box bottom right instead of flooring, because it is exclusive const bottomRight = scaleGlobalPositionWithResolution(bbox.max, resolution, true); @@ -2103,13 +2103,13 @@ class DataApi { topLeft: Vector3, bottomRight: Vector3, token: string, - resolution?: Vector3, + magnification?: Vector3, ): string { const { dataset } = Store.getState(); - const resolutionInfo = getResolutionInfo(getLayerByName(dataset, layerName, true).resolutions); - resolution = resolution || resolutionInfo.getFinestMag(); + const resolutionInfo = getMagInfo(getLayerByName(dataset, layerName, true).resolutions); + magnification = magnification || resolutionInfo.getFinestMag(); - const magString = resolution.join("-"); + const magString = magnification.join("-"); return ( `${dataset.dataStore.url}/data/datasets/${dataset.owningOrganization}/${dataset.name}/layers/${layerName}/data?mag=${magString}&` + `token=${token}&` + @@ -2147,7 +2147,7 @@ class DataApi { layerName: string, topLeft: Vector3, bottomRight: Vector3, - resolution?: Vector3, + magnification?: Vector3, ): Promise { return doWithToken((token) => { const downloadUrl = this._getDownloadUrlForRawDataCuboid( @@ -2155,7 +2155,7 @@ class DataApi { topLeft, bottomRight, token, - resolution, + magnification, ); return Request.receiveArraybuffer(downloadUrl); }); diff --git a/frontend/javascripts/oxalis/controller/scene_controller.ts b/frontend/javascripts/oxalis/controller/scene_controller.ts index 2fc6e7a82a8..382c6a5a7b3 100644 --- a/frontend/javascripts/oxalis/controller/scene_controller.ts +++ b/frontend/javascripts/oxalis/controller/scene_controller.ts @@ -106,7 +106,7 @@ class SceneController { this.meshesRootGroup = new THREE.Group(); this.highlightedBBoxId = null; - // The dimension(s) with the highest resolution will not be distorted + // The dimension(s) with the highest mag will not be distorted this.rootGroup.scale.copy( new THREE.Vector3(...Store.getState().dataset.dataSource.scale.factor), ); @@ -125,13 +125,13 @@ class SceneController { window.addBucketMesh = ( position: Vector3, zoomStep: number, - resolution: Vector3, + mag: Vector3, optColor?: string, ) => { const bucketSize = [ - constants.BUCKET_WIDTH * resolution[0], - constants.BUCKET_WIDTH * resolution[1], - constants.BUCKET_WIDTH * resolution[2], + constants.BUCKET_WIDTH * mag[0], + constants.BUCKET_WIDTH * mag[1], + constants.BUCKET_WIDTH * mag[2], ]; const boxGeometry = new THREE.BoxGeometry(...bucketSize); const edgesGeometry = new THREE.EdgesGeometry(boxGeometry); diff --git a/frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts b/frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts index 4c1d7574dfb..4005d0e0f14 100644 --- a/frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts +++ b/frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts @@ -31,8 +31,8 @@ import { getLayerByName, invertAndTranspose, getTransformsForLayer, - getResolutionInfoByLayer, - getResolutionInfo, + getMagInfoByLayer, + getMagInfo, getTransformsPerLayer, } from "oxalis/model/accessors/dataset_accessor"; import { @@ -484,7 +484,7 @@ class PlaneMaterialFactory { const state = Store.getState(); for (const [layerName, activeMagIndex] of Object.entries(activeMagIndices)) { const layer = getLayerByName(state.dataset, layerName); - const resolutionInfo = getResolutionInfo(layer.resolutions); + const resolutionInfo = getMagInfo(layer.resolutions); // If the active mag doesn't exist, a fallback mag is likely rendered. Use that // to determine a representative mag. const suitableMagIndex = resolutionInfo.getIndexOrClosestHigherIndex(activeMagIndex); @@ -564,7 +564,7 @@ class PlaneMaterialFactory { ); this.storePropertyUnsubscribers.push( listenToStoreProperty( - (storeState) => getResolutionInfoByLayer(storeState.dataset), + (storeState) => getMagInfoByLayer(storeState.dataset), (resolutionInfosByLayer) => { const allDenseResolutions = Object.values(resolutionInfosByLayer).map((resInfo) => resInfo.getDenseMags(), @@ -1099,7 +1099,7 @@ class PlaneMaterialFactory { colorLayerNames, segmentationLayerNames, textureLayerInfos, - resolutionsCount: this.getTotalResolutionCount(), + resolutionsCount: this.getTotalMagCount(), voxelSizeFactor, isOrthogonal: this.isOrthogonal, tpsTransformPerLayer: this.scaledTpsInvPerLayer, @@ -1110,9 +1110,9 @@ class PlaneMaterialFactory { ]; } - getTotalResolutionCount(): number { + getTotalMagCount(): number { const storeState = Store.getState(); - const allDenseResolutions = Object.values(getResolutionInfoByLayer(storeState.dataset)).map( + const allDenseResolutions = Object.values(getMagInfoByLayer(storeState.dataset)).map( (resInfo) => resInfo.getDenseMags(), ); const flatResolutions = _.flatten(allDenseResolutions); @@ -1134,7 +1134,7 @@ class PlaneMaterialFactory { colorLayerNames, segmentationLayerNames, textureLayerInfos, - resolutionsCount: this.getTotalResolutionCount(), + resolutionsCount: this.getTotalMagCount(), voxelSizeFactor, isOrthogonal: this.isOrthogonal, tpsTransformPerLayer: this.scaledTpsInvPerLayer, diff --git a/frontend/javascripts/oxalis/geometries/plane.ts b/frontend/javascripts/oxalis/geometries/plane.ts index 5ed629c790f..78369e54852 100644 --- a/frontend/javascripts/oxalis/geometries/plane.ts +++ b/frontend/javascripts/oxalis/geometries/plane.ts @@ -47,7 +47,7 @@ class Plane { this.displayCrosshair = true; this.lastScaleFactors = [-1, -1]; // VIEWPORT_WIDTH means that the plane should be that many voxels wide in the - // dimension with the highest resolution. In all other dimensions, the plane + // dimension with the highest mag. In all other dimensions, the plane // is smaller in voxels, so that it is squared in nm. // --> scaleInfo.baseVoxel const baseVoxelFactors = getBaseVoxelFactorsInUnit(Store.getState().dataset.dataSource.scale); diff --git a/frontend/javascripts/oxalis/model/accessors/dataset_accessor.ts b/frontend/javascripts/oxalis/model/accessors/dataset_accessor.ts index 4d7a3813294..e3db232e4d4 100644 --- a/frontend/javascripts/oxalis/model/accessors/dataset_accessor.ts +++ b/frontend/javascripts/oxalis/model/accessors/dataset_accessor.ts @@ -44,31 +44,31 @@ import { transformPointUnscaled, } from "../helpers/transformation_helpers"; -function _getResolutionInfo(resolutions: Array): MagInfo { - return new MagInfo(resolutions); +function _getMagInfo(magnifications: Array): MagInfo { + return new MagInfo(magnifications); } -// Don't use memoizeOne here, since we want to cache the resolutions for all layers +// Don't use memoizeOne here, since we want to cache the mags for all layers // (which are not that many). -export const getResolutionInfo = _.memoize(_getResolutionInfo); +export const getMagInfo = _.memoize(_getMagInfo); -function _getResolutionInfoByLayer(dataset: APIDataset): Record { +function _getMagInfoByLayer(dataset: APIDataset): Record { const infos: Record = {}; for (const layer of dataset.dataSource.dataLayers) { - infos[layer.name] = getResolutionInfo(layer.resolutions); + infos[layer.name] = getMagInfo(layer.resolutions); } return infos; } -export const getResolutionInfoByLayer = _.memoize(_getResolutionInfoByLayer); +export const getMagInfoByLayer = _.memoize(_getMagInfoByLayer); -export function getDenseResolutionsForLayerName(dataset: APIDataset, layerName: string) { - return getResolutionInfoByLayer(dataset)[layerName].getDenseMags(); +export function getDenseMagsForLayerName(dataset: APIDataset, layerName: string) { + return getMagInfoByLayer(dataset)[layerName].getDenseMags(); } -export const getResolutionUnion = memoizeOne((dataset: APIDataset): Array => { +export const getMagnificationUnion = memoizeOne((dataset: APIDataset): Array => { /* * Returns a list of existent mags per mag level. For example: * [ @@ -104,7 +104,7 @@ export const getResolutionUnion = memoizeOne((dataset: APIDataset): Array resolutionUnionDict[key]); }); -export function getWidestResolutions(dataset: APIDataset): Vector3[] { +export function getWidestMags(dataset: APIDataset): Vector3[] { const allLayerResolutions = dataset.dataSource.dataLayers.map((layer) => convertToDenseMag(layer.resolutions), ); @@ -112,14 +112,14 @@ export function getWidestResolutions(dataset: APIDataset): Vector3[] { return _.maxBy(allLayerResolutions, (resolutions) => resolutions.length) || []; } -export const getSomeResolutionInfoForDataset = memoizeOne((dataset: APIDataset): MagInfo => { - const resolutionUnion = getResolutionUnion(dataset); +export const getSomeMagInfoForDataset = memoizeOne((dataset: APIDataset): MagInfo => { + const resolutionUnion = getMagnificationUnion(dataset); const areMagsDistinct = resolutionUnion.every((mags) => mags.length <= 1); if (areMagsDistinct) { return new MagInfo(resolutionUnion.map((mags) => mags[0])); } else { - return new MagInfo(getWidestResolutions(dataset)); + return new MagInfo(getWidestMags(dataset)); } }); @@ -132,7 +132,7 @@ function _getMaxZoomStep(dataset: APIDataset | null | undefined): number { const maxZoomstep = Math.max( minimumZoomStepCount, - _.max(_.flattenDeep(getResolutionUnion(dataset))) || minimumZoomStepCount, + _.max(_.flattenDeep(getMagnificationUnion(dataset))) || minimumZoomStepCount, ); return maxZoomstep; @@ -143,18 +143,18 @@ export function getDataLayers(dataset: APIDataset): DataLayerType[] { return dataset.dataSource.dataLayers; } -function _getResolutionInfoOfVisibleSegmentationLayer(state: OxalisState): MagInfo { +function _getMagInfoOfVisibleSegmentationLayer(state: OxalisState): MagInfo { const segmentationLayer = getVisibleSegmentationLayer(state); if (!segmentationLayer) { return new MagInfo([]); } - return getResolutionInfo(segmentationLayer.resolutions); + return getMagInfo(segmentationLayer.resolutions); } -export const getResolutionInfoOfVisibleSegmentationLayer = memoizeOne( - _getResolutionInfoOfVisibleSegmentationLayer, +export const getMagInfoOfVisibleSegmentationLayer = memoizeOne( + _getMagInfoOfVisibleSegmentationLayer, ); export function getLayerByName( dataset: APIDataset, diff --git a/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts b/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts index 97caf204200..1407dc874da 100644 --- a/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts +++ b/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts @@ -11,7 +11,7 @@ import { getEnabledLayers, getLayerByName, getMaxZoomStep, - getResolutionInfo, + getMagInfo, getTransformsForLayer, invertAndTranspose, } from "oxalis/model/accessors/dataset_accessor"; @@ -241,7 +241,7 @@ function getMaximumZoomForAllResolutionsFromStore( viewMode, state.datasetConfiguration.loadingStrategy, state.dataset.dataSource.scale.factor, - getResolutionInfo(layer.resolutions).getDenseMags(), + getMagInfo(layer.resolutions).getDenseMags(), getViewportRects(state), Math.min( state.temporaryConfiguration.gpuSetup.smallestCommonBucketCapacity, @@ -362,7 +362,7 @@ export function getCurrentResolution( state: OxalisState, layerName: string, ): Vector3 | null | undefined { - const resolutionInfo = getResolutionInfo(getLayerByName(state.dataset, layerName).resolutions); + const resolutionInfo = getMagInfo(getLayerByName(state.dataset, layerName).resolutions); const magIndex = getActiveMagIndexForLayer(state, layerName); const existingMagIndex = resolutionInfo.getIndexOrClosestHigherIndex(magIndex); if (existingMagIndex == null) { @@ -587,7 +587,7 @@ function _getUnrenderableLayerInfosForCurrentZoom( .map((layer: DataLayerType) => ({ layer, activeMagIdx: activeMagIndices[layer.name], - resolutionInfo: getResolutionInfo(layer.resolutions), + resolutionInfo: getMagInfo(layer.resolutions), })) .filter(({ activeMagIdx, resolutionInfo }) => { const isPresent = resolutionInfo.hasIndex(activeMagIdx); @@ -635,7 +635,7 @@ function _getActiveResolutionInfo(state: OxalisState) { const activeMagOfEnabledLayers = Object.fromEntries( enabledLayers.map((l) => [ l.name, - getResolutionInfo(l.resolutions).getMagByIndex(activeMagIndices[l.name]), + getMagInfo(l.resolutions).getMagByIndex(activeMagIndices[l.name]), ]), ); diff --git a/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts b/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts index d6071e37abe..f70702777b3 100644 --- a/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts +++ b/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts @@ -31,7 +31,7 @@ import { import { AnnotationToolEnum, VolumeTools } from "oxalis/constants"; import { getMappingInfo, - getResolutionInfo, + getMagInfo, getSegmentationLayerByName, getSegmentationLayers, getVisibleSegmentationLayer, @@ -187,7 +187,7 @@ function _getResolutionInfoOfActiveSegmentationTracingLayer(state: OxalisState): } const segmentationLayer = getSegmentationLayerForTracing(state, volumeTracing); - return getResolutionInfo(segmentationLayer.resolutions); + return getMagInfo(segmentationLayer.resolutions); } const getResolutionInfoOfActiveSegmentationTracingLayer = memoizeOne( @@ -496,7 +496,7 @@ function _getRenderableResolutionForSegmentationTracing( const requestedZoomStep = getActiveMagIndexForLayer(state, segmentationLayer.name); const { renderMissingDataBlack } = state.datasetConfiguration; - const resolutionInfo = getResolutionInfo(segmentationLayer.resolutions); + const resolutionInfo = getMagInfo(segmentationLayer.resolutions); // Check whether the segmentation layer is enabled const segmentationSettings = state.datasetConfiguration.layers[segmentationLayer.name]; @@ -647,7 +647,7 @@ export function getLabelActionFromPreviousSlice( // Gets the last label action which was performed on a different slice. // Note that in coarser mags (e.g., 8-8-2), the comparison of the coordinates // is done while respecting how the coordinates are clipped due to that resolution. - const adapt = (vec: Vector3) => V3.roundElementToResolution(vec, resolution, dim); + const adapt = (vec: Vector3) => V3.roundElementToMag(vec, resolution, dim); const position = adapt(getFlooredPosition(state.flycam)); return volumeTracing.lastLabelActions.find( diff --git a/frontend/javascripts/oxalis/model/bucket_data_handling/layer_rendering_manager.ts b/frontend/javascripts/oxalis/model/bucket_data_handling/layer_rendering_manager.ts index a33ae2ff9a6..b19e1a51333 100644 --- a/frontend/javascripts/oxalis/model/bucket_data_handling/layer_rendering_manager.ts +++ b/frontend/javascripts/oxalis/model/bucket_data_handling/layer_rendering_manager.ts @@ -10,7 +10,7 @@ import { getElementClass, isLayerVisible, getLayerByName, - getResolutionInfo, + getMagInfo, invertAndTranspose, getTransformsForLayer, } from "oxalis/model/accessors/dataset_accessor"; @@ -187,7 +187,7 @@ export default class LayerRenderingManager { const state = Store.getState(); const { dataset, datasetConfiguration } = state; const layer = getLayerByName(dataset, this.name); - const resolutionInfo = getResolutionInfo(layer.resolutions); + const resolutionInfo = getMagInfo(layer.resolutions); const maximumResolutionIndex = resolutionInfo.getCoarsestMagIndex(); if (logZoomStep > maximumResolutionIndex) { @@ -196,7 +196,7 @@ export default class LayerRenderingManager { return; } - const resolutions = getResolutionInfo(layer.resolutions).getDenseMags(); + const resolutions = getMagInfo(layer.resolutions).getDenseMags(); const layerMatrix = invertAndTranspose( getTransformsForLayer(dataset, layer, datasetConfiguration.nativelyRenderedLayerName) .affineMatrix, diff --git a/frontend/javascripts/oxalis/model/bucket_data_handling/wkstore_adapter.ts b/frontend/javascripts/oxalis/model/bucket_data_handling/wkstore_adapter.ts index 26623be1275..e459478c944 100644 --- a/frontend/javascripts/oxalis/model/bucket_data_handling/wkstore_adapter.ts +++ b/frontend/javascripts/oxalis/model/bucket_data_handling/wkstore_adapter.ts @@ -5,7 +5,7 @@ import { doWithToken } from "admin/admin_rest_api"; import { isSegmentationLayer, getByteCountFromLayer, - getResolutionInfo, + getMagInfo, getMappingInfo, } from "oxalis/model/accessors/dataset_accessor"; import { @@ -198,7 +198,7 @@ export async function requestFromStore( : null; })(); - const resolutionInfo = getResolutionInfo(layerInfo.resolutions); + const resolutionInfo = getMagInfo(layerInfo.resolutions); const version = !isVolumeFallback && isSegmentation && maybeVolumeTracing != null ? maybeVolumeTracing.version diff --git a/frontend/javascripts/oxalis/model/data_layer.ts b/frontend/javascripts/oxalis/model/data_layer.ts index d5b3053c543..a473c9edc7d 100644 --- a/frontend/javascripts/oxalis/model/data_layer.ts +++ b/frontend/javascripts/oxalis/model/data_layer.ts @@ -1,5 +1,5 @@ import type { Vector3 } from "oxalis/constants"; -import { getLayerBoundingBox, getResolutionInfo } from "oxalis/model/accessors/dataset_accessor"; +import { getLayerBoundingBox, getMagInfo } from "oxalis/model/accessors/dataset_accessor"; import DataCube from "oxalis/model/bucket_data_handling/data_cube"; import ErrorHandling from "libs/error_handling"; import LayerRenderingManager from "oxalis/model/bucket_data_handling/layer_rendering_manager"; @@ -35,12 +35,12 @@ class DataLayer { this.resolutions = layerInfo.resolutions; const { dataset } = Store.getState(); - ErrorHandling.assert(this.resolutions.length > 0, "Resolutions for layer cannot be empty"); + ErrorHandling.assert(this.resolutions.length > 0, "Magnifications for layer cannot be empty"); this.cube = new DataCube( getLayerBoundingBox(dataset, this.name), layerInfo.additionalAxes || [], - getResolutionInfo(this.resolutions), + getMagInfo(this.resolutions), layerInfo.elementClass, this.isSegmentation, this.name, diff --git a/frontend/javascripts/oxalis/model/sagas/dataset_saga.ts b/frontend/javascripts/oxalis/model/sagas/dataset_saga.ts index 628314d3830..63c07032184 100644 --- a/frontend/javascripts/oxalis/model/sagas/dataset_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/dataset_saga.ts @@ -9,7 +9,7 @@ import { getEnabledLayers, getLayerByName, getMaybeSegmentIndexAvailability, - getResolutionInfo, + getMagInfo, getTransformsForLayer, invertAndTranspose, isLayerVisible, @@ -111,7 +111,7 @@ export function* watchZ1Downsampling(): Saga { // is no appropriate mag for that layer. break; } - const resolutionInfo = getResolutionInfo(dataLayer.resolutions); + const resolutionInfo = getMagInfo(dataLayer.resolutions); const bestExistingIndex = resolutionInfo.getFinestMagIndex(); const currentIndex = resolutionInfo.getIndexByMag(currentRes); if (currentIndex <= bestExistingIndex) { diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index 624e99028b1..f4849c6eca8 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -10,7 +10,7 @@ import type { ActionPattern } from "redux-saga/effects"; import Store from "oxalis/store"; import { - getResolutionInfo, + getMagInfo, getMappingInfo, getVisibleSegmentationLayer, getSegmentationLayerByName, @@ -240,7 +240,7 @@ function* getInfoForMeshLoading( zoomStep: number; resolutionInfo: MagInfo; }> { - const resolutionInfo = getResolutionInfo(layer.resolutions); + const resolutionInfo = getMagInfo(layer.resolutions); const preferredZoomStep = meshExtraInfo.preferredQuality != null ? meshExtraInfo.preferredQuality diff --git a/frontend/javascripts/oxalis/model/sagas/min_cut_saga.ts b/frontend/javascripts/oxalis/model/sagas/min_cut_saga.ts index 252400ae50f..e474de2f17f 100644 --- a/frontend/javascripts/oxalis/model/sagas/min_cut_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/min_cut_saga.ts @@ -12,7 +12,7 @@ import { getActiveSegmentationTracingLayer, } from "oxalis/model/accessors/volumetracing_accessor"; import { finishAnnotationStrokeAction } from "oxalis/model/actions/volumetracing_actions"; -import { getResolutionInfo } from "oxalis/model/accessors/dataset_accessor"; +import { getMagInfo } from "oxalis/model/accessors/dataset_accessor"; import { takeEveryUnlessBusy } from "oxalis/model/sagas/saga_helpers"; import BoundingBox from "oxalis/model/bucket_data_handling/bounding_box"; import Toast from "libs/toast"; @@ -284,7 +284,7 @@ function* performMinCut(action: Action): Saga { return; } - const resolutionInfo = getResolutionInfo(volumeTracingLayer.resolutions); + const resolutionInfo = getMagInfo(volumeTracingLayer.resolutions); const appropriateResolutionInfos = selectAppropriateResolutions(boundingBoxMag1, resolutionInfo); if (appropriateResolutionInfos.length === 0) { diff --git a/frontend/javascripts/oxalis/model/sagas/prefetch_saga.ts b/frontend/javascripts/oxalis/model/sagas/prefetch_saga.ts index 016340c32a4..56fae8eaa60 100644 --- a/frontend/javascripts/oxalis/model/sagas/prefetch_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/prefetch_saga.ts @@ -15,7 +15,7 @@ import { getActiveMagIndexForLayer, getAreasFromState, } from "oxalis/model/accessors/flycam_accessor"; -import { isLayerVisible, getResolutionInfo } from "oxalis/model/accessors/dataset_accessor"; +import { isLayerVisible, getMagInfo } from "oxalis/model/accessors/dataset_accessor"; import type DataLayer from "oxalis/model/data_layer"; import { Model } from "oxalis/singletons"; import type { Vector3 } from "oxalis/constants"; @@ -103,7 +103,7 @@ export function* prefetchForPlaneMode( ): Saga { const position = yield* select((state) => getPosition(state.flycam)); const zoomStep = yield* select((state) => getActiveMagIndexForLayer(state, layer.name)); - const resolutionInfo = getResolutionInfo(layer.resolutions); + const resolutionInfo = getMagInfo(layer.resolutions); const activePlane = yield* select((state) => state.viewModeData.plane.activeViewport); const tracingTypes = yield* select(getTracingTypes); const additionalCoordinates = yield* select((state) => state.flycam.additionalCoordinates); @@ -171,7 +171,7 @@ export function* prefetchForArbitraryMode( const matrix = yield* select((state) => state.flycam.currentMatrix); const zoomStep = yield* select((state) => getActiveMagIndexForLayer(state, layer.name)); const tracingTypes = yield* select(getTracingTypes); - const resolutionInfo = getResolutionInfo(layer.resolutions); + const resolutionInfo = getMagInfo(layer.resolutions); const resolutions = resolutionInfo.getDenseMags(); const layerRenderingManager = yield* call( [Model, Model.getLayerRenderingManagerByName], diff --git a/frontend/javascripts/oxalis/model/sagas/proofread_saga.ts b/frontend/javascripts/oxalis/model/sagas/proofread_saga.ts index dc0ecc2c14c..cfb66c3a355 100644 --- a/frontend/javascripts/oxalis/model/sagas/proofread_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/proofread_saga.ts @@ -54,7 +54,7 @@ import { import { getLayerByName, getMappingInfo, - getResolutionInfo, + getMagInfo, } from "oxalis/model/accessors/dataset_accessor"; import { type NeighborInfo, @@ -1036,7 +1036,7 @@ function* prepareSplitOrMerge(isSkeletonProofreading: boolean): Saga getCurrentResolution(state, volumeTracingLayer.name)); const agglomerateFileMag = isSkeletonProofreading diff --git a/frontend/javascripts/oxalis/model/sagas/quick_select_heuristic_saga.ts b/frontend/javascripts/oxalis/model/sagas/quick_select_heuristic_saga.ts index e937a6b23c1..e2d6782adf4 100644 --- a/frontend/javascripts/oxalis/model/sagas/quick_select_heuristic_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/quick_select_heuristic_saga.ts @@ -56,7 +56,7 @@ import { getDefaultValueRangeOfLayer, getEnabledColorLayers, getLayerBoundingBox, - getResolutionInfo, + getMagInfo, getTransformsForLayer, } from "../accessors/dataset_accessor"; import Dimensions, { type DimensionIndices } from "../dimensions"; @@ -145,7 +145,7 @@ export function* prepareQuickSelect( const requestedZoomStep = yield* select((store) => getActiveMagIndexForLayer(store, colorLayer.name), ); - const resolutionInfo = getResolutionInfo( + const resolutionInfo = getMagInfo( // Ensure that a magnification is used which exists in the color layer as well as the // target segmentation layer. _.intersectionBy(colorLayer.resolutions, volumeLayer.resolutions, (mag) => mag.join("-")), diff --git a/frontend/javascripts/oxalis/model/sagas/save_saga.ts b/frontend/javascripts/oxalis/model/sagas/save_saga.ts index 7301b806bbf..e9e09a12a32 100644 --- a/frontend/javascripts/oxalis/model/sagas/save_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/save_saga.ts @@ -9,7 +9,7 @@ import window, { alert, document, location } from "libs/window"; import _ from "lodash"; import messages from "messages"; import { ControlModeEnum } from "oxalis/constants"; -import { getResolutionInfo } from "oxalis/model/accessors/dataset_accessor"; +import { getMagInfo } from "oxalis/model/accessors/dataset_accessor"; import { selectQueue } from "oxalis/model/accessors/save_accessor"; import { selectTracing } from "oxalis/model/accessors/tracing_accessor"; import { getVolumeTracingById } from "oxalis/model/accessors/volumetracing_accessor"; @@ -288,7 +288,7 @@ export function* sendRequestToServer( function* markBucketsAsNotDirty(saveQueue: Array, tracingId: string) { const segmentationLayer = Model.getSegmentationTracingLayer(tracingId); - const segmentationResolutionInfo = yield* call(getResolutionInfo, segmentationLayer.resolutions); + const segmentationResolutionInfo = yield* call(getMagInfo, segmentationLayer.resolutions); if (segmentationLayer != null) { for (const saveEntry of saveQueue) { diff --git a/frontend/javascripts/oxalis/model/sagas/volume/helpers.ts b/frontend/javascripts/oxalis/model/sagas/volume/helpers.ts index 7333db24e0e..8a4d7f5ab89 100644 --- a/frontend/javascripts/oxalis/model/sagas/volume/helpers.ts +++ b/frontend/javascripts/oxalis/model/sagas/volume/helpers.ts @@ -9,7 +9,7 @@ import Constants, { type Vector2, type Vector3, } from "oxalis/constants"; -import { getDatasetBoundingBox, getResolutionInfo } from "oxalis/model/accessors/dataset_accessor"; +import { getDatasetBoundingBox, getMagInfo } from "oxalis/model/accessors/dataset_accessor"; import BoundingBox from "oxalis/model/bucket_data_handling/bounding_box"; import type { Saga } from "oxalis/model/sagas/effect-generators"; import { select } from "oxalis/model/sagas/effect-generators"; @@ -185,7 +185,7 @@ export function* labelWithVoxelBuffer2D( const { cube } = segmentationLayer; const currentLabeledVoxelMap: LabeledVoxelsMap = new Map(); const dimensionIndices = Dimensions.getIndices(viewport); - const resolutionInfo = yield* call(getResolutionInfo, segmentationLayer.resolutions); + const resolutionInfo = yield* call(getMagInfo, segmentationLayer.resolutions); const labeledResolution = resolutionInfo.getMagByIndexOrThrow(labeledZoomStep); const get3DCoordinateFromLocal2D = ([x, y]: Vector2) => diff --git a/frontend/javascripts/oxalis/model/sagas/volume/volume_interpolation_saga.ts b/frontend/javascripts/oxalis/model/sagas/volume/volume_interpolation_saga.ts index fb29d27210a..6a123d96060 100644 --- a/frontend/javascripts/oxalis/model/sagas/volume/volume_interpolation_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/volume/volume_interpolation_saga.ts @@ -13,7 +13,7 @@ import { type Vector3, } from "oxalis/constants"; import { reuseInstanceOnEquality } from "oxalis/model/accessors/accessor_helpers"; -import { getResolutionInfo } from "oxalis/model/accessors/dataset_accessor"; +import { getMagInfo } from "oxalis/model/accessors/dataset_accessor"; import { getActiveMagIndexForLayer, getFlooredPosition, @@ -84,7 +84,7 @@ function _getInterpolationInfo(state: OxalisState, explanationPrefix: string) { const segmentationLayer = Model.getSegmentationTracingLayer(volumeTracing.tracingId); const requestedZoomStep = getActiveMagIndexForLayer(state, segmentationLayer.name); - const resolutionInfo = getResolutionInfo(segmentationLayer.resolutions); + const resolutionInfo = getMagInfo(segmentationLayer.resolutions); const labeledZoomStep = resolutionInfo.getClosestExistingIndex(requestedZoomStep); const labeledResolution = resolutionInfo.getMagByIndexOrThrow(labeledZoomStep); @@ -104,7 +104,7 @@ function _getInterpolationInfo(state: OxalisState, explanationPrefix: string) { // is done while respecting how the coordinates are clipped due to that resolution. // For example, in mag 8-8-2, the z distance needs to be divided by two, since it is measured // in global coordinates. - const adapt = (vec: Vector3) => V3.roundElementToResolution(vec, labeledResolution, thirdDim); + const adapt = (vec: Vector3) => V3.roundElementToMag(vec, labeledResolution, thirdDim); const signedInterpolationDepth = Math.floor( V3.sub(adapt(position), adapt(previousCentroid))[thirdDim] / labeledResolution[thirdDim], ); diff --git a/frontend/javascripts/oxalis/model/sagas/volumetracing_saga.tsx b/frontend/javascripts/oxalis/model/sagas/volumetracing_saga.tsx index e68f02071ec..48179962aa2 100644 --- a/frontend/javascripts/oxalis/model/sagas/volumetracing_saga.tsx +++ b/frontend/javascripts/oxalis/model/sagas/volumetracing_saga.tsx @@ -29,7 +29,7 @@ import { CONTOUR_COLOR_DELETE, CONTOUR_COLOR_NORMAL } from "oxalis/geometries/he import { getDatasetBoundingBox, getMaximumSegmentIdForLayer, - getResolutionInfo, + getMagInfo, } from "oxalis/model/accessors/dataset_accessor"; import { getPosition, @@ -431,7 +431,7 @@ export function* floodFill(): Saga { const requestedZoomStep = yield* select((state) => getActiveMagIndexForLayer(state, segmentationLayer.name), ); - const resolutionInfo = yield* call(getResolutionInfo, segmentationLayer.resolutions); + const resolutionInfo = yield* call(getMagInfo, segmentationLayer.resolutions); const labeledZoomStep = resolutionInfo.getClosestExistingIndex(requestedZoomStep); const additionalCoordinates = yield* select((state) => state.flycam.additionalCoordinates); const oldSegmentIdAtSeed = cube.getDataValue( diff --git a/frontend/javascripts/oxalis/model/scaleinfo.ts b/frontend/javascripts/oxalis/model/scaleinfo.ts index 44365fdaa1b..9e83455cf08 100644 --- a/frontend/javascripts/oxalis/model/scaleinfo.ts +++ b/frontend/javascripts/oxalis/model/scaleinfo.ts @@ -2,7 +2,7 @@ import type { Vector3 } from "oxalis/constants"; import type { VoxelSize } from "types/api_flow_types"; export function getBaseVoxelInUnit(voxelSizeFactor: Vector3): number { - // base voxel should be a cube with highest resolution + // base voxel should be a cube with highest mag return Math.min(...voxelSizeFactor); } @@ -24,7 +24,7 @@ export function voxelToVolumeInUnit( export function getBaseVoxelFactorsInUnit(voxelSize: VoxelSize): Vector3 { const scaleFactor = voxelSize.factor; - // base voxel should be a cube with highest resolution + // base voxel should be a cube with highest mag const baseVoxel = getBaseVoxelInUnit(scaleFactor); // scale factor to calculate the voxels in a certain // dimension from baseVoxels diff --git a/frontend/javascripts/oxalis/model_initialization.ts b/frontend/javascripts/oxalis/model_initialization.ts index 811e6ba3c9b..7f0c582a2f2 100644 --- a/frontend/javascripts/oxalis/model_initialization.ts +++ b/frontend/javascripts/oxalis/model_initialization.ts @@ -505,8 +505,8 @@ function getMergedDataLayersFromDatasetAndVolumeTracings( const boundingBox = getDatasetBoundingBox(dataset).asServerBoundingBox(); const resolutions = tracing.mags || []; const tracingHasResolutionList = resolutions.length > 0; - // Legacy tracings don't have the `tracing.resolutions` property - // since they were created before WK started to maintain multiple resolution + // Legacy tracings don't have the `tracing.mags` property // TODO_c check + // since they were created before WK started to maintain multiple magnifications // in volume annotations. Therefore, this code falls back to mag (1, 1, 1) for // that case. const tracingResolutions: Vector3[] = tracingHasResolutionList diff --git a/frontend/javascripts/oxalis/store.ts b/frontend/javascripts/oxalis/store.ts index dc4847893e1..4fea149aee7 100644 --- a/frontend/javascripts/oxalis/store.ts +++ b/frontend/javascripts/oxalis/store.ts @@ -89,7 +89,7 @@ export type MutableNode = { rotation: Vector3; bitDepth: number; viewport: number; - resolution: number; + resolution: number; // TODO_c maybe change? is this mag? radius: number; timestamp: number; interpolation: boolean; diff --git a/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx b/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx index c973b0950c7..c827907339a 100644 --- a/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx +++ b/frontend/javascripts/oxalis/view/action-bar/create_animation_modal.tsx @@ -10,7 +10,7 @@ import { getColorLayers, getEffectiveIntensityRange, getLayerByName, - getResolutionInfo, + getMagInfo, is2dDataset, } from "oxalis/model/accessors/dataset_accessor"; import { @@ -205,7 +205,7 @@ function CreateAnimationModal(props: Props) { const layer = getLayerByName(state.dataset, layerName) as APISegmentationLayer; const fullLayerName = layer.fallbackLayerInfo?.name || layerName; - const adhocMagIndex = getResolutionInfo(layer.resolutions).getClosestExistingIndex( + const adhocMagIndex = getMagInfo(layer.resolutions).getClosestExistingIndex( preferredQualityForMeshAdHocComputation, ); const adhocMag = layer.resolutions[adhocMagIndex]; diff --git a/frontend/javascripts/oxalis/view/action-bar/download_modal_view.tsx b/frontend/javascripts/oxalis/view/action-bar/download_modal_view.tsx index 7e364fa5edd..ce921bfad1d 100644 --- a/frontend/javascripts/oxalis/view/action-bar/download_modal_view.tsx +++ b/frontend/javascripts/oxalis/view/action-bar/download_modal_view.tsx @@ -45,7 +45,7 @@ import { getByteCountFromLayer, getDataLayers, getLayerByName, - getResolutionInfo, + getMagInfo, } from "oxalis/model/accessors/dataset_accessor"; import { useSelector } from "react-redux"; import type { HybridTracing, OxalisState, UserBoundingBox } from "oxalis/store"; @@ -307,7 +307,7 @@ function _DownloadModalView({ const selectedLayer = getLayerByName(dataset, selectedLayerName); const selectedLayerInfos = getExportLayerInfos(selectedLayer, tracing); - const selectedLayerResolutionInfo = getResolutionInfo(selectedLayer.resolutions); + const selectedLayerResolutionInfo = getMagInfo(selectedLayer.resolutions); const userBoundingBoxes = [ ...rawUserBoundingBoxes, diff --git a/frontend/javascripts/oxalis/view/context_menu.tsx b/frontend/javascripts/oxalis/view/context_menu.tsx index 0511ab4f90b..7d497c373e5 100644 --- a/frontend/javascripts/oxalis/view/context_menu.tsx +++ b/frontend/javascripts/oxalis/view/context_menu.tsx @@ -79,7 +79,7 @@ import { import { getVisibleSegmentationLayer, getMappingInfo, - getResolutionInfo, + getMagInfo, getMaybeSegmentIndexAvailability, } from "oxalis/model/accessors/dataset_accessor"; import { @@ -1459,7 +1459,7 @@ function ContextMenuInner(propsWithInputRef: Props) { const tracingId = volumeTracing?.tracingId; const additionalCoordinates = flycam.additionalCoordinates; const requestUrl = getVolumeRequestUrl(dataset, tracing, tracingId, visibleSegmentationLayer); - const magInfo = getResolutionInfo(visibleSegmentationLayer.resolutions); + const magInfo = getMagInfo(visibleSegmentationLayer.resolutions); const layersFinestResolution = magInfo.getFinestMag(); const voxelSize = dataset.dataSource.scale; diff --git a/frontend/javascripts/oxalis/view/jobs/train_ai_model.tsx b/frontend/javascripts/oxalis/view/jobs/train_ai_model.tsx index 9e3528aae1c..70b2c9a11f1 100644 --- a/frontend/javascripts/oxalis/view/jobs/train_ai_model.tsx +++ b/frontend/javascripts/oxalis/view/jobs/train_ai_model.tsx @@ -17,7 +17,7 @@ import type { HybridTracing, OxalisState, UserBoundingBox, VolumeTracing } from import { getSomeTracing } from "oxalis/model/accessors/tracing_accessor"; import { getColorLayers, - getResolutionInfo, + getMagInfo, getSegmentationLayers, } from "oxalis/model/accessors/dataset_accessor"; import { @@ -128,7 +128,7 @@ export function TrainAiModelFromAnnotationTab({ onClose }: { onClose: () => void const getMagForSegmentationLayer = async (_annotationId: string, layerName: string) => { const segmentationLayer = getSegmentationLayerByHumanReadableName(dataset, tracing, layerName); - return getResolutionInfo(segmentationLayer.resolutions).getFinestMag(); + return getMagInfo(segmentationLayer.resolutions).getFinestMag(); }; const userBoundingBoxes = getSomeTracing(tracing).userBoundingBoxes; diff --git a/frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx b/frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx index d34ce424c14..30cf80483aa 100644 --- a/frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx +++ b/frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx @@ -59,9 +59,9 @@ import { getElementClass, isColorLayer as getIsColorLayer, getLayerByName, - getResolutionInfo, + getMagInfo, getTransformsForLayerOrNull, - getWidestResolutions, + getWidestMags, getLayerBoundingBox, getTransformsForLayer, hasDatasetTransforms, @@ -305,7 +305,7 @@ function LayerInfoIconWithTooltip({ }: { layer: APIDataLayer; dataset: APIDataset }) { const renderTooltipContent = useCallback(() => { const elementClass = getElementClass(dataset, layer.name); - const resolutionInfo = getResolutionInfo(layer.resolutions); + const resolutionInfo = getMagInfo(layer.resolutions); const resolutions = resolutionInfo.getMagList(); return (
@@ -1096,7 +1096,7 @@ class DatasetSettings extends React.PureComponent { ? fallbackLayerInfo.resolutions : // This is only a heuristic. At some point, user configuration // might make sense here. - getWidestResolutions(this.props.dataset); + getWidestMags(this.props.dataset); const getMaxDim = (resolution: Vector3) => Math.max(...resolution); diff --git a/frontend/javascripts/oxalis/view/left-border-tabs/modals/add_volume_layer_modal.tsx b/frontend/javascripts/oxalis/view/left-border-tabs/modals/add_volume_layer_modal.tsx index c8bf5c178d8..565b8ace677 100644 --- a/frontend/javascripts/oxalis/view/left-border-tabs/modals/add_volume_layer_modal.tsx +++ b/frontend/javascripts/oxalis/view/left-border-tabs/modals/add_volume_layer_modal.tsx @@ -7,16 +7,16 @@ import type { APIDataset, APISegmentationLayer } from "types/api_flow_types"; import { AsyncButton } from "components/async_clickables"; import { NewVolumeLayerSelection, - RestrictResolutionSlider, + RestrictMagnificationSlider, } from "dashboard/advanced_dataset/create_explorative_modal"; import Store, { type Tracing } from "oxalis/store"; import { addAnnotationLayer } from "admin/admin_rest_api"; import { - getSomeResolutionInfoForDataset, + getSomeMagInfoForDataset, getLayerByName, getMappingInfo, getSegmentationLayers, - getResolutionInfo, + getMagInfo, } from "oxalis/model/accessors/dataset_accessor"; import { getAllReadableLayerNames, @@ -131,8 +131,8 @@ export default function AddVolumeLayerModal({ const resolutionInfo = selectedSegmentationLayer == null - ? getSomeResolutionInfoForDataset(dataset) - : getResolutionInfo(selectedSegmentationLayer.resolutions); + ? getSomeMagInfoForDataset(dataset) + : getMagInfo(selectedSegmentationLayer.resolutions); const [resolutionIndices, setResolutionIndices] = useState([0, 10000]); const handleSetNewLayerName = (evt: React.ChangeEvent) => @@ -166,7 +166,7 @@ export default function AddVolumeLayerModal({ typ: "Volume", name: newLayerName, fallbackLayerName: undefined, - resolutionRestrictions: { + magRestrictions: { min: minResolutionAllowed, max: maxResolutionAllowed, }, @@ -193,7 +193,7 @@ export default function AddVolumeLayerModal({ typ: "Volume", name: newLayerName, fallbackLayerName, - resolutionRestrictions: { + magRestrictions: { min: minResolutionAllowed, max: maxResolutionAllowed, }, @@ -233,11 +233,11 @@ export default function AddVolumeLayerModal({ disableLayerSelection={disableLayerSelection ?? false} /> ) : null} - }> diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/dataset_info_tab_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/dataset_info_tab_view.tsx index 8baf46f4507..a1ac6eafe92 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/dataset_info_tab_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/dataset_info_tab_view.tsx @@ -10,7 +10,7 @@ import { ControlModeEnum } from "oxalis/constants"; import { formatScale } from "libs/format_utils"; import { getDatasetExtentAsString, - getResolutionUnion, + getMagnificationUnion, } from "oxalis/model/accessors/dataset_accessor"; import { getActiveResolutionInfo } from "oxalis/model/accessors/flycam_accessor"; import { @@ -530,7 +530,7 @@ export class DatasetInfoTabView extends React.PureComponent { renderResolutionsTooltip = () => { const { dataset, annotation, activeResolutionInfo } = this.props; const { activeMagOfEnabledLayers } = activeResolutionInfo; - const resolutionUnion = getResolutionUnion(dataset); + const resolutionUnion = getMagnificationUnion(dataset); return (
Rendered magnification per layer: diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_statistics_modal.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_statistics_modal.tsx index ccbe94e8a52..04a82101df0 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_statistics_modal.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segment_statistics_modal.tsx @@ -4,7 +4,7 @@ import saveAs from "file-saver"; import { formatNumberToVolume } from "libs/format_utils"; import { useFetch } from "libs/react_helpers"; import { LongUnitToShortUnitMap, type Vector3 } from "oxalis/constants"; -import { getMappingInfo, getResolutionInfo } from "oxalis/model/accessors/dataset_accessor"; +import { getMappingInfo, getMagInfo } from "oxalis/model/accessors/dataset_accessor"; import type { OxalisState, Segment } from "oxalis/store"; import { type SegmentHierarchyNode, @@ -105,7 +105,7 @@ export function SegmentStatisticsModal({ groupTree, }: Props) { const { dataset, tracing, temporaryConfiguration } = useSelector((state: OxalisState) => state); - const magInfo = getResolutionInfo(visibleSegmentationLayer.resolutions); + const magInfo = getMagInfo(visibleSegmentationLayer.resolutions); const layersFinestResolution = magInfo.getFinestMag(); const voxelSize = dataset.dataSource.scale; // Omit checking that all prerequisites for segment stats (such as a segment index) are diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx index c0a5838aa4d..8d314383399 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx @@ -43,7 +43,7 @@ import { EMPTY_OBJECT, MappingStatusEnum } from "oxalis/constants"; import { getMappingInfo, getMaybeSegmentIndexAvailability, - getResolutionInfoOfVisibleSegmentationLayer, + getMagInfoOfVisibleSegmentationLayer, getVisibleSegmentationLayer, } from "oxalis/model/accessors/dataset_accessor"; import { getAdditionalCoordinatesAsString } from "oxalis/model/accessors/flycam_accessor"; @@ -220,7 +220,7 @@ const mapStateToProps = (state: OxalisState): StateProps => { state.temporaryConfiguration.preferredQualityForMeshPrecomputation, preferredQualityForMeshAdHocComputation: state.temporaryConfiguration.preferredQualityForMeshAdHocComputation, - resolutionInfoOfVisibleSegmentationLayer: getResolutionInfoOfVisibleSegmentationLayer(state), + resolutionInfoOfVisibleSegmentationLayer: getMagInfoOfVisibleSegmentationLayer(state), }; }; diff --git a/frontend/javascripts/test/model/model_resolutions.spec.ts b/frontend/javascripts/test/model/model_resolutions.spec.ts index 22327ac02a6..51cc9fe66ab 100644 --- a/frontend/javascripts/test/model/model_resolutions.spec.ts +++ b/frontend/javascripts/test/model/model_resolutions.spec.ts @@ -1,6 +1,6 @@ import "test/mocks/lz4"; import test from "ava"; -import { getResolutionUnion } from "oxalis/model/accessors/dataset_accessor"; +import { getMagnificationUnion } from "oxalis/model/accessors/dataset_accessor"; import type { Vector3 } from "oxalis/constants"; import type { APIDataset } from "types/api_flow_types"; import { convertToDenseMag } from "oxalis/model/helpers/mag_info"; @@ -67,7 +67,7 @@ test("Test empty getResolutionUnion", (t) => { }, } as any as APIDataset; const expectedResolutions: Array = []; - const union = getResolutionUnion(dataset); + const union = getMagnificationUnion(dataset); t.deepEqual(union, expectedResolutions); }); test("Test getResolutionUnion", (t) => { @@ -93,7 +93,7 @@ test("Test getResolutionUnion", (t) => { }, } as any as APIDataset; const expectedResolutions = [[[2, 2, 1]], [[4, 4, 1]], [[8, 8, 1]], [[16, 16, 2]], [[32, 32, 4]]]; - const union = getResolutionUnion(dataset); + const union = getMagnificationUnion(dataset); t.deepEqual(union, expectedResolutions); }); @@ -129,6 +129,6 @@ test("Test getResolutionUnion with mixed mags", (t) => { [[16, 16, 2]], [[32, 32, 4]], ]; - const union = getResolutionUnion(dataset); + const union = getMagnificationUnion(dataset); t.deepEqual(union, expectedResolutions); }); From f21b745fb4bca8755ec0561b211ada85e5b09ca8 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Sat, 12 Oct 2024 16:40:46 +0200 Subject: [PATCH 06/37] rename in frontend code --- .../admin/voxelytics/ai_model_list_view.tsx | 7 +- frontend/javascripts/oxalis/api/api_latest.ts | 6 +- .../controller/combinations/move_handlers.ts | 4 +- .../oxalis/model/accessors/flycam_accessor.ts | 67 ++++++------- .../oxalis/model/accessors/tool_accessor.ts | 6 +- .../model/accessors/volumetracing_accessor.ts | 38 +++---- .../bucket_data_handling/bounding_box.ts | 14 +-- .../model/bucket_data_handling/bucket.ts | 10 +- .../flight_bucket_picker.ts | 12 +-- .../oblique_bucket_picker.ts | 19 ++-- .../bucket_data_handling/bucket_traversals.ts | 8 +- .../model/bucket_data_handling/data_cube.ts | 34 +++---- .../bucket_data_handling/wkstore_adapter.ts | 2 +- .../oxalis/model/helpers/mag_info.ts | 10 +- .../model/helpers/position_converter.ts | 99 ++++++++----------- .../oxalis/model/sagas/clip_histogram_saga.ts | 4 +- .../oxalis/model/sagas/dataset_saga.ts | 4 +- .../oxalis/model/sagas/mesh_saga.ts | 24 +++-- .../oxalis/model/sagas/min_cut_saga.ts | 20 ++-- .../oxalis/model/sagas/proofread_saga.ts | 14 +-- .../sagas/quick_select_heuristic_saga.ts | 16 +-- .../model/sagas/quick_select_ml_saga.ts | 2 +- .../oxalis/model/sagas/volume/helpers.ts | 24 ++--- .../oxalis/model/sagas/volumetracing_saga.tsx | 16 +-- .../volume_annotation_sampling.ts | 36 +++---- .../oxalis/model/volumetracing/volumelayer.ts | 55 +++++------ .../view/action-bar/download_modal_view.tsx | 4 +- .../view/action-bar/starting_job_modals.tsx | 10 +- .../oxalis/view/action-bar/toolbar_view.tsx | 8 +- .../oxalis/view/jobs/train_ai_model.tsx | 8 +- .../left-border-tabs/layer_settings_tab.tsx | 10 +- .../modals/downsample_volume_modal.tsx | 20 ++-- .../dataset_info_tab_view.tsx | 14 +-- .../segments_tab/segments_view.tsx | 10 +- .../javascripts/oxalis/view/statusbar.tsx | 13 ++- .../test/fixtures/volumetracing_object.ts | 2 +- .../test/model/flycam_accessors.spec.ts | 2 +- 37 files changed, 311 insertions(+), 341 deletions(-) diff --git a/frontend/javascripts/admin/voxelytics/ai_model_list_view.tsx b/frontend/javascripts/admin/voxelytics/ai_model_list_view.tsx index 800b9ac9cd6..a0b94e6c23a 100644 --- a/frontend/javascripts/admin/voxelytics/ai_model_list_view.tsx +++ b/frontend/javascripts/admin/voxelytics/ai_model_list_view.tsx @@ -122,7 +122,12 @@ function TrainNewAiJobModal({ onClose }: { onClose: () => void }) { throw new Error("Cannot find annotation for specified id."); } - const { annotation, dataset, volumeTracings, volumeTracingResolutions } = annotationWithDataset; + const { + annotation, + dataset, + volumeTracings, + volumeTracingMags: volumeTracingResolutions, + } = annotationWithDataset; let annotationLayer = annotation.annotationLayers.find((l) => l.name === layerName); if (annotationLayer != null) { diff --git a/frontend/javascripts/oxalis/api/api_latest.ts b/frontend/javascripts/oxalis/api/api_latest.ts index cab50b1d5a9..13d8cb52713 100644 --- a/frontend/javascripts/oxalis/api/api_latest.ts +++ b/frontend/javascripts/oxalis/api/api_latest.ts @@ -31,7 +31,7 @@ import { import { bucketPositionToGlobalAddress, globalPositionToBucketPosition, - scaleGlobalPositionWithResolution, + scaleGlobalPositionWithMagnification, zoomedAddressToZoomedPosition, } from "oxalis/model/helpers/position_converter"; import { @@ -2038,9 +2038,9 @@ class DataApi { const resolution = magnifications[zoomStep]; // All calculations in this method are in zoomStep-space, so in global coordinates which are divided // by the mag - const topLeft = scaleGlobalPositionWithResolution(bbox.min, resolution); + const topLeft = scaleGlobalPositionWithMagnification(bbox.min, resolution); // Ceil the bounding box bottom right instead of flooring, because it is exclusive - const bottomRight = scaleGlobalPositionWithResolution(bbox.max, resolution, true); + const bottomRight = scaleGlobalPositionWithMagnification(bbox.max, resolution, true); const extent: Vector3 = V3.sub(bottomRight, topLeft); const [TypedArrayClass, channelCount] = getConstructorForElementClass(elementClass); const result = new TypedArrayClass(channelCount * extent[0] * extent[1] * extent[2]); diff --git a/frontend/javascripts/oxalis/controller/combinations/move_handlers.ts b/frontend/javascripts/oxalis/controller/combinations/move_handlers.ts index 4ab6334c70f..60727fe4ba8 100644 --- a/frontend/javascripts/oxalis/controller/combinations/move_handlers.ts +++ b/frontend/javascripts/oxalis/controller/combinations/move_handlers.ts @@ -10,7 +10,7 @@ import { zoomByDeltaAction, } from "oxalis/model/actions/flycam_actions"; import { setViewportAction, zoomTDViewAction } from "oxalis/model/actions/view_mode_actions"; -import { getActiveResolutionInfo } from "oxalis/model/accessors/flycam_accessor"; +import { getActiveMagInfo } from "oxalis/model/accessors/flycam_accessor"; import { setMousePositionAction } from "oxalis/model/actions/volumetracing_actions"; export function setMousePosition(position: Point2 | null | undefined): void { @@ -49,7 +49,7 @@ export const moveW = (deltaW: number, oneSlide: boolean): void => { // The following logic might not always make sense when having layers // that are transformed each. Todo: Rethink / adapt the logic once // problems occur. Tracked in #6926. - const { representativeResolution } = getActiveResolutionInfo(Store.getState()); + const { representativeResolution } = getActiveMagInfo(Store.getState()); const wDim = Dimensions.getIndices(activeViewport)[2]; const wStep = (representativeResolution || [1, 1, 1])[wDim]; Store.dispatch( diff --git a/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts b/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts index 1407dc874da..ac78958929c 100644 --- a/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts +++ b/frontend/javascripts/oxalis/model/accessors/flycam_accessor.ts @@ -43,7 +43,7 @@ export const ZOOM_STEP_INTERVAL = 1.1; function calculateTotalBucketCountForZoomLevel( viewMode: ViewMode, loadingStrategy: LoadingStrategy, - resolutions: Array, + mags: Array, logZoomStep: number, zoomFactor: number, viewportRects: OrthoViewRects, @@ -67,7 +67,7 @@ function calculateTotalBucketCountForZoomLevel( determineBucketsForOblique( viewMode, loadingStrategy, - resolutions, + mags, position, enqueueFunction, matrix, @@ -77,7 +77,7 @@ function calculateTotalBucketCountForZoomLevel( ); } else if (viewMode === constants.MODE_ARBITRARY) { determineBucketsForFlight( - resolutions, + mags, position, sphericalCapRadius, enqueueFunction, @@ -89,7 +89,7 @@ function calculateTotalBucketCountForZoomLevel( determineBucketsForOblique( viewMode, loadingStrategy, - resolutions, + mags, position, enqueueFunction, matrix, @@ -109,15 +109,15 @@ function calculateTotalBucketCountForZoomLevel( // Example: // The function might return 1.3 for resolutionIndex 0, which means that until a zoom value of 1.3 // the first magnification can still be rendered. -// For resolutionIndex 1, the function might return 1.5 etc. +// For magIndex 1, the function might return 1.5 etc. // These values are used to determine the appropriate magnification for a given zoom value (e.g., a zoom value of 1.4 // would require the second magnification). // This function is only exported for testing purposes -export function _getMaximumZoomForAllResolutions( +export function _getMaximumZoomForAllMags( viewMode: ViewMode, loadingStrategy: LoadingStrategy, voxelSizeFactor: Vector3, - resolutions: Array, + mags: Array, viewportRects: OrthoViewRects, maximumCapacity: number, layerMatrix: Matrix4x4, @@ -159,15 +159,12 @@ export function _getMaximumZoomForAllResolutions( throw new Error("Internal error: Invalid maximum capacity provided."); } - while ( - currentIterationCount < maximumIterationCount && - currentResolutionIndex < resolutions.length - ) { + while (currentIterationCount < maximumIterationCount && currentResolutionIndex < mags.length) { const nextZoomValue = currentMaxZoomValue * ZOOM_STEP_INTERVAL; const nextCapacity = calculateTotalBucketCountForZoomLevel( viewMode, loadingStrategy, - resolutions, + mags, currentResolutionIndex, nextZoomValue, viewportRects, @@ -192,7 +189,7 @@ export function _getMaximumZoomForAllResolutions( // todo: make this cleaner. since the maximum zoom depends on the layer name and the right matrix, // a memoization cache size of one doesn't work anymore. move cache to store and update explicitly? -const perLayerFnCache: Map = new Map(); +const perLayerFnCache: Map = new Map(); // Only exported for testing. export const _getDummyFlycamMatrix = memoizeOne((scale: Vector3) => { @@ -214,10 +211,7 @@ export function getMoveOffset3d(state: OxalisState, timeFactor: number) { return (moveValue3d * timeFactor) / baseVoxel / constants.FPS; } -function getMaximumZoomForAllResolutionsFromStore( - state: OxalisState, - layerName: string, -): Array { +function getMaximumZoomForAllMagsFromStore(state: OxalisState, layerName: string): Array { const { viewMode } = state.temporaryConfiguration; const layer = getLayerByName(state.dataset, layerName); @@ -231,7 +225,7 @@ function getMaximumZoomForAllResolutionsFromStore( let fn = perLayerFnCache.get(layerName); if (fn == null) { - fn = memoizeOne(_getMaximumZoomForAllResolutions); + fn = memoizeOne(_getMaximumZoomForAllMags); perLayerFnCache.set(layerName, fn); } @@ -323,10 +317,10 @@ function _getActiveMagIndicesForLayers(state: OxalisState): { [layerName: string const magIndices: { [layerName: string]: number } = {}; for (const layer of getDataLayers(state.dataset)) { - const maximumZoomSteps = getMaximumZoomForAllResolutionsFromStore(state, layer.name); + const maximumZoomSteps = getMaximumZoomForAllMagsFromStore(state, layer.name); const maxLogZoomStep = Math.log2(getMaxZoomStep(state.dataset)); - // Linearly search for the resolution index, for which the zoomFactor + // Linearly search for the mag index, for which the zoomFactor // is acceptable. const zoomStep = _.findIndex( maximumZoomSteps, @@ -354,14 +348,11 @@ export function getActiveMagIndexForLayer(state: OxalisState, layerName: string) } /* - Returns the resolution that is supposed to be rendered for the given layer. The return resolution + Returns the mag that is supposed to be rendered for the given layer. The return mag is independent of the actually loaded data. If null is returned, the layer cannot be rendered, because no appropriate mag exists. */ -export function getCurrentResolution( - state: OxalisState, - layerName: string, -): Vector3 | null | undefined { +export function getCurrentMag(state: OxalisState, layerName: string): Vector3 | null | undefined { const resolutionInfo = getMagInfo(getLayerByName(state.dataset, layerName).resolutions); const magIndex = getActiveMagIndexForLayer(state, layerName); const existingMagIndex = resolutionInfo.getIndexOrClosestHigherIndex(magIndex); @@ -374,7 +365,7 @@ export function getCurrentResolution( function _getValidZoomRangeForUser(state: OxalisState): [number, number] { const maxOfLayers = _.max( getDataLayers(state.dataset).map((layer) => { - const maximumZoomSteps = getMaximumZoomForAllResolutionsFromStore(state, layer.name); + const maximumZoomSteps = getMaximumZoomForAllMagsFromStore(state, layer.name); return _.last(maximumZoomSteps); }), ); @@ -393,21 +384,21 @@ export function getMaxZoomValueForResolution( ): number { const targetResolutionIdentifier = Math.max(...targetResolution); // Extract the max value from the range - const maxZoom = getValidZoomRangeForResolution(state, layerName, targetResolutionIdentifier)[1]; + const maxZoom = getValidZoomRangeForMag(state, layerName, targetResolutionIdentifier)[1]; if (maxZoom == null) { - // This should never happen as long as a valid target resolution is passed to this function. - throw new Error("Zoom range could not be determined for target resolution."); + // This should never happen as long as a valid target mag is passed to this function. + throw new Error("Zoom range could not be determined for target magnification."); } return maxZoom; } -function getValidZoomRangeForResolution( +function getValidZoomRangeForMag( state: OxalisState, layerName: string, resolutionIdentifier: number, ): Vector2 | [null, null] { - const maximumZoomSteps = getMaximumZoomForAllResolutionsFromStore(state, layerName); - // maximumZoomSteps is densely defined for all resolutions starting from resolution 1,1,1. + const maximumZoomSteps = getMaximumZoomForAllMagsFromStore(state, layerName); + // maximumZoomSteps is densely defined for all mags starting from magnification 1,1,1. // Therefore, we can use log2 as an index. const targetResolutionIndex = Math.log2(resolutionIdentifier); @@ -452,7 +443,7 @@ export function getValidTaskZoomRange( (magIdentifier == null ? defaultRange[idx] : // If the magIdentifier is defined, but doesn't match any resolution, we default to the defaultRange values - getValidZoomRangeForResolution(state, firstColorLayerName, magIdentifier)[idx]) || + getValidZoomRangeForMag(state, firstColorLayerName, magIdentifier)[idx]) || defaultRange[idx] ); } @@ -572,10 +563,10 @@ type UnrenderableLayersInfos = { }; /* - This function returns layers that cannot be rendered (since the current resolution is missing), + This function returns layers that cannot be rendered (since the current mag is missing), even though they should be rendered (since they are enabled). For each layer, this method additionally returns whether data of this layer can be rendered by zooming in or out. - The function takes fallback resolutions into account if renderMissingDataBlack is disabled. + The function takes fallback mags into account if renderMissingDataBlack is disabled. */ function _getUnrenderableLayerInfosForCurrentZoom( state: OxalisState, @@ -599,12 +590,12 @@ function _getUnrenderableLayerInfosForCurrentZoom( if (renderMissingDataBlack) { // We already know that the layer is missing. Since `renderMissingDataBlack` - // is enabled, the fallback resolutions don't matter. The layer cannot be + // is enabled, the fallback mags don't matter. The layer cannot be // rendered. return true; } - // The current resolution is missing and fallback rendering + // The current mag is missing and fallback rendering // is activated. Thus, check whether one of the fallback // zoomSteps can be rendered. return !_.range(1, MAX_ZOOM_STEP_DIFF + 1).some((diff) => { @@ -672,4 +663,4 @@ function _getActiveResolutionInfo(state: OxalisState) { }; } -export const getActiveResolutionInfo = reuseInstanceOnEquality(_getActiveResolutionInfo); +export const getActiveMagInfo = reuseInstanceOnEquality(_getActiveResolutionInfo); diff --git a/frontend/javascripts/oxalis/model/accessors/tool_accessor.ts b/frontend/javascripts/oxalis/model/accessors/tool_accessor.ts index 7ebd8943adb..7c4bb6d8b03 100644 --- a/frontend/javascripts/oxalis/model/accessors/tool_accessor.ts +++ b/frontend/javascripts/oxalis/model/accessors/tool_accessor.ts @@ -5,7 +5,7 @@ import type { OxalisState } from "oxalis/store"; import { type AgglomerateState, getActiveSegmentationTracing, - getRenderableResolutionForSegmentationTracing, + getRenderableMagForSegmentationTracing, hasAgglomerateMapping, isVolumeAnnotationDisallowedForZoom, } from "oxalis/model/accessors/volumetracing_accessor"; @@ -24,7 +24,7 @@ import { isSkeletonLayerTransformed } from "./skeletontracing_accessor"; import { reuseInstanceOnEquality } from "./accessor_helpers"; const zoomInToUseToolMessage = - "Please zoom in further to use this tool. If you want to edit volume data on this zoom level, create an annotation with restricted resolutions from the extended annotation menu in the dashboard."; + "Please zoom in further to use this tool. If you want to edit volume data on this zoom level, create an annotation with restricted magnifications from the extended annotation menu in the dashboard."; const getExplanationForDisabledVolume = ( isSegmentationTracingVisible: boolean, @@ -274,7 +274,7 @@ function getDisabledVolumeInfo(state: OxalisState) { const hasVolume = state.tracing.volumes.length > 0; const hasSkeleton = state.tracing.skeleton != null; const segmentationTracingLayer = getActiveSegmentationTracing(state); - const labeledResolution = getRenderableResolutionForSegmentationTracing( + const labeledResolution = getRenderableMagForSegmentationTracing( state, segmentationTracingLayer, )?.resolution; diff --git a/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts b/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts index f70702777b3..7d556ae3294 100644 --- a/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts +++ b/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts @@ -179,7 +179,7 @@ export function getSegmentationLayerForTracing( return getSegmentationLayerByName(state.dataset, volumeTracing.tracingId); } -function _getResolutionInfoOfActiveSegmentationTracingLayer(state: OxalisState): MagInfo { +function _getMagInfoOfActiveSegmentationTracingLayer(state: OxalisState): MagInfo { const volumeTracing = getActiveSegmentationTracing(state); if (!volumeTracing) { @@ -190,8 +190,8 @@ function _getResolutionInfoOfActiveSegmentationTracingLayer(state: OxalisState): return getMagInfo(segmentationLayer.resolutions); } -const getResolutionInfoOfActiveSegmentationTracingLayer = memoizeOne( - _getResolutionInfoOfActiveSegmentationTracingLayer, +const getMagInfoOfActiveSegmentationTracingLayer = memoizeOne( + _getMagInfoOfActiveSegmentationTracingLayer, ); export function getServerVolumeTracings( tracings: Array | null | undefined, @@ -211,9 +211,9 @@ export function getContourTracingMode(volumeTracing: VolumeTracing): ContourMode } const MAG_THRESHOLDS_FOR_ZOOM: Partial> = { - // Note that these are relative to the lowest existing resolution index. + // Note that these are relative to the lowest existing mag index. // A threshold of 1 indicates that the respective tool can be used in the - // lowest existing resolution as well as the next highest one. + // lowest existing magnification as well as the next highest one. [AnnotationToolEnum.TRACE]: 1, [AnnotationToolEnum.ERASE_TRACE]: 1, [AnnotationToolEnum.BRUSH]: 3, @@ -242,9 +242,9 @@ export function isVolumeAnnotationDisallowedForZoom(tool: AnnotationTool, state: return true; } - const volumeResolutions = getResolutionInfoOfActiveSegmentationTracingLayer(state); + const volumeResolutions = getMagInfoOfActiveSegmentationTracingLayer(state); const lowestExistingResolutionIndex = volumeResolutions.getFinestMagIndex(); - // The current resolution is too high for the tool + // The current mag is too high for the tool // because too many voxels could be annotated at the same time. const isZoomStepTooHigh = getActiveMagIndexForLayer(state, activeSegmentation.tracingId) > @@ -254,7 +254,7 @@ export function isVolumeAnnotationDisallowedForZoom(tool: AnnotationTool, state: const MAX_BRUSH_SIZE_FOR_MAG1 = 300; export function getMaximumBrushSize(state: OxalisState) { - const volumeResolutions = getResolutionInfoOfActiveSegmentationTracingLayer(state); + const volumeResolutions = getMagInfoOfActiveSegmentationTracingLayer(state); if (volumeResolutions.mags.length === 0) { return MAX_BRUSH_SIZE_FOR_MAG1; @@ -474,11 +474,11 @@ export function getActiveSegmentPosition(state: OxalisState): Vector3 | null | u } /* - This function returns the resolution and zoom step in which the given segmentation + This function returns the magnification and zoom step in which the given segmentation tracing layer is currently rendered (if it is rendered). These properties should be used when labeling volume data. */ -function _getRenderableResolutionForSegmentationTracing( +function _getRenderableMagForSegmentationTracing( state: OxalisState, segmentationTracing: VolumeTracing | null | undefined, ): @@ -512,13 +512,13 @@ function _getRenderableResolutionForSegmentationTracing( }; } - // Since `renderMissingDataBlack` is enabled, the fallback resolutions + // Since `renderMissingDataBlack` is enabled, the fallback mags // should not be considered. if (renderMissingDataBlack) { return null; } - // The current resolution is missing and fallback rendering + // The current mag is missing and fallback rendering // is activated. Thus, check whether one of the fallback // zoomSteps can be rendered. for ( @@ -537,11 +537,11 @@ function _getRenderableResolutionForSegmentationTracing( return null; } -export const getRenderableResolutionForSegmentationTracing = reuseInstanceOnEquality( - _getRenderableResolutionForSegmentationTracing, +export const getRenderableMagForSegmentationTracing = reuseInstanceOnEquality( + _getRenderableMagForSegmentationTracing, ); -function _getRenderableResolutionForActiveSegmentationTracing(state: OxalisState): +function _getRenderableMagForActiveSegmentationTracing(state: OxalisState): | { resolution: Vector3; zoomStep: number; @@ -549,11 +549,11 @@ function _getRenderableResolutionForActiveSegmentationTracing(state: OxalisState | null | undefined { const activeSegmentationTracing = getActiveSegmentationTracing(state); - return getRenderableResolutionForSegmentationTracing(state, activeSegmentationTracing); + return getRenderableMagForSegmentationTracing(state, activeSegmentationTracing); } -export const getRenderableResolutionForActiveSegmentationTracing = reuseInstanceOnEquality( - _getRenderableResolutionForActiveSegmentationTracing, +export const getRenderableMagForActiveSegmentationTracing = reuseInstanceOnEquality( + _getRenderableMagForActiveSegmentationTracing, ); export function getMappingInfoForVolumeTracing( @@ -646,7 +646,7 @@ export function getLabelActionFromPreviousSlice( ): LabelAction | undefined { // Gets the last label action which was performed on a different slice. // Note that in coarser mags (e.g., 8-8-2), the comparison of the coordinates - // is done while respecting how the coordinates are clipped due to that resolution. + // is done while respecting how the coordinates are clipped due to that mag. const adapt = (vec: Vector3) => V3.roundElementToMag(vec, resolution, dim); const position = adapt(getFlooredPosition(state.flycam)); diff --git a/frontend/javascripts/oxalis/model/bucket_data_handling/bounding_box.ts b/frontend/javascripts/oxalis/model/bucket_data_handling/bounding_box.ts index 3a203dc86aa..9769bc95984 100644 --- a/frontend/javascripts/oxalis/model/bucket_data_handling/bounding_box.ts +++ b/frontend/javascripts/oxalis/model/bucket_data_handling/bounding_box.ts @@ -34,13 +34,13 @@ class BoundingBox { return [u, v]; } - getBoxForZoomStep = _.memoize((resolution: Vector3): BoundingBoxType => { + getBoxForZoomStep = _.memoize((mag: Vector3): BoundingBoxType => { // No `map` for performance reasons const min = [0, 0, 0] as Vector3; const max = [0, 0, 0] as Vector3; for (let i = 0; i < 3; i++) { - const divisor = constants.BUCKET_WIDTH * resolution[i]; + const divisor = constants.BUCKET_WIDTH * mag[i]; min[i] = Math.floor(this.min[i] / divisor); max[i] = Math.ceil(this.max[i] / divisor); } @@ -51,16 +51,16 @@ class BoundingBox { }; }); - containsBucket([x, y, z, zoomStep]: Vector4, resolutionInfo: MagInfo): boolean { + containsBucket([x, y, z, zoomStep]: Vector4, magInfo: MagInfo): boolean { /* Checks whether a bucket is contained in the active bounding box. - * If the passed resolutionInfo does not contain the passed zoomStep, this method + * If the passed magInfo does not contain the passed zoomStep, this method * returns false. */ - const resolutionIndex = resolutionInfo.getMagByIndex(zoomStep); - if (resolutionIndex == null) { + const magIndex = magInfo.getMagByIndex(zoomStep); + if (magIndex == null) { return false; } - const { min, max } = this.getBoxForZoomStep(resolutionIndex); + const { min, max } = this.getBoxForZoomStep(magIndex); return min[0] <= x && x < max[0] && min[1] <= y && y < max[1] && min[2] <= z && z < max[2]; } diff --git a/frontend/javascripts/oxalis/model/bucket_data_handling/bucket.ts b/frontend/javascripts/oxalis/model/bucket_data_handling/bucket.ts index 0703ace532d..9bfd878cf21 100644 --- a/frontend/javascripts/oxalis/model/bucket_data_handling/bucket.ts +++ b/frontend/javascripts/oxalis/model/bucket_data_handling/bucket.ts @@ -187,8 +187,8 @@ export class DataBucket { } getBoundingBox(): BoundingBoxType { - const min = bucketPositionToGlobalAddress(this.zoomedAddress, this.cube.resolutionInfo); - const bucketResolution = this.cube.resolutionInfo.getMagByIndexOrThrow(this.zoomedAddress[3]); + const min = bucketPositionToGlobalAddress(this.zoomedAddress, this.cube.magInfo); + const bucketResolution = this.cube.magInfo.getMagByIndexOrThrow(this.zoomedAddress[3]); const max: Vector3 = [ min[0] + Constants.BUCKET_WIDTH * bucketResolution[0], min[1] + Constants.BUCKET_WIDTH * bucketResolution[1], @@ -201,7 +201,7 @@ export class DataBucket { } getGlobalPosition(): Vector3 { - return bucketPositionToGlobalAddress(this.zoomedAddress, this.cube.resolutionInfo); + return bucketPositionToGlobalAddress(this.zoomedAddress, this.cube.magInfo); } getTopLeftInMag(): Vector3 { @@ -707,9 +707,9 @@ export class DataBucket { if (this.zoomedAddress[3] === zoomStep) { // @ts-ignore this.visualizedMesh = window.addBucketMesh( - bucketPositionToGlobalAddress(this.zoomedAddress, this.cube.resolutionInfo), + bucketPositionToGlobalAddress(this.zoomedAddress, this.cube.magInfo), this.zoomedAddress[3], - this.cube.resolutionInfo.getMagByIndex(this.zoomedAddress[3]), + this.cube.magInfo.getMagByIndex(this.zoomedAddress[3]), colors[this.zoomedAddress[3]] || this.visualizationColor, ); } diff --git a/frontend/javascripts/oxalis/model/bucket_data_handling/bucket_picker_strategies/flight_bucket_picker.ts b/frontend/javascripts/oxalis/model/bucket_data_handling/bucket_picker_strategies/flight_bucket_picker.ts index 7a9063dc086..7c2e0ad7d98 100644 --- a/frontend/javascripts/oxalis/model/bucket_data_handling/bucket_picker_strategies/flight_bucket_picker.ts +++ b/frontend/javascripts/oxalis/model/bucket_data_handling/bucket_picker_strategies/flight_bucket_picker.ts @@ -40,7 +40,7 @@ function createDistinctBucketAdder(bucketsWithPriorities: Array<[Vector4, number } export default function determineBucketsForFlight( - resolutions: Array, + mags: Array, centerPosition: Vector3, sphericalCapRadius: number, enqueueFunction: EnqueueFunction, @@ -53,7 +53,7 @@ export default function determineBucketsForFlight( const halfWidth = width / 2; const cameraVertex: Vector3 = [0, 0, -sphericalCapRadius]; const fallbackZoomStep = logZoomStep + 1; - const isFallbackAvailable = fallbackZoomStep < resolutions.length; + const isFallbackAvailable = fallbackZoomStep < mags.length; const transformToSphereCap = (_vec: Vector3) => { const vec = V3.sub(_vec, cameraVertex); @@ -81,7 +81,7 @@ export default function determineBucketsForFlight( const transformedVec = transformAndApplyMatrix([x, y, z]); const bucketPos = globalPositionToBucketPositionFloat( transformedVec as Vector3, - resolutions, + mags, logZoomStep, ); @@ -119,7 +119,7 @@ export default function determineBucketsForFlight( // null is passed as additionalCoordinates, since the bucket picker doesn't care about the // additional coordinates. It simply sticks to 3D and the caller is responsible for augmenting // potential other coordinates. - globalPositionToBucketPosition(position, resolutions, logZoomStep, null), + globalPositionToBucketPosition(position, mags, logZoomStep, null), ); const traverseFallbackBBox = (boundingBoxBuckets: { @@ -131,12 +131,12 @@ export default function determineBucketsForFlight( // use all fallback buckets in bbox const min = zoomedAddressToAnotherZoomStep( [...boundingBoxBuckets.cornerMin, logZoomStep], - resolutions, + mags, fallbackZoomStep, ); const max = zoomedAddressToAnotherZoomStep( [...boundingBoxBuckets.cornerMax, logZoomStep], - resolutions, + mags, fallbackZoomStep, ); diff --git a/frontend/javascripts/oxalis/model/bucket_data_handling/bucket_picker_strategies/oblique_bucket_picker.ts b/frontend/javascripts/oxalis/model/bucket_data_handling/bucket_picker_strategies/oblique_bucket_picker.ts index 203b3cd7e60..7585718a1a0 100644 --- a/frontend/javascripts/oxalis/model/bucket_data_handling/bucket_picker_strategies/oblique_bucket_picker.ts +++ b/frontend/javascripts/oxalis/model/bucket_data_handling/bucket_picker_strategies/oblique_bucket_picker.ts @@ -38,7 +38,7 @@ const ROTATIONS = { export default function determineBucketsForOblique( viewMode: ViewMode, loadingStrategy: LoadingStrategy, - resolutions: Array, + mags: Array, position: Vector3, enqueueFunction: EnqueueFunction, matrix: Matrix4x4, @@ -48,11 +48,11 @@ export default function determineBucketsForOblique( ): void { let zoomStepDiff = 0; - while (logZoomStep + zoomStepDiff < resolutions.length && zoomStepDiff <= MAX_ZOOM_STEP_DIFF) { + while (logZoomStep + zoomStepDiff < mags.length && zoomStepDiff <= MAX_ZOOM_STEP_DIFF) { addNecessaryBucketsToPriorityQueueOblique( loadingStrategy, viewMode, - resolutions, + mags, position, enqueueFunction, matrix, @@ -68,7 +68,7 @@ export default function determineBucketsForOblique( function addNecessaryBucketsToPriorityQueueOblique( loadingStrategy: LoadingStrategy, viewMode: ViewMode, - resolutions: Array, + mags: Array, position: Vector3, enqueueFunction: EnqueueFunction, matrix: Matrix4x4, @@ -117,12 +117,7 @@ function addNecessaryBucketsToPriorityQueueOblique( [-enlargedHalfExtent[0], -enlargedHalfExtent[1], 0], [-enlargedHalfExtent[0], +enlargedHalfExtent[1], 0], ]); - const stepRateBuckets = traverse( - stepRatePoints[0], - stepRatePoints[1], - resolutions, - logZoomStep, - ); + const stepRateBuckets = traverse(stepRatePoints[0], stepRatePoints[1], mags, logZoomStep); const steps = stepRateBuckets.length + 1; const stepSize = [enlargedExtent[0] / steps, enlargedExtent[1] / steps]; // This array holds the start and end points @@ -148,7 +143,7 @@ function addNecessaryBucketsToPriorityQueueOblique( ); for (const [a, b] of chunk2(scanLinesPoints)) { - for (const bucket of traverse(a, b, resolutions, logZoomStep)) { + for (const bucket of traverse(a, b, mags, logZoomStep)) { traversedBuckets.push(bucket); } } @@ -160,7 +155,7 @@ function addNecessaryBucketsToPriorityQueueOblique( // null is passed as additionalCoordinates, since the bucket picker doesn't care about the // additional coordinates. It simply sticks to 3D and the caller is responsible for augmenting // potential other coordinates. - const centerAddress = globalPositionToBucketPosition(position, resolutions, logZoomStep, null); + const centerAddress = globalPositionToBucketPosition(position, mags, logZoomStep, null); for (const bucketAddress of traversedBucketsVec4) { const bucketVector3 = bucketAddress.slice(0, 3) as any as Vector3; diff --git a/frontend/javascripts/oxalis/model/bucket_data_handling/bucket_traversals.ts b/frontend/javascripts/oxalis/model/bucket_data_handling/bucket_traversals.ts index c19f95bf2e4..1a623cd89e5 100644 --- a/frontend/javascripts/oxalis/model/bucket_data_handling/bucket_traversals.ts +++ b/frontend/javascripts/oxalis/model/bucket_data_handling/bucket_traversals.ts @@ -14,7 +14,7 @@ import { mod } from "libs/utils"; // Attention: Note that the implemented paper export default function traverse( startPosition: Vector3, endPosition: Vector3, - resolutions: Array, + mags: Array, zoomStep: number, ): Vector3[] { // The equation of the ray is →u + t→ v for t ≥ 0. The new traversal algorithm breaks down the ray into intervals of t, @@ -22,11 +22,11 @@ export default function traverse( const u = startPosition; const v = V3.sub(endPosition, startPosition); // The initialization phase begins by identifying the voxel in which the ray origin, → u, is found. - const uBucket = globalPositionToBucketPosition(startPosition, resolutions, zoomStep, null); - const lastBucket = globalPositionToBucketPosition(endPosition, resolutions, zoomStep, null); + const uBucket = globalPositionToBucketPosition(startPosition, mags, zoomStep, null); + const lastBucket = globalPositionToBucketPosition(endPosition, mags, zoomStep, null); // The integer variables X and Y are initialized to the starting voxel coordinates. let [X, Y, Z] = uBucket; - const voxelSize = getBucketExtent(resolutions[zoomStep]); + const voxelSize = getBucketExtent(mags[zoomStep]); // In addition, the variables stepX and stepY are initialized to either 1 or -1 indicating whether X and Y are // incremented or decremented as the ray crosses voxel boundaries (this is determined by the sign of the x and y components of → v). const [stepX, stepY, stepZ] = v.map((el) => Math.sign(el)); diff --git a/frontend/javascripts/oxalis/model/bucket_data_handling/data_cube.ts b/frontend/javascripts/oxalis/model/bucket_data_handling/data_cube.ts index ac0f3bff585..fed8881871c 100644 --- a/frontend/javascripts/oxalis/model/bucket_data_handling/data_cube.ts +++ b/frontend/javascripts/oxalis/model/bucket_data_handling/data_cube.ts @@ -82,7 +82,7 @@ class DataCube { temporalBucketManager: TemporalBucketManager; isSegmentation: boolean; elementClass: ElementClass; - resolutionInfo: MagInfo; + magInfo: MagInfo; layerName: string; emitter: Emitter; lastRequestForValueSet: number | null = null; @@ -106,14 +106,14 @@ class DataCube { constructor( layerBBox: BoundingBox, additionalAxes: AdditionalAxis[], - resolutionInfo: MagInfo, + magInfo: MagInfo, elementClass: ElementClass, isSegmentation: boolean, layerName: string, ) { this.elementClass = elementClass; this.isSegmentation = isSegmentation; - this.resolutionInfo = resolutionInfo; + this.magInfo = magInfo; this.layerName = layerName; this.additionalAxes = _.keyBy(additionalAxes, "name"); this.emitter = createNanoEvents(); @@ -213,7 +213,7 @@ class DataCube { return false; } - return this.boundingBox.containsBucket([x, y, z, zoomStep], this.resolutionInfo); + return this.boundingBox.containsBucket([x, y, z, zoomStep], this.magInfo); } getBucketIndexAndCube([x, y, z, zoomStep, coords]: BucketAddress): [ @@ -239,7 +239,7 @@ class DataCube { ): CubeEntry | null { const cubeKey = this.getCubeKey(zoomStep, coords); if (this.cubes[cubeKey] == null) { - const resolution = this.resolutionInfo.getMagByIndex(zoomStep); + const resolution = this.magInfo.getMagByIndex(zoomStep); if (resolution == null) { return null; } @@ -342,7 +342,7 @@ class DataCube { { elementClass: this.elementClass, isSegmentation: this.isSegmentation, - resolutionInfo: this.resolutionInfo, + magInfo: this.magInfo, }, "warning", ); @@ -433,7 +433,7 @@ class DataCube { // Please make use of a LabeledVoxelsMap instead. const promises = []; - for (const [resolutionIndex] of this.resolutionInfo.getMagsWithIndices()) { + for (const [resolutionIndex] of this.magInfo.getMagsWithIndices()) { promises.push( this._labelVoxelInResolution_DEPRECATED( voxel, @@ -535,9 +535,9 @@ class DataCube { }; } - if (!this.resolutionInfo.hasIndex(zoomStep)) { + if (!this.magInfo.hasIndex(zoomStep)) { throw new Error( - `DataCube.floodFill was called with a zoomStep of ${zoomStep} which does not exist for the current resolution.`, + `DataCube.floodFill was called with a zoomStep of ${zoomStep} which does not exist for the current magnification.`, ); } @@ -630,9 +630,7 @@ class DataCube { const currentLabeledVoxelMap = bucketsWithLabeledVoxelsMap.get(currentBucket.zoomedAddress) || new Map(); - const currentResolution = this.resolutionInfo.getMagByIndexOrThrow( - currentBucket.zoomedAddress[3], - ); + const currentResolution = this.magInfo.getMagByIndexOrThrow(currentBucket.zoomedAddress[3]); const markUvwInSliceAsLabeled = ([firstCoord, secondCoord, thirdCoord]: Vector3) => { // Convert bucket local W coordinate to global W (both mag-dependent) @@ -785,7 +783,7 @@ class DataCube { additionalCoordinates: AdditionalCoordinate[] | null, zoomStep: number = 0, ): boolean { - // When this method returns false, this means that the next resolution (if it exists) + // When this method returns false, this means that the next mag (if it exists) // needs to be examined for rendering. const bucket = this.getBucket( this.positionToZoomedAddress(voxel, additionalCoordinates, zoomStep), @@ -823,7 +821,7 @@ class DataCube { additionalCoordinates: AdditionalCoordinate[] | null, zoomStep: number, ): number { - const resolutions = this.resolutionInfo.getDenseMags(); + const resolutions = this.magInfo.getDenseMags(); let usableZoomStep = zoomStep; while ( @@ -842,7 +840,7 @@ class DataCube { additionalCoordinates: AdditionalCoordinate[] | null, zoomStep: number, ): Promise { - const resolutions = this.resolutionInfo.getDenseMags(); + const resolutions = this.magInfo.getDenseMags(); let usableZoomStep = zoomStep; while ( @@ -866,7 +864,7 @@ class DataCube { mapping: Mapping | null | undefined, zoomStep: number = 0, ): number { - if (!this.resolutionInfo.hasIndex(zoomStep)) { + if (!this.magInfo.hasIndex(zoomStep)) { return 0; } @@ -922,7 +920,7 @@ class DataCube { getVoxelOffset(voxel: Vector3, zoomStep: number = 0): Vector3 { // No `map` for performance reasons const voxelOffset: Vector3 = [0, 0, 0]; - const resolution = this.resolutionInfo.getMagByIndexOrThrow(zoomStep); + const resolution = this.magInfo.getMagByIndexOrThrow(zoomStep); for (let i = 0; i < 3; i++) { voxelOffset[i] = Math.floor(voxel[i] / resolution[i]) % constants.BUCKET_WIDTH; @@ -944,7 +942,7 @@ class DataCube { // return the bucket a given voxel lies in return globalPositionToBucketPosition( position, - this.resolutionInfo.getDenseMags(), + this.magInfo.getDenseMags(), zoomStep, additionalCoordinates, ); diff --git a/frontend/javascripts/oxalis/model/bucket_data_handling/wkstore_adapter.ts b/frontend/javascripts/oxalis/model/bucket_data_handling/wkstore_adapter.ts index e459478c944..cfcd44bfba1 100644 --- a/frontend/javascripts/oxalis/model/bucket_data_handling/wkstore_adapter.ts +++ b/frontend/javascripts/oxalis/model/bucket_data_handling/wkstore_adapter.ts @@ -288,7 +288,7 @@ export async function createCompressedUpdateBucketActions( const compressedBase64Strings = await compressionPool.submit(byteArrays); return compressedBase64Strings.map((compressedBase64, index) => { const bucket = batchSubset[index]; - const bucketInfo = createSendBucketInfo(bucket.zoomedAddress, bucket.cube.resolutionInfo); + const bucketInfo = createSendBucketInfo(bucket.zoomedAddress, bucket.cube.magInfo); return updateBucket(bucketInfo, compressedBase64); }); }), diff --git a/frontend/javascripts/oxalis/model/helpers/mag_info.ts b/frontend/javascripts/oxalis/model/helpers/mag_info.ts index c766f2468b0..38c9b1977d6 100644 --- a/frontend/javascripts/oxalis/model/helpers/mag_info.ts +++ b/frontend/javascripts/oxalis/model/helpers/mag_info.ts @@ -18,11 +18,11 @@ export class MagInfo { } _buildMagnificationMap() { - // Each resolution entry can be characterized by it's greatest resolution dimension. - // E.g., the resolution array [[1, 1, 1], [2, 2, 1], [4, 4, 2]] defines that - // a zoomstep of 2 corresponds to the resolution [2, 2, 1] (and not [4, 4, 2]). - // Therefore, the largest dim for each resolution has to be unique across all resolutions. - // This function creates a map which maps from powerOfTwo (2**index) to resolution. + // Each magnification entry can be characterized by it's greatest mag dimension. + // E.g., the mag array [[1, 1, 1], [2, 2, 1], [4, 4, 2]] defines that + // a zoomstep of 2 corresponds to the mag [2, 2, 1] (and not [4, 4, 2]). + // Therefore, the largest dim for each mag has to be unique across all mags. + // This function creates a map which maps from powerOfTwo (2**index) to mag. // E.g. // { // 0: [1, 1, 1], diff --git a/frontend/javascripts/oxalis/model/helpers/position_converter.ts b/frontend/javascripts/oxalis/model/helpers/position_converter.ts index 6e105972e5e..5769f4bd9d1 100644 --- a/frontend/javascripts/oxalis/model/helpers/position_converter.ts +++ b/frontend/javascripts/oxalis/model/helpers/position_converter.ts @@ -5,29 +5,26 @@ import type { MagInfo } from "./mag_info"; export function globalPositionToBucketPosition( [x, y, z]: Vector3, - resolutions: Array, - resolutionIndex: number, + mags: Array, + magIndex: number, additionalCoordinates: AdditionalCoordinate[] | null | undefined, ): BucketAddress { - const resolution = - resolutionIndex < resolutions.length - ? resolutions[resolutionIndex] - : upsampleResolution(resolutions, resolutionIndex); + const resolution = magIndex < mags.length ? mags[magIndex] : upsampleMag(mags, magIndex); return [ Math.floor(x / (constants.BUCKET_WIDTH * resolution[0])), Math.floor(y / (constants.BUCKET_WIDTH * resolution[1])), Math.floor(z / (constants.BUCKET_WIDTH * resolution[2])), - resolutionIndex, + magIndex, additionalCoordinates || [], ]; } -export function scaleGlobalPositionWithResolution( +export function scaleGlobalPositionWithMagnification( [x, y, z]: Vector3, - resolution: Vector3, + mag: Vector3, ceil: boolean = false, ): Vector3 { const round = ceil ? Math.ceil : Math.floor; - return [round(x / resolution[0]), round(y / resolution[1]), round(z / resolution[2])]; + return [round(x / mag[0]), round(y / mag[1]), round(z / mag[2])]; } export function zoomedPositionToGlobalPosition( [x, y, z]: Vector3, @@ -35,29 +32,26 @@ export function zoomedPositionToGlobalPosition( ): Vector3 { return [x * currentResolution[0], y * currentResolution[1], z * currentResolution[2]]; } -export function scaleGlobalPositionWithResolutionFloat( +export function scaleGlobalPositionWithMagnificationFloat( [x, y, z]: Vector3, - resolution: Vector3, + mag: Vector3, ): Vector3 { - return [x / resolution[0], y / resolution[1], z / resolution[2]]; + return [x / mag[0], y / mag[1], z / mag[2]]; } export function globalPositionToBucketPositionFloat( [x, y, z]: Vector3, - resolutions: Array, - resolutionIndex: number, + mags: Array, + magIndex: number, ): Vector4 { - const resolution = - resolutionIndex < resolutions.length - ? resolutions[resolutionIndex] - : upsampleResolution(resolutions, resolutionIndex); + const resolution = magIndex < mags.length ? mags[magIndex] : upsampleMag(mags, magIndex); return [ x / (constants.BUCKET_WIDTH * resolution[0]), y / (constants.BUCKET_WIDTH * resolution[1]), z / (constants.BUCKET_WIDTH * resolution[2]), - resolutionIndex, + magIndex, ]; } -export function upsampleResolution(resolutions: Array, resolutionIndex: number): Vector3 { +export function upsampleMag(resolutions: Array, resolutionIndex: number): Vector3 { const lastResolutionIndex = resolutions.length - 1; const lastResolution = resolutions[lastResolutionIndex]; const multiplier = Math.pow(2, resolutionIndex - lastResolutionIndex); @@ -79,23 +73,19 @@ export function bucketPositionToGlobalAddress( z * constants.BUCKET_WIDTH * resolution[2], ]; } -export function getResolutionsFactors(resolutionA: Vector3, resolutionB: Vector3): Vector3 { - return [ - resolutionA[0] / resolutionB[0], - resolutionA[1] / resolutionB[1], - resolutionA[2] / resolutionB[2], - ]; +export function getMagFactors(magA: Vector3, magB: Vector3): Vector3 { + return [magA[0] / magB[0], magA[1] / magB[1], magA[2] / magB[2]]; } export function zoomedPositionToZoomedAddress( [x, y, z]: Vector3, - resolutionIndex: number, + magIndex: number, additionalCoordinates: AdditionalCoordinate[] | null, ): BucketAddress { return [ Math.floor(x / constants.BUCKET_WIDTH), Math.floor(y / constants.BUCKET_WIDTH), Math.floor(z / constants.BUCKET_WIDTH), - resolutionIndex, + magIndex, additionalCoordinates || [], ]; } @@ -104,48 +94,48 @@ export function zoomedAddressToZoomedPosition([x, y, z, _]: BucketAddress): Vect } // TODO: zoomedAddressToAnotherZoomStep usages should be converted to zoomedAddressToAnotherZoomStepWithInfo // Note that this is not trivial since zoomedAddressToAnotherZoomStepWithInfo will throw on not existing -// resolution indices (in contrast to zoomedAddressToAnotherZoomStep). +// mag indices (in contrast to zoomedAddressToAnotherZoomStep). // See: https://github.com/scalableminds/webknossos/issues/4838 export function zoomedAddressToAnotherZoomStep( - [x, y, z, resolutionIndex]: Vector4, - resolutions: Array, - targetResolutionIndex: number, + [x, y, z, magIndex]: Vector4, + mags: Array, + targetMagIndex: number, ): Vector4 { - const currentResolution = resolutions[resolutionIndex]; - const targetResolution = resolutions[targetResolutionIndex]; - const factors = getResolutionsFactors(currentResolution, targetResolution); + const currentResolution = mags[magIndex]; + const targetResolution = mags[targetMagIndex]; + const factors = getMagFactors(currentResolution, targetResolution); return [ Math.floor(x * factors[0]), Math.floor(y * factors[1]), Math.floor(z * factors[2]), - targetResolutionIndex, + targetMagIndex, ]; } /* - Please note that this function will fail if the passed resolutionIndex or - targetResolutionIndex don't exist in the resolutionInfo. + Please note that this function will fail if the passed magIndex or + targetMagIndex don't exist in the magInfo. */ export function zoomedAddressToAnotherZoomStepWithInfo( - [x, y, z, resolutionIndex]: Vector4, - resolutionInfo: MagInfo, - targetResolutionIndex: number, + [x, y, z, magIndex]: Vector4, + magInfo: MagInfo, + targetMagIndex: number, ): Vector4 { - const currentResolution = resolutionInfo.getMagByIndexWithFallback(resolutionIndex, null); - const targetResolution = resolutionInfo.getMagByIndexWithFallback(targetResolutionIndex, null); - const factors = getResolutionsFactors(currentResolution, targetResolution); + const currentResolution = magInfo.getMagByIndexWithFallback(magIndex, null); + const targetResolution = magInfo.getMagByIndexWithFallback(targetMagIndex, null); + const factors = getMagFactors(currentResolution, targetResolution); return [ Math.floor(x * factors[0]), Math.floor(y * factors[1]), Math.floor(z * factors[2]), - targetResolutionIndex, + targetMagIndex, ]; } -export function getBucketExtent(resolution: Vector3): Vector3 { +export function getBucketExtent(mag: Vector3): Vector3 { return [ - constants.BUCKET_WIDTH * resolution[0], - constants.BUCKET_WIDTH * resolution[1], - constants.BUCKET_WIDTH * resolution[2], + constants.BUCKET_WIDTH * mag[0], + constants.BUCKET_WIDTH * mag[1], + constants.BUCKET_WIDTH * mag[2], ]; } // This function returns all bucket addresses for which the fallback bucket @@ -153,21 +143,18 @@ export function getBucketExtent(resolution: Vector3): Vector3 { export function getBaseBucketsForFallbackBucket( fallbackBucketAddress: Vector4, zoomStepDifference: number, - resolutions: Array, + mags: Array, ): Array { const fallbackBucketZoomStep = fallbackBucketAddress[3]; const betterZoomStep = fallbackBucketZoomStep - zoomStepDifference; const betterBucketAddress = zoomedAddressToAnotherZoomStep( fallbackBucketAddress, - resolutions, + mags, betterZoomStep, ); // resolutionFactors is a [x, y, z] tuple with x, y, z being 1 or 2 each (because // zoomStepDifference === 1). In the case of isotropic resolutions, it's simply [2, 2, 2] - const resolutionFactors = getResolutionsFactors( - resolutions[fallbackBucketZoomStep], - resolutions[betterZoomStep], - ); + const resolutionFactors = getMagFactors(mags[fallbackBucketZoomStep], mags[betterZoomStep]); const bucketAddresses = []; const [baseX, baseY, baseZ] = betterBucketAddress; diff --git a/frontend/javascripts/oxalis/model/sagas/clip_histogram_saga.ts b/frontend/javascripts/oxalis/model/sagas/clip_histogram_saga.ts index 4cfdd204401..c44689fa09f 100644 --- a/frontend/javascripts/oxalis/model/sagas/clip_histogram_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/clip_histogram_saga.ts @@ -24,8 +24,8 @@ async function getClippingValues( const [TypedArrayClass] = getConstructorForElementClass(elementClass); const { additionalCoordinates } = state.flycam; - // Find a viable resolution to compute the histogram on - // Ideally, we want to avoid resolutions 1 and 2 to keep + // Find a viable mag to compute the histogram on + // Ideally, we want to avoid mags 1 and 2 to keep // the amount of data that has to be loaded small and // to de-noise the data const desiredResolutionIndex = Math.max(2, getActiveMagIndexForLayer(state, layerName) + 1); diff --git a/frontend/javascripts/oxalis/model/sagas/dataset_saga.ts b/frontend/javascripts/oxalis/model/sagas/dataset_saga.ts index 63c07032184..934adc87162 100644 --- a/frontend/javascripts/oxalis/model/sagas/dataset_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/dataset_saga.ts @@ -14,7 +14,7 @@ import { invertAndTranspose, isLayerVisible, } from "../accessors/dataset_accessor"; -import { getCurrentResolution } from "../accessors/flycam_accessor"; +import { getCurrentMag } from "../accessors/flycam_accessor"; import { getViewportExtents } from "../accessors/view_mode_accessor"; import { V3 } from "libs/mjs"; import { Identity4x4 } from "oxalis/constants"; @@ -105,7 +105,7 @@ export function* watchZ1Downsampling(): Saga { scaleY = V3.length([matrix[1], matrix[5], matrix[9]]); } - const currentRes = yield* select((state) => getCurrentResolution(state, dataLayer.name)); + const currentRes = yield* select((state) => getCurrentMag(state, dataLayer.name)); if (currentRes == null) { // The layer cannot be rendered. For example, because the user zoomed out and there // is no appropriate mag for that layer. diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index f4849c6eca8..4337303f200 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -155,12 +155,12 @@ function removeMapForSegment( adhocMeshesMapByLayer[additionalCoordinateKey][layerName].delete(segmentId); } -function getCubeSizeInMag1(zoomStep: number, resolutionInfo: MagInfo): Vector3 { +function getCubeSizeInMag1(zoomStep: number, magInfo: MagInfo): Vector3 { // Convert marchingCubeSizeInTargetMag to mag1 via zoomStep // Drop the last element of the Vector4; const [x, y, z] = zoomedAddressToAnotherZoomStepWithInfo( [...marchingCubeSizeInTargetMag(), zoomStep], - resolutionInfo, + magInfo, 0, ); return [x, y, z]; @@ -169,9 +169,9 @@ function getCubeSizeInMag1(zoomStep: number, resolutionInfo: MagInfo): Vector3 { function clipPositionToCubeBoundary( position: Vector3, zoomStep: number, - resolutionInfo: MagInfo, + magInfo: MagInfo, ): Vector3 { - const cubeSizeInMag1 = getCubeSizeInMag1(zoomStep, resolutionInfo); + const cubeSizeInMag1 = getCubeSizeInMag1(zoomStep, magInfo); const currentCube = V3.floor(V3.divide3(position, cubeSizeInMag1)); const clippedPosition = V3.scale3(currentCube, cubeSizeInMag1); return clippedPosition; @@ -191,10 +191,10 @@ function getNeighborPosition( clippedPosition: Vector3, neighborId: number, zoomStep: number, - resolutionInfo: MagInfo, + magInfo: MagInfo, ): Vector3 { const neighborMultiplier = NEIGHBOR_LOOKUP[neighborId]; - const cubeSizeInMag1 = getCubeSizeInMag1(zoomStep, resolutionInfo); + const cubeSizeInMag1 = getCubeSizeInMag1(zoomStep, magInfo); const neighboringPosition: Vector3 = [ clippedPosition[0] + neighborMultiplier[0] * cubeSizeInMag1[0], clippedPosition[1] + neighborMultiplier[1] * cubeSizeInMag1[1], @@ -238,7 +238,7 @@ function* getInfoForMeshLoading( meshExtraInfo: AdHocMeshInfo, ): Saga<{ zoomStep: number; - resolutionInfo: MagInfo; + magInfo: MagInfo; }> { const resolutionInfo = getMagInfo(layer.resolutions); const preferredZoomStep = @@ -250,7 +250,7 @@ function* getInfoForMeshLoading( const zoomStep = resolutionInfo.getClosestExistingIndex(preferredZoomStep); return { zoomStep, - resolutionInfo, + magInfo: resolutionInfo, }; } @@ -273,7 +273,11 @@ function* loadAdHocMesh( const meshExtraInfo = yield* call(getMeshExtraInfo, layer.name, maybeExtraInfo); - const { zoomStep, resolutionInfo } = yield* call(getInfoForMeshLoading, layer, meshExtraInfo); + const { zoomStep, magInfo: resolutionInfo } = yield* call( + getInfoForMeshLoading, + layer, + meshExtraInfo, + ); batchCounterPerSegment[segmentId] = 0; // If a REMOVE_MESH action is dispatched and consumed @@ -1027,7 +1031,7 @@ function _getLoadChunksTasks( // Compute vertex normals to achieve smooth shading bufferGeometries.forEach((geometry) => geometry.computeVertexNormals()); - // Check if the mesh scale is different to all supported resolutions of the active segmentation scaled by the dataset scale and warn in the console to make debugging easier in such a case. + // Check if the mesh scale is different to all supported mags of the active segmentation scaled by the dataset scale and warn in the console to make debugging easier in such a case. // This hint at the mesh file being computed when the dataset scale was different than currently configured. const segmentationLayerResolutions = yield* select( (state) => getVisibleSegmentationLayer(state)?.resolutions, diff --git a/frontend/javascripts/oxalis/model/sagas/min_cut_saga.ts b/frontend/javascripts/oxalis/model/sagas/min_cut_saga.ts index e474de2f17f..4ddf0fdd28a 100644 --- a/frontend/javascripts/oxalis/model/sagas/min_cut_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/min_cut_saga.ts @@ -60,11 +60,11 @@ const VOXEL_THRESHOLD = 2000000; // optimization (unless it's the only existent mag). const ALWAYS_IGNORE_FIRST_MAG_INITIALLY = true; -function selectAppropriateResolutions( +function selectAppropriateMags( boundingBoxMag1: BoundingBox, - resolutionInfo: MagInfo, + magInfo: MagInfo, ): Array<[number, Vector3]> { - const resolutionsWithIndices = resolutionInfo.getMagsWithIndices(); + const resolutionsWithIndices = magInfo.getMagsWithIndices(); const appropriateResolutions: Array<[number, Vector3]> = []; for (const [resolutionIndex, resolution] of resolutionsWithIndices) { @@ -285,7 +285,7 @@ function* performMinCut(action: Action): Saga { } const resolutionInfo = getMagInfo(volumeTracingLayer.resolutions); - const appropriateResolutionInfos = selectAppropriateResolutions(boundingBoxMag1, resolutionInfo); + const appropriateResolutionInfos = selectAppropriateMags(boundingBoxMag1, resolutionInfo); if (appropriateResolutionInfos.length === 0) { yield* call( @@ -300,9 +300,9 @@ function* performMinCut(action: Action): Saga { successMessageDelay: 5000, }); - // Try to perform a min-cut on the selected resolutions. If the min-cut - // fails for one resolution, it's tried again on the next resolution. - // If the min-cut succeeds, it's refined again with the better resolutions. + // Try to perform a min-cut on the selected mags. If the min-cut + // fails for one mag, it's tried again on the next mag. + // If the min-cut succeeds, it's refined again with the better mags. for (const [resolutionIndex, targetMag] of appropriateResolutionInfos) { try { yield* call( @@ -327,7 +327,7 @@ function* performMinCut(action: Action): Saga { refiningResolutionIndex >= 0; refiningResolutionIndex-- ) { - // Refine min-cut on lower resolutions, if they exist. + // Refine min-cut on lower mags, if they exist. if (!resolutionInfo.hasIndex(refiningResolutionIndex)) { continue; } @@ -419,7 +419,7 @@ function* performMinCut(action: Action): Saga { // to separate A from B. function* tryMinCutAtMag( targetMag: Vector3, - resolutionIndex: number, + magIndex: number, boundingBoxMag1: BoundingBox, nodes: MutableNode[], volumeTracingLayer: APISegmentationLayer, @@ -440,7 +440,7 @@ function* tryMinCutAtMag( [api.data, api.data.getDataForBoundingBox], volumeTracingLayer.name, boundingBoxMag1, - resolutionIndex, + magIndex, additionalCoordinates, ); // For the 3D volume flat arrays are constructed diff --git a/frontend/javascripts/oxalis/model/sagas/proofread_saga.ts b/frontend/javascripts/oxalis/model/sagas/proofread_saga.ts index cfb66c3a355..aa2727ae8af 100644 --- a/frontend/javascripts/oxalis/model/sagas/proofread_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/proofread_saga.ts @@ -82,7 +82,7 @@ import type { AdditionalCoordinate } from "types/api_flow_types"; import { takeEveryUnlessBusy } from "./saga_helpers"; import type { Action } from "../actions/actions"; import { isBigInt, isNumberMap, SoftError } from "libs/utils"; -import { getCurrentResolution } from "../accessors/flycam_accessor"; +import { getCurrentMag } from "../accessors/flycam_accessor"; function runSagaAndCatchSoftError(saga: (...args: any[]) => Saga) { return function* (...args: any[]) { @@ -128,7 +128,7 @@ export default function* proofreadRootSaga(): Saga { ); } -function proofreadCoarseResolutionIndex(): number { +function proofreadCoarseMagIndex(): number { // @ts-ignore return window.__proofreadCoarseResolutionIndex != null ? // @ts-ignore @@ -204,8 +204,8 @@ function* loadCoarseMesh( ); const { mappingName, mappingType } = mappingInfo; - // Load the whole agglomerate mesh in a coarse resolution for performance reasons - const preferredQuality = proofreadCoarseResolutionIndex(); + // Load the whole agglomerate mesh in a coarse mag for performance reasons + const preferredQuality = proofreadCoarseMagIndex(); yield* put( loadAdHocMeshAction(segmentId, position, additionalCoordinates, { mappingName, @@ -1037,12 +1037,12 @@ function* prepareSplitOrMerge(isSkeletonProofreading: boolean): Saga getCurrentResolution(state, volumeTracingLayer.name)); + const currentMag = yield* select((state) => getCurrentMag(state, volumeTracingLayer.name)); const agglomerateFileMag = isSkeletonProofreading - ? // In case of skeleton proofreading, the finest resolution should be used. + ? // In case of skeleton proofreading, the finest mag should be used. resolutionInfo.getFinestMag() - : // For non-skeleton proofreading, the active resolution suffices + : // For non-skeleton proofreading, the active mag suffices currentMag; if (agglomerateFileMag == null) { return null; diff --git a/frontend/javascripts/oxalis/model/sagas/quick_select_heuristic_saga.ts b/frontend/javascripts/oxalis/model/sagas/quick_select_heuristic_saga.ts index e2d6782adf4..03e5b0965a7 100644 --- a/frontend/javascripts/oxalis/model/sagas/quick_select_heuristic_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/quick_select_heuristic_saga.ts @@ -87,7 +87,7 @@ export function* prepareQuickSelect( colorLayer: APIDataLayer; quickSelectConfig: QuickSelectConfig; activeViewport: OrthoViewWithoutTD; - labeledResolution: Vector3; + labeledMag: Vector3; volumeTracing: VolumeTracing; } | null> { const activeViewport = yield* select( @@ -164,7 +164,7 @@ export function* prepareQuickSelect( colorLayer, quickSelectConfig, activeViewport, - labeledResolution, + labeledMag: labeledResolution, volumeTracing, }; } @@ -184,7 +184,7 @@ export default function* performQuickSelect( colorLayer, quickSelectConfig, activeViewport, - labeledResolution, + labeledMag: labeledResolution, volumeTracing, } = preparation; const { startPosition, endPosition, quickSelectGeometry } = action; @@ -501,7 +501,7 @@ export function* finalizeQuickSelectForSlice( quickSelectGeometry: QuickSelectGeometry, volumeTracing: VolumeTracing, activeViewport: OrthoView, - labeledResolution: Vector3, + labeledMag: Vector3, boundingBoxMag1: BoundingBox, w: number, mask: ndarray.NdArray, @@ -510,13 +510,7 @@ export function* finalizeQuickSelectForSlice( skipFinishAnnotationStroke: boolean = false, ) { quickSelectGeometry.setCoordinates([0, 0, 0], [0, 0, 0]); - const volumeLayer = yield* call( - createVolumeLayer, - volumeTracing, - activeViewport, - labeledResolution, - w, - ); + const volumeLayer = yield* call(createVolumeLayer, volumeTracing, activeViewport, labeledMag, w); const sizeUVWInMag = mask.shape; const voxelBuffer2D = volumeLayer.createVoxelBuffer2D( V2.floor(volumeLayer.globalCoordToMag2DFloat(boundingBoxMag1.min)), diff --git a/frontend/javascripts/oxalis/model/sagas/quick_select_ml_saga.ts b/frontend/javascripts/oxalis/model/sagas/quick_select_ml_saga.ts index 9083bfeea7b..d1e66647247 100644 --- a/frontend/javascripts/oxalis/model/sagas/quick_select_ml_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/quick_select_ml_saga.ts @@ -177,7 +177,7 @@ export default function* performQuickSelect( try { const { labeledZoomStep, - labeledResolution, + labeledMag: labeledResolution, thirdDim, activeViewport, volumeTracing, diff --git a/frontend/javascripts/oxalis/model/sagas/volume/helpers.ts b/frontend/javascripts/oxalis/model/sagas/volume/helpers.ts index 8a4d7f5ab89..aa017a04584 100644 --- a/frontend/javascripts/oxalis/model/sagas/volume/helpers.ts +++ b/frontend/javascripts/oxalis/model/sagas/volume/helpers.ts @@ -15,7 +15,7 @@ import type { Saga } from "oxalis/model/sagas/effect-generators"; import { select } from "oxalis/model/sagas/effect-generators"; import { getHalfViewportExtentsInVx } from "oxalis/model/sagas/saga_selectors"; import { call } from "typed-redux-saga"; -import sampleVoxelMapToResolution, { +import sampleVoxelMapToMagnification, { applyVoxelMap, } from "oxalis/model/volumetracing/volume_annotation_sampling"; import Dimensions, { type DimensionMap } from "oxalis/model/dimensions"; @@ -75,11 +75,11 @@ export function getBoundingBoxInMag1(boudingBox: BoundingBoxObject, magOfBB: Vec }; } -export function applyLabeledVoxelMapToAllMissingResolutions( +export function applyLabeledVoxelMapToAllMissingMags( inputLabeledVoxelMap: LabeledVoxelsMap, labeledZoomStep: number, dimensionIndices: DimensionMap, - resolutionInfo: MagInfo, + magInfo: MagInfo, segmentationCube: DataCube, segmentId: number, thirdDimensionOfSlice: number, // this value is specified in global (mag1) coords @@ -104,21 +104,21 @@ export function applyLabeledVoxelMapToAllMissingResolutions( }; }; - // Get all available resolutions and divide the list into two parts. + // Get all available magnifications and divide the list into two parts. // The pivotIndex is the index within allResolutionsWithIndices which refers to - // the labeled resolution. + // the labeled mag. // `downsampleSequence` contains the current mag and all higher mags (to which // should be downsampled) // `upsampleSequence` contains the current mag and all lower mags (to which // should be upsampled) - const labeledResolution = resolutionInfo.getMagByIndexOrThrow(labeledZoomStep); - const allResolutionsWithIndices = resolutionInfo.getMagsWithIndices(); + const labeledResolution = magInfo.getMagByIndexOrThrow(labeledZoomStep); + const allResolutionsWithIndices = magInfo.getMagsWithIndices(); const pivotIndex = allResolutionsWithIndices.findIndex(([index]) => index === labeledZoomStep); const downsampleSequence = allResolutionsWithIndices.slice(pivotIndex); const upsampleSequence = allResolutionsWithIndices.slice(0, pivotIndex + 1).reverse(); - // Given a sequence of resolutions, the inputLabeledVoxelMap is applied - // over all these resolutions. + // Given a sequence of mags, the inputLabeledVoxelMap is applied + // over all these mags. function processSamplingSequence( samplingSequence: Array<[number, Vector3]>, getNumberOfSlices: (arg0: Vector3) => number, @@ -130,7 +130,7 @@ export function applyLabeledVoxelMapToAllMissingResolutions( for (const [source, target] of pairwise(samplingSequence)) { const [sourceZoomStep, sourceResolution] = source; const [targetZoomStep, targetResolution] = target; - currentLabeledVoxelMap = sampleVoxelMapToResolution( + currentLabeledVoxelMap = sampleVoxelMapToMagnification( currentLabeledVoxelMap, segmentationCube, sourceResolution, @@ -154,7 +154,7 @@ export function applyLabeledVoxelMapToAllMissingResolutions( } } - // First upsample the voxel map and apply it to all better resolutions. + // First upsample the voxel map and apply it to all better mags. // sourceZoomStep will be higher than targetZoomStep processSamplingSequence(upsampleSequence, (targetResolution) => Math.ceil(labeledResolution[thirdDim] / targetResolution[thirdDim]), @@ -263,7 +263,7 @@ export function* labelWithVoxelBuffer2D( // thirdDimensionOfSlice needs to be provided in global coordinates const thirdDimensionOfSlice = topLeft3DCoord[dimensionIndices[2]] * labeledResolution[dimensionIndices[2]]; - applyLabeledVoxelMapToAllMissingResolutions( + applyLabeledVoxelMapToAllMissingMags( currentLabeledVoxelMap, labeledZoomStep, dimensionIndices, diff --git a/frontend/javascripts/oxalis/model/sagas/volumetracing_saga.tsx b/frontend/javascripts/oxalis/model/sagas/volumetracing_saga.tsx index 48179962aa2..13fc5f01d81 100644 --- a/frontend/javascripts/oxalis/model/sagas/volumetracing_saga.tsx +++ b/frontend/javascripts/oxalis/model/sagas/volumetracing_saga.tsx @@ -46,7 +46,7 @@ import { enforceActiveVolumeTracing, getActiveSegmentationTracing, getMaximumBrushSize, - getRenderableResolutionForSegmentationTracing, + getRenderableMagForSegmentationTracing, getRequestedOrVisibleSegmentationLayer, getSegmentsForLayer, isVolumeAnnotationDisallowedForZoom, @@ -104,7 +104,7 @@ import { Model, api } from "oxalis/singletons"; import type { Flycam, SegmentMap, VolumeTracing } from "oxalis/store"; import { actionChannel, call, fork, put, takeEvery, takeLatest } from "typed-redux-saga"; import { - applyLabeledVoxelMapToAllMissingResolutions, + applyLabeledVoxelMapToAllMissingMags, createVolumeLayer, labelWithVoxelBuffer2D, type BooleanBox, @@ -213,7 +213,7 @@ export function* editVolumeLayerAsync(): Saga { } const maybeLabeledResolutionWithZoomStep = yield* select((state) => - getRenderableResolutionForSegmentationTracing(state, volumeTracing), + getRenderableMagForSegmentationTracing(state, volumeTracing), ); if (!maybeLabeledResolutionWithZoomStep) { @@ -499,7 +499,7 @@ export function* floodFill(): Saga { } } - console.time("applyLabeledVoxelMapToAllMissingResolutions"); + console.time("applyLabeledVoxelMapToAllMissingMags"); for (const indexZ of indexSet) { const labeledVoxelMapFromFloodFill: LabeledVoxelsMap = new Map(); @@ -512,7 +512,7 @@ export function* floodFill(): Saga { } } - applyLabeledVoxelMapToAllMissingResolutions( + applyLabeledVoxelMapToAllMissingMags( labeledVoxelMapFromFloodFill, labeledZoomStep, dimensionIndices, @@ -536,7 +536,7 @@ export function* floodFill(): Saga { ), ); - console.timeEnd("applyLabeledVoxelMapToAllMissingResolutions"); + console.timeEnd("applyLabeledVoxelMapToAllMissingMags"); if (wasBoundingBoxExceeded) { yield* call( @@ -604,7 +604,7 @@ export function* finishLayer( yield* put(registerLabelPointAction(layer.getUnzoomedCentroid())); } -export function* ensureToolIsAllowedInResolution(): Saga { +export function* ensureToolIsAllowedInMag(): Saga { yield* take("INITIALIZE_VOLUMETRACING"); while (true) { @@ -969,7 +969,7 @@ function* handleDeleteSegmentData(): Saga { export default [ editVolumeLayerAsync, handleDeleteSegmentData, - ensureToolIsAllowedInResolution, + ensureToolIsAllowedInMag, floodFill, watchVolumeTracingAsync, maintainSegmentsMap, diff --git a/frontend/javascripts/oxalis/model/volumetracing/volume_annotation_sampling.ts b/frontend/javascripts/oxalis/model/volumetracing/volume_annotation_sampling.ts index 0a7671fe7c6..70f88445805 100644 --- a/frontend/javascripts/oxalis/model/volumetracing/volume_annotation_sampling.ts +++ b/frontend/javascripts/oxalis/model/volumetracing/volume_annotation_sampling.ts @@ -10,9 +10,9 @@ import type { DimensionMap } from "oxalis/model/dimensions"; function upsampleVoxelMap( labeledVoxelMap: LabeledVoxelsMap, dataCube: DataCube, - sourceResolution: Vector3, + sourceMag: Vector3, sourceZoomStep: number, - targetResolution: Vector3, + targetMag: Vector3, targetZoomStep: number, dimensionIndices: DimensionMap, thirdDimensionVoxelValue: number, @@ -26,9 +26,9 @@ function upsampleVoxelMap( } const labeledVoxelMapInTargetResolution: LabeledVoxelsMap = new Map(); - const scaleToSource = map3((val, index) => val / sourceResolution[index], targetResolution); + const scaleToSource = map3((val, index) => val / sourceMag[index], targetMag); // This array serves multiple purposes. It has a name / variable for each purpose. - const scaleToGoal = map3((val, index) => val / targetResolution[index], sourceResolution); + const scaleToGoal = map3((val, index) => val / targetMag[index], sourceMag); const numberOfBucketWithinSourceBucket = scaleToGoal; const singleVoxelBoundsInTargetResolution = scaleToGoal; const boundsOfGoalBucketWithinSourceBucket = map3( @@ -37,7 +37,7 @@ function upsampleVoxelMap( ); // This is the buckets zoomed address part of the third dimension. const thirdDimensionBucketValue = Math.floor( - thirdDimensionVoxelValue / targetResolution[dimensionIndices[2]] / constants.BUCKET_WIDTH, + thirdDimensionVoxelValue / targetMag[dimensionIndices[2]] / constants.BUCKET_WIDTH, ); const warnAboutCouldNotCreate = _.once((zoomedAddress) => { @@ -155,23 +155,23 @@ function upsampleVoxelMap( function downsampleVoxelMap( labeledVoxelMap: LabeledVoxelsMap, dataCube: DataCube, - sourceResolution: Vector3, + sourceMag: Vector3, sourceZoomStep: number, - targetResolution: Vector3, + targetMag: Vector3, targetZoomStep: number, dimensionIndices: DimensionMap, ): LabeledVoxelsMap { // This method downsamples a LabeledVoxelsMap. For each bucket of the LabeledVoxelsMap - // the matching bucket of the lower resolution is determined and all the labeledVoxels - // are downsampled to the lower resolution bucket. The downsampling uses a kernel to skip + // the matching bucket of the lower magnification is determined and all the labeledVoxels + // are downsampled to the lower mag bucket. The downsampling uses a kernel to skip // checking whether to label a downsampled voxel if already one labeled voxel matching the downsampled voxel is found. if (targetZoomStep <= sourceZoomStep) { throw new Error("Trying to upsample a LabeledVoxelMap with the downsample function."); } const labeledVoxelMapInTargetResolution: LabeledVoxelsMap = new Map(); - const scaleToSource = map3((val, index) => val / sourceResolution[index], targetResolution); - const scaleToGoal = map3((val, index) => val / targetResolution[index], sourceResolution); + const scaleToSource = map3((val, index) => val / sourceMag[index], targetMag); + const scaleToGoal = map3((val, index) => val / targetMag[index], sourceMag); const warnAboutCouldNotCreate = _.once((zoomedAddress) => { console.warn(messages["sampling.could_not_get_or_create_bucket"](zoomedAddress)); @@ -270,12 +270,12 @@ function downsampleVoxelMap( return labeledVoxelMapInTargetResolution; } -export default function sampleVoxelMapToResolution( +export default function sampleVoxelMapToMagnification( labeledVoxelMap: LabeledVoxelsMap, dataCube: DataCube, - sourceResolution: Vector3, + sourceMag: Vector3, sourceZoomStep: number, - targetResolution: Vector3, + targetMag: Vector3, targetZoomStep: number, dimensionIndices: DimensionMap, thirdDimensionVoxelValue: number, @@ -284,9 +284,9 @@ export default function sampleVoxelMapToResolution( return downsampleVoxelMap( labeledVoxelMap, dataCube, - sourceResolution, + sourceMag, sourceZoomStep, - targetResolution, + targetMag, targetZoomStep, dimensionIndices, ); @@ -294,9 +294,9 @@ export default function sampleVoxelMapToResolution( return upsampleVoxelMap( labeledVoxelMap, dataCube, - sourceResolution, + sourceMag, sourceZoomStep, - targetResolution, + targetMag, targetZoomStep, dimensionIndices, thirdDimensionVoxelValue, diff --git a/frontend/javascripts/oxalis/model/volumetracing/volumelayer.ts b/frontend/javascripts/oxalis/model/volumetracing/volumelayer.ts index 2345139f094..81d4ffaff79 100644 --- a/frontend/javascripts/oxalis/model/volumetracing/volumelayer.ts +++ b/frontend/javascripts/oxalis/model/volumetracing/volumelayer.ts @@ -4,8 +4,8 @@ import { getBaseVoxelFactorsInUnit } from "oxalis/model/scaleinfo"; import { getVolumeTracingById } from "oxalis/model/accessors/volumetracing_accessor"; import { isBrushTool } from "oxalis/model/accessors/tool_accessor"; import { - scaleGlobalPositionWithResolution, - scaleGlobalPositionWithResolutionFloat, + scaleGlobalPositionWithMagnification, + scaleGlobalPositionWithMagnificationFloat, zoomedPositionToGlobalPosition, } from "oxalis/model/helpers/position_converter"; import type { OrthoView, Vector2, Vector3, AnnotationTool } from "oxalis/constants"; @@ -138,9 +138,9 @@ export class VoxelNeighborQueue2D extends VoxelNeighborQueue3D { class VolumeLayer { /* From the outside, the VolumeLayer accepts only global positions. Internally, - these are converted to the actual used resolution (activeResolution). - Therefore, members of this class are in the resolution space of - `activeResolution`. + these are converted to the actual used mags (activeMag). + Therefore, members of this class are in the mag space of + `activeMag`. */ volumeTracingId: string; plane: OrthoView; @@ -150,21 +150,21 @@ class VolumeLayer { minCoord: Vector3 | null | undefined; maxCoord: Vector3 | null | undefined; - activeResolution: Vector3; + activeMag: Vector3; constructor( volumeTracingId: string, plane: OrthoView, thirdDimensionValue: number, - activeResolution: Vector3, + activeMag: Vector3, ) { this.volumeTracingId = volumeTracingId; this.plane = plane; this.maxCoord = null; this.minCoord = null; - this.activeResolution = activeResolution; + this.activeMag = activeMag; const thirdDim = Dimensions.thirdDimensionForPlane(this.plane); - this.thirdDimensionValue = Math.floor(thirdDimensionValue / this.activeResolution[thirdDim]); + this.thirdDimensionValue = Math.floor(thirdDimensionValue / this.activeMag[thirdDim]); } addContour(globalPos: Vector3): void { @@ -172,7 +172,7 @@ class VolumeLayer { } updateArea(globalPos: Vector3): void { - const pos = scaleGlobalPositionWithResolution(globalPos, this.activeResolution); + const pos = scaleGlobalPositionWithMagnification(globalPos, this.activeMag); let [maxCoord, minCoord] = [this.maxCoord, this.minCoord]; if (maxCoord == null || minCoord == null) { @@ -204,8 +204,8 @@ class VolumeLayer { if (this.minCoord == null || this.maxCoord == null) { return null; } - const min = zoomedPositionToGlobalPosition(this.minCoord, this.activeResolution); - const max = zoomedPositionToGlobalPosition(this.maxCoord, this.activeResolution); + const min = zoomedPositionToGlobalPosition(this.minCoord, this.activeMag); + const max = zoomedPositionToGlobalPosition(this.maxCoord, this.activeMag); return new BoundingBox({ min, max }); } @@ -220,7 +220,7 @@ class VolumeLayer { } return globalContourList.map((point) => - scaleGlobalPositionWithResolutionFloat(point, this.activeResolution), + scaleGlobalPositionWithMagnificationFloat(point, this.activeMag), ); } @@ -378,19 +378,16 @@ class VolumeLayer { lastUnzoomedPosition: Vector3, unzoomedPosition: Vector3, ): VoxelBuffer2D | null { - const lastPosition = scaleGlobalPositionWithResolution( - lastUnzoomedPosition, - this.activeResolution, - ); - const position = scaleGlobalPositionWithResolution(unzoomedPosition, this.activeResolution); + const lastPosition = scaleGlobalPositionWithMagnification(lastUnzoomedPosition, this.activeMag); + const position = scaleGlobalPositionWithMagnification(unzoomedPosition, this.activeMag); const state = Store.getState(); const { brushSize } = state.userConfiguration; const radius = Math.round(brushSize / 2); // Use the baseVoxelFactors to scale the rectangle, otherwise it'll become deformed const scale = this.get2DCoordinate( - scaleGlobalPositionWithResolutionFloat( + scaleGlobalPositionWithMagnificationFloat( getBaseVoxelFactorsInUnit(state.dataset.dataSource.scale), - this.activeResolution, + this.activeMag, ), ); const floatingCoord2dLastPosition = this.get2DCoordinate(lastPosition); @@ -440,7 +437,7 @@ class VolumeLayer { globalCoordToMag2DFloat(position: Vector3): Vector2 { return this.get2DCoordinate( - scaleGlobalPositionWithResolutionFloat(position, this.activeResolution), + scaleGlobalPositionWithMagnificationFloat(position, this.activeMag), ); } @@ -449,8 +446,8 @@ class VolumeLayer { const { brushSize } = state.userConfiguration; const dimIndices = Dimensions.getIndices(this.plane); const unzoomedRadius = Math.round(brushSize / 2); - const width = Math.floor((2 * unzoomedRadius) / this.activeResolution[dimIndices[0]]); - const height = Math.floor((2 * unzoomedRadius) / this.activeResolution[dimIndices[1]]); + const width = Math.floor((2 * unzoomedRadius) / this.activeMag[dimIndices[0]]); + const height = Math.floor((2 * unzoomedRadius) / this.activeMag[dimIndices[1]]); const floatingCoord2d = this.globalCoordToMag2DFloat(position); const radiusOffset = Dimensions.transDim([unzoomedRadius, unzoomedRadius, 0], this.plane); @@ -475,12 +472,12 @@ class VolumeLayer { }; Drawing.fillCircle( - Math.floor(unzoomedRadius / this.activeResolution[dimIndices[0]]), - Math.floor(unzoomedRadius / this.activeResolution[dimIndices[1]]), // the unzoomedRadius is adapted to the correct resolution by the + Math.floor(unzoomedRadius / this.activeMag[dimIndices[0]]), + Math.floor(unzoomedRadius / this.activeMag[dimIndices[1]]), // the unzoomedRadius is adapted to the correct mag by the // following scale parameters unzoomedRadius, - scaleX / this.activeResolution[dimIndices[0]], - scaleY / this.activeResolution[dimIndices[1]], + scaleX / this.activeMag[dimIndices[0]], + scaleY / this.activeMag[dimIndices[1]], setMap, ); return buffer2D; @@ -545,13 +542,13 @@ class VolumeLayer { const area = sumArea / 2; if (area === 0) { - return zoomedPositionToGlobalPosition(contourList[0], this.activeResolution); + return zoomedPositionToGlobalPosition(contourList[0], this.activeMag); } const cx = sumCx / 6 / area; const cy = sumCy / 6 / area; const zoomedPosition = this.get3DCoordinate([cx, cy]); - const pos = zoomedPositionToGlobalPosition(zoomedPosition, this.activeResolution); + const pos = zoomedPositionToGlobalPosition(zoomedPosition, this.activeMag); return pos; } } diff --git a/frontend/javascripts/oxalis/view/action-bar/download_modal_view.tsx b/frontend/javascripts/oxalis/view/action-bar/download_modal_view.tsx index ce921bfad1d..9be46f578f4 100644 --- a/frontend/javascripts/oxalis/view/action-bar/download_modal_view.tsx +++ b/frontend/javascripts/oxalis/view/action-bar/download_modal_view.tsx @@ -674,7 +674,7 @@ function _DownloadModalView({ @@ -695,7 +695,7 @@ function _DownloadModalView({ Estimated file size:{" "} {estimateFileSize(selectedLayer, mag, selectedBoundingBox.boundingBox, exportFormat)}
- Resolution: {formatSelectedScale(dataset, mag)} + Magnification: {formatSelectedScale(dataset, mag)} diff --git a/frontend/javascripts/oxalis/view/action-bar/starting_job_modals.tsx b/frontend/javascripts/oxalis/view/action-bar/starting_job_modals.tsx index 9f84dbeffbb..2315658ed8b 100644 --- a/frontend/javascripts/oxalis/view/action-bar/starting_job_modals.tsx +++ b/frontend/javascripts/oxalis/view/action-bar/starting_job_modals.tsx @@ -252,16 +252,16 @@ function BoundingBoxSelectionFormItem({ } export function MagSlider({ - resolutionInfo, + magnificationInfo, value, onChange, }: { - resolutionInfo: MagInfo; + magnificationInfo: MagInfo; value: Vector3; onChange: (v: Vector3) => void; }) { // Use `getResolutionsWithIndices` because returns a sorted list - const allMags = resolutionInfo.getMagsWithIndices(); + const allMags = magnificationInfo.getMagsWithIndices(); return ( Note that this feature is still experimental. Nuclei detection currently only works - with EM data and a resolution of approximately 200{ThinSpace}nm per voxel. The + with EM data and a magnification of approximately 200{ThinSpace}nm per voxel. The segmentation process will automatically use the magnification that matches that - resolution best. + magnification best.

diff --git a/frontend/javascripts/oxalis/view/action-bar/toolbar_view.tsx b/frontend/javascripts/oxalis/view/action-bar/toolbar_view.tsx index c5b0237d690..b748e064d87 100644 --- a/frontend/javascripts/oxalis/view/action-bar/toolbar_view.tsx +++ b/frontend/javascripts/oxalis/view/action-bar/toolbar_view.tsx @@ -38,7 +38,7 @@ import { getActiveSegmentationTracing, getMappingInfoForVolumeTracing, getMaximumBrushSize, - getRenderableResolutionForActiveSegmentationTracing, + getRenderableMagForActiveSegmentationTracing, getSegmentColorAsRGBA, hasAgglomerateMapping, hasEditableMapping, @@ -864,15 +864,13 @@ export default function ToolbarView() { (state: OxalisState) => state.userConfiguration.useLegacyBindings, ); const activeTool = useSelector((state: OxalisState) => state.uiInformation.activeTool); - const maybeResolutionWithZoomStep = useSelector( - getRenderableResolutionForActiveSegmentationTracing, - ); + const maybeResolutionWithZoomStep = useSelector(getRenderableMagForActiveSegmentationTracing); const labeledResolution = maybeResolutionWithZoomStep != null ? maybeResolutionWithZoomStep.resolution : null; const hasResolutionWithHigherDimension = (labeledResolution || []).some((val) => val > 1); const multiSliceAnnotationInfoIcon = hasResolutionWithHigherDimension ? ( - + = { dataset: APIDataset; volumeTracings: VolumeTracing[]; userBoundingBoxes: UserBoundingBox[]; - volumeTracingResolutions: Vector3[][]; + volumeTracingMags: Vector3[][]; }; enum AiModelCategory { @@ -142,7 +142,7 @@ export function TrainAiModelFromAnnotationTab({ onClose }: { onClose: () => void annotation: tracing, dataset, volumeTracings: tracing.volumes, - volumeTracingResolutions: [], + volumeTracingMags: [], userBoundingBoxes, }, ]} @@ -511,7 +511,7 @@ function AnnotationsCsvInput({ annotation, dataset, volumeTracings, - volumeTracingResolutions: volumeServerTracings.map(({ mags: resolutions }) => + volumeTracingMags: volumeServerTracings.map(({ mags: resolutions }) => resolutions ? resolutions.map(Utils.point3ToVector3) : ([[1, 1, 1]] as Vector3[]), ), userBoundingBoxes: userBoundingBoxes || [], diff --git a/frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx b/frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx index 30cf80483aa..08814f1a275 100644 --- a/frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx +++ b/frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx @@ -142,7 +142,7 @@ type DatasetSettingsProps = { onChangeRadius: (value: number) => void; onChangeShowSkeletons: (arg0: boolean) => void; onSetPosition: (arg0: Vector3) => void; - onZoomToResolution: (layerName: string, arg0: Vector3) => number; + onZoomToMag: (layerName: string, arg0: Vector3) => number; onChangeUser: (key: keyof UserConfiguration, value: any) => void; reloadHistogram: (layerName: string) => void; tracing: Tracing; @@ -311,7 +311,7 @@ function LayerInfoIconWithTooltip({
Data Type: {elementClass}
- Available resolutions: + Available magnifications:
    {resolutions.map((r) => (
  • {r.join("-")}
  • @@ -1060,7 +1060,7 @@ class DatasetSettings extends React.PureComponent { } this.props.onSetPosition(foundPosition); - const zoomValue = this.props.onZoomToResolution(layerName, foundResolution); + const zoomValue = this.props.onZoomToMag(layerName, foundResolution); Toast.success( `Jumping to position ${foundPosition .map((el) => Math.floor(el)) @@ -1141,7 +1141,7 @@ class DatasetSettings extends React.PureComponent { verticalAlign: "top", cursor: "pointer", }} - alt="Resolution Icon" + alt="Magnification Icon" /> @@ -1609,7 +1609,7 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ dispatch(setShowSkeletonsAction(showSkeletons)); }, - onZoomToResolution(layerName: string, resolution: Vector3) { + onZoomToMag(layerName: string, resolution: Vector3) { const targetZoomValue = getMaxZoomValueForResolution(Store.getState(), layerName, resolution); dispatch(setZoomStepAction(targetZoomValue)); return targetZoomValue; diff --git a/frontend/javascripts/oxalis/view/left-border-tabs/modals/downsample_volume_modal.tsx b/frontend/javascripts/oxalis/view/left-border-tabs/modals/downsample_volume_modal.tsx index 22a77369817..7142b602e27 100644 --- a/frontend/javascripts/oxalis/view/left-border-tabs/modals/downsample_volume_modal.tsx +++ b/frontend/javascripts/oxalis/view/left-border-tabs/modals/downsample_volume_modal.tsx @@ -32,24 +32,26 @@ export default function DownsampleVolumeModal({ open >

    - This annotation does not have volume annotation data in all resolutions. Consequently, + This annotation does not have volume annotation data in all magnifications. Consequently, annotation data cannot be rendered at all zoom values. By clicking "Downsample", - WEBKNOSSOS will use the best resolution of the volume data to create all dependent - resolutions. + WEBKNOSSOS will use the best magnification of the volume data to create all dependent mags.

    - The following resolutions will be added when clicking "Downsample":{" "} + The following magnifications will be added when clicking "Downsample":{" "} {magsToDownsample.map((mag) => mag.join("-")).join(", ")}.

    - The cause for the missing resolutions can be one of the following: + The cause for the missing magnifications can be one of the following:
    • - The annotation was created before WEBKNOSSOS supported multi-resolution volume tracings. + The annotation was created before WEBKNOSSOS supported multi-magnification volume + tracings.
    • -
    • An old annotation was uploaded which did not include all resolutions.
    • -
    • The annotation was created in a task that was restricted to certain resolutions.
    • -
    • The dataset was mutated to have more resolutions.
    • +
    • An old annotation was uploaded which did not include all magnifications.
    • +
    • + The annotation was created in a task that was restricted to certain magnifications. +
    • +
    • The dataset was mutated to have more magnifications.

    ; + activeMagInfo: ReturnType; isDatasetViewMode: boolean; mayEditAnnotation: boolean; }; @@ -528,7 +528,7 @@ export class DatasetInfoTabView extends React.PureComponent { } renderResolutionsTooltip = () => { - const { dataset, annotation, activeResolutionInfo } = this.props; + const { dataset, annotation, activeMagInfo: activeResolutionInfo } = this.props; const { activeMagOfEnabledLayers } = activeResolutionInfo; const resolutionUnion = getMagnificationUnion(dataset); return ( @@ -545,7 +545,7 @@ export class DatasetInfoTabView extends React.PureComponent { ); })}

- Available resolutions: + Available magnifications:
    {resolutionUnion.map((mags) => (
  • {mags.map((mag) => mag.join("-")).join(", ")}
  • @@ -556,7 +556,7 @@ export class DatasetInfoTabView extends React.PureComponent { }; getResolutionInfo() { - const { activeResolutionInfo } = this.props; + const { activeMagInfo: activeResolutionInfo } = this.props; const { representativeResolution, isActiveResolutionGlobal } = activeResolutionInfo; return representativeResolution != null ? ( @@ -570,7 +570,7 @@ export class DatasetInfoTabView extends React.PureComponent { Resolution ({ task: state.task, activeUser: state.activeUser, isDatasetViewMode: state.temporaryConfiguration.controlMode === ControlModeEnum.VIEW, - activeResolutionInfo: getActiveResolutionInfo(state), + activeMagInfo: getActiveMagInfo(state), mayEditAnnotation: mayEditAnnotationProperties(state), }); diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx index 8d314383399..454346265c4 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/segments_tab/segments_view.tsx @@ -165,7 +165,7 @@ type StateProps = { activeCellId: number | null | undefined; preferredQualityForMeshPrecomputation: number; preferredQualityForMeshAdHocComputation: number; - resolutionInfoOfVisibleSegmentationLayer: MagInfo; + magInfoOfVisibleSegmentationLayer: MagInfo; }; const mapStateToProps = (state: OxalisState): StateProps => { @@ -220,7 +220,7 @@ const mapStateToProps = (state: OxalisState): StateProps => { state.temporaryConfiguration.preferredQualityForMeshPrecomputation, preferredQualityForMeshAdHocComputation: state.temporaryConfiguration.preferredQualityForMeshAdHocComputation, - resolutionInfoOfVisibleSegmentationLayer: getMagInfoOfVisibleSegmentationLayer(state), + magInfoOfVisibleSegmentationLayer: getMagInfoOfVisibleSegmentationLayer(state), }; }; @@ -826,7 +826,7 @@ class SegmentsView extends React.Component { const { mappingInfo, preferredQualityForMeshPrecomputation, - resolutionInfoOfVisibleSegmentationLayer: resolutionInfo, + magInfoOfVisibleSegmentationLayer: resolutionInfo, } = this.props; const defaultOrHigherIndex = resolutionInfo.getIndexOrClosestHigherIndex( preferredQualityForMeshPrecomputation, @@ -902,7 +902,7 @@ class SegmentsView extends React.Component { getAdHocMeshSettings = () => { const { preferredQualityForMeshAdHocComputation, - resolutionInfoOfVisibleSegmentationLayer: resolutionInfo, + magInfoOfVisibleSegmentationLayer: resolutionInfo, } = this.props; return (
    @@ -933,7 +933,7 @@ class SegmentsView extends React.Component { const { disabled, title } = this.getPrecomputeMeshesTooltipInfo(); const { preferredQualityForMeshPrecomputation, - resolutionInfoOfVisibleSegmentationLayer: resolutionInfo, + magInfoOfVisibleSegmentationLayer: resolutionInfo, } = this.props; return (
    ) : null} - + ); } @@ -498,13 +498,12 @@ function DownloadSpeedometer() { ); } -function ResolutionInfo() { - const { representativeResolution, isActiveResolutionGlobal } = - useSelector(getActiveResolutionInfo); +function MagnificationInfo() { + const { representativeResolution, isActiveResolutionGlobal } = useSelector(getActiveMagInfo); const renderMagTooltipContent = useCallback(() => { const state = Store.getState(); - const { activeMagOfEnabledLayers } = getActiveResolutionInfo(state); + const { activeMagOfEnabledLayers } = getActiveMagInfo(state); const dataset = state.dataset; const tracing = state.tracing; @@ -535,7 +534,7 @@ function ResolutionInfo() { Resolution{" "} {representativeResolution.join("-")} diff --git a/frontend/javascripts/test/fixtures/volumetracing_object.ts b/frontend/javascripts/test/fixtures/volumetracing_object.ts index e516a787cfa..d08dd93e64e 100644 --- a/frontend/javascripts/test/fixtures/volumetracing_object.ts +++ b/frontend/javascripts/test/fixtures/volumetracing_object.ts @@ -56,7 +56,7 @@ export const initialState = update(defaultState, { dataLayers: { $set: [ { - // We need to have some resolutions. Otherwise, + // We need to have some mags. Otherwise, // getRequestLogZoomStep will always return 0 resolutions: [ [1, 1, 1], diff --git a/frontend/javascripts/test/model/flycam_accessors.spec.ts b/frontend/javascripts/test/model/flycam_accessors.spec.ts index 5830b63e866..2440a2b0ac4 100644 --- a/frontend/javascripts/test/model/flycam_accessors.spec.ts +++ b/frontend/javascripts/test/model/flycam_accessors.spec.ts @@ -108,7 +108,7 @@ test("Flycam Accessors should calculate appropriate zoom factors for datasets wi TDView: rect, }; - const maximumZoomPerResolution = accessors._getMaximumZoomForAllResolutions( + const maximumZoomPerResolution = accessors._getMaximumZoomForAllMags( constants.MODE_PLANE_TRACING, "BEST_QUALITY_FIRST", scale, From ca6b25fe287ec861789c22827b1196f75f313f07 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Sat, 12 Oct 2024 16:46:54 +0200 Subject: [PATCH 07/37] rename in docs --- docs/automated_analysis.md | 2 +- docs/terminology.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/automated_analysis.md b/docs/automated_analysis.md index fff3bafd6b4..b447fdced71 100644 --- a/docs/automated_analysis.md +++ b/docs/automated_analysis.md @@ -2,7 +2,7 @@ While WEBKNOSSOS is great for manual annotation, some datasets are either too big to do by hand or you need results quicker. WEBKNOSSOS contains early access to automated analysis, including machine learning classifiers for dataset segmentations. The WEBKNOSSOS developer team has many years of experience with training AI models for large-scale data analysis outside of WEBKNOSSOS. We aim to bring some of this know-how directly into WEBKNOSSOS itself. -The automated analysis features are designed to provide a general solution to a wide range of (EM) datasets. Since datasets differ in staining protocols, imaging modalities, imaging resolution & fidelity, your results may vary. [Please contact us](mailto:hello@webknossos.org) for customized, fine-tuned solutions for your dataset. +The automated analysis features are designed to provide a general solution to a wide range of (EM) datasets. Since datasets differ in staining protocols, imaging modalities, imaging magnification & fidelity, your results may vary. [Please contact us](mailto:hello@webknossos.org) for customized, fine-tuned solutions for your dataset. We plan to add more automated analysis features in the future. If you want to work with us on an automated analysis project, [please contact us](mailto:hello@webknossos.org). We would love to integrate analysis solutions for more modalities and use cases. diff --git a/docs/terminology.md b/docs/terminology.md index a2b431a29f1..21f68764fa5 100644 --- a/docs/terminology.md +++ b/docs/terminology.md @@ -9,7 +9,7 @@ A **dataset**, can consist of multiple **layer**s, which may be Layers contain image data in one or multiple **magnification**s or short **mag**s (see [mipmap](https://en.wikipedia.org/wiki/Mipmap) or [image pyramids](https://en.wikipedia.org/wiki/Pyramid_(image_processing)) for similar concepts). The magnification `4` or `4-4-4` describes a downsampling factor of 4 in each dimension, `4-4-2` specifies anisotropic downsampling. -The image data in full resolution is referred to as the **finest** mag, e.g. `1-1-1`, downsampled variants are more **coarse**. +The image data in full magnification is referred to as the **finest** mag, e.g. `1-1-1`, downsampled variants are more **coarse**. The **voxel size** describes the size of a voxel in mag `1`, the default unit is *nm* if not specified otherwise. From 6ec5cf54c093c0c149413c5f003fd858335fa400 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Sat, 12 Oct 2024 17:35:16 +0200 Subject: [PATCH 08/37] rename node resolution to mag --- frontend/javascripts/oxalis/model/helpers/nml_helpers.ts | 4 ++-- .../oxalis/model/reducers/skeletontracing_reducer_helpers.ts | 4 ++-- frontend/javascripts/oxalis/store.ts | 2 +- .../view/right-border-tabs/connectome_tab/connectome_view.tsx | 2 +- frontend/javascripts/test/libs/nml.spec.ts | 2 +- .../javascripts/test/reducers/skeletontracing_reducer.spec.ts | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/frontend/javascripts/oxalis/model/helpers/nml_helpers.ts b/frontend/javascripts/oxalis/model/helpers/nml_helpers.ts index 95400af562d..e594576d314 100644 --- a/frontend/javascripts/oxalis/model/helpers/nml_helpers.ts +++ b/frontend/javascripts/oxalis/model/helpers/nml_helpers.ts @@ -412,7 +412,7 @@ function serializeNodes( rotY: node.rotation[1], rotZ: node.rotation[2], inVp: node.viewport, - inMag: node.resolution, + inMag: node.mag, bitDepth: node.bitDepth, interpolation: node.interpolation, time: node.timestamp, @@ -963,7 +963,7 @@ export function parseNml(nmlString: string): Promise<{ }), bitDepth: _parseInt(attr, "bitDepth", { defaultValue: DEFAULT_BITDEPTH }), viewport: _parseInt(attr, "inVp", { defaultValue: DEFAULT_VIEWPORT }), - resolution: _parseInt(attr, "inMag", { defaultValue: DEFAULT_RESOLUTION }), + mag: _parseInt(attr, "inMag", { defaultValue: DEFAULT_RESOLUTION }), radius: _parseFloat(attr, "radius", { defaultValue: Constants.DEFAULT_NODE_RADIUS }), timestamp: _parseTimestamp(attr, "time", { defaultValue: DEFAULT_TIMESTAMP }), }; diff --git a/frontend/javascripts/oxalis/model/reducers/skeletontracing_reducer_helpers.ts b/frontend/javascripts/oxalis/model/reducers/skeletontracing_reducer_helpers.ts index 08b30fe4d26..0864007e8c4 100644 --- a/frontend/javascripts/oxalis/model/reducers/skeletontracing_reducer_helpers.ts +++ b/frontend/javascripts/oxalis/model/reducers/skeletontracing_reducer_helpers.ts @@ -145,7 +145,7 @@ export function createNode( radius, rotation, viewport, - resolution, + mag: resolution, id: nextNewId, timestamp, bitDepth: state.datasetConfiguration.fourBit ? 4 : 8, @@ -817,7 +817,7 @@ function serverNodeToMutableNode(n: ServerNode): MutableNode { rotation: Utils.point3ToVector3(n.rotation), bitDepth: n.bitDepth, viewport: n.viewport, - resolution: n.resolution, + mag: n.resolution, radius: n.radius, timestamp: n.createdTimestamp, interpolation: n.interpolation, diff --git a/frontend/javascripts/oxalis/store.ts b/frontend/javascripts/oxalis/store.ts index 4fea149aee7..f36f1c95592 100644 --- a/frontend/javascripts/oxalis/store.ts +++ b/frontend/javascripts/oxalis/store.ts @@ -89,7 +89,7 @@ export type MutableNode = { rotation: Vector3; bitDepth: number; viewport: number; - resolution: number; // TODO_c maybe change? is this mag? + mag: number; radius: number; timestamp: number; interpolation: boolean; diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/connectome_tab/connectome_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/connectome_tab/connectome_view.tsx index 1b22d003da9..75535fa47b8 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/connectome_tab/connectome_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/connectome_tab/connectome_view.tsx @@ -163,7 +163,7 @@ const synapseNodeCreator = (synapseId: number, synapsePosition: Vector3): Mutabl radius: Constants.DEFAULT_NODE_RADIUS, rotation: [0, 0, 0], viewport: 0, - resolution: 0, + mag: 0, id: synapseId, timestamp: Date.now(), bitDepth: 8, diff --git a/frontend/javascripts/test/libs/nml.spec.ts b/frontend/javascripts/test/libs/nml.spec.ts index 5506e01c012..e73937386f7 100644 --- a/frontend/javascripts/test/libs/nml.spec.ts +++ b/frontend/javascripts/test/libs/nml.spec.ts @@ -34,7 +34,7 @@ const createDummyNode = (id: number): Node => ({ untransformedPosition: [id, id, id], additionalCoordinates: [], radius: id, - resolution: 10, + mag: 10, rotation: [id, id, id], timestamp: id, viewport: 1, diff --git a/frontend/javascripts/test/reducers/skeletontracing_reducer.spec.ts b/frontend/javascripts/test/reducers/skeletontracing_reducer.spec.ts index 2eb6a6858b4..6fcecc60bd7 100644 --- a/frontend/javascripts/test/reducers/skeletontracing_reducer.spec.ts +++ b/frontend/javascripts/test/reducers/skeletontracing_reducer.spec.ts @@ -295,7 +295,7 @@ test("SkeletonTracing should delete nodes and split the tree", (t) => { untransformedPosition: [0, 0, 0], additionalCoordinates: null, radius: 10, - resolution: 10, + mag: 10, rotation: [0, 0, 0], timestamp: 0, viewport: 1, @@ -453,7 +453,7 @@ test("SkeletonTracing should delete an edge and split the tree", (t) => { untransformedPosition: [0, 0, 0], additionalCoordinates: null, radius: 10, - resolution: 10, + mag: 10, rotation: [0, 0, 0], timestamp: 0, viewport: 1, From 84e733314b377b5ce7b11aa5ba4ffdb4980a2b01 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Sat, 12 Oct 2024 17:40:07 +0200 Subject: [PATCH 09/37] adjust mag slider --- .../dashboard/advanced_dataset/create_explorative_modal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/javascripts/dashboard/advanced_dataset/create_explorative_modal.tsx b/frontend/javascripts/dashboard/advanced_dataset/create_explorative_modal.tsx index d06ed04ecfe..94aec07d97d 100644 --- a/frontend/javascripts/dashboard/advanced_dataset/create_explorative_modal.tsx +++ b/frontend/javascripts/dashboard/advanced_dataset/create_explorative_modal.tsx @@ -113,7 +113,7 @@ export function RestrictMagnificationSlider({ marginBottom: 0, }} > - Restrict Volume Resolutions{" "} + Restrict Volume Magnifications{" "} Date: Sat, 12 Oct 2024 19:48:11 +0200 Subject: [PATCH 10/37] fix tests --- .../test/model/binary/layers/wkstore_adapter.spec.ts | 2 +- .../javascripts/test/reducers/skeletontracing_reducer.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/javascripts/test/model/binary/layers/wkstore_adapter.spec.ts b/frontend/javascripts/test/model/binary/layers/wkstore_adapter.spec.ts index de63f0f8dd9..d165419b8af 100644 --- a/frontend/javascripts/test/model/binary/layers/wkstore_adapter.spec.ts +++ b/frontend/javascripts/test/model/binary/layers/wkstore_adapter.spec.ts @@ -23,7 +23,7 @@ function setFourBit(bool: boolean) { const mockedCube = { isSegmentation: true, - resolutionInfo: new MagInfo([ + magInfo: new MagInfo([ [1, 1, 1], [2, 2, 2], ]), diff --git a/frontend/javascripts/test/reducers/skeletontracing_reducer.spec.ts b/frontend/javascripts/test/reducers/skeletontracing_reducer.spec.ts index 6fcecc60bd7..5cf6b5767bd 100644 --- a/frontend/javascripts/test/reducers/skeletontracing_reducer.spec.ts +++ b/frontend/javascripts/test/reducers/skeletontracing_reducer.spec.ts @@ -130,7 +130,7 @@ test("SkeletonTracing should add a new node", (t) => { untransformedPosition: position, rotation, viewport, - resolution, + mag: resolution, id: 1, radius: 1, }); From 7626df1416ffe8412badd0c5dee0058858d55138 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Sat, 12 Oct 2024 20:26:49 +0200 Subject: [PATCH 11/37] add explaining sentences --- .../oxalis/view/right-border-tabs/dataset_info_tab_view.tsx | 4 ++++ frontend/javascripts/oxalis/view/statusbar.tsx | 2 ++ 2 files changed, 6 insertions(+) diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/dataset_info_tab_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/dataset_info_tab_view.tsx index 9fed5275bbf..bc1c694df01 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/dataset_info_tab_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/dataset_info_tab_view.tsx @@ -551,6 +551,10 @@ export class DatasetInfoTabView extends React.PureComponent {
  • {mags.map((mag) => mag.join("-")).join(", ")}
  • ))}
+ Layers contain image data in one or multiple magnifications, or short mags. The + magnification `4` or `4-4-4` describes a downsampling factor of 4 in each dimension. The + image data in full magnification is referred to as the finest mag, e.g. `1-1-1`, downsampled + variants are more coarse.
); }; diff --git a/frontend/javascripts/oxalis/view/statusbar.tsx b/frontend/javascripts/oxalis/view/statusbar.tsx index 210811cc79a..0ae21ebb63a 100644 --- a/frontend/javascripts/oxalis/view/statusbar.tsx +++ b/frontend/javascripts/oxalis/view/statusbar.tsx @@ -521,6 +521,8 @@ function MagnificationInfo() { ); })} + Layers contain image data in one or multiple magnifications, or short mags. The + magnification `4` or `4-4-4` describes a downsampling factor of 4 in each dimension. ); }, []); From d93c850d2ca999174c0e29adc3d0ba63560935c2 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Mon, 14 Oct 2024 10:59:40 +0200 Subject: [PATCH 12/37] refresh snapshots --- .../annotations.e2e.js.md | 84 +++++++++--------- .../annotations.e2e.js.snap | Bin 12677 -> 12678 bytes .../backend-snapshot-tests/tasks.e2e.js.md | 26 +++--- .../backend-snapshot-tests/tasks.e2e.js.snap | Bin 5358 -> 5376 bytes .../tasktypes.e2e.js.md | 12 +-- .../tasktypes.e2e.js.snap | Bin 1341 -> 1343 bytes 6 files changed, 61 insertions(+), 61 deletions(-) diff --git a/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/annotations.e2e.js.md b/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/annotations.e2e.js.md index a04266f4614..c9937289818 100644 --- a/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/annotations.e2e.js.md +++ b/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/annotations.e2e.js.md @@ -78,8 +78,8 @@ Generated by [AVA](https://avajs.dev). 'flight', ], branchPointsAllowed: true, + magRestrictions: {}, mergerMode: false, - resolutionRestrictions: {}, somaClickingAllowed: true, volumeInterpolationAllowed: true, }, @@ -201,8 +201,8 @@ Generated by [AVA](https://avajs.dev). 'flight', ], branchPointsAllowed: true, + magRestrictions: {}, mergerMode: false, - resolutionRestrictions: {}, somaClickingAllowed: true, volumeInterpolationAllowed: true, }, @@ -445,8 +445,8 @@ Generated by [AVA](https://avajs.dev). 'flight', ], branchPointsAllowed: true, + magRestrictions: {}, mergerMode: true, - resolutionRestrictions: {}, somaClickingAllowed: true, volumeInterpolationAllowed: false, }, @@ -497,8 +497,8 @@ Generated by [AVA](https://avajs.dev). 'flight', ], branchPointsAllowed: true, + magRestrictions: {}, mergerMode: true, - resolutionRestrictions: {}, somaClickingAllowed: true, volumeInterpolationAllowed: false, }, @@ -617,8 +617,8 @@ Generated by [AVA](https://avajs.dev). 'flight', ], branchPointsAllowed: true, + magRestrictions: {}, mergerMode: true, - resolutionRestrictions: {}, somaClickingAllowed: true, volumeInterpolationAllowed: false, }, @@ -669,8 +669,8 @@ Generated by [AVA](https://avajs.dev). 'flight', ], branchPointsAllowed: true, + magRestrictions: {}, mergerMode: true, - resolutionRestrictions: {}, somaClickingAllowed: true, volumeInterpolationAllowed: false, }, @@ -796,8 +796,8 @@ Generated by [AVA](https://avajs.dev). 'flight', ], branchPointsAllowed: true, + magRestrictions: {}, mergerMode: false, - resolutionRestrictions: {}, somaClickingAllowed: true, volumeInterpolationAllowed: true, }, @@ -922,8 +922,8 @@ Generated by [AVA](https://avajs.dev). 'flight', ], branchPointsAllowed: true, + magRestrictions: {}, mergerMode: false, - resolutionRestrictions: {}, somaClickingAllowed: true, volumeInterpolationAllowed: true, }, @@ -1045,8 +1045,8 @@ Generated by [AVA](https://avajs.dev). 'flight', ], branchPointsAllowed: true, + magRestrictions: {}, mergerMode: false, - resolutionRestrictions: {}, somaClickingAllowed: true, volumeInterpolationAllowed: true, }, @@ -1168,8 +1168,8 @@ Generated by [AVA](https://avajs.dev). 'flight', ], branchPointsAllowed: true, + magRestrictions: {}, mergerMode: false, - resolutionRestrictions: {}, somaClickingAllowed: true, volumeInterpolationAllowed: true, }, @@ -1276,7 +1276,7 @@ Generated by [AVA](https://avajs.dev). hasSegmentIndex: true, id: 'id', largestSegmentId: 0, - resolutions: [ + mags: [ { x: 1, y: 1, @@ -1366,7 +1366,7 @@ Generated by [AVA](https://avajs.dev). hasSegmentIndex: true, id: 'id', largestSegmentId: 0, - resolutions: [ + mags: [ { x: 1, y: 1, @@ -1510,13 +1510,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 10120, y: 3727, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -1530,13 +1530,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 9120, y: 3727, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -1550,13 +1550,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 8120, y: 3727, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -1570,13 +1570,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 7120, y: 3727, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -1590,13 +1590,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 6120, y: 3727, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -1610,13 +1610,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 5120, y: 3727, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -1671,13 +1671,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 10120, y: 3726, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -1691,13 +1691,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 9120, y: 3726, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -1711,13 +1711,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 8120, y: 3726, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -1731,13 +1731,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 7120, y: 3726, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -1751,13 +1751,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 6120, y: 3726, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -1771,13 +1771,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 5120, y: 3726, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -1832,13 +1832,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 10120, y: 3726, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -1852,13 +1852,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 9120, y: 3726, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -1872,13 +1872,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 8120, y: 3726, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -1892,13 +1892,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 7120, y: 3726, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -1912,13 +1912,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 6120, y: 3726, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -1932,13 +1932,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 5120, y: 3726, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -1993,13 +1993,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 10120, y: 3725, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -2013,13 +2013,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 9120, y: 3725, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -2033,13 +2033,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 8120, y: 3725, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -2053,13 +2053,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 7120, y: 3725, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -2073,13 +2073,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 6120, y: 3725, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -2093,13 +2093,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 5120, y: 3725, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -2154,13 +2154,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 10120, y: 3725, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -2174,13 +2174,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 9120, y: 3725, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -2194,13 +2194,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 8120, y: 3725, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -2214,13 +2214,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 7120, y: 3725, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -2234,13 +2234,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 6120, y: 3725, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, @@ -2254,13 +2254,13 @@ Generated by [AVA](https://avajs.dev). createdTimestamp: 'createdTimestamp', id: 'id', interpolation: true, + mag: 1, position: { x: 5120, y: 3725, z: 1545, }, radius: 112.39999389648438, - resolution: 1, rotation: { x: 0, y: 270, diff --git a/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/annotations.e2e.js.snap b/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/annotations.e2e.js.snap index 3b4745a1a18190744be0daf75af08b2cc81fa465..9a5087412946c754e9cc636e65b11c9f39711ae6 100644 GIT binary patch literal 12678 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 A=Kufz literal 12677 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 literal 5358 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 BofrTB literal 1341 zcmV-D1;YA4RzVx)Pvv-^B?wRI6tbd-Y+9#2q71~GfLFhyAp-Kf6d=mrv2#u9S2%XobUrV((AB8WTavUqN4rQJ;$PoV|D@GpZ1s(#{NP4xTb_9VnCQ>hTc6K7|h(bC&I|k{kW)3^>Ai(#wH=9S6uY{*CS2@RuwTJz3O-iw zi-N2H4;T;{@V1d~k#2Rd63HT+tP!~1XlKB$6x~ua zuo{t#mB=cQ&kd*>WJ4WAZlx3^9A(04xoibv_2;#E_;7w}@0P<4yCc451p{%X6E^8c zTjgET`dpao^Oi!b&vwEr8=LL1%DNFdt~h+~zGNb!z_Q&+DPB=i-5=JTj{M4!r@mNR zNa~fjz|{Bl^~@*rz1#Zxy9-N}d}YEvW1W3Er>6 zC~)n#I4xx8m#qQMwTInGY1WXpJm-7WvffpRbP!K-?5)M1dBon;A93eIpz&oMaaPQ; zX*idL3u*X0-MTxNExSXub-ARHE6r@nKxYQ-&cHyX)m3&jS8c7X+KKzOA|6a5JMDfD z4f!R%VtKV!A`Mmf1+_VYLvjRfY^B|%NMM>p7kwjy2EZqBMWIVYa%M%9#asi61KVNGiy&&+dz$Jk@6g*lJ>iCt4 z^oyku>91C44FI*(zGzkIt;B+JP%n>AuyJ> z$*SZ{RwnMRFE?Vp2Yir>ZOjxqx%6#YhhhEso|q81D5l26WAD^-aPip5T&-QP>Um=| zm^d}wj8sRQCfqb173y>`A8X1}dpz_(AijN{2fq35*w8MpQD9KuxWJjY3DdU3$AwiQ z-^q-VHh(OcYW`U2s_4;8kq)Cx*CwCTg9>)4*%oeAGJ6#~r65#rQo%&K7!&KL0kt7xiJ6e>FtQ1O-uD&xx!m9qw%n;R;B$0;)5Rug(|EU5eozQ6Q;e;5D&Wr&Q8 From bedf85cdd3b0d0091d854874c40c0cbb870eb4f2 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Mon, 14 Oct 2024 11:44:43 +0200 Subject: [PATCH 13/37] improve explanation in info tab and status bar --- frontend/javascripts/messages.tsx | 2 ++ .../view/right-border-tabs/dataset_info_tab_view.tsx | 8 +++----- frontend/javascripts/oxalis/view/statusbar.tsx | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/frontend/javascripts/messages.tsx b/frontend/javascripts/messages.tsx index b0f8eae1260..f14403762a6 100644 --- a/frontend/javascripts/messages.tsx +++ b/frontend/javascripts/messages.tsx @@ -371,6 +371,8 @@ instead. Only enable this option if you understand its effect. All layers will n "This dataset contains multiple layers which differ in their magnification. Please convert the layers to make their magnifications match. Otherwise, rendering errors cannot be avoided.", "dataset.z1_downsampling_hint": "The currently rendered quality is not optimal due to the available magnifications and the viewport arrangement. To improve the quality try to increase the size of the XY viewport (e.g. by maximizing it).", + "dataset.mag_explanation": + "Layers contain image data in one or multiple magnifications. The image data in full magnification is referred to as the finest mag, e.g. mag 1-1-1. Magnification 4-4-4 describes a downsampling factor of 4 in each dimension.", "annotation.finish": "Are you sure you want to permanently finish this annotation?", "annotation.was_finished": "Annotation was archived", "annotation.no_fallback_data_included": diff --git a/frontend/javascripts/oxalis/view/right-border-tabs/dataset_info_tab_view.tsx b/frontend/javascripts/oxalis/view/right-border-tabs/dataset_info_tab_view.tsx index bc1c694df01..384633c2089 100644 --- a/frontend/javascripts/oxalis/view/right-border-tabs/dataset_info_tab_view.tsx +++ b/frontend/javascripts/oxalis/view/right-border-tabs/dataset_info_tab_view.tsx @@ -31,6 +31,7 @@ import { getReadableNameForLayerName } from "oxalis/model/accessors/volumetracin import { getOrganization } from "admin/admin_rest_api"; import { MarkdownModal } from "../components/markdown_modal"; import FastTooltip from "components/fast_tooltip"; +import messages from "messages"; type StateProps = { annotation: Tracing; @@ -532,7 +533,7 @@ export class DatasetInfoTabView extends React.PureComponent { const { activeMagOfEnabledLayers } = activeResolutionInfo; const resolutionUnion = getMagnificationUnion(dataset); return ( -
+
Rendered magnification per layer:
    {Object.entries(activeMagOfEnabledLayers).map(([layerName, mag]) => { @@ -551,10 +552,7 @@ export class DatasetInfoTabView extends React.PureComponent {
  • {mags.map((mag) => mag.join("-")).join(", ")}
  • ))}
- Layers contain image data in one or multiple magnifications, or short mags. The - magnification `4` or `4-4-4` describes a downsampling factor of 4 in each dimension. The - image data in full magnification is referred to as the finest mag, e.g. `1-1-1`, downsampled - variants are more coarse. + {messages["dataset.mag_explanation"]}
); }; diff --git a/frontend/javascripts/oxalis/view/statusbar.tsx b/frontend/javascripts/oxalis/view/statusbar.tsx index 0ae21ebb63a..3a60b06ccc0 100644 --- a/frontend/javascripts/oxalis/view/statusbar.tsx +++ b/frontend/javascripts/oxalis/view/statusbar.tsx @@ -38,6 +38,7 @@ import { useInterval } from "libs/react_helpers"; import type { AdditionalCoordinate } from "types/api_flow_types"; import FastTooltip from "components/fast_tooltip"; import { Store } from "oxalis/singletons"; +import messages from "messages"; const lineColor = "rgba(255, 255, 255, 0.67)"; const moreIconStyle = { @@ -508,7 +509,7 @@ function MagnificationInfo() { const tracing = state.tracing; return ( - <> +
Rendered magnification per layer:
    {Object.entries(activeMagOfEnabledLayers).map(([layerName, mag]) => { @@ -521,9 +522,8 @@ function MagnificationInfo() { ); })}
- Layers contain image data in one or multiple magnifications, or short mags. The - magnification `4` or `4-4-4` describes a downsampling factor of 4 in each dimension. - + {messages["dataset.mag_explanation"]} +
); }, []); From eaf7fec83b4c12c695e138eb88face1539e62ad2 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Mon, 14 Oct 2024 12:27:44 +0200 Subject: [PATCH 14/37] adapt param name --- conf/application.conf | 4 ++-- frontend/javascripts/admin/admin_rest_api.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/conf/application.conf b/conf/application.conf index 07d8b5d2dd1..85db10f41cf 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -151,8 +151,8 @@ features { taskReopenAllowedInSeconds = 30 allowDeleteDatasets = true # to enable jobs for local development, use "yarn enable-jobs" to also activate it in the database - jobsEnabled = false - voxelyticsEnabled = false + jobsEnabled = true + voxelyticsEnabled = true # For new users, the dashboard will show a banner which encourages the user to check out the following dataset. # If isWkorgInstance == true, `/createExplorative/hybrid/true` is appended to the URL so that a new tracing is opened. # If isWkorgInstance == false, `/view` is appended to the URL so that it's opened in view mode (since the user might not diff --git a/frontend/javascripts/admin/admin_rest_api.ts b/frontend/javascripts/admin/admin_rest_api.ts index db0ed5d2386..5d15a414597 100644 --- a/frontend/javascripts/admin/admin_rest_api.ts +++ b/frontend/javascripts/admin/admin_rest_api.ts @@ -795,7 +795,7 @@ export function createExplorational( autoFallbackLayer: boolean, fallbackLayerName?: string | null | undefined, mappingName?: string | null | undefined, - resolutionRestrictions?: APIMagRestrictions | null | undefined, + magRestrictions?: APIMagRestrictions | null | undefined, options: RequestOptions = {}, ): Promise { const url = `/api/datasets/${datasetId.owningOrganization}/${datasetId.name}/createExplorational`; @@ -816,7 +816,7 @@ export function createExplorational( fallbackLayerName, autoFallbackLayer, mappingName, - magRestrictions: resolutionRestrictions, + magRestrictions: magRestrictions, }, ]; } else { @@ -831,7 +831,7 @@ export function createExplorational( fallbackLayerName, autoFallbackLayer, mappingName, - magRestrictions: resolutionRestrictions, + magRestrictions: magRestrictions, }, ]; } From 9be7f8b87859025b820c449af50b2d1b5c3b4ffc Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Mon, 14 Oct 2024 13:41:53 +0200 Subject: [PATCH 15/37] remove application conf edits --- conf/application.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conf/application.conf b/conf/application.conf index 85db10f41cf..07d8b5d2dd1 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -151,8 +151,8 @@ features { taskReopenAllowedInSeconds = 30 allowDeleteDatasets = true # to enable jobs for local development, use "yarn enable-jobs" to also activate it in the database - jobsEnabled = true - voxelyticsEnabled = true + jobsEnabled = false + voxelyticsEnabled = false # For new users, the dashboard will show a banner which encourages the user to check out the following dataset. # If isWkorgInstance == true, `/createExplorative/hybrid/true` is appended to the URL so that a new tracing is opened. # If isWkorgInstance == false, `/view` is appended to the URL so that it's opened in view mode (since the user might not From b7c4eccaa9e5a2753c05de5e338ccbaf6e33cd3b Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Mon, 14 Oct 2024 13:57:36 +0200 Subject: [PATCH 16/37] lint --- .../create_explorative_modal.tsx | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/frontend/javascripts/dashboard/advanced_dataset/create_explorative_modal.tsx b/frontend/javascripts/dashboard/advanced_dataset/create_explorative_modal.tsx index 375975bf542..358e72d3722 100644 --- a/frontend/javascripts/dashboard/advanced_dataset/create_explorative_modal.tsx +++ b/frontend/javascripts/dashboard/advanced_dataset/create_explorative_modal.tsx @@ -41,8 +41,8 @@ export function NewVolumeLayerSelection({ const selectedSegmentationLayerIndex = selectedSegmentationLayerName != null ? segmentationLayers.indexOf( - getSegmentationLayerByName(dataset, selectedSegmentationLayerName), - ) + getSegmentationLayerByName(dataset, selectedSegmentationLayerName), + ) : -1; return (
0 && - selectedSegmentationLayerName != null + segmentationLayers.length > 0 && + selectedSegmentationLayerName != null ? getSegmentationLayerByName(dataset, selectedSegmentationLayerName) : null; const fallbackLayerGetParameter = @@ -242,12 +242,13 @@ function CreateExplorativeModal({ datasetId, onClose }: Props) { }} >