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

fix: LEAP-148: Fix MagicWand in MIG scenario with image preloading #5385

Merged
merged 13 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion web/dist/libs/editor/main.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion web/dist/libs/editor/main.js.map

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions web/libs/editor/src/components/ImageView/Image.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const Image = observer(forwardRef(({
updateImageSize,
usedValue,
size,
overlay,
}, ref) => {
const imageSize = useMemo(() => {
return {
Expand All @@ -45,6 +46,7 @@ export const Image = observer(forwardRef(({

return (
<Block name="image" style={imageSize}>
{overlay}
<ImageProgress
downloading={imageEntity.downloading}
progress={imageEntity.progress}
Expand Down Expand Up @@ -76,7 +78,7 @@ const ImageProgress = observer(({
return downloading ? (
<Block name="image-progress">
<Elem name="message">Downloading image</Elem>
<Elem tag="progress" name="bar" value={progress} min="0" max={1} step={0.0001}/>
<Elem tag="progress" name="bar" value={progress} min="0" max={1} step={0.0001} />
</Block>
) : error ? (
<ImageLoadingError src={src} value={usedValue} />
Expand Down Expand Up @@ -121,6 +123,6 @@ const ImageLoadingError = ({ src, value }) => {
}, [src]);

return (
<ErrorMessage error={error}/>
<ErrorMessage error={error} />
);
};
27 changes: 18 additions & 9 deletions web/libs/editor/src/components/ImageView/ImageView.js
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,22 @@ const Crosshair = memo(forwardRef(({ width, height }, ref) => {
);
}));

/**
* Component that creates an overlay on top
* of the image to support Magic Wand tool
*/
const CanvasOverlay = observer(({ item }) => {
return isFF(FF_DEV_4081) ? (
<canvas
className={styles.overlay}
ref={ref => {
item.setOverlayRef(ref);
}}
style={item.imageTransform}
/>
) : null;
});

export default observer(
class ImageView extends Component {
// stored position of canvas before creating region
Expand Down Expand Up @@ -1020,6 +1036,7 @@ export default observer(
imageTransform={item.imageTransform}
updateImageSize={item.updateImageSize}
size={item.canvasSize}
overlay={<CanvasOverlay item={item} />}
/>
) : (
<div
Expand All @@ -1045,15 +1062,7 @@ export default observer(
crossOrigin={item.imageCrossOrigin}
alt="LS"
/>
{isFF(FF_DEV_4081) ? (
<canvas
className={styles.overlay}
ref={ref => {
item.setOverlayRef(ref);
}}
style={item.imageTransform}
/>
) : null}
<CanvasOverlay item={item} />
</div>
)}
{/* @todo this is dirty hack; rewrite to proper async waiting for data to load */}
Expand Down
29 changes: 29 additions & 0 deletions web/libs/editor/src/mixins/PerItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,35 @@ const PerItemMixin = types
},
}))
.actions(self => ({
/**
* Validates all values related to the current classification per item (Multi Items Segmentation).
*
* - This method should not be overridden.
* - It is used only in validate method of the ClassificationBase mixin.
*
* @returns {boolean}
* @private
*/
_validatePerItem() {
const objectTag = self.toNameTag;

return self.annotation.regions
.every((reg) => {
const result = reg.results.find(s => s.from_name === self);

if (!result?.hasValue) {
return true;
}
const value = result.mainValue;
const isValid = self.validateValue(value);

if (!isValid) {
objectTag.setCurrentItem(reg.item_index);
return false;
}
return true;
});
},
createPerItemResult() {
self.createPerObjectResult({
item_index: self.toNameTag.currentItemIndex,
Expand Down
24 changes: 24 additions & 0 deletions web/libs/editor/src/mixins/PerRegion.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,30 @@ const PerRegionMixin = types
},
}))
.actions(self => ({
/**
* Validates all values related to the current classification per region.
*
* - This method should not be overridden.
* - It is used only in validate method of the ClassificationBase mixin.
*
* @returns {boolean}
* @private
*/
_validatePerRegion() {
const objectTag = self.toNameTag;

for (const reg of objectTag.allRegs) {
const value = reg.results.find(s => s.from_name === self)?.mainValue;
const isValid = self.validateValue(value);

if (!isValid) {
self.annotation.selectArea(reg);
return false;
}
}

return true;
},
createPerRegionResult() {
self.perRegionArea?.setValue(self);
},
Expand Down
100 changes: 53 additions & 47 deletions web/libs/editor/src/mixins/Required.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,71 +6,77 @@ const RequiredMixin = types
required: types.optional(types.boolean, false),
requiredmessage: types.maybeNull(types.string),
})
.actions(self => ({
validate() {
if (!self.required) return true;
.actions(self => {
const Super = {
validate: self.validate,
};

if (self.perregion) {
return {
validate() {
if (!Super.validate()) return false;
if (!self.required) return true;

if (self.perregion) {
// validating when choices labeling is done per region,
// for example choice may be required to be selected for
// every bbox
const objectTag = self.toNameTag;
const objectTag = self.toNameTag;

// if regions don't meet visibility conditions skip validation
for (const reg of objectTag.allRegs) {
const s = reg.results.find(s => s.from_name === self);
// if regions don't meet visibility conditions skip validation
for (const reg of objectTag.allRegs) {
const s = reg.results.find(s => s.from_name === self);

if (self.visiblewhen === 'region-selected') {
if (self.whentagname) {
const label = reg.labeling?.from_name?.name;
if (self.visiblewhen === 'region-selected') {
if (self.whentagname) {
const label = reg.labeling?.from_name?.name;

if (label && label !== self.whentagname) continue;
if (label && label !== self.whentagname) continue;
}
}
}

if (self.whenlabelvalue && !reg.hasLabel(self.whenlabelvalue)) {
continue;
}
if (self.whenlabelvalue && !reg.hasLabel(self.whenlabelvalue)) {
continue;
}

if (!s?.hasValue) {
self.annotation.selectArea(reg);
self.requiredModal();
if (!s?.hasValue) {
self.annotation.selectArea(reg);
self.requiredModal();

return false;
return false;
}
}
}
} else if (isFF(FF_LSDV_4583) && self.peritem) {
} else if (isFF(FF_LSDV_4583) && self.peritem) {
// validating when choices labeling is done per item,
const objectTag = self.toNameTag;
const maxItemIndex = objectTag.maxItemIndex;
const existingResultsIndexes = self.annotation.regions
.reduce((existingResultsIndexes, reg) => {
const result = reg.results.find(s => s.from_name === self);
const objectTag = self.toNameTag;
const maxItemIndex = objectTag.maxItemIndex;
const existingResultsIndexes = self.annotation.regions
.reduce((existingResultsIndexes, reg) => {
const result = reg.results.find(s => s.from_name === self);

if (result?.hasValue) {
existingResultsIndexes.add(reg.item_index);
}
return existingResultsIndexes;
}, new Set());
if (result?.hasValue) {
existingResultsIndexes.add(reg.item_index);
}
return existingResultsIndexes;
}, new Set());

for (let idx = 0; idx <= maxItemIndex; idx++) {
if (!existingResultsIndexes.has(idx)) {
objectTag.setCurrentItem(idx);
self.requiredModal();
return false;
for (let idx = 0; idx <= maxItemIndex; idx++) {
if (!existingResultsIndexes.has(idx)) {
objectTag.setCurrentItem(idx);
self.requiredModal();
return false;
}
}
}
} else {
} else {
// validation when its classifying the whole object
// isVisible can be undefined (so comparison is true) or boolean (so check for visibility)
if (!self.holdsState && self.isVisible !== false && getParent(self, 2)?.isVisible !== false) {
self.requiredModal();
return false;
if (!self.holdsState && self.isVisible !== false && getParent(self, 2)?.isVisible !== false) {
self.requiredModal();
return false;
}
}
}

return true;
},
}));
return true;
},
};
});

export default RequiredMixin;
60 changes: 60 additions & 0 deletions web/libs/editor/src/tags/control/ClassificationBase.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { types } from 'mobx-state-tree';
import { FF_LSDV_4583, isFF } from '../../utils/feature-flags';

/**
* This is a mixin for a control-tag that is a base of creating classification-like tags.
Expand Down Expand Up @@ -40,6 +41,65 @@ const ClassificationBase = types.model('ClassificationBase', {
};
}).actions(self => {
return {
/**
* Validates the input based on certain conditions.
*
* Generally, this method does not need to be overridden. And you need to override the validateValue method instead.
* However, there are exceptions. For example, RequiredMixin, Choices, and
* Taxonomy have their own additional logic, for which a broader context is needed.
* In this case, the parent method call is added at the beginning or end
* of the method to maintain all functionality in a predictable manner.
*
* @returns {boolean}
*/
validate() {
if (self.perregion) {
return self._validatePerRegion();
} else if (self.peritem && isFF(FF_LSDV_4583)) {
return self._validatePerItem();
} else {
return self._validatePerObject();
}
},
/**
* Validates the value.
*
* Override to add your custom validation logic specific for the tag.
* Per-item, per-region and per-object validation will be applied automatically.
*
* @example
* SomeModel.actions(self => {
* const Super = { validateValue: self.validateValue };
*
* return {
* validateValue(value) {
* if (!Super.validateValue(value)) return false;
* // your validation logic
* }
* // other actions
* }
* });
*
* @param {*} value - The value to be validated.
* @returns {boolean}
*
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
validateValue(value) {
return true;
},
/**
* Validates all values related to the current classification per object.
*
* - This method should not be overridden.
* - It is used only in validate method of the ClassificationBase mixin.
*
* @returns {boolean}
* @private
*/
_validatePerObject() {
return self.validateValue(self.selectedValues());
},
createPerObjectResult(areaValues = {}) {
self.annotation.createResult(
areaValues,
Expand Down
Loading
Loading