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

Metadata for Segments & Trees #7875

Merged
merged 91 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
85476f8
WIP: user-defined key-value properties for skeletons
fm3 Jun 11, 2024
3e8fcb1
adapt UpdateNode test
fm3 Jun 11, 2024
b08384e
also for trees,segments. add to backend NMLWriter
fm3 Jun 12, 2024
c46d931
parse string list values too
fm3 Jun 12, 2024
4ca3f7b
Merge branch 'master' into skeleton-properties
fm3 Jul 4, 2024
54ed581
Merge branch 'master' into skeleton-properties
philippotto Aug 19, 2024
3a62991
allow to create string tags for segments; styling tweaks
philippotto Aug 19, 2024
ff8472c
don't use inline tags; instead use a resizable two-split-pane and a t…
philippotto Aug 21, 2024
de4c61e
tweak layout so that the details pane only occupies as much space as …
philippotto Aug 21, 2024
7860b72
allow to edit values in segment details table
philippotto Aug 21, 2024
90e6654
refactor
philippotto Aug 21, 2024
152b0d5
add property UI to skeleton trees
philippotto Aug 21, 2024
86aecdb
add missing file
philippotto Aug 21, 2024
56fce0a
fix tests
philippotto Aug 21, 2024
e6e569c
fix spinner margin in dashboard
philippotto Aug 23, 2024
ae4e73b
allow user to resize pane larger than needed
philippotto Aug 23, 2024
fe796f4
fix styling in light theme
philippotto Aug 23, 2024
b1ec6e0
filter duplicate keys in action creator
philippotto Aug 23, 2024
e173dfd
implement NML parsing and serialization of user defined properties fo…
philippotto Aug 23, 2024
9442e46
adapt parser and serialize for userDefinedProperties; add test
philippotto Aug 28, 2024
2988aec
Merge branch 'master' of github.com:scalableminds/webknossos into ske…
philippotto Aug 29, 2024
79d0269
start unifying metadata tables
philippotto Sep 2, 2024
22d1f08
Merge branch 'master' of github.com:scalableminds/webknossos into ske…
philippotto Sep 2, 2024
e13ad22
remove superfluous type wrapper
philippotto Sep 2, 2024
b807515
integrate dashboards metadata table into tree hierarchy view (styling…
philippotto Sep 2, 2024
907dc16
also integrate with segments tab
philippotto Sep 3, 2024
a28d924
make adding and removing properties work properly
philippotto Sep 4, 2024
0b7bc4b
also support number and string array as property types
philippotto Sep 4, 2024
d58960e
format
philippotto Sep 4, 2024
9b19f09
re-order props
philippotto Sep 4, 2024
16ec2d6
extract InputWithUpdateOnBlur to fix cyclic dependency
philippotto Sep 4, 2024
7c77f55
temporarily disable ci
philippotto Sep 4, 2024
501142a
fix metadata editing for segments
philippotto Sep 5, 2024
02de243
change trash to close icon in dashboard
philippotto Sep 5, 2024
a03ed44
give metadata table rounded corners
philippotto Sep 5, 2024
b3a2cfe
warn user when keys are not unique; avoid frequent rerenders via memo
philippotto Sep 5, 2024
f3ed54c
iterate on validation, styling and tag input
philippotto Sep 6, 2024
7d3fee5
try to fix stringListValue: [] error
philippotto Sep 6, 2024
eed64e5
don't save duplicate keys in case the user ignores the validation errors
philippotto Sep 6, 2024
c9d8a04
auto-focus just added key fields
philippotto Sep 6, 2024
2aff336
remove context-menu way for adding metadata; iterate on icon in segme…
philippotto Sep 6, 2024
8650006
change dashboard metadata table style
philippotto Sep 6, 2024
8f09a03
also adapt validation styling
philippotto Sep 6, 2024
7ee43a3
make some props optional
philippotto Sep 6, 2024
b2387bc
move multiple-trees-selected note into details pane
philippotto Sep 6, 2024
9d64f0a
make fields read only if no update permissions exist
philippotto Sep 6, 2024
5d6675c
fix theader style for segments tab
philippotto Sep 6, 2024
d2defb3
store height for details pane in local storage
philippotto Sep 6, 2024
664768c
fix lost highlighting of segments that are hovered in the data viewports
philippotto Sep 6, 2024
235807e
also print statistic of previous runs in wk dev benchmark
philippotto Sep 6, 2024
d63ec33
move isCentered logic into SegmentListItem to avoid rerenders
philippotto Sep 6, 2024
f666426
fix type error
philippotto Sep 6, 2024
48fefe4
fix linting
philippotto Sep 6, 2024
d87c04a
Merge branch 'master' into skeleton-properties
fm3 Sep 12, 2024
40b5010
rename UserDefinedProperty to MetadataEntry
fm3 Sep 12, 2024
3ccdddd
wrap metadata in parent tag in nml
fm3 Sep 12, 2024
64bd89c
rename userDefinedProperty/ies to metadata and metadata entry
philippotto Sep 12, 2024
871ac94
clean up
philippotto Sep 12, 2024
5fbe505
rename metadata-table-wrapper class
philippotto Sep 12, 2024
38d6aec
more renaming
philippotto Sep 12, 2024
9b78808
remove debug code
philippotto Sep 12, 2024
0fb7f3f
clarify comments
philippotto Sep 12, 2024
ccc2960
delete unused InputNumberWithUpdateOnBlur
philippotto Sep 12, 2024
a6296ea
minor clean up
philippotto Sep 12, 2024
dcbdbe9
re-enable ci
philippotto Sep 13, 2024
ce62e05
fix compact_toggle_actions.spec.ts and make it type check
philippotto Sep 13, 2024
95e711d
Merge branch 'master' into skeleton-properties
fm3 Sep 16, 2024
6870faf
update snapshots
fm3 Sep 16, 2024
deaa6af
remove todo comment
philippotto Sep 16, 2024
06d938c
make tooltip color consistent orange
philippotto Sep 16, 2024
706d3a9
add tooltip to tag icon
philippotto Sep 16, 2024
cc733a2
Update frontend/stylesheets/_variables.less
philippotto Sep 16, 2024
a09f31d
integrate feedback
philippotto Sep 16, 2024
d8028dd
Merge branch 'skeleton-properties' of github.com:scalableminds/webkno…
philippotto Sep 16, 2024
a4dd272
rename record to entry
philippotto Sep 16, 2024
76e9d16
adapt parseTimestamp to defaultValue options interface
philippotto Sep 16, 2024
54ddfe7
also adapt _parseTreeType
philippotto Sep 16, 2024
6e8a483
rename MetadataEntry to MetadataEntryProto
philippotto Sep 16, 2024
df0a089
also rename APIMetadata to APIMetadataEntry and add comments about th…
philippotto Sep 16, 2024
6b269e3
fix e2e tests
philippotto Sep 17, 2024
c05ac35
deduplicate metadata entries when applying update actions
fm3 Sep 23, 2024
2c53eef
changelog, deduplicate for segments too
fm3 Sep 23, 2024
dba57db
remove unused imports in frontend code
Sep 23, 2024
6068c07
integrate backend pr feedback
fm3 Sep 23, 2024
a946881
Merge branch 'skeleton-properties' of github.com:scalableminds/webkno…
fm3 Sep 23, 2024
d10156e
Merge branch 'master' into skeleton-properties
fm3 Sep 23, 2024
80a6aa1
refresh snapshots
fm3 Sep 23, 2024
6f39114
Merge branch 'skeleton-properties' of github.com:scalableminds/webkno…
fm3 Sep 23, 2024
ff00fde
readd border around empty metadata placeholder in dashboard
Sep 23, 2024
c3e5ce1
Merge branch 'skeleton-properties' of github.com:scalableminds/webkno…
Sep 23, 2024
96ba268
Merge branch 'master' into skeleton-properties
MichaelBuessemeyer Sep 23, 2024
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 @@ -15,6 +15,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
- Added the option to export nd datasets as ome tiff or tiff stack. Previously, this was only possible for 3d datasets. [#7971](https://github.com/scalableminds/webknossos/pull/7971)
- Added an assertion to the backend to ensure unique keys in the metadata info of datasets and folders. [#8068](https://github.com/scalableminds/webknossos/issues/8068)
- The feature to register all segments within a bounding box now takes the current magnification into consideration, e.g. for calculating the volume limit for a bounding box. [#8082](https://github.com/scalableminds/webknossos/pull/8082)
- It is now possible to add metadata in annotations to Trees and Segments. [#7875](https://github.com/scalableminds/webknossos/pull/7875)

### Changed
- Clicking on a bounding box within the bounding box tab centers it within the viewports and focusses it in the list. [#8049](https://github.com/scalableminds/webknossos/pull/8049)
Expand Down
36 changes: 34 additions & 2 deletions app/models/annotation/nml/NmlParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.scalableminds.util.geometry.{BoundingBox, Vec3Double, Vec3Int}
import com.scalableminds.util.tools.ExtendedTypes.{ExtendedDouble, ExtendedString}
import com.scalableminds.util.tools.JsonHelper.bool2Box
import com.scalableminds.webknossos.datastore.SkeletonTracing._
import com.scalableminds.webknossos.datastore.MetadataEntry.MetadataEntryProto
import com.scalableminds.webknossos.datastore.VolumeTracing.{Segment, SegmentGroup, VolumeTracing}
import com.scalableminds.webknossos.datastore.geometry.{
AdditionalAxisProto,
Expand Down Expand Up @@ -210,17 +211,46 @@ object NmlParser extends LazyLogging with ProtoGeometryImplicits with ColorGener
case _ => None
}
val anchorPositionAdditionalCoordinates = parseAdditionalCoordinateValues(node)
val metadata = parseMetadata(node \ "metadata" \ "metadataEntry")
fm3 marked this conversation as resolved.
Show resolved Hide resolved
Segment(
segmentId = getSingleAttribute(node, "id").toLong,
anchorPosition = anchorPosition,
name = getSingleAttributeOpt(node, "name"),
creationTime = getSingleAttributeOpt(node, "created").flatMap(_.toLongOpt),
color = parseColorOpt(node),
groupId = getSingleAttribute(node, "groupId").toIntOpt,
anchorPositionAdditionalCoordinates = anchorPositionAdditionalCoordinates
anchorPositionAdditionalCoordinates = anchorPositionAdditionalCoordinates,
metadata = metadata
)
})

private def parseMetadata(metadataEntryNodes: NodeSeq): Seq[MetadataEntryProto] =
metadataEntryNodes.map(node => {
MetadataEntryProto(
getSingleAttribute(node, "key"),
getSingleAttributeOpt(node, "stringValue"),
getSingleAttributeOpt(node, "boolValue").flatMap(_.toBooleanOpt),
getSingleAttributeOpt(node, "numberValue").flatMap(_.toDoubleOpt),
parseStringListValue(node)
)
})

private def parseStringListValue(node: XMLNode): Seq[String] = {
val regex = "^stringListValue-(\\d+)".r
val valuesWithIndex: Seq[(Int, String)] = node.attributes.flatMap {
case attribute: Attribute =>
attribute.key match {
case regex(indexStr) =>
indexStr.toIntOpt.map { index =>
(index, attribute.value.toString)
}
case _ => None
}
case _ => None
}.toSeq
valuesWithIndex.sortBy(_._1).map(_._2)
}

private def parseTrees(treeNodes: NodeSeq,
branchPoints: Map[Int, List[BranchPoint]],
comments: Map[Int, List[Comment]])(implicit m: MessagesProvider) =
Expand Down Expand Up @@ -414,6 +444,7 @@ object NmlParser extends LazyLogging with ProtoGeometryImplicits with ColorGener
nodeIds = nodes.map(_.id)
treeBranchPoints = nodeIds.flatMap(nodeId => branchPoints.getOrElse(nodeId, List()))
treeComments = nodeIds.flatMap(nodeId => comments.getOrElse(nodeId, List()))
metadata = parseMetadata(tree \ "metadata" \ "metadataEntry")
createdTimestamp = if (nodes.isEmpty) System.currentTimeMillis()
else nodes.minBy(_.createdTimestamp).createdTimestamp
} yield
Expand All @@ -427,7 +458,8 @@ object NmlParser extends LazyLogging with ProtoGeometryImplicits with ColorGener
createdTimestamp,
groupId,
isVisible,
treeType)
treeType,
metadata = metadata)
}

private def parseComments(comments: NodeSeq)(implicit m: MessagesProvider): Box[List[Comment]] =
Expand Down
27 changes: 25 additions & 2 deletions app/models/annotation/nml/NmlWriter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.scalableminds.util.time.Instant
import com.scalableminds.util.tools.{Fox, FoxImplicits}
import com.scalableminds.util.xml.Xml
import com.scalableminds.webknossos.datastore.SkeletonTracing._
import com.scalableminds.webknossos.datastore.MetadataEntry.MetadataEntryProto
import com.scalableminds.webknossos.datastore.VolumeTracing.{Segment, SegmentGroup}
import com.scalableminds.webknossos.datastore.geometry._
import com.scalableminds.webknossos.datastore.models.VoxelSize
Expand Down Expand Up @@ -274,13 +275,13 @@ class NmlWriter @Inject()(implicit ec: ExecutionContext) extends FoxImplicits {
if (skipVolumeData) {
writer.writeComment(f"Note that volume data was omitted when downloading this annotation.")
}
writeVolumeSegmentMetadata(volumeTracing.segments)
writeVolumeSegmentInfos(volumeTracing.segments)
Xml.withinElementSync("groups")(writeSegmentGroupsAsXml(volumeTracing.segmentGroups))
case _ => ()
}
}

private def writeVolumeSegmentMetadata(segments: Seq[Segment])(implicit writer: XMLStreamWriter): Unit =
private def writeVolumeSegmentInfos(segments: Seq[Segment])(implicit writer: XMLStreamWriter): Unit =
Xml.withinElementSync("segments") {
segments.foreach { s =>
Xml.withinElementSync("segment") {
Expand All @@ -299,10 +300,30 @@ class NmlWriter @Inject()(implicit ec: ExecutionContext) extends FoxImplicits {
}
s.color.foreach(_ => writeColor(s.color))
s.groupId.foreach(groupId => writer.writeAttribute("groupId", groupId.toString))
if (s.metadata.nonEmpty)
Xml.withinElementSync("metadata")(s.metadata.foreach(writeMetadataEntry))
}
}
}

private def writeMetadataEntry(p: MetadataEntryProto)(implicit writer: XMLStreamWriter): Unit =
Xml.withinElementSync("metadataEntry") {
writer.writeAttribute("key", p.key)
p.stringValue.foreach { v =>
writer.writeAttribute("stringValue", v)
}
p.boolValue.foreach { v =>
writer.writeAttribute("boolValue", v.toString)
}
p.numberValue.foreach { v =>
writer.writeAttribute("numberValue", v.toString)
}
p.stringListValue.zipWithIndex.foreach {
case (v, index) =>
writer.writeAttribute(s"stringListValue-$index", v)
}
}

private def writeSkeletonThings(skeletonTracing: SkeletonTracing)(implicit writer: XMLStreamWriter): Unit = {
writeTreesAsXml(skeletonTracing.trees)
Xml.withinElementSync("branchpoints")(
Expand All @@ -321,6 +342,8 @@ class NmlWriter @Inject()(implicit ec: ExecutionContext) extends FoxImplicits {
t.`type`.foreach(t => writer.writeAttribute("type", t.toString))
Xml.withinElementSync("nodes")(writeNodesAsXml(t.nodes.sortBy(_.id)))
Xml.withinElementSync("edges")(writeEdgesAsXml(t.edges))
if (t.metadata.nonEmpty)
Xml.withinElementSync("metadata")(t.metadata.foreach(writeMetadataEntry))
}
}

Expand Down
6 changes: 6 additions & 0 deletions frontend/javascripts/components/fast_tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ export default function FastTooltip({
onMouseLeave,
wrapper,
html,
className,
style,
variant,
dynamicRenderer,
}: {
title?: string | null | undefined;
Expand All @@ -71,7 +73,9 @@ export default function FastTooltip({
onMouseLeave?: () => void;
wrapper?: "div" | "span" | "p" | "tr"; // Any valid HTML tag, span by default.
html?: string | null | undefined;
className?: string; // class name attached to the wrapper
style?: React.CSSProperties; // style attached to the wrapper
variant?: "dark" | "light" | "success" | "warning" | "error" | "info";
dynamicRenderer?: () => React.ReactElement;
}) {
const Tag = wrapper || "span";
Expand Down Expand Up @@ -107,8 +111,10 @@ export default function FastTooltip({
data-tooltip-place={placement || "top"}
data-tooltip-html={html}
data-unique-key={uniqueKeyForDynamic}
data-tooltip-variant={variant}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
className={className}
style={style}
>
{children}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -805,7 +805,7 @@ class ExplorativeAnnotationsView extends React.PureComponent<Props, State> {

render() {
return (
<div className="TestExplorativeAnnotationsView">
<div>
<TopBar
isAdminView={this.props.isAdminView}
handleOnSearch={this.handleOnSearch}
Expand All @@ -816,7 +816,7 @@ class ExplorativeAnnotationsView extends React.PureComponent<Props, State> {
archiveAll={this.archiveAll}
/>
{this.renderSearchTags()}
<Spin spinning={this.state.isLoading} size="large">
<Spin spinning={this.state.isLoading} size="large" style={{ marginTop: 4 }}>
{this.renderTable()}
</Spin>
<div
Expand Down
Loading