From 90d82f52984fb3ce5b1d8f90231796f243b82d2b Mon Sep 17 00:00:00 2001 From: Koshevarov Sergey Date: Wed, 18 Aug 2021 15:50:59 +0300 Subject: [PATCH 01/32] [ext] Add multiple regions selection - [ext] add selection extending by pressing ctrl+click on the region in the object - [ext] selecting multiple regions in the list by pressing ctrl+click - [ext] selecting multiple regions in the list by range by pressing click at the start of the range and shift+click at the end - [fix] unselect region in list by click on selected region - [ext] deleting multiple regions - [ext] applying labels for multiple regions - displaying intersection of sets of applied labels for results in multi selection state - [ext] add selection tool to the image view - [ext] allow range selecting interaction for audio plus, timeseries and all text based object tags - fix annotation mixing working without control tag - implement bboxCoords getter in the all image regions - [ext] brush regions optimizations - caching image data in brush regions for the purpose of optimization - caching result of mask coloring which improves rerenders performance --- src/components/AnnotationTab/AnnotationTab.js | 4 +- src/components/Entities/Entities.module.scss | 1 + src/components/Entities/LabelList.js | 4 +- src/components/Entities/RegionItem.js | 6 +- src/components/Entities/RegionTree.js | 4 +- src/components/Entity/Entity.js | 32 +- src/components/ImageView/ImageView.js | 100 +++- src/components/Waveform/Waveform.js | 8 +- src/mixins/AnnotationMixin.js | 6 +- src/mixins/AreaMixin.js | 13 + src/mixins/HighlightMixin.js | 2 +- src/mixins/Regions.js | 34 +- src/mixins/SpanText.js | 6 +- src/regions/AudioRegion.js | 6 +- src/regions/BrushRegion.js | 105 +++-- src/regions/EllipseRegion.js | 8 + src/regions/ImageRegion.js | 62 +-- src/regions/KeyPointRegion.js | 8 + src/regions/PolygonRegion.js | 15 + src/regions/RectRegion.js | 8 + src/regions/Result.js | 15 +- src/regions/TimeSeriesRegion.js | 1 + src/stores/AnnotationStore.js | 57 ++- src/stores/RegionStore.js | 416 ++++++++++++----- src/tags/control/Label.js | 42 +- src/tags/object/AudioPlus.js | 15 +- src/tags/object/Image.js | 426 +++++++++++------- src/tags/object/Paragraphs.js | 47 +- src/tags/object/RichText/view.js | 42 +- src/tags/object/TimeSeries/Channel.js | 40 +- src/tools/Selection.js | 49 ++ src/tools/index.js | 3 +- src/utils/selection-tools.js | 15 + 33 files changed, 1132 insertions(+), 468 deletions(-) create mode 100644 src/tools/Selection.js diff --git a/src/components/AnnotationTab/AnnotationTab.js b/src/components/AnnotationTab/AnnotationTab.js index 3d0bf381e..7100d275a 100644 --- a/src/components/AnnotationTab/AnnotationTab.js +++ b/src/components/AnnotationTab/AnnotationTab.js @@ -7,7 +7,7 @@ import Relations from "../Relations/Relations"; export const AnnotationTab = observer(({ store }) => { const as = store.annotationStore; const annotation = as.selectedHistory ?? as.selected; - const node = annotation.highlightedNode; + const { selectionSize } = annotation || {}; const hasSegmentation = store.hasSegmentation; return ( @@ -22,7 +22,7 @@ export const AnnotationTab = observer(({ store }) => { /> )} - {node ? ( + {selectionSize ? ( ) : hasSegmentation ? (

diff --git a/src/components/Entities/Entities.module.scss b/src/components/Entities/Entities.module.scss index a5d7a667f..861890f5b 100644 --- a/src/components/Entities/Entities.module.scss +++ b/src/components/Entities/Entities.module.scss @@ -39,6 +39,7 @@ } .lstitem { + user-select: none; cursor: pointer; align-items: center; justify-content: flex-start; diff --git a/src/components/Entities/LabelList.js b/src/components/Entities/LabelList.js index 62d8695b8..1e187b3b4 100644 --- a/src/components/Entities/LabelList.js +++ b/src/components/Entities/LabelList.js @@ -6,13 +6,13 @@ import { observer } from "mobx-react"; import { LsChevron } from "../../assets/icons"; export const LabelList = observer(({ regionStore }) => { - const treeData = regionStore.asLabelsTree((item, idx, isLabel, children) => { + const treeData = regionStore.asLabelsTree((item, idx, isLabel, children, onClick) => { return { key: item.id, title: isLabel ? ( ) : ( - + ), }; }); diff --git a/src/components/Entities/RegionItem.js b/src/components/Entities/RegionItem.js index 8c0612cea..fed2621a8 100644 --- a/src/components/Entities/RegionItem.js +++ b/src/components/Entities/RegionItem.js @@ -103,7 +103,7 @@ const RegionItemContent = observer(({ idx, item, setDraggable }) => { ); }); -export const RegionItem = observer(({ item, idx, flat, setDraggable }) => { +export const RegionItem = observer(({ item, idx, flat, setDraggable, onClick }) => { const getVars = useMemo(()=>{ let vars; @@ -125,7 +125,7 @@ export const RegionItem = observer(({ item, idx, flat, setDraggable }) => { styles.lstitem, flat && styles.flat, item.hidden === true && styles.hidden, - item.selected && styles.selected, + item.inSelection && styles.selected, ].filter(Boolean); const vars = getVars(); @@ -134,7 +134,7 @@ export const RegionItem = observer(({ item, idx, flat, setDraggable }) => { anno.selectArea(item)} + onClick={(e)=>{onClick(e, item);}} onMouseOver={() => item.setHighlight(true)} onMouseOut={() => item.setHighlight(false)} style={vars} diff --git a/src/components/Entities/RegionTree.js b/src/components/Entities/RegionTree.js index 3ef4ef32e..058e06f3c 100644 --- a/src/components/Entities/RegionTree.js +++ b/src/components/Entities/RegionTree.js @@ -30,10 +30,10 @@ export const RegionTree = observer(({ regionStore }) => { ); const isFlat = !regionStore.sortedRegions.some(r => r.parentID); - const regions = regionStore.asTree((item, idx) => { + const regions = regionStore.asTree((item, idx, onClick) => { return { key: item.id, - title: , + title: , }; }); diff --git a/src/components/Entity/Entity.js b/src/components/Entity/Entity.js index 87f8c2c68..a5c4cb37b 100644 --- a/src/components/Entity/Entity.js +++ b/src/components/Entity/Entity.js @@ -56,12 +56,14 @@ const renderResult = result => { }; export default observer(({ store, annotation }) => { - const node = annotation.highlightedNode; + const { highlightedNode: node, selectedRegions: nodes, selectionSize } = annotation; const [editMode, setEditMode] = React.useState(false); const entityButtons = []; + const hasEditableNodes = !!nodes.find(node => node.editable); + const hasEditableRegions = !!nodes.find(node => node.editable && !node.classification); - if (node.editable && !node.classification) { + if (hasEditableRegions) { entityButtons.push( @@ -111,14 +115,18 @@ export default observer(({ store, annotation }) => { - - {" "} - (ID: {node.id}) + {node ? ( + <> + + {" "} + (ID: {node.id}) + + ) : `${selectionSize} Region${(selectionSize > 1) ? "s are" : " is"} selected` } - {!node.editable && } + {!hasEditableNodes && }

- {node.score && ( + {node?.score && ( Score: {node.score} @@ -126,7 +134,7 @@ export default observer(({ store, annotation }) => { )} - {node.meta?.text && ( + {node?.meta?.text && ( Meta: {node.meta.text}   @@ -140,7 +148,7 @@ export default observer(({ store, annotation }) => { )} - {node.results.map(renderResult)} + {node?.results.map(renderResult)}
@@ -149,13 +157,13 @@ export default observer(({ store, annotation }) => { {entityButtons} - {node.editable && ( - + {hasEditableNodes && ( +