| | import { rgbaColorToString } from 'common/util/colorCodeTransformers'; |
| | import { deepClone } from 'common/util/deepClone'; |
| | import { withResultAsync } from 'common/util/result'; |
| | import type { CanvasEntityAdapterControlLayer } from 'features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterControlLayer'; |
| | import type { CanvasEntityAdapterRasterLayer } from 'features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterRasterLayer'; |
| | import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; |
| | import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase'; |
| | import { CanvasObjectImage } from 'features/controlLayers/konva/CanvasObject/CanvasObjectImage'; |
| | import { |
| | addCoords, |
| | getKonvaNodeDebugAttrs, |
| | getPrefixedId, |
| | offsetCoord, |
| | roundCoord, |
| | } from 'features/controlLayers/konva/util'; |
| | import { selectAutoProcess } from 'features/controlLayers/store/canvasSettingsSlice'; |
| | import type { |
| | CanvasImageState, |
| | CanvasRenderableEntityType, |
| | Coordinate, |
| | RgbaColor, |
| | SAMPointLabel, |
| | SAMPointLabelString, |
| | SAMPointWithId, |
| | } from 'features/controlLayers/store/types'; |
| | import { SAM_POINT_LABEL_NUMBER_TO_STRING } from 'features/controlLayers/store/types'; |
| | import { imageDTOToImageObject } from 'features/controlLayers/store/util'; |
| | import { Graph } from 'features/nodes/util/graph/generation/Graph'; |
| | import Konva from 'konva'; |
| | import type { KonvaEventObject } from 'konva/lib/Node'; |
| | import { debounce } from 'lodash-es'; |
| | import type { Atom } from 'nanostores'; |
| | import { atom, computed } from 'nanostores'; |
| | import type { Logger } from 'roarr'; |
| | import { serializeError } from 'serialize-error'; |
| | import type { ImageDTO } from 'services/api/types'; |
| | import stableHash from 'stable-hash'; |
| | import type { Equals } from 'tsafe'; |
| | import { assert } from 'tsafe'; |
| |
|
| | type CanvasSegmentAnythingModuleConfig = { |
| | |
| | |
| | |
| | SAM_POINT_RADIUS: number; |
| | |
| | |
| | |
| | SAM_POINT_BORDER_WIDTH: number; |
| | |
| | |
| | |
| | SAM_POINT_BORDER_COLOR: RgbaColor; |
| | |
| | |
| | |
| | SAM_POINT_FOREGROUND_COLOR: RgbaColor; |
| | |
| | |
| | |
| | SAM_POINT_BACKGROUND_COLOR: RgbaColor; |
| | |
| | |
| | |
| | SAM_POINT_NEUTRAL_COLOR: RgbaColor; |
| | |
| | |
| | |
| | MASK_COLOR: RgbaColor; |
| | |
| | |
| | |
| | PROCESS_DEBOUNCE_MS: number; |
| | }; |
| |
|
| | const DEFAULT_CONFIG: CanvasSegmentAnythingModuleConfig = { |
| | SAM_POINT_RADIUS: 8, |
| | SAM_POINT_BORDER_WIDTH: 2, |
| | SAM_POINT_BORDER_COLOR: { r: 0, g: 0, b: 0, a: 1 }, |
| | SAM_POINT_FOREGROUND_COLOR: { r: 50, g: 255, b: 0, a: 1 }, |
| | SAM_POINT_BACKGROUND_COLOR: { r: 255, g: 0, b: 50, a: 1 }, |
| | SAM_POINT_NEUTRAL_COLOR: { r: 0, g: 225, b: 255, a: 1 }, |
| | MASK_COLOR: { r: 0, g: 225, b: 255, a: 1 }, |
| | PROCESS_DEBOUNCE_MS: 1000, |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | type SAMPointState = { |
| | id: string; |
| | label: SAMPointLabel; |
| | coord: Coordinate; |
| | konva: { |
| | circle: Konva.Circle; |
| | }; |
| | }; |
| |
|
| | export class CanvasSegmentAnythingModule extends CanvasModuleBase { |
| | readonly type = 'canvas_segment_anything'; |
| | readonly id: string; |
| | readonly path: string[]; |
| | readonly parent: CanvasEntityAdapterRasterLayer | CanvasEntityAdapterControlLayer; |
| | readonly manager: CanvasManager; |
| | readonly log: Logger; |
| |
|
| | config: CanvasSegmentAnythingModuleConfig = DEFAULT_CONFIG; |
| |
|
| | subscriptions = new Set<() => void>(); |
| |
|
| | |
| | |
| | |
| | abortController: AbortController | null = null; |
| |
|
| | |
| | |
| | |
| | $isSegmenting = atom<boolean>(false); |
| |
|
| | |
| | |
| | |
| | $lastProcessedHash = atom<string>(''); |
| |
|
| | |
| | |
| | |
| | $isProcessing = atom<boolean>(false); |
| |
|
| | |
| | |
| | |
| | $pointType = atom<SAMPointLabel>(1); |
| |
|
| | |
| | |
| | |
| | $pointTypeString = computed<SAMPointLabelString, Atom<SAMPointLabel>>( |
| | this.$pointType, |
| | (pointType) => SAM_POINT_LABEL_NUMBER_TO_STRING[pointType] |
| | ); |
| |
|
| | |
| | |
| | |
| | |
| | $isDraggingPoint = atom<boolean>(false); |
| |
|
| | |
| | |
| | |
| | $imageState = atom<CanvasImageState | null>(null); |
| |
|
| | |
| | |
| | |
| | $hasImageState = computed(this.$imageState, (imageState) => imageState !== null); |
| |
|
| | |
| | |
| | |
| | $points = atom<SAMPointState[]>([]); |
| |
|
| | |
| | |
| | |
| | $hasPoints = computed(this.$points, (points) => points.length > 0); |
| |
|
| | |
| | |
| | |
| | $invert = atom<boolean>(false); |
| |
|
| | |
| | |
| | |
| | imageModule: CanvasObjectImage | null = null; |
| |
|
| | |
| | |
| | |
| | konva: { |
| | |
| | |
| | |
| | group: Konva.Group; |
| | |
| | |
| | |
| | |
| | |
| | pointGroup: Konva.Group; |
| | |
| | |
| | |
| | |
| | |
| | maskGroup: Konva.Group; |
| | |
| | |
| | |
| | |
| | |
| | compositingRect: Konva.Rect; |
| | |
| | |
| | |
| | maskTween: Konva.Tween | null; |
| | }; |
| |
|
| | KONVA_CIRCLE_NAME = `${this.type}:circle`; |
| | KONVA_GROUP_NAME = `${this.type}:group`; |
| | KONVA_POINT_GROUP_NAME = `${this.type}:point_group`; |
| | KONVA_MASK_GROUP_NAME = `${this.type}:mask_group`; |
| | KONVA_COMPOSITING_RECT_NAME = `${this.type}:compositing_rect`; |
| |
|
| | constructor(parent: CanvasEntityAdapterRasterLayer | CanvasEntityAdapterControlLayer) { |
| | super(); |
| | this.id = getPrefixedId(this.type); |
| | this.parent = parent; |
| | this.manager = this.parent.manager; |
| | this.path = this.manager.buildPath(this); |
| | this.log = this.manager.buildLogger(this); |
| |
|
| | this.log.debug('Creating module'); |
| |
|
| | |
| | this.konva = { |
| | group: new Konva.Group({ name: this.KONVA_GROUP_NAME }), |
| | pointGroup: new Konva.Group({ name: this.KONVA_POINT_GROUP_NAME }), |
| | maskGroup: new Konva.Group({ name: this.KONVA_MASK_GROUP_NAME, opacity: 0.6 }), |
| | compositingRect: new Konva.Rect({ |
| | name: this.KONVA_COMPOSITING_RECT_NAME, |
| | fill: rgbaColorToString(this.config.MASK_COLOR), |
| | globalCompositeOperation: 'source-atop', |
| | listening: false, |
| | strokeEnabled: false, |
| | perfectDrawEnabled: false, |
| | visible: false, |
| | }), |
| | maskTween: null, |
| | }; |
| |
|
| | |
| | this.konva.group.add(this.konva.maskGroup); |
| | this.konva.group.add(this.konva.pointGroup); |
| |
|
| | |
| | |
| | this.konva.maskGroup.add(this.konva.compositingRect); |
| | } |
| |
|
| | |
| | |
| | |
| | syncCursorStyle = (): void => { |
| | if (this.$isProcessing.get()) { |
| | this.manager.stage.setCursor('wait'); |
| | } else if (this.$isSegmenting.get()) { |
| | this.manager.stage.setCursor('crosshair'); |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | createPoint(coord: Coordinate, label: SAMPointLabel): SAMPointState { |
| | const id = getPrefixedId('sam_point'); |
| |
|
| | const roundedCoord = roundCoord(coord); |
| |
|
| | const circle = new Konva.Circle({ |
| | name: this.KONVA_CIRCLE_NAME, |
| | x: roundedCoord.x, |
| | y: roundedCoord.y, |
| | radius: this.manager.stage.unscale(this.config.SAM_POINT_RADIUS), |
| | fill: rgbaColorToString(this.getSAMPointColor(label)), |
| | stroke: rgbaColorToString(this.config.SAM_POINT_BORDER_COLOR), |
| | strokeWidth: this.manager.stage.unscale(this.config.SAM_POINT_BORDER_WIDTH), |
| | draggable: true, |
| | perfectDrawEnabled: true, |
| | opacity: 0.6, |
| | dragDistance: 3, |
| | }); |
| |
|
| | |
| | circle.on('pointerup', (e) => { |
| | |
| | if (this.$isDraggingPoint.get()) { |
| | return; |
| | } |
| | if (e.evt.button !== 0) { |
| | return; |
| | } |
| | |
| | e.cancelBubble = true; |
| | circle.destroy(); |
| |
|
| | const newPoints = this.$points.get().filter((point) => point.id !== id); |
| | if (newPoints.length === 0) { |
| | this.resetEphemeralState(); |
| | } else { |
| | this.$points.set(newPoints); |
| | } |
| | }); |
| |
|
| | circle.on('dragstart', () => { |
| | this.$isDraggingPoint.set(true); |
| | }); |
| |
|
| | circle.on('dragend', () => { |
| | const roundedCoord = roundCoord(circle.position()); |
| |
|
| | this.log.trace({ ...roundedCoord, label: SAM_POINT_LABEL_NUMBER_TO_STRING[label] }, 'Moved SAM point'); |
| | this.$isDraggingPoint.set(false); |
| |
|
| | const newPoints = this.$points.get().map((point) => { |
| | if (point.id === id) { |
| | return { ...point, coord: roundedCoord }; |
| | } |
| | return point; |
| | }); |
| |
|
| | this.$points.set(newPoints); |
| | }); |
| |
|
| | this.konva.pointGroup.add(circle); |
| |
|
| | this.log.trace({ ...roundedCoord, label: SAM_POINT_LABEL_NUMBER_TO_STRING[label] }, 'Created SAM point'); |
| |
|
| | return { |
| | id, |
| | coord: roundedCoord, |
| | label, |
| | konva: { circle }, |
| | }; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | syncPointScales = () => { |
| | const radius = this.manager.stage.unscale(this.config.SAM_POINT_RADIUS); |
| | const borderWidth = this.manager.stage.unscale(this.config.SAM_POINT_BORDER_WIDTH); |
| | for (const point of this.$points.get()) { |
| | point.konva.circle.radius(radius); |
| | point.konva.circle.strokeWidth(borderWidth); |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| | getSAMPoints = (): SAMPointWithId[] => { |
| | const points: SAMPointWithId[] = []; |
| |
|
| | for (const { id, coord, label } of this.$points.get()) { |
| | points.push({ |
| | id, |
| | x: coord.x, |
| | y: coord.y, |
| | label, |
| | }); |
| | } |
| |
|
| | return points; |
| | }; |
| |
|
| | |
| | |
| | |
| | onStagePointerUp = (e: KonvaEventObject<PointerEvent>) => { |
| | |
| | if (e.evt.button !== 0) { |
| | return; |
| | } |
| |
|
| | |
| | if (this.manager.stage.getIsDragging()) { |
| | return; |
| | } |
| |
|
| | |
| | if (this.$isDraggingPoint.get()) { |
| | return; |
| | } |
| |
|
| | |
| | if (this.$isProcessing.get()) { |
| | return; |
| | } |
| |
|
| | |
| | const cursorPos = this.manager.tool.$cursorPos.get(); |
| | if (!cursorPos) { |
| | return; |
| | } |
| |
|
| | |
| | const pixelRect = this.parent.transformer.$pixelRect.get(); |
| | const parentPosition = addCoords(this.parent.state.position, pixelRect); |
| |
|
| | |
| | const normalizedPoint = offsetCoord(cursorPos.relative, parentPosition); |
| |
|
| | |
| | const point = this.createPoint(normalizedPoint, this.$pointType.get()); |
| | const newPoints = [...this.$points.get(), point]; |
| | this.$points.set(newPoints); |
| | }; |
| |
|
| | |
| | |
| | |
| | subscribe = () => { |
| | this.manager.stage.konva.stage.on('pointerup', this.onStagePointerUp); |
| | this.subscriptions.add(() => { |
| | this.manager.stage.konva.stage.off('pointerup', this.onStagePointerUp); |
| | }); |
| |
|
| | |
| | |
| | |
| | this.subscriptions.add( |
| | this.$isProcessing.listen((isProcessing) => { |
| | this.syncCursorStyle(); |
| | this.parent.konva.layer.listening(!isProcessing); |
| | }) |
| | ); |
| |
|
| | |
| | this.subscriptions.add( |
| | this.manager.stage.$stageAttrs.listen((stageAttrs, oldStageAttrs) => { |
| | if (stageAttrs.scale !== oldStageAttrs.scale) { |
| | this.syncPointScales(); |
| | } |
| | }) |
| | ); |
| |
|
| | |
| | this.subscriptions.add( |
| | this.$points.listen((points) => { |
| | if (points.length === 0) { |
| | return; |
| | } |
| |
|
| | if (this.manager.stateApi.getSettings().autoProcess) { |
| | this.process(); |
| | } |
| | }) |
| | ); |
| |
|
| | |
| | this.subscriptions.add( |
| | this.$invert.listen(() => { |
| | if (this.$points.get().length === 0) { |
| | return; |
| | } |
| |
|
| | if (this.manager.stateApi.getSettings().autoProcess) { |
| | this.process(); |
| | } |
| | }) |
| | ); |
| |
|
| | |
| | this.subscriptions.add( |
| | this.manager.stateApi.createStoreSubscription(selectAutoProcess, (autoProcess) => { |
| | if (this.$points.get().length === 0) { |
| | return; |
| | } |
| | if (autoProcess) { |
| | this.process(); |
| | } |
| | }) |
| | ); |
| | }; |
| |
|
| | |
| | |
| | |
| | unsubscribe = () => { |
| | this.subscriptions.forEach((unsubscribe) => unsubscribe()); |
| | this.subscriptions.clear(); |
| | }; |
| |
|
| | |
| | |
| | |
| | start = () => { |
| | const segmentingAdapter = this.manager.stateApi.$segmentingAdapter.get(); |
| | if (segmentingAdapter) { |
| | this.log.error(`Already segmenting an entity: ${segmentingAdapter.id}`); |
| | return; |
| | } |
| | this.log.trace('Starting segment anything'); |
| |
|
| | |
| | this.resetEphemeralState(); |
| | this.$isSegmenting.set(true); |
| |
|
| | |
| | const pixelRect = this.parent.transformer.$pixelRect.get(); |
| | const position = addCoords(this.parent.state.position, pixelRect); |
| | this.konva.group.setAttrs(position); |
| |
|
| | |
| | this.parent.konva.layer.add(this.konva.group); |
| |
|
| | |
| | this.parent.konva.layer.listening(true); |
| |
|
| | |
| | this.subscribe(); |
| |
|
| | |
| | this.manager.stateApi.$segmentingAdapter.set(this.parent); |
| |
|
| | |
| | this.syncCursorStyle(); |
| | }; |
| |
|
| | |
| | |
| | |
| | processImmediate = async () => { |
| | if (!this.$isSegmenting.get()) { |
| | this.log.warn('Cannot process segmentation when not initialized'); |
| | return; |
| | } |
| |
|
| | if (this.$isProcessing.get()) { |
| | this.log.warn('Already processing'); |
| | return; |
| | } |
| |
|
| | const points = this.getSAMPoints(); |
| |
|
| | if (points.length === 0) { |
| | this.log.trace('No points to segment'); |
| | return; |
| | } |
| |
|
| | const invert = this.$invert.get(); |
| |
|
| | const hash = stableHash({ points, invert }); |
| | if (hash === this.$lastProcessedHash.get()) { |
| | this.log.trace('Already processed points'); |
| | return; |
| | } |
| |
|
| | this.$isProcessing.set(true); |
| |
|
| | this.log.trace({ points }, 'Segmenting'); |
| |
|
| | |
| | const rect = this.parent.transformer.getRelativeRect(); |
| | const rasterizeResult = await withResultAsync(() => |
| | this.parent.renderer.rasterize({ rect, attrs: { filters: [], opacity: 1 } }) |
| | ); |
| |
|
| | if (rasterizeResult.isErr()) { |
| | this.log.error({ error: serializeError(rasterizeResult.error) }, 'Error rasterizing entity'); |
| | this.$isProcessing.set(false); |
| | return; |
| | } |
| |
|
| | |
| | const controller = new AbortController(); |
| | this.abortController = controller; |
| |
|
| | |
| | const { graph, outputNodeId } = CanvasSegmentAnythingModule.buildGraph(rasterizeResult.value, points, invert); |
| |
|
| | |
| | const segmentResult = await withResultAsync(() => |
| | this.manager.stateApi.runGraphAndReturnImageOutput({ |
| | graph, |
| | outputNodeId, |
| | prepend: true, |
| | signal: controller.signal, |
| | }) |
| | ); |
| |
|
| | |
| | if (segmentResult.isErr()) { |
| | this.log.error({ error: serializeError(segmentResult.error) }, 'Error segmenting'); |
| | this.$isProcessing.set(false); |
| | |
| | if (!this.abortController.signal.aborted) { |
| | this.abortController.abort(); |
| | } |
| | this.abortController = null; |
| | return; |
| | } |
| |
|
| | this.log.trace({ imageDTO: segmentResult.value }, 'Segmented'); |
| |
|
| | |
| | const imageState = imageDTOToImageObject(segmentResult.value); |
| | this.$imageState.set(imageState); |
| |
|
| | |
| | if (this.imageModule) { |
| | this.imageModule.destroy(); |
| | } |
| | if (this.konva.maskTween) { |
| | this.konva.maskTween.destroy(); |
| | this.konva.maskTween = null; |
| | } |
| |
|
| | this.imageModule = new CanvasObjectImage(imageState, this); |
| |
|
| | |
| | await this.imageModule.update(imageState, true); |
| |
|
| | |
| | this.konva.compositingRect.setAttrs({ |
| | width: imageState.image.width, |
| | height: imageState.image.height, |
| | visible: true, |
| | }); |
| |
|
| | |
| | |
| | this.konva.maskGroup.add(this.imageModule.konva.group); |
| | this.konva.compositingRect.moveToTop(); |
| |
|
| | |
| | this.konva.maskGroup.cache(); |
| |
|
| | |
| | this.konva.maskTween = new Konva.Tween({ |
| | node: this.konva.maskGroup, |
| | duration: 1, |
| | opacity: 0.4, |
| | yoyo: true, |
| | repeat: Infinity, |
| | easing: Konva.Easings.EaseOut, |
| | }); |
| |
|
| | |
| | this.konva.maskTween.play(); |
| |
|
| | this.$lastProcessedHash.set(hash); |
| |
|
| | |
| | this.$isProcessing.set(false); |
| |
|
| | |
| | if (!this.abortController.signal.aborted) { |
| | this.abortController.abort(); |
| | } |
| | this.abortController = null; |
| | }; |
| |
|
| | |
| | |
| | |
| | process = debounce(this.processImmediate, this.config.PROCESS_DEBOUNCE_MS); |
| |
|
| | |
| | |
| | |
| | apply = () => { |
| | const imageState = this.$imageState.get(); |
| | if (!imageState) { |
| | this.log.error('No image state to apply'); |
| | return; |
| | } |
| | this.log.trace('Applying'); |
| |
|
| | |
| | const rect = this.parent.transformer.getRelativeRect(); |
| | this.manager.stateApi.rasterizeEntity({ |
| | entityIdentifier: this.parent.entityIdentifier, |
| | imageObject: imageState, |
| | position: { |
| | x: Math.round(rect.x), |
| | y: Math.round(rect.y), |
| | }, |
| | replaceObjects: true, |
| | }); |
| |
|
| | |
| | this.teardown(); |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | saveAs = (type: CanvasRenderableEntityType) => { |
| | const imageState = this.$imageState.get(); |
| | if (!imageState) { |
| | this.log.error('No image state to save as'); |
| | return; |
| | } |
| | this.log.trace(`Saving as ${type}`); |
| |
|
| | |
| | |
| | if (this.imageModule) { |
| | this.parent.renderer.adoptObjectRenderer(this.imageModule); |
| | } |
| |
|
| | |
| | const rect = this.parent.transformer.getRelativeRect(); |
| | const arg = { |
| | overrides: { |
| | objects: [imageState], |
| | position: { |
| | x: Math.round(rect.x), |
| | y: Math.round(rect.y), |
| | }, |
| | }, |
| | isSelected: true, |
| | }; |
| |
|
| | switch (type) { |
| | case 'raster_layer': |
| | this.manager.stateApi.addRasterLayer(arg); |
| | break; |
| | case 'control_layer': |
| | this.manager.stateApi.addControlLayer(arg); |
| | break; |
| | case 'inpaint_mask': |
| | this.manager.stateApi.addInpaintMask(arg); |
| | break; |
| | case 'regional_guidance': |
| | this.manager.stateApi.addRegionalGuidance(arg); |
| | break; |
| | default: |
| | assert<Equals<typeof type, never>>(false); |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | reset = () => { |
| | this.log.trace('Resetting'); |
| | this.resetEphemeralState(); |
| | }; |
| |
|
| | |
| | |
| | |
| | cancel = () => { |
| | this.log.trace('Canceling'); |
| | |
| | this.teardown(); |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | teardown = () => { |
| | this.unsubscribe(); |
| | this.konva.group.remove(); |
| | |
| | |
| | this.resetEphemeralState(); |
| | this.$isSegmenting.set(false); |
| | this.manager.stateApi.$segmentingAdapter.set(null); |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | resetEphemeralState = () => { |
| | |
| | this.abortController?.abort(); |
| | this.abortController = null; |
| |
|
| | |
| | for (const point of this.$points.get()) { |
| | point.konva.circle.destroy(); |
| | } |
| |
|
| | |
| | |
| | if (this.imageModule && this.imageModule.konva.group.parent === this.konva.group) { |
| | this.imageModule.destroy(); |
| | this.imageModule = null; |
| | } |
| | if (this.konva.maskTween) { |
| | this.konva.maskTween.destroy(); |
| | this.konva.maskTween = null; |
| | } |
| |
|
| | |
| | this.$points.set([]); |
| | this.$imageState.set(null); |
| | this.$pointType.set(1); |
| | this.$invert.set(false); |
| | this.$lastProcessedHash.set(''); |
| | this.$isProcessing.set(false); |
| |
|
| | |
| | this.konva.compositingRect.visible(false); |
| | this.konva.maskGroup.clearCache(); |
| | }; |
| |
|
| | |
| | |
| | |
| | static buildGraph = ( |
| | { image_name }: ImageDTO, |
| | points: SAMPointWithId[], |
| | invert: boolean |
| | ): { graph: Graph; outputNodeId: string } => { |
| | const graph = new Graph(getPrefixedId('canvas_segment_anything')); |
| |
|
| | |
| | |
| | const segmentAnything = graph.addNode({ |
| | id: getPrefixedId('segment_anything'), |
| | type: 'segment_anything', |
| | model: 'segment-anything-huge', |
| | image: { image_name }, |
| | point_lists: [{ points: points.map(({ x, y, label }) => ({ x, y, label })) }], |
| | mask_filter: 'largest', |
| | }); |
| |
|
| | |
| | const applyMask = graph.addNode({ |
| | id: getPrefixedId('apply_tensor_mask_to_image'), |
| | type: 'apply_tensor_mask_to_image', |
| | image: { image_name }, |
| | invert, |
| | }); |
| | graph.addEdge(segmentAnything, 'mask', applyMask, 'mask'); |
| |
|
| | return { |
| | graph, |
| | outputNodeId: applyMask.id, |
| | }; |
| | }; |
| |
|
| | |
| | |
| | |
| | getSAMPointColor(label: SAMPointLabel): RgbaColor { |
| | if (label === 0) { |
| | return this.config.SAM_POINT_NEUTRAL_COLOR; |
| | } else if (label === 1) { |
| | return this.config.SAM_POINT_FOREGROUND_COLOR; |
| | } else { |
| | |
| | return this.config.SAM_POINT_BACKGROUND_COLOR; |
| | } |
| | } |
| |
|
| | repr = () => { |
| | return { |
| | id: this.id, |
| | type: this.type, |
| | path: this.path, |
| | parent: this.parent.id, |
| | points: this.$points.get().map(({ id, konva, label }) => ({ |
| | id, |
| | label, |
| | circle: getKonvaNodeDebugAttrs(konva.circle), |
| | })), |
| | imageState: deepClone(this.$imageState.get()), |
| | imageModule: this.imageModule?.repr() ?? null, |
| | config: deepClone(this.config), |
| | $isSegmenting: this.$isSegmenting.get(), |
| | $lastProcessedHash: this.$lastProcessedHash.get(), |
| | $isProcessing: this.$isProcessing.get(), |
| | $pointType: this.$pointType.get(), |
| | $pointTypeString: this.$pointTypeString.get(), |
| | $isDraggingPoint: this.$isDraggingPoint.get(), |
| | konva: { |
| | group: getKonvaNodeDebugAttrs(this.konva.group), |
| | compositingRect: getKonvaNodeDebugAttrs(this.konva.compositingRect), |
| | maskGroup: getKonvaNodeDebugAttrs(this.konva.maskGroup), |
| | pointGroup: getKonvaNodeDebugAttrs(this.konva.pointGroup), |
| | }, |
| | }; |
| | }; |
| |
|
| | destroy = () => { |
| | this.log.debug('Destroying module'); |
| | if (this.abortController && !this.abortController.signal.aborted) { |
| | this.abortController.abort(); |
| | } |
| | this.abortController = null; |
| | this.unsubscribe(); |
| | this.konva.group.destroy(); |
| | }; |
| | } |
| |
|