From 9cdad95f488a21011d532814082119403ce594f5 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 2 Jun 2023 17:26:05 +1000 Subject: [PATCH] feat(ui): add rest of controlnet processors --- .../middleware/listenerMiddleware/index.ts | 2 + .../listeners/controlNetImageProcessed.ts | 23 +- .../controlNetProcessorParamsChanged.ts | 27 ++ .../controlNet/components/ControlNet.tsx | 105 +++--- .../ControlNetProcessorCollapse.tsx | 76 ----- .../components/ProcessorComponent.tsx | 131 +++++++ .../hooks/useProcessorNodeChanged.ts | 20 ++ .../ParamControlNetProcessorSelect.tsx | 46 +++ .../components/processors/CannyProcessor.tsx | 86 ++--- .../processors/ContentShuffleProcessor.tsx | 98 ++++++ .../components/processors/HedProcessor.tsx | 55 ++- .../processors/LineartAnimeProcessor.tsx | 40 ++- .../processors/LineartProcessor.tsx | 54 ++- .../processors/MediapipeFaceProcessor.tsx | 57 ++++ .../processors/MidasDepthProcessor.tsx | 55 +++ .../processors/MlsdImageProcessor.tsx | 85 +++++ .../processors/NormalBaeProcessor.tsx | 53 +++ .../processors/OpenposeProcessor.tsx | 66 ++++ .../components/processors/PidiProcessor.tsx | 74 ++++ .../processors/ZoeDepthProcessor.tsx | 14 + .../common/ControlNetProcessorButtons.tsx | 18 +- .../src/features/controlNet/store/actions.ts | 2 - .../features/controlNet/store/constants.ts | 166 +++++++++ .../controlNet/store/controlNetSlice.ts | 67 ++-- .../src/features/controlNet/store/types.ts | 323 +++++++++++++++++- .../graphBuilders/buildTextToImageGraph.ts | 6 +- 26 files changed, 1458 insertions(+), 291 deletions(-) create mode 100644 invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetProcessorParamsChanged.ts delete mode 100644 invokeai/frontend/web/src/features/controlNet/components/ControlNetProcessorCollapse.tsx create mode 100644 invokeai/frontend/web/src/features/controlNet/components/ProcessorComponent.tsx create mode 100644 invokeai/frontend/web/src/features/controlNet/components/hooks/useProcessorNodeChanged.ts create mode 100644 invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetProcessorSelect.tsx create mode 100644 invokeai/frontend/web/src/features/controlNet/components/processors/ContentShuffleProcessor.tsx create mode 100644 invokeai/frontend/web/src/features/controlNet/components/processors/MediapipeFaceProcessor.tsx create mode 100644 invokeai/frontend/web/src/features/controlNet/components/processors/MidasDepthProcessor.tsx create mode 100644 invokeai/frontend/web/src/features/controlNet/components/processors/MlsdImageProcessor.tsx create mode 100644 invokeai/frontend/web/src/features/controlNet/components/processors/NormalBaeProcessor.tsx create mode 100644 invokeai/frontend/web/src/features/controlNet/components/processors/OpenposeProcessor.tsx create mode 100644 invokeai/frontend/web/src/features/controlNet/components/processors/PidiProcessor.tsx create mode 100644 invokeai/frontend/web/src/features/controlNet/components/processors/ZoeDepthProcessor.tsx create mode 100644 invokeai/frontend/web/src/features/controlNet/store/constants.ts diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index 7089707217..9d938755f0 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -71,6 +71,7 @@ import { addStagingAreaImageSavedListener } from './listeners/stagingAreaImageSa import { addCommitStagingAreaImageListener } from './listeners/addCommitStagingAreaImageListener'; import { addImageCategoriesChangedListener } from './listeners/imageCategoriesChanged'; import { addControlNetImageProcessedListener } from './listeners/controlNetImageProcessed'; +import { addControlNetProcessorParamsChangedListener } from './listeners/controlNetProcessorParamsChanged'; export const listenerMiddleware = createListenerMiddleware(); @@ -177,3 +178,4 @@ addImageCategoriesChangedListener(); // ControlNet addControlNetImageProcessedListener(); +addControlNetProcessorParamsChangedListener(); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetImageProcessed.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetImageProcessed.ts index 901cb99bef..00cc2d2474 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetImageProcessed.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetImageProcessed.ts @@ -8,6 +8,7 @@ import { sessionReadyToInvoke } from 'features/system/store/actions'; import { socketInvocationComplete } from 'services/events/actions'; import { isImageOutput } from 'services/types/guards'; import { controlNetProcessedImageChanged } from 'features/controlNet/store/controlNetSlice'; +import { pick } from 'lodash-es'; const moduleLog = log.child({ namespace: 'controlNet' }); @@ -15,11 +16,27 @@ export const addControlNetImageProcessedListener = () => { startAppListening({ actionCreator: controlNetImageProcessed, effect: async (action, { dispatch, getState, take }) => { - const { controlNetId, processorNode } = action.payload; + const { controlNetId } = action.payload; + const controlNet = getState().controlNet.controlNets[controlNetId]; - // ControlNet one-off procressing graph is just he processor node, no edges + if (!controlNet.controlImage) { + moduleLog.error('Unable to process ControlNet image'); + return; + } + + // ControlNet one-off procressing graph is just the processor node, no edges. + // Also we need to grab the image. const graph: Graph = { - nodes: { [processorNode.id]: processorNode }, + nodes: { + [controlNet.processorNode.id]: { + ...controlNet.processorNode, + is_intermediate: true, + image: pick(controlNet.controlImage, [ + 'image_name', + 'image_origin', + ]), + }, + }, }; // Create a session to run the graph & wait til it's ready to invoke diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetProcessorParamsChanged.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetProcessorParamsChanged.ts new file mode 100644 index 0000000000..315b793e53 --- /dev/null +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/controlNetProcessorParamsChanged.ts @@ -0,0 +1,27 @@ +import { startAppListening } from '..'; +import { log } from 'app/logging/useLogger'; +import { controlNetImageProcessed } from 'features/controlNet/store/actions'; +import { + controlNetProcessorParamsChanged, + controlNetProcessorTypeChanged, +} from 'features/controlNet/store/controlNetSlice'; + +const moduleLog = log.child({ namespace: 'controlNet' }); + +export const addControlNetProcessorParamsChangedListener = () => { + startAppListening({ + predicate: (action) => + controlNetProcessorParamsChanged.match(action) || + controlNetProcessorTypeChanged.match(action), + effect: async (action, { dispatch, cancelActiveListeners, delay }) => { + const { controlNetId } = action.payload; + // Cancel any in-progress instances of this listener + cancelActiveListeners(); + + // Delay before starting actual work + await delay(1000); + + dispatch(controlNetImageProcessed({ controlNetId })); + }, + }); +}; diff --git a/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx b/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx index 0626b08fd9..b9b8e77fcc 100644 --- a/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx +++ b/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx @@ -1,51 +1,33 @@ -import { memo, useCallback, useState } from 'react'; -import { ControlNetProcessorNode } from '../store/types'; +import { memo, useCallback } from 'react'; +import { RequiredControlNetProcessorNode } from '../store/types'; import { ImageDTO } from 'services/api'; import CannyProcessor from './processors/CannyProcessor'; import { - CONTROLNET_PROCESSORS, ControlNet, - ControlNetModel, - ControlNetProcessor, - controlNetBeginStepPctChanged, - controlNetEndStepPctChanged, controlNetImageChanged, - controlNetModelChanged, controlNetProcessedImageChanged, - controlNetProcessorChanged, controlNetRemoved, - controlNetToggled, - controlNetWeightChanged, - isControlNetImageProcessedToggled, } from '../store/controlNetSlice'; import { useAppDispatch } from 'app/store/storeHooks'; -import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; -import IAISlider from 'common/components/IAISlider'; -import ParamControlNetIsEnabled from './parameters/ParamControlNetIsEnabled'; import ParamControlNetModel from './parameters/ParamControlNetModel'; import ParamControlNetWeight from './parameters/ParamControlNetWeight'; import ParamControlNetBeginStepPct from './parameters/ParamControlNetBeginStepPct'; import ParamControlNetEndStepPct from './parameters/ParamControlNetEndStepPct'; import { - Box, Flex, - HStack, Tab, TabList, TabPanel, TabPanels, Tabs, - VStack, - useDisclosure, } from '@chakra-ui/react'; import IAISelectableImage from './parameters/IAISelectableImage'; import IAIButton from 'common/components/IAIButton'; -import IAIIconButton from 'common/components/IAIIconButton'; -import IAISwitch from 'common/components/IAISwitch'; -import ParamControlNetIsPreprocessed from './parameters/ParamControlNetIsPreprocessed'; -import IAICollapse from 'common/components/IAICollapse'; -import ControlNetProcessorCollapse from './ControlNetProcessorCollapse'; -import IAICustomSelect from 'common/components/IAICustomSelect'; +import { controlNetImageProcessed } from '../store/actions'; +import { FaUndo } from 'react-icons/fa'; +import HedProcessor from './processors/HedProcessor'; +import ParamControlNetProcessorSelect from './parameters/ParamControlNetProcessorSelect'; +import ProcessorComponent from './ProcessorComponent'; type ControlNetProps = { controlNet: ControlNet; @@ -62,22 +44,10 @@ const ControlNet = (props: ControlNetProps) => { controlImage, isControlImageProcessed, processedControlImage, - processor, + processorNode, } = props.controlNet; const dispatch = useAppDispatch(); - const handleProcessorTypeChanged = useCallback( - (processor: string | null | undefined) => { - dispatch( - controlNetProcessorChanged({ - controlNetId, - processor: processor as ControlNetProcessor, - }) - ); - }, - [controlNetId, dispatch] - ); - const handleControlImageChanged = useCallback( (controlImage: ImageDTO) => { dispatch(controlNetImageChanged({ controlNetId, controlImage })); @@ -85,6 +55,23 @@ const ControlNet = (props: ControlNetProps) => { [controlNetId, dispatch] ); + const handleProcess = useCallback(() => { + dispatch( + controlNetImageProcessed({ + controlNetId, + }) + ); + }, [controlNetId, dispatch]); + + const handleReset = useCallback(() => { + dispatch( + controlNetProcessedImageChanged({ + controlNetId, + processedControlImage: null, + }) + ); + }, [controlNetId, dispatch]); + const handleControlImageReset = useCallback(() => { dispatch(controlNetImageChanged({ controlNetId, controlImage: null })); }, [controlNetId, dispatch]); @@ -137,18 +124,29 @@ const ControlNet = (props: ControlNetProps) => { /> - + + Preprocess + + } + onClick={handleReset} + isDisabled={Boolean(!processedControlImage)} + > + Reset Processing + @@ -158,18 +156,3 @@ const ControlNet = (props: ControlNetProps) => { }; export default memo(ControlNet); - -export type ControlNetProcessorProps = { - controlNetId: string; - controlImage: ImageDTO | null; - processedControlImage: ImageDTO | null; - type: ControlNetProcessor; -}; - -const ProcessorComponent = (props: ControlNetProcessorProps) => { - const { type } = props; - if (type === 'canny') { - return ; - } - return null; -}; diff --git a/invokeai/frontend/web/src/features/controlNet/components/ControlNetProcessorCollapse.tsx b/invokeai/frontend/web/src/features/controlNet/components/ControlNetProcessorCollapse.tsx deleted file mode 100644 index 0ce675f4ed..0000000000 --- a/invokeai/frontend/web/src/features/controlNet/components/ControlNetProcessorCollapse.tsx +++ /dev/null @@ -1,76 +0,0 @@ -// import { Collapse, Flex, useDisclosure } from '@chakra-ui/react'; -// import { memo, useState } from 'react'; -// import CannyProcessor from './processors/CannyProcessor'; -// import { ImageDTO } from 'services/api'; -// import IAICustomSelect from 'common/components/IAICustomSelect'; -// import { -// CONTROLNET_PROCESSORS, -// ControlNetProcessor, -// } from '../store/controlNetSlice'; -// import IAISwitch from 'common/components/IAISwitch'; - -// export type ControlNetProcessorProps = { -// controlNetId: string; -// controlImage: ImageDTO | null; -// processedControlImage: ImageDTO | null; -// type: ControlNetProcessor; -// }; - -// const ProcessorComponent = (props: ControlNetProcessorProps) => { -// const { type } = props; -// if (type === 'canny') { -// return ; -// } -// return null; -// }; - -// type ControlNetProcessorCollapseProps = { -// isOpen: boolean; -// controlNetId: string; -// controlImage: ImageDTO | null; -// processedControlImage: ImageDTO | null; -// }; -// const ControlNetProcessorCollapse = ( -// props: ControlNetProcessorCollapseProps -// ) => { -// const { isOpen, controlImage, controlNetId, processedControlImage } = props; - -// const [processorType, setProcessorType] = -// useState('canny'); - -// const handleProcessorTypeChanged = (type: string | null | undefined) => { -// setProcessorType(type as ControlNetProcessor); -// }; - -// return ( -// -// -// {controlImage && ( -// -// )} -// -// ); -// }; - -// export default memo(ControlNetProcessorCollapse); - -export default {}; diff --git a/invokeai/frontend/web/src/features/controlNet/components/ProcessorComponent.tsx b/invokeai/frontend/web/src/features/controlNet/components/ProcessorComponent.tsx new file mode 100644 index 0000000000..246aea70c7 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/ProcessorComponent.tsx @@ -0,0 +1,131 @@ +import { memo } from 'react'; +import { RequiredControlNetProcessorNode } from '../store/types'; +import CannyProcessor from './processors/CannyProcessor'; +import HedProcessor from './processors/HedProcessor'; +import LineartProcessor from './processors/LineartProcessor'; +import LineartAnimeProcessor from './processors/LineartAnimeProcessor'; +import ContentShuffleProcessor from './processors/ContentShuffleProcessor'; +import MediapipeFaceProcessor from './processors/MediapipeFaceProcessor'; +import MidasDepthProcessor from './processors/MidasDepthProcessor'; +import MlsdImageProcessor from './processors/MlsdImageProcessor'; +import NormalBaeProcessor from './processors/NormalBaeProcessor'; +import OpenposeProcessor from './processors/OpenposeProcessor'; +import PidiProcessor from './processors/PidiProcessor'; +import ZoeDepthProcessor from './processors/ZoeDepthProcessor'; + +export type ControlNetProcessorProps = { + controlNetId: string; + processorNode: RequiredControlNetProcessorNode; +}; + +const ProcessorComponent = (props: ControlNetProcessorProps) => { + const { controlNetId, processorNode } = props; + if (processorNode.type === 'canny_image_processor') { + return ( + + ); + } + + if (processorNode.type === 'hed_image_processor') { + return ( + + ); + } + + if (processorNode.type === 'lineart_image_processor') { + return ( + + ); + } + + if (processorNode.type === 'content_shuffle_image_processor') { + return ( + + ); + } + + if (processorNode.type === 'lineart_anime_image_processor') { + return ( + + ); + } + + if (processorNode.type === 'mediapipe_face_processor') { + return ( + + ); + } + + if (processorNode.type === 'midas_depth_image_processor') { + return ( + + ); + } + + if (processorNode.type === 'mlsd_image_processor') { + return ( + + ); + } + + if (processorNode.type === 'normalbae_image_processor') { + return ( + + ); + } + + if (processorNode.type === 'openpose_image_processor') { + return ( + + ); + } + + if (processorNode.type === 'pidi_image_processor') { + return ( + + ); + } + + if (processorNode.type === 'zoe_depth_image_processor') { + return ( + + ); + } + + return null; +}; + +export default memo(ProcessorComponent); diff --git a/invokeai/frontend/web/src/features/controlNet/components/hooks/useProcessorNodeChanged.ts b/invokeai/frontend/web/src/features/controlNet/components/hooks/useProcessorNodeChanged.ts new file mode 100644 index 0000000000..79a502cb0e --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/hooks/useProcessorNodeChanged.ts @@ -0,0 +1,20 @@ +import { useAppDispatch } from 'app/store/storeHooks'; +import { controlNetProcessorParamsChanged } from 'features/controlNet/store/controlNetSlice'; +import { ControlNetProcessorNode } from 'features/controlNet/store/types'; +import { useCallback } from 'react'; + +export const useProcessorNodeChanged = () => { + const dispatch = useAppDispatch(); + const handleProcessorNodeChanged = useCallback( + (controlNetId: string, changes: Partial) => { + dispatch( + controlNetProcessorParamsChanged({ + controlNetId, + changes, + }) + ); + }, + [dispatch] + ); + return handleProcessorNodeChanged; +}; diff --git a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetProcessorSelect.tsx b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetProcessorSelect.tsx new file mode 100644 index 0000000000..9d21727a3c --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetProcessorSelect.tsx @@ -0,0 +1,46 @@ +import IAICustomSelect from 'common/components/IAICustomSelect'; +import { memo, useCallback } from 'react'; +import { + ControlNetProcessorNode, + ControlNetProcessorType, +} from '../../store/types'; +import { controlNetProcessorTypeChanged } from '../../store/controlNetSlice'; +import { useAppDispatch } from 'app/store/storeHooks'; +import { CONTROLNET_PROCESSORS } from '../../store/constants'; + +type ParamControlNetProcessorSelectProps = { + controlNetId: string; + processorNode: ControlNetProcessorNode; +}; + +const CONTROLNET_PROCESSOR_TYPES = Object.keys( + CONTROLNET_PROCESSORS +) as ControlNetProcessorType[]; + +const ParamControlNetProcessorSelect = ( + props: ParamControlNetProcessorSelectProps +) => { + const { controlNetId, processorNode } = props; + const dispatch = useAppDispatch(); + const handleProcessorTypeChanged = useCallback( + (v: string | null | undefined) => { + dispatch( + controlNetProcessorTypeChanged({ + controlNetId, + processorType: v as ControlNetProcessorType, + }) + ); + }, + [controlNetId, dispatch] + ); + return ( + + ); +}; + +export default memo(ParamControlNetProcessorSelect); diff --git a/invokeai/frontend/web/src/features/controlNet/components/processors/CannyProcessor.tsx b/invokeai/frontend/web/src/features/controlNet/components/processors/CannyProcessor.tsx index a30c003e86..872be06177 100644 --- a/invokeai/frontend/web/src/features/controlNet/components/processors/CannyProcessor.tsx +++ b/invokeai/frontend/web/src/features/controlNet/components/processors/CannyProcessor.tsx @@ -1,75 +1,61 @@ import { Flex } from '@chakra-ui/react'; import IAISlider from 'common/components/IAISlider'; -import { useAppDispatch } from 'app/store/storeHooks'; -import { controlNetImageProcessed } from 'features/controlNet/store/actions'; -import { controlNetProcessedImageChanged } from 'features/controlNet/store/controlNetSlice'; -import ControlNetProcessorButtons from './common/ControlNetProcessorButtons'; -import { memo, useCallback, useState } from 'react'; -import { ControlNetProcessorProps } from '../ControlNet'; +import { memo, useCallback } from 'react'; +import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; +import { RequiredCannyImageProcessorInvocation } from 'features/controlNet/store/types'; -export const CANNY_PROCESSOR = 'canny_image_processor'; +type CannyProcessorProps = { + controlNetId: string; + processorNode: RequiredCannyImageProcessorInvocation; +}; -const CannyProcessor = (props: ControlNetProcessorProps) => { - const { controlNetId, controlImage, processedControlImage, type } = props; - const dispatch = useAppDispatch(); - const [lowThreshold, setLowThreshold] = useState(100); - const [highThreshold, setHighThreshold] = useState(200); +const CannyProcessor = (props: CannyProcessorProps) => { + const { controlNetId, processorNode } = props; + const { low_threshold, high_threshold } = processorNode; + const processorChanged = useProcessorNodeChanged(); - const handleProcess = useCallback(() => { - if (!controlImage) { - return; - } + const handleLowThresholdChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { low_threshold: v }); + }, + [controlNetId, processorChanged] + ); - dispatch( - controlNetImageProcessed({ - controlNetId, - processorNode: { - id: CANNY_PROCESSOR, - type: 'canny_image_processor', - image: { - image_name: controlImage.image_name, - image_origin: controlImage.image_origin, - }, - low_threshold: lowThreshold, - high_threshold: highThreshold, - }, - }) - ); - }, [controlNetId, dispatch, highThreshold, controlImage, lowThreshold]); + const handleLowThresholdReset = useCallback(() => { + processorChanged(controlNetId, { low_threshold: 100 }); + }, [controlNetId, processorChanged]); - const handleReset = useCallback(() => { - dispatch( - controlNetProcessedImageChanged({ - controlNetId, - processedControlImage: null, - }) - ); - }, [controlNetId, dispatch]); + const handleHighThresholdChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { high_threshold: v }); + }, + [controlNetId, processorChanged] + ); + + const handleHighThresholdReset = useCallback(() => { + processorChanged(controlNetId, { high_threshold: 200 }); + }, [controlNetId, processorChanged]); return ( - ); }; diff --git a/invokeai/frontend/web/src/features/controlNet/components/processors/ContentShuffleProcessor.tsx b/invokeai/frontend/web/src/features/controlNet/components/processors/ContentShuffleProcessor.tsx new file mode 100644 index 0000000000..480275cd1c --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/processors/ContentShuffleProcessor.tsx @@ -0,0 +1,98 @@ +import { Flex } from '@chakra-ui/react'; +import IAISlider from 'common/components/IAISlider'; +import { memo, useCallback } from 'react'; +import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; +import { RequiredContentShuffleImageProcessorInvocation } from 'features/controlNet/store/types'; + +type Props = { + controlNetId: string; + processorNode: RequiredContentShuffleImageProcessorInvocation; +}; + +const ContentShuffleProcessor = (props: Props) => { + const { controlNetId, processorNode } = props; + const { image_resolution, detect_resolution, w, h, f } = processorNode; + const processorChanged = useProcessorNodeChanged(); + + const handleDetectResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { detect_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleImageResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { image_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleWChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { w: v }); + }, + [controlNetId, processorChanged] + ); + + const handleHChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { h: v }); + }, + [controlNetId, processorChanged] + ); + + const handleFChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { f: v }); + }, + [controlNetId, processorChanged] + ); + + return ( + + + + + + + + ); +}; + +export default memo(ContentShuffleProcessor); diff --git a/invokeai/frontend/web/src/features/controlNet/components/processors/HedProcessor.tsx b/invokeai/frontend/web/src/features/controlNet/components/processors/HedProcessor.tsx index 891f6d0adc..22c5c487cd 100644 --- a/invokeai/frontend/web/src/features/controlNet/components/processors/HedProcessor.tsx +++ b/invokeai/frontend/web/src/features/controlNet/components/processors/HedProcessor.tsx @@ -1,39 +1,66 @@ import { Flex } from '@chakra-ui/react'; import IAISlider from 'common/components/IAISlider'; import IAISwitch from 'common/components/IAISwitch'; -import { ChangeEvent, memo, useState } from 'react'; +import { ChangeEvent, memo, useCallback } from 'react'; +import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; +import { RequiredHedImageProcessorInvocation } from 'features/controlNet/store/types'; -const HedPreprocessor = () => { - const [detectResolution, setDetectResolution] = useState(512); - const [imageResolution, setImageResolution] = useState(512); - const [isScribbleEnabled, setIsScribbleEnabled] = useState(false); +type HedProcessorProps = { + controlNetId: string; + processorNode: RequiredHedImageProcessorInvocation; +}; - const handleChangeScribble = (e: ChangeEvent) => { - setIsScribbleEnabled(e.target.checked); - }; +const HedPreprocessor = (props: HedProcessorProps) => { + const { + controlNetId, + processorNode: { detect_resolution, image_resolution, scribble }, + } = props; + + const processorChanged = useProcessorNodeChanged(); + + const handleDetectResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { detect_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleImageResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { image_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleScribbleChanged = useCallback( + (e: ChangeEvent) => { + processorChanged(controlNetId, { scribble: e.target.checked }); + }, + [controlNetId, processorChanged] + ); return ( ); diff --git a/invokeai/frontend/web/src/features/controlNet/components/processors/LineartAnimeProcessor.tsx b/invokeai/frontend/web/src/features/controlNet/components/processors/LineartAnimeProcessor.tsx index 6d4f61d8af..87a21f95f0 100644 --- a/invokeai/frontend/web/src/features/controlNet/components/processors/LineartAnimeProcessor.tsx +++ b/invokeai/frontend/web/src/features/controlNet/components/processors/LineartAnimeProcessor.tsx @@ -1,25 +1,47 @@ import { Flex } from '@chakra-ui/react'; import IAISlider from 'common/components/IAISlider'; -import { memo, useState } from 'react'; +import { memo, useCallback } from 'react'; +import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; +import { RequiredLineartAnimeImageProcessorInvocation } from 'features/controlNet/store/types'; -const LineartPreprocessor = () => { - const [detectResolution, setDetectResolution] = useState(512); - const [imageResolution, setImageResolution] = useState(512); +type Props = { + controlNetId: string; + processorNode: RequiredLineartAnimeImageProcessorInvocation; +}; + +const LineartAnimeProcessor = (props: Props) => { + const { controlNetId, processorNode } = props; + const { image_resolution, detect_resolution } = processorNode; + const processorChanged = useProcessorNodeChanged(); + + const handleDetectResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { detect_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleImageResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { image_resolution: v }); + }, + [controlNetId, processorChanged] + ); return ( { ); }; -export default memo(LineartPreprocessor); +export default memo(LineartAnimeProcessor); diff --git a/invokeai/frontend/web/src/features/controlNet/components/processors/LineartProcessor.tsx b/invokeai/frontend/web/src/features/controlNet/components/processors/LineartProcessor.tsx index 763d6f2b37..503fd73fa8 100644 --- a/invokeai/frontend/web/src/features/controlNet/components/processors/LineartProcessor.tsx +++ b/invokeai/frontend/web/src/features/controlNet/components/processors/LineartProcessor.tsx @@ -1,42 +1,66 @@ import { Flex } from '@chakra-ui/react'; import IAISlider from 'common/components/IAISlider'; +import { ChangeEvent, memo, useCallback } from 'react'; +import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; +import { RequiredLineartImageProcessorInvocation } from 'features/controlNet/store/types'; import IAISwitch from 'common/components/IAISwitch'; -import { ChangeEvent, memo, useState } from 'react'; -const LineartPreprocessor = () => { - const [detectResolution, setDetectResolution] = useState(512); - const [imageResolution, setImageResolution] = useState(512); - const [isCoarseEnabled, setIsCoarseEnabled] = useState(false); +type LineartProcessorProps = { + controlNetId: string; + processorNode: RequiredLineartImageProcessorInvocation; +}; - const handleChangeScribble = (e: ChangeEvent) => { - setIsCoarseEnabled(e.target.checked); - }; +const LineartProcessor = (props: LineartProcessorProps) => { + const { controlNetId, processorNode } = props; + const { image_resolution, detect_resolution, coarse } = processorNode; + const processorChanged = useProcessorNodeChanged(); + + const handleDetectResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { detect_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleImageResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { image_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleCoarseChanged = useCallback( + (e: ChangeEvent) => { + processorChanged(controlNetId, { coarse: e.target.checked }); + }, + [controlNetId, processorChanged] + ); return ( ); }; -export default memo(LineartPreprocessor); +export default memo(LineartProcessor); diff --git a/invokeai/frontend/web/src/features/controlNet/components/processors/MediapipeFaceProcessor.tsx b/invokeai/frontend/web/src/features/controlNet/components/processors/MediapipeFaceProcessor.tsx new file mode 100644 index 0000000000..f76f00c3b7 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/processors/MediapipeFaceProcessor.tsx @@ -0,0 +1,57 @@ +import { Flex } from '@chakra-ui/react'; +import IAISlider from 'common/components/IAISlider'; +import { memo, useCallback } from 'react'; +import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; +import { + RequiredContentShuffleImageProcessorInvocation, + RequiredMediapipeFaceProcessorInvocation, +} from 'features/controlNet/store/types'; + +type Props = { + controlNetId: string; + processorNode: RequiredMediapipeFaceProcessorInvocation; +}; + +const MediapipeFaceProcessor = (props: Props) => { + const { controlNetId, processorNode } = props; + const { max_faces, min_confidence } = processorNode; + const processorChanged = useProcessorNodeChanged(); + + const handleMaxFacesChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { max_faces: v }); + }, + [controlNetId, processorChanged] + ); + + const handleMinConfidenceChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { min_confidence: v }); + }, + [controlNetId, processorChanged] + ); + + return ( + + + + + ); +}; + +export default memo(MediapipeFaceProcessor); diff --git a/invokeai/frontend/web/src/features/controlNet/components/processors/MidasDepthProcessor.tsx b/invokeai/frontend/web/src/features/controlNet/components/processors/MidasDepthProcessor.tsx new file mode 100644 index 0000000000..9a205f28b5 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/processors/MidasDepthProcessor.tsx @@ -0,0 +1,55 @@ +import { Flex } from '@chakra-ui/react'; +import IAISlider from 'common/components/IAISlider'; +import { memo, useCallback } from 'react'; +import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; +import { RequiredMidasDepthImageProcessorInvocation } from 'features/controlNet/store/types'; + +type Props = { + controlNetId: string; + processorNode: RequiredMidasDepthImageProcessorInvocation; +}; + +const MidasDepthProcessor = (props: Props) => { + const { controlNetId, processorNode } = props; + const { a_mult, bg_th } = processorNode; + const processorChanged = useProcessorNodeChanged(); + + const handleAMultChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { a_mult: v }); + }, + [controlNetId, processorChanged] + ); + + const handleBgThChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { bg_th: v }); + }, + [controlNetId, processorChanged] + ); + + return ( + + + + + ); +}; + +export default memo(MidasDepthProcessor); diff --git a/invokeai/frontend/web/src/features/controlNet/components/processors/MlsdImageProcessor.tsx b/invokeai/frontend/web/src/features/controlNet/components/processors/MlsdImageProcessor.tsx new file mode 100644 index 0000000000..e33b2102d1 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/processors/MlsdImageProcessor.tsx @@ -0,0 +1,85 @@ +import { Flex } from '@chakra-ui/react'; +import IAISlider from 'common/components/IAISlider'; +import { memo, useCallback } from 'react'; +import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; +import { RequiredMlsdImageProcessorInvocation } from 'features/controlNet/store/types'; + +type Props = { + controlNetId: string; + processorNode: RequiredMlsdImageProcessorInvocation; +}; + +const MlsdImageProcessor = (props: Props) => { + const { controlNetId, processorNode } = props; + const { image_resolution, detect_resolution, thr_d, thr_v } = processorNode; + const processorChanged = useProcessorNodeChanged(); + + const handleDetectResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { detect_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleImageResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { image_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleThrDChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { thr_d: v }); + }, + [controlNetId, processorChanged] + ); + + const handleThrVChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { thr_v: v }); + }, + [controlNetId, processorChanged] + ); + + return ( + + + + + + + ); +}; + +export default memo(MlsdImageProcessor); diff --git a/invokeai/frontend/web/src/features/controlNet/components/processors/NormalBaeProcessor.tsx b/invokeai/frontend/web/src/features/controlNet/components/processors/NormalBaeProcessor.tsx new file mode 100644 index 0000000000..61836e7668 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/processors/NormalBaeProcessor.tsx @@ -0,0 +1,53 @@ +import { Flex } from '@chakra-ui/react'; +import IAISlider from 'common/components/IAISlider'; +import { memo, useCallback } from 'react'; +import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; +import { RequiredNormalbaeImageProcessorInvocation } from 'features/controlNet/store/types'; + +type Props = { + controlNetId: string; + processorNode: RequiredNormalbaeImageProcessorInvocation; +}; + +const NormalBaeProcessor = (props: Props) => { + const { controlNetId, processorNode } = props; + const { image_resolution, detect_resolution } = processorNode; + const processorChanged = useProcessorNodeChanged(); + + const handleDetectResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { detect_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleImageResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { image_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + return ( + + + + + ); +}; + +export default memo(NormalBaeProcessor); diff --git a/invokeai/frontend/web/src/features/controlNet/components/processors/OpenposeProcessor.tsx b/invokeai/frontend/web/src/features/controlNet/components/processors/OpenposeProcessor.tsx new file mode 100644 index 0000000000..63556d4da4 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/processors/OpenposeProcessor.tsx @@ -0,0 +1,66 @@ +import { Flex } from '@chakra-ui/react'; +import IAISlider from 'common/components/IAISlider'; +import { ChangeEvent, memo, useCallback } from 'react'; +import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; +import { RequiredOpenposeImageProcessorInvocation } from 'features/controlNet/store/types'; +import IAISwitch from 'common/components/IAISwitch'; + +type Props = { + controlNetId: string; + processorNode: RequiredOpenposeImageProcessorInvocation; +}; + +const OpenposeProcessor = (props: Props) => { + const { controlNetId, processorNode } = props; + const { image_resolution, detect_resolution, hand_and_face } = processorNode; + const processorChanged = useProcessorNodeChanged(); + + const handleDetectResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { detect_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleImageResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { image_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleHandAndFaceChanged = useCallback( + (e: ChangeEvent) => { + processorChanged(controlNetId, { hand_and_face: e.target.checked }); + }, + [controlNetId, processorChanged] + ); + + return ( + + + + + + ); +}; + +export default memo(OpenposeProcessor); diff --git a/invokeai/frontend/web/src/features/controlNet/components/processors/PidiProcessor.tsx b/invokeai/frontend/web/src/features/controlNet/components/processors/PidiProcessor.tsx new file mode 100644 index 0000000000..711d4930ab --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/processors/PidiProcessor.tsx @@ -0,0 +1,74 @@ +import { Flex } from '@chakra-ui/react'; +import IAISlider from 'common/components/IAISlider'; +import { ChangeEvent, memo, useCallback } from 'react'; +import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; +import { RequiredPidiImageProcessorInvocation } from 'features/controlNet/store/types'; +import IAISwitch from 'common/components/IAISwitch'; + +type Props = { + controlNetId: string; + processorNode: RequiredPidiImageProcessorInvocation; +}; + +const PidiProcessor = (props: Props) => { + const { controlNetId, processorNode } = props; + const { image_resolution, detect_resolution, scribble, safe } = processorNode; + const processorChanged = useProcessorNodeChanged(); + + const handleDetectResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { detect_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleImageResolutionChanged = useCallback( + (v: number) => { + processorChanged(controlNetId, { image_resolution: v }); + }, + [controlNetId, processorChanged] + ); + + const handleScribbleChanged = useCallback( + (e: ChangeEvent) => { + processorChanged(controlNetId, { scribble: e.target.checked }); + }, + [controlNetId, processorChanged] + ); + + const handleSafeChanged = useCallback( + (e: ChangeEvent) => { + processorChanged(controlNetId, { safe: e.target.checked }); + }, + [controlNetId, processorChanged] + ); + + return ( + + + + + + + ); +}; + +export default memo(PidiProcessor); diff --git a/invokeai/frontend/web/src/features/controlNet/components/processors/ZoeDepthProcessor.tsx b/invokeai/frontend/web/src/features/controlNet/components/processors/ZoeDepthProcessor.tsx new file mode 100644 index 0000000000..20a1ec4493 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/components/processors/ZoeDepthProcessor.tsx @@ -0,0 +1,14 @@ +import { memo } from 'react'; +import { RequiredZoeDepthImageProcessorInvocation } from 'features/controlNet/store/types'; + +type Props = { + controlNetId: string; + processorNode: RequiredZoeDepthImageProcessorInvocation; +}; + +const ZoeDepthProcessor = (props: Props) => { + // Has no parameters? + return null; +}; + +export default memo(ZoeDepthProcessor); diff --git a/invokeai/frontend/web/src/features/controlNet/components/processors/common/ControlNetProcessorButtons.tsx b/invokeai/frontend/web/src/features/controlNet/components/processors/common/ControlNetProcessorButtons.tsx index afa94d6ada..a051990f67 100644 --- a/invokeai/frontend/web/src/features/controlNet/components/processors/common/ControlNetProcessorButtons.tsx +++ b/invokeai/frontend/web/src/features/controlNet/components/processors/common/ControlNetProcessorButtons.tsx @@ -21,23 +21,7 @@ const ControlNetProcessorButtons = (props: ControlNetProcessorButtonsProps) => { alignItems: 'center', justifyContent: 'stretch', }} - > - - Preprocess - - } - onClick={handleReset} - isDisabled={isResetDisabled} - > - Reset Processing - - + > ); }; diff --git a/invokeai/frontend/web/src/features/controlNet/store/actions.ts b/invokeai/frontend/web/src/features/controlNet/store/actions.ts index 9b6c11f22d..3d9f56a36b 100644 --- a/invokeai/frontend/web/src/features/controlNet/store/actions.ts +++ b/invokeai/frontend/web/src/features/controlNet/store/actions.ts @@ -1,7 +1,5 @@ import { createAction } from '@reduxjs/toolkit'; -import { ControlNetProcessorNode } from './types'; export const controlNetImageProcessed = createAction<{ controlNetId: string; - processorNode: ControlNetProcessorNode; }>('controlNet/imageProcessed'); diff --git a/invokeai/frontend/web/src/features/controlNet/store/constants.ts b/invokeai/frontend/web/src/features/controlNet/store/constants.ts new file mode 100644 index 0000000000..d7a76b6a5e --- /dev/null +++ b/invokeai/frontend/web/src/features/controlNet/store/constants.ts @@ -0,0 +1,166 @@ +import { + ControlNetProcessorType, + RequiredControlNetProcessorNode, +} from './types'; + +type ControlNetProcessorsDict = Record< + ControlNetProcessorType, + { + type: ControlNetProcessorType; + label: string; + description: string; + default: RequiredControlNetProcessorNode; + } +>; + +/** + * A dict of ControlNet processors, including: + * - type + * - label + * - description + * - default values + * + * TODO: Generate from the OpenAPI schema + */ +export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = { + canny_image_processor: { + type: 'canny_image_processor', + label: 'Canny', + description: '', + default: { + id: 'canny_image_processor', + type: 'canny_image_processor', + low_threshold: 100, + high_threshold: 200, + }, + }, + content_shuffle_image_processor: { + type: 'content_shuffle_image_processor', + label: 'Content Shuffle', + description: '', + default: { + id: 'content_shuffle_image_processor', + type: 'content_shuffle_image_processor', + detect_resolution: 512, + image_resolution: 512, + h: 512, + w: 512, + f: 256, + }, + }, + hed_image_processor: { + type: 'hed_image_processor', + label: 'HED', + description: '', + default: { + id: 'hed_image_processor', + type: 'hed_image_processor', + detect_resolution: 512, + image_resolution: 512, + scribble: false, + }, + }, + lineart_anime_image_processor: { + type: 'lineart_anime_image_processor', + label: 'Lineart Anime', + description: '', + default: { + id: 'lineart_anime_image_processor', + type: 'lineart_anime_image_processor', + detect_resolution: 512, + image_resolution: 512, + }, + }, + lineart_image_processor: { + type: 'lineart_image_processor', + label: 'Lineart', + description: '', + default: { + id: 'lineart_image_processor', + type: 'lineart_image_processor', + detect_resolution: 512, + image_resolution: 512, + coarse: false, + }, + }, + mediapipe_face_processor: { + type: 'mediapipe_face_processor', + label: 'Mediapipe Face', + description: '', + default: { + id: 'mediapipe_face_processor', + type: 'mediapipe_face_processor', + max_faces: 1, + min_confidence: 0.5, + }, + }, + midas_depth_image_processor: { + type: 'midas_depth_image_processor', + label: 'Depth (Midas)', + description: '', + default: { + id: 'midas_depth_image_processor', + type: 'midas_depth_image_processor', + a_mult: 2, + bg_th: 0.1, + }, + }, + mlsd_image_processor: { + type: 'mlsd_image_processor', + label: 'MLSD', + description: '', + default: { + id: 'mlsd_image_processor', + type: 'mlsd_image_processor', + detect_resolution: 512, + image_resolution: 512, + thr_d: 0.1, + thr_v: 0.1, + }, + }, + normalbae_image_processor: { + type: 'normalbae_image_processor', + label: 'NormalBae', + description: '', + default: { + id: 'normalbae_image_processor', + type: 'normalbae_image_processor', + detect_resolution: 512, + image_resolution: 512, + }, + }, + openpose_image_processor: { + type: 'openpose_image_processor', + label: 'Openpose', + description: '', + default: { + id: 'openpose_image_processor', + type: 'openpose_image_processor', + detect_resolution: 512, + image_resolution: 512, + hand_and_face: false, + }, + }, + pidi_image_processor: { + type: 'pidi_image_processor', + label: 'PIDI', + description: '', + default: { + id: 'pidi_image_processor', + type: 'pidi_image_processor', + detect_resolution: 512, + image_resolution: 512, + scribble: false, + safe: false, + }, + }, + zoe_depth_image_processor: { + type: 'zoe_depth_image_processor', + label: 'Depth (Zoe)', + description: '', + default: { + id: 'zoe_depth_image_processor', + type: 'zoe_depth_image_processor', + }, + }, +}; diff --git a/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts b/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts index a87b591bad..674e130bdc 100644 --- a/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts +++ b/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts @@ -1,11 +1,12 @@ -import { - $CombinedState, - PayloadAction, - createSelector, -} from '@reduxjs/toolkit'; +import { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; import { RootState } from 'app/store/store'; import { ImageDTO } from 'services/api'; +import { + ControlNetProcessorType, + RequiredControlNetProcessorNode, +} from './types'; +import { CONTROLNET_PROCESSORS } from './constants'; export const CONTROLNET_MODELS = [ 'lllyasviel/sd-controlnet-canny', @@ -18,23 +19,6 @@ export const CONTROLNET_MODELS = [ 'lllyasviel/sd-controlnet-mlsd', ]; -export const CONTROLNET_PROCESSORS = [ - 'canny', - 'contentShuffle', - 'hed', - 'lineart', - 'lineartAnime', - 'mediapipeFace', - 'midasDepth', - 'mlsd', - 'normalBae', - 'openpose', - 'pidi', - 'zoeDepth', -]; - -export type ControlNetProcessor = (typeof CONTROLNET_PROCESSORS)[number]; - export type ControlNetModel = (typeof CONTROLNET_MODELS)[number]; export const initialControlNet: Omit = { @@ -46,7 +30,7 @@ export const initialControlNet: Omit = { controlImage: null, isControlImageProcessed: false, processedControlImage: null, - processor: 'canny', + processorNode: CONTROLNET_PROCESSORS.canny_image_processor.default, }; export type ControlNet = { @@ -59,17 +43,19 @@ export type ControlNet = { controlImage: ImageDTO | null; isControlImageProcessed: boolean; processedControlImage: ImageDTO | null; - processor: ControlNetProcessor; + processorNode: RequiredControlNetProcessorNode; }; export type ControlNetState = { controlNets: Record; isEnabled: boolean; + shouldAutoProcess: boolean; }; export const initialControlNetState: ControlNetState = { controlNets: {}, isEnabled: false, + shouldAutoProcess: true, }; export const controlNetSlice = createSlice({ @@ -169,15 +155,36 @@ export const controlNetSlice = createSlice({ const { controlNetId, endStepPct } = action.payload; state.controlNets[controlNetId].endStepPct = endStepPct; }, - controlNetProcessorChanged: ( + controlNetProcessorParamsChanged: ( state, action: PayloadAction<{ controlNetId: string; - processor: ControlNetProcessor; + changes: Omit< + Partial, + 'id' | 'type' | 'is_intermediate' + >; }> ) => { - const { controlNetId, processor } = action.payload; - state.controlNets[controlNetId].processor = processor; + const { controlNetId, changes } = action.payload; + const processorNode = state.controlNets[controlNetId].processorNode; + state.controlNets[controlNetId].processorNode = { + ...processorNode, + ...changes, + }; + }, + controlNetProcessorTypeChanged: ( + state, + action: PayloadAction<{ + controlNetId: string; + processorType: ControlNetProcessorType; + }> + ) => { + const { controlNetId, processorType } = action.payload; + state.controlNets[controlNetId].processorNode = + CONTROLNET_PROCESSORS[processorType].default; + }, + shouldAutoProcessToggled: (state) => { + state.shouldAutoProcess = !state.shouldAutoProcess; }, }, }); @@ -195,7 +202,9 @@ export const { controlNetWeightChanged, controlNetBeginStepPctChanged, controlNetEndStepPctChanged, - controlNetProcessorChanged, + controlNetProcessorParamsChanged, + controlNetProcessorTypeChanged, + shouldAutoProcessToggled, } = controlNetSlice.actions; export default controlNetSlice.reducer; diff --git a/invokeai/frontend/web/src/features/controlNet/store/types.ts b/invokeai/frontend/web/src/features/controlNet/store/types.ts index ca3af7b406..808a50010b 100644 --- a/invokeai/frontend/web/src/features/controlNet/store/types.ts +++ b/invokeai/frontend/web/src/features/controlNet/store/types.ts @@ -1,7 +1,8 @@ +import { isObject } from 'lodash-es'; import { CannyImageProcessorInvocation, ContentShuffleImageProcessorInvocation, - HedImageprocessorInvocation, + HedImageProcessorInvocation, LineartAnimeImageProcessorInvocation, LineartImageProcessorInvocation, MediapipeFaceProcessorInvocation, @@ -12,17 +13,317 @@ import { PidiImageProcessorInvocation, ZoeDepthImageProcessorInvocation, } from 'services/api'; +import { O } from 'ts-toolbelt'; +/** + * Any ControlNet processor node + */ export type ControlNetProcessorNode = | CannyImageProcessorInvocation - | HedImageprocessorInvocation - | LineartImageProcessorInvocation - | LineartAnimeImageProcessorInvocation - | OpenposeImageProcessorInvocation - | MidasDepthImageProcessorInvocation - | NormalbaeImageProcessorInvocation - | MlsdImageProcessorInvocation - | PidiImageProcessorInvocation | ContentShuffleImageProcessorInvocation - | ZoeDepthImageProcessorInvocation - | MediapipeFaceProcessorInvocation; + | HedImageProcessorInvocation + | LineartAnimeImageProcessorInvocation + | LineartImageProcessorInvocation + | MediapipeFaceProcessorInvocation + | MidasDepthImageProcessorInvocation + | MlsdImageProcessorInvocation + | NormalbaeImageProcessorInvocation + | OpenposeImageProcessorInvocation + | PidiImageProcessorInvocation + | ZoeDepthImageProcessorInvocation; + +/** + * Any ControlNet processor type + */ +export type ControlNetProcessorType = NonNullable< + ControlNetProcessorNode['type'] +>; + +/** + * The Canny processor node, with parameters flagged as required + */ +export type RequiredCannyImageProcessorInvocation = O.Required< + CannyImageProcessorInvocation, + 'type' | 'low_threshold' | 'high_threshold' +>; + +/** + * The ContentShuffle processor node, with parameters flagged as required + */ +export type RequiredContentShuffleImageProcessorInvocation = O.Required< + ContentShuffleImageProcessorInvocation, + 'type' | 'detect_resolution' | 'image_resolution' | 'w' | 'h' | 'f' +>; + +/** + * The HED processor node, with parameters flagged as required + */ +export type RequiredHedImageProcessorInvocation = O.Required< + HedImageProcessorInvocation, + 'type' | 'detect_resolution' | 'image_resolution' | 'scribble' +>; + +/** + * The Lineart Anime processor node, with parameters flagged as required + */ +export type RequiredLineartAnimeImageProcessorInvocation = O.Required< + LineartAnimeImageProcessorInvocation, + 'type' | 'detect_resolution' | 'image_resolution' +>; + +/** + * The Lineart processor node, with parameters flagged as required + */ +export type RequiredLineartImageProcessorInvocation = O.Required< + LineartImageProcessorInvocation, + 'type' | 'detect_resolution' | 'image_resolution' | 'coarse' +>; + +/** + * The MediapipeFace processor node, with parameters flagged as required + */ +export type RequiredMediapipeFaceProcessorInvocation = O.Required< + MediapipeFaceProcessorInvocation, + 'type' | 'max_faces' | 'min_confidence' +>; + +/** + * The MidasDepth processor node, with parameters flagged as required + */ +export type RequiredMidasDepthImageProcessorInvocation = O.Required< + MidasDepthImageProcessorInvocation, + 'type' | 'a_mult' | 'bg_th' +>; + +/** + * The MLSD processor node, with parameters flagged as required + */ +export type RequiredMlsdImageProcessorInvocation = O.Required< + MlsdImageProcessorInvocation, + 'type' | 'detect_resolution' | 'image_resolution' | 'thr_v' | 'thr_d' +>; + +/** + * The NormalBae processor node, with parameters flagged as required + */ +export type RequiredNormalbaeImageProcessorInvocation = O.Required< + NormalbaeImageProcessorInvocation, + 'type' | 'detect_resolution' | 'image_resolution' +>; + +/** + * The Openpose processor node, with parameters flagged as required + */ +export type RequiredOpenposeImageProcessorInvocation = O.Required< + OpenposeImageProcessorInvocation, + 'type' | 'detect_resolution' | 'image_resolution' | 'hand_and_face' +>; + +/** + * The Pidi processor node, with parameters flagged as required + */ +export type RequiredPidiImageProcessorInvocation = O.Required< + PidiImageProcessorInvocation, + 'type' | 'detect_resolution' | 'image_resolution' | 'safe' | 'scribble' +>; + +/** + * The ZoeDepth processor node, with parameters flagged as required + */ +export type RequiredZoeDepthImageProcessorInvocation = O.Required< + ZoeDepthImageProcessorInvocation, + 'type' +>; + +/** + * Any ControlNet Processor node, with its parameters flagged as required + */ +export type RequiredControlNetProcessorNode = + | RequiredCannyImageProcessorInvocation + | RequiredContentShuffleImageProcessorInvocation + | RequiredHedImageProcessorInvocation + | RequiredLineartAnimeImageProcessorInvocation + | RequiredLineartImageProcessorInvocation + | RequiredMediapipeFaceProcessorInvocation + | RequiredMidasDepthImageProcessorInvocation + | RequiredMlsdImageProcessorInvocation + | RequiredNormalbaeImageProcessorInvocation + | RequiredOpenposeImageProcessorInvocation + | RequiredPidiImageProcessorInvocation + | RequiredZoeDepthImageProcessorInvocation; + +/** + * Type guard for CannyImageProcessorInvocation + */ +export const isCannyImageProcessorInvocation = ( + obj: unknown +): obj is CannyImageProcessorInvocation => { + if (isObject(obj) && 'type' in obj && obj.type === 'canny_image_processor') { + return true; + } + return false; +}; + +/** + * Type guard for ContentShuffleImageProcessorInvocation + */ +export const isContentShuffleImageProcessorInvocation = ( + obj: unknown +): obj is ContentShuffleImageProcessorInvocation => { + if ( + isObject(obj) && + 'type' in obj && + obj.type === 'content_shuffle_image_processor' + ) { + return true; + } + return false; +}; + +/** + * Type guard for HedImageprocessorInvocation + */ +export const isHedImageprocessorInvocation = ( + obj: unknown +): obj is HedImageProcessorInvocation => { + if (isObject(obj) && 'type' in obj && obj.type === 'hed_image_processor') { + return true; + } + return false; +}; + +/** + * Type guard for LineartAnimeImageProcessorInvocation + */ +export const isLineartAnimeImageProcessorInvocation = ( + obj: unknown +): obj is LineartAnimeImageProcessorInvocation => { + if ( + isObject(obj) && + 'type' in obj && + obj.type === 'lineart_anime_image_processor' + ) { + return true; + } + return false; +}; + +/** + * Type guard for LineartImageProcessorInvocation + */ +export const isLineartImageProcessorInvocation = ( + obj: unknown +): obj is LineartImageProcessorInvocation => { + if ( + isObject(obj) && + 'type' in obj && + obj.type === 'lineart_image_processor' + ) { + return true; + } + return false; +}; + +/** + * Type guard for MediapipeFaceProcessorInvocation + */ +export const isMediapipeFaceProcessorInvocation = ( + obj: unknown +): obj is MediapipeFaceProcessorInvocation => { + if ( + isObject(obj) && + 'type' in obj && + obj.type === 'mediapipe_face_processor' + ) { + return true; + } + return false; +}; + +/** + * Type guard for MidasDepthImageProcessorInvocation + */ +export const isMidasDepthImageProcessorInvocation = ( + obj: unknown +): obj is MidasDepthImageProcessorInvocation => { + if ( + isObject(obj) && + 'type' in obj && + obj.type === 'midas_depth_image_processor' + ) { + return true; + } + return false; +}; + +/** + * Type guard for MlsdImageProcessorInvocation + */ +export const isMlsdImageProcessorInvocation = ( + obj: unknown +): obj is MlsdImageProcessorInvocation => { + if (isObject(obj) && 'type' in obj && obj.type === 'mlsd_image_processor') { + return true; + } + return false; +}; + +/** + * Type guard for NormalbaeImageProcessorInvocation + */ +export const isNormalbaeImageProcessorInvocation = ( + obj: unknown +): obj is NormalbaeImageProcessorInvocation => { + if ( + isObject(obj) && + 'type' in obj && + obj.type === 'normalbae_image_processor' + ) { + return true; + } + return false; +}; + +/** + * Type guard for OpenposeImageProcessorInvocation + */ +export const isOpenposeImageProcessorInvocation = ( + obj: unknown +): obj is OpenposeImageProcessorInvocation => { + if ( + isObject(obj) && + 'type' in obj && + obj.type === 'openpose_image_processor' + ) { + return true; + } + return false; +}; + +/** + * Type guard for PidiImageProcessorInvocation + */ +export const isPidiImageProcessorInvocation = ( + obj: unknown +): obj is PidiImageProcessorInvocation => { + if (isObject(obj) && 'type' in obj && obj.type === 'pidi_image_processor') { + return true; + } + return false; +}; + +/** + * Type guard for ZoeDepthImageProcessorInvocation + */ +export const isZoeDepthImageProcessorInvocation = ( + obj: unknown +): obj is ZoeDepthImageProcessorInvocation => { + if ( + isObject(obj) && + 'type' in obj && + obj.type === 'zoe_depth_image_processor' + ) { + return true; + } + return false; +}; diff --git a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildTextToImageGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildTextToImageGraph.ts index d52310abdd..9975d446a3 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildTextToImageGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graphBuilders/buildTextToImageGraph.ts @@ -12,9 +12,7 @@ import { TextToLatentsInvocation, } from 'services/api'; import { NonNullableGraph } from 'features/nodes/types/types'; -import { forEach, map, size } from 'lodash-es'; -import { ControlNetProcessorNode } from 'features/controlNet/store/types'; -import { ControlNetModel } from 'features/controlNet/store/controlNetSlice'; +import { forEach, size } from 'lodash-es'; const POSITIVE_CONDITIONING = 'positive_conditioning'; const NEGATIVE_CONDITIONING = 'negative_conditioning'; @@ -344,7 +342,7 @@ export const buildTextToImageGraph = (state: RootState): Graph => { beginStepPct, endStepPct, model, - processor, + processorNode, weight, } = controlNet;