Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Auto-Select via SAM #7051

Merged
merged 83 commits into from
May 17, 2023
Merged
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
dd5fd1a
WIP: Auto-Select via SAM
fm3 May 4, 2023
f069f52
load data from datastore
fm3 May 4, 2023
6604240
send element class to sam server
fm3 May 4, 2023
3df1b35
mag handling
fm3 May 5, 2023
6d985ef
rough sam integration into frontend
philippotto May 5, 2023
1ab28af
Merge branch 'sam' of github.com:scalableminds/webknossos into sam
philippotto May 5, 2023
86b7466
use embedding route from backend instead of fetching precomputed embe…
philippotto May 5, 2023
1992858
small unrelated bug fix for canceling poll under certain race condition
philippotto May 8, 2023
7d82f92
switch for dynamic vs hardcoded embedding
philippotto May 8, 2023
46bf263
better error handling and disable fetching of hardcoded embedding
philippotto May 8, 2023
3077639
only use user-requested bbox when applying results for better perform…
philippotto May 8, 2023
14c40d9
request embedding for actual user position
philippotto May 8, 2023
d3c4fe9
implement caching and reuse of embedding
philippotto May 8, 2023
3f28c99
temporarily disable most CI checks
philippotto May 8, 2023
d87c3c3
restore application.conf setting for slick and swagger; add another c…
philippotto May 8, 2023
4f1d606
make it work in arbitrary mags
philippotto May 8, 2023
c5d8d7a
integrate mag into embedding cache
philippotto May 8, 2023
e88bc82
show busy indicator when loading embedding
philippotto May 8, 2023
ff85c21
remove the entire heuristic-based quickselect code
philippotto May 8, 2023
26ef93e
refactor and clean up so that ML and heuristic based quick select are…
philippotto May 8, 2023
289af62
allow to select AI or not for quick select in UI
philippotto May 8, 2023
8d53d74
fix inverted style
philippotto May 8, 2023
fe9b8f2
only allow quick-select with SAM on wkorg
philippotto May 9, 2023
bacc9fc
make sam select compatible with other viewports
philippotto May 9, 2023
205d0dc
refactor embedding request
philippotto May 9, 2023
c9760c5
fix linting
philippotto May 9, 2023
13bafd4
Revert "temporarily disable most CI checks"
philippotto May 9, 2023
e3cce00
prefetch embedding as soon as user presses mouse down
philippotto May 9, 2023
81545e9
use segmentAnythingEnabled instead of isWkorgInstance
philippotto May 9, 2023
36ae23c
update snapshots
philippotto May 9, 2023
798d198
also prefetch ORT session
philippotto May 9, 2023
2918785
use camel case in infer code
philippotto May 9, 2023
5151943
optimize extraction of mask
philippotto May 9, 2023
e17e4e2
rename some vars
philippotto May 9, 2023
c266b98
adapt analytics event
philippotto May 9, 2023
346a46a
update changelog
philippotto May 9, 2023
2ec15cd
Merge branch 'master' into sam
philippotto May 9, 2023
988d212
update docs
philippotto May 9, 2023
158f627
fix some deprecations
philippotto May 9, 2023
8f6b70a
Merge branch 'sam' of github.com:scalableminds/webknossos into sam
philippotto May 9, 2023
1f8adf7
don't cache failed embeddings; only support uint8 in ai mode
philippotto May 9, 2023
cb33595
Merge branch 'master' into sam
fm3 May 10, 2023
36c6ed9
re-add assertion, disable in conf by default
fm3 May 10, 2023
8d74ea5
update snapshot (segmentAnything disabled by default)
fm3 May 10, 2023
4400736
don't clamp min coord of bounding boxes to 0
philippotto May 10, 2023
099c3cc
remove unnecessary V3.max calls
philippotto May 10, 2023
e75a637
pr feedback
philippotto May 10, 2023
805c62d
catch error better if wasm cannot be loaded
philippotto May 10, 2023
5b2cc11
avoid redundant error toast
philippotto May 10, 2023
70eaf6f
further simplication for bbox
philippotto May 10, 2023
fa0bd00
add assertion for bbox < 1024**2
philippotto May 10, 2023
2b6bbcf
slice cache when adding to it instead of when accessing it
philippotto May 10, 2023
a469fcb
remove time measurement code
philippotto May 10, 2023
4706963
align user bbox to mag before using it to ai-select
philippotto May 10, 2023
df37965
more bbox alignment (except for geometry)
philippotto May 11, 2023
b940e28
fix onnxCoord interpretation for yz viewport
philippotto May 11, 2023
0568f5c
fix mag-alignment logic by rewriting the alignWithMag function to pro…
philippotto May 11, 2023
ec78745
fix usage of wrong cache entries because of zero-volume bounding boxe…
philippotto May 11, 2023
cf3ebed
fix that QuickSelectGeometry would be invisible when the third dimens…
philippotto May 11, 2023
5170fe5
extrude bounding boxes by correctly mag-adapted depth
philippotto May 11, 2023
32d79ce
add comments to inference code
philippotto May 11, 2023
bdde1a7
prefetch session as soon as quick select tool is activated
philippotto May 12, 2023
f30c90b
allow to cancel quick select with escape while drawing rectangle
philippotto May 12, 2023
e8606a1
fix invisible geometry
philippotto May 12, 2023
84046a3
clean up console.logs
philippotto May 12, 2023
d9ec7ad
fix linting
philippotto May 12, 2023
a1ef7ee
Merge branch 'master' into sam
normanrz May 15, 2023
1eb5585
include intensity min/max and element class in byte array sent to sam…
fm3 May 16, 2023
04b6479
Merge branch 'master' of github.com:scalableminds/webknossos into sam
philippotto May 16, 2023
6b25b11
assert min/max intensity is supplied if element class is float or double
fm3 May 16, 2023
deb3461
fix frontend typecheck
fm3 May 16, 2023
910495c
send intensity range to embedding endpoint if layer is not uint8
philippotto May 16, 2023
6c1c50f
Merge branch 'sam' of github.com:scalableminds/webknossos into sam
philippotto May 16, 2023
a81efb1
backend error messages
fm3 May 16, 2023
fd85947
use 4 bytes each for min/max of intensity range, not 8
fm3 May 16, 2023
b19c0cf
only show center marker when using old quick-select mode; fix scale o…
philippotto May 16, 2023
9990703
Merge branch 'sam' of github.com:scalableminds/webknossos into sam
philippotto May 16, 2023
d28e105
add comment
philippotto May 16, 2023
40aec28
send metadata with correct endianness
fm3 May 16, 2023
1ae7278
Merge branch 'sam' of github.com:scalableminds/webknossos into sam
fm3 May 16, 2023
1967637
take layer name into account when caching embedding
philippotto May 16, 2023
a28d084
Merge branch 'sam' of github.com:scalableminds/webknossos into sam
philippotto May 16, 2023
0c137d1
Merge branch 'master' into sam
fm3 May 17, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
### Added
- Added segment groups so that segments can be organized in a hierarchy (similar to skeletons). [#6966](https://github.com/scalableminds/webknossos/pull/6966)
- In addition to drag and drop, the selected tree(s) in the Skeleton tab can also be moved into another group by right-clicking the target group and selecting "Move selected tree(s) here". [#7005](https://github.com/scalableminds/webknossos/pull/7005)
- Added a machine-learning based quick select mode. Activate it via the "AI" button in the toolbar after selecting the quick-select tool. [#7051](https://github.com/scalableminds/webknossos/pull/7051)
- Added support for remote datasets encoded with [brotli](https://datatracker.ietf.org/doc/html/rfc7932). [#7041](https://github.com/scalableminds/webknossos/pull/7041)
- Teams can be edited more straight-forwardly in a popup in the team edit page. [#7043](https://github.com/scalableminds/webknossos/pull/7043)
- Annotations with Editable Mappings (a.k.a Supervoxel Proofreading) can now be merged. [#7026](https://github.com/scalableminds/webknossos/pull/7026)
Expand Down
42 changes: 40 additions & 2 deletions app/controllers/DataSetController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import play.api.i18n.{Messages, MessagesProvider}
import play.api.libs.functional.syntax._
import play.api.libs.json._
import play.api.mvc.{Action, AnyContent, PlayBodyParsers}
import utils.ObjectId
import utils.{ObjectId, WkConf}

import javax.inject.Inject
import scala.collection.mutable.ListBuffer
Expand All @@ -42,17 +42,27 @@ object DatasetUpdateParameters extends TristateOptionJsonHelper {
Json.configured(tristateOptionParsing).format[DatasetUpdateParameters]
}

case class SegmentAnythingEmbeddingParameters(
mag: Vec3Int,
boundingBox: BoundingBox
)

object SegmentAnythingEmbeddingParameters {
implicit val jsonFormat: Format[SegmentAnythingEmbeddingParameters] = Json.format[SegmentAnythingEmbeddingParameters]
}

@Api
class DataSetController @Inject()(userService: UserService,
userDAO: UserDAO,
dataSetService: DataSetService,
dataSetDataLayerDAO: DataSetDataLayerDAO,
dataStoreDAO: DataStoreDAO,
dataSetLastUsedTimesDAO: DataSetLastUsedTimesDAO,
organizationDAO: OrganizationDAO,
teamDAO: TeamDAO,
wKRemoteSegmentAnythingClient: WKRemoteSegmentAnythingClient,
teamService: TeamService,
dataSetDAO: DataSetDAO,
conf: WkConf,
analyticsService: AnalyticsService,
mailchimpClient: MailchimpClient,
exploreRemoteLayerService: ExploreRemoteLayerService,
Expand Down Expand Up @@ -520,4 +530,32 @@ Expects:
case _ => Messages("dataSet.notFoundConsiderLogin", dataSetName)
}

@ApiOperation(hidden = true, value = "")
def segmentAnythingEmbedding(organizationName: String,
dataSetName: String,
dataLayerName: String): Action[SegmentAnythingEmbeddingParameters] =
sil.SecuredAction.async(validateJson[SegmentAnythingEmbeddingParameters]) { implicit request =>
log() {
for {
_ <- bool2Fox(conf.Features.segmentAnythingEnabled) ?~> "segmentAnything.notEnabled"
_ <- bool2Fox(conf.SegmentAnything.uri.nonEmpty) ?~> "segmentAnything.noUri"
dataset <- dataSetDAO.findOneByNameAndOrganizationName(dataSetName, organizationName) ?~> notFoundMessage(
dataSetName) ~> NOT_FOUND
dataSource <- dataSetService.dataSourceFor(dataset) ?~> "dataSource.notFound" ~> NOT_FOUND
usableDataSource <- dataSource.toUsable ?~> "dataSet.notImported"
dataLayer <- usableDataSource.dataLayers.find(_.name == dataLayerName) ?~> "dataSet.noLayers"
datastoreClient <- dataSetService.clientFor(dataset)(GlobalAccessContext)
targetMagBbox: BoundingBox = request.body.boundingBox / request.body.mag
_ <- bool2Fox(targetMagBbox.dimensions.sorted == Vec3Int(1, 1024, 1024)) ?~> s"Target-mag bbox must be sized 1024×1024×1 (or transposed), got ${targetMagBbox.dimensions}"
data <- datastoreClient.getLayerData(organizationName,
dataset,
dataLayer.name,
request.body.boundingBox,
request.body.mag) ?~> "segmentAnything.getData.failed"
_ = logger.debug(s"Sending ${data.length} bytes to SAM server, element class is ${dataLayer.elementClass}...")
embedding <- wKRemoteSegmentAnythingClient.getEmbedding(data, dataLayer.elementClass) ?~> "segmentAnything.getEmbedding.failed"
} yield Ok(embedding)
}
}

}
7 changes: 3 additions & 4 deletions app/models/binary/DataSetService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import com.scalableminds.webknossos.datastore.models.datasource.{
import com.scalableminds.webknossos.datastore.rpc.RPC
import com.scalableminds.webknossos.datastore.storage.TemporaryStore
import com.typesafe.scalalogging.LazyLogging
import models.folder.{FolderDAO, FolderService}
import models.folder.FolderDAO

import javax.inject.Inject
import models.job.WorkerDAO
Expand All @@ -41,7 +41,6 @@ class DataSetService @Inject()(organizationDAO: OrganizationDAO,
dataStoreService: DataStoreService,
teamService: TeamService,
userService: UserService,
folderService: FolderService,
val thumbnailCache: TemporaryStore[String, Array[Byte]],
rpc: RPC,
conf: WkConf)(implicit ec: ExecutionContext)
Expand All @@ -66,7 +65,7 @@ class DataSetService @Inject()(organizationDAO: OrganizationDAO,
createDataSet(dataStore, organizationName, unreportedDatasource)
}

def createDataSet(
private def createDataSet(
dataStore: DataStore,
owningOrganization: String,
dataSource: InboxDataSource,
Expand Down Expand Up @@ -256,7 +255,7 @@ class DataSetService @Inject()(organizationDAO: OrganizationDAO,
dataStore <- dataStoreFor(dataSet)
} yield new WKRemoteDataStoreClient(dataStore, rpc)

def lastUsedTimeFor(_dataSet: ObjectId, userOpt: Option[User]): Fox[Instant] =
private def lastUsedTimeFor(_dataSet: ObjectId, userOpt: Option[User]): Fox[Instant] =
userOpt match {
case Some(user) =>
(for {
Expand Down
22 changes: 21 additions & 1 deletion app/models/binary/WKRemoteDataStoreClient.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package models.binary

import com.scalableminds.util.geometry.Vec3Int
import com.scalableminds.util.geometry.{BoundingBox, Vec3Int}
import com.scalableminds.util.tools.Fox
import com.scalableminds.webknossos.datastore.rpc.RPC
import com.scalableminds.webknossos.datastore.services.DirectoryStorageReport
Expand Down Expand Up @@ -29,6 +29,26 @@ class WKRemoteDataStoreClient(dataStore: DataStore, rpc: RPC) extends LazyLoggin
.getWithBytesResponse
}

def getLayerData(organizationName: String,
dataset: DataSet,
layerName: String,
mag1BoundingBox: BoundingBox,
mag: Vec3Int): Fox[Array[Byte]] = {
val targetMagBoundingBox = mag1BoundingBox / mag
logger.debug(s"Fetching raw data. Mag $mag, mag1 bbox: $mag1BoundingBox, target-mag bbox: $targetMagBoundingBox")
rpc(
s"${dataStore.url}/data/datasets/${urlEncode(organizationName)}/${dataset.urlEncodedName}/layers/$layerName/data")
.addQueryString("token" -> RpcTokenHolder.webKnossosToken)
.addQueryString("mag" -> mag.toMagLiteral())
.addQueryString("x" -> mag1BoundingBox.topLeft.x.toString)
.addQueryString("y" -> mag1BoundingBox.topLeft.y.toString)
.addQueryString("z" -> mag1BoundingBox.topLeft.z.toString)
.addQueryString("width" -> targetMagBoundingBox.width.toString)
.addQueryString("height" -> targetMagBoundingBox.height.toString)
.addQueryString("depth" -> targetMagBoundingBox.depth.toString)
.getWithBytesResponse
}

def findPositionWithData(organizationName: String, dataSet: DataSet, dataLayerName: String): Fox[JsObject] =
rpc(
s"${dataStore.url}/data/datasets/${urlEncode(organizationName)}/${dataSet.urlEncodedName}/layers/$dataLayerName/findData")
Expand Down
15 changes: 15 additions & 0 deletions app/models/binary/WKRemoteSegmentAnythingClient.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package models.binary

import com.scalableminds.util.tools.Fox
import com.scalableminds.webknossos.datastore.rpc.RPC
import com.scalableminds.webknossos.datastore.models.datasource.ElementClass
import utils.WkConf

import javax.inject.Inject

class WKRemoteSegmentAnythingClient @Inject()(rpc: RPC, conf: WkConf) {
def getEmbedding(imageData: Array[Byte], elementClass: ElementClass.Value): Fox[Array[Byte]] =
rpc(s"${conf.SegmentAnything.uri}/predictions/sam_vit_l")
.addQueryString("elementClass" -> elementClass.toString)
.postBytesWithBytesResponse(imageData)
}
8 changes: 7 additions & 1 deletion app/utils/WkConf.scala
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ class WkConf @Inject()(configuration: Configuration) extends ConfigReader with L
val exportTiffMaxVolumeMVx: Long = get[Long]("features.exportTiffMaxVolumeMVx")
val exportTiffMaxEdgeLengthVx: Long = get[Long]("features.exportTiffMaxEdgeLengthVx")
val openIdConnectEnabled: Boolean = get[Boolean]("features.openIdConnectEnabled")
val segmentAnythingEnabled: Boolean = get[Boolean]("features.segmentAnythingEnabled")
}

object Datastore {
Expand Down Expand Up @@ -230,6 +231,10 @@ class WkConf @Inject()(configuration: Configuration) extends ConfigReader with L
val children = List(Loki)
}

object SegmentAnything {
val uri: String = get[String]("segmentAnything.uri")
}

val children =
List(
Http,
Expand All @@ -246,7 +251,8 @@ class WkConf @Inject()(configuration: Configuration) extends ConfigReader with L
GoogleAnalytics,
BackendAnalytics,
Slick,
Voxelytics
Voxelytics,
SegmentAnything
)

}
8 changes: 7 additions & 1 deletion conf/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ features {
# The Only valid item value is currently "ConnectomeView":
optInTabs = []
openIdConnectEnabled = false
segmentAnythingEnabled = false
}

# Serve annotations. Only active if the corresponding play module is enabled
Expand Down Expand Up @@ -289,10 +290,15 @@ voxelytics {
}
}

segmentAnything {
uri = "http://localhost:8080"
}

# Avoid creation of a pid file
pidfile.path = "/dev/null"


# # uncomment these lines for faster restart during local backend development (but beware the then-missing features):
# # Uncomment these lines for faster restart during local backend development (but beware the then-missing features):
# # Uncommenting these lines also means that a DB has to be set up manually for a dev deployment.
# slick.checkSchemaOnStartup = false
# play.modules.disabled += "play.modules.swagger.SwaggerModule"
Loading