Repository: olegchomp/TouchDiffusion Branch: main Commit: 33dd7e822d3d Files: 4 Total size: 15.1 KB Directory structure: gitextract_4poqc4yq/ ├── README.md ├── TouchDiffusion.tox ├── TouchDiffusionExt.py └── webui.bat ================================================ FILE CONTENTS ================================================ ================================================ FILE: README.md ================================================ # TouchDiffusion Discord Shield TouchDesigner implementation for real-time Stable Diffusion interactive generation with [StreamDiffusion](https://github.com/cumulo-autumn/StreamDiffusion). **Benchmarks with stabilityai/sd-turbo, 512x512 and 1 batch size.** | GPU | FPS | | --- | --- | | 4090 | 55-60 FPS | | 4080 | 47 FPS | | 3090ti | 37 FPS | | 3090 | 30-32 FPS | | 4070 Laptop | 24 FPS | | 3060 12GB | 16 FPS | ## Disclaimer **Notice:** This repository is in an early testing phase and may undergo significant changes. Use it at your own risk. ## Usage > [!TIP] > TouchDiffusion can be installed in multiple ways. **Portable version** have prebuild dendencies, so it prefered way to install or **Manuall install** is step by step instruction. #### Portable version: Includes preinstalled configurations, ensuring everything is readily available for immediate use. 1. Download and extract [archive](https://boosty.to/vjschool/posts/39931cd6-b9c5-4c27-93ff-d7a09b0918c5?share=post_link) 2. Run ```webui.bat```. It will provide url to web interface (ex. ```http://127.0.0.1:7860```) 3. Open ```install & update``` tab and run ```Update dependencies```. #### Manuall install: You can follow [YouTube tutorial](https://youtu.be/3WqUrWfCX1A) Required TouchDesigner 2023 & Python 3.11 1. Install [Python 3.11](https://www.python.org/downloads/release/python-3118/) 2. Install [Git](https://git-scm.com/downloads) 3. Install [CUDA Toolkit](https://developer.nvidia.com/cuda-11-8-0-download-archive) 11.8 (required PC restart) 4. Download [TouchDiffusion](https://github.com/olegchomp/TouchDiffusion/archive/refs/heads/main.zip). 5. Open ```webui.bat``` with text editor and set path to Python 3.11 in ```set PYTHON_PATH=```. (ex. ```set PYTHON_PATH="C:\Program Files\Python311\python.exe"```) 6. Run ```webui.bat```. After installation it will provide url to web interface (ex. ```http://127.0.0.1:7860```) 7. Open ```install & update``` tab and run ```Update dependencies```. (could take ~10 minutes, depending on your internet connection) 8. If you get pop up window with error related to .dll, run ```Fix pop up``` 9. Restart webui.bat #### Accelerate model: Models in ```.safetensors``` format must be in ```models\checkpoints``` folder. (as for sd_turbo, it will be auto-downloaded). **Internet connection required, while making engines.** 1) Run ```webui.bat``` 2) Select model type. 3) Select model. 4) Set width, height and amount of sampling steps (Batch size) 5) Select acceleration lora if available. 6) Run ```Make engine``` and wait for acceleration to finish. (could take ~10 minutes, depending on your hardware) #### TouchDesigner inference: 1. Add **TouchDiffusion.tox** to project 2. On ```Settings``` page change path to ```TouchDiffusion``` folder (same as where webui.bat). 3. Save and restart TouchDesigner project. 4. On ```Settings``` page select Engine and click **Load Engine**. 5. Connect animated TOP to input. Component cook only if input updates. #### Known issues / Roadmap: - [x] Fix Re-init. Sometimes required to restart TouchDesigner for initializing site-packages. - [ ] Code clean-up and rework. - [x] Custom resolution (for now fixed 512x512) - [ ] CFG not affecting image - [ ] Add Lora - [x] Add Hyper Lora support - [ ] Add ControlNet support - [ ] Add SDXL support ## Acknowledgement Based on the following projects: * [StreamDiffusion](https://github.com/cumulo-autumn/StreamDiffusion) - Pipeline-Level Solution for Real-Time Interactive Generation * [TopArray](https://github.com/IntentDev/TopArray) - Interaction between Python/PyTorch tensor operations and TouchDesigner TOPs. ================================================ FILE: TouchDiffusionExt.py ================================================ from TDStoreTools import StorageManager import TDFunctions as TDF import numpy as np import torch import os import webbrowser import json from datetime import datetime import webbrowser try: from StreamDiffusion.utils.wrapper import StreamDiffusionWrapper except Exception as e: current_time = datetime.now() formated_time = current_time.strftime("%H:%M:%S") op('fifo1').appendRow([formated_time, 'Error', e]) class TouchDiffusionExt: """ DefaultExt description """ def __init__(self, ownerComp): self.ownerComp = ownerComp self.source = op('null1') self.device = "cuda" self.to_tensor = TopArrayInterface(self.source) self.stream_toparray = torch.cuda.current_stream(device=self.device) self.rgba_tensor = torch.zeros((512, 512, 4), dtype=torch.float32).to(self.device) #512,768 self.rgba_tensor[..., 3] = 0 self.output_interface = TopCUDAInterface(512,512,4,np.float32) #768,512 self.stream = None def activate_stream(self): self.update_size() acceleration_lora = op('parameter1')['Accelerationlora',1].val if acceleration_lora == 'LCM': use_lcm_lora = True elif acceleration_lora == 'HyperSD': use_hyper_lora = True else: use_lcm_lora = False use_hyper_lora = False try: self.stream = StreamDiffusionWrapper( model_id_or_path=f"{op('parameter1')['Checkpoint',1].val}", lora_dict=op('parameter1')['Loralist',1].val, t_index_list=self.generate_t_index_list(), frame_buffer_size=1, width= int(op('parameter1')['Sizex',1]), height=int(op('parameter1')['Sizey',1]), warmup=0, acceleration="tensorrt", mode= op('parameter1')['Checkpointmode',1].val, use_denoising_batch=True, cfg_type="self", seed=int(op('parameter1')['Seed',1]), use_lcm_lora=use_lcm_lora, use_hyper_lora=use_hyper_lora, output_type='pt', model_type=op('parameter1')['Checkpointtype',1].val, touchdiffusion=True, #turbo=False ) self.stream.prepare( prompt = parent().par.Prompt.val, negative_prompt = parent().par.Negprompt.val, guidance_scale=parent().par.Cfgscale.val, delta=parent().par.Deltamult.val, t_index_list=self.update_denoising_strength() ) self.fifolog('Status', 'Engine activated') except Exception as e: self.fifolog('Error', e) def generate(self, scriptOp): stream = self.stream self.to_tensor.update(self.stream_toparray.cuda_stream) image = torch.as_tensor(self.to_tensor, device=self.device) image_tensor = self.preprocess_image(image) if hasattr(self.stream, 'batch_size'): last_element = 1 if stream.batch_size != 1 else 0 for _ in range(stream.batch_size - last_element): output_image = stream(image=image_tensor) output_tensor = self.postprocess_image(output_image) scriptOp.copyCUDAMemory( output_tensor.data_ptr(), self.output_interface.size, self.output_interface.mem_shape) def update_size(self): width = int(op('parameter1')['Sizex',1]) height = int(op('parameter1')['Sizey',1]) print(width,height) self.rgba_tensor = torch.zeros((height, width, 4), dtype=torch.float32).to(self.device) self.rgba_tensor[..., 3] = 0 self.output_interface = TopCUDAInterface(width,height,4,np.float32) def preprocess_image(self, image): image = torch.flip(image, [1]) image = torch.clamp(image, 0, 1) image = image[:3, :, :] _, h, w = image.shape # Resize to integer multiple of 32 h, w = map(lambda x: x - x % 32, (h, w)) #image = self.blend_tensors(self.prev_frame, image, 0.5) image = image.unsqueeze(0) return image def postprocess_image(self, image): image = torch.flip(image, [1]) image = image.permute(1, 2, 0) self.rgba_tensor[..., :3] = image return self.rgba_tensor def acceleration_mode(self): turbo = False lcm = False acceleration_mode = parent().par.Acceleration.val if acceleration_mode == 'LCM': lcm = True if acceleration_mode == 'sd_turbo': turbo = True return lcm, turbo def update_engines(self): menuNames = [] menuLabels = [] for root, dirs, files in os.walk('engines'): if 'unet.engine' in files: folder_name = os.path.basename(root) split_folder_name = folder_name.split('--') if len(split_folder_name) >= 10: name = [split_folder_name[0], split_folder_name[2], split_folder_name[3], split_folder_name[5]] name = '-'.join(name) menuLabels.append(name) menuNames.append(folder_name) parent().par.Enginelist.menuNames = menuNames parent().par.Enginelist.menuLabels = menuLabels self.update_selected_engine() def update_selected_engine(self): try: vals = parent().par.Enginelist.val.split('--') parent().par.Checkpoint = vals[0] parent().par.Checkpointtype = vals[1] parent().par.Accelerationlora = vals[4] parent().par.Checkpointmode = vals[7] parent().par.Controlnet = vals[9] parent().par.Loralist = vals[8] parent().par.Sizex = vals[2] parent().par.Sizey = vals[3] parent().par.Batchsizex = vals[5] parent().par.Batchsizey = vals[6] except: parent().par.Checkpoint, parent().par.Checkpointtype, parent().par.Accelerationlora = '', '', '' parent().par.Checkpointmode, parent().par.Controlnet, parent().par.Loralist = '', '', '' parent().par.Sizex, parent().par.Sizey, parent().par.Batchsizex, parent().par.Batchsizey = 0,0,0,0 def update_prompt(self): prompt = parent().par.Prompt.val self.stream.touchdiffusion_prompt(prompt) def prompt_to_str(self): prompt_list = [] seq = parent().seq.Promptblock enable_weights = parent().par.Enableweight for block in seq.blocks: if block.par.Weight.val > 0: if enable_weights: prompt_with_weight = f'({block.par.Prompt.val}){block.par.Weight.val}' else: prompt_with_weight = block.par.Prompt.val prompt_list.append(prompt_with_weight) prompt_str = ", ".join(prompt_list) return prompt_str def update_scheduler(self): t_index_list = [] seq = parent().seq.Schedulerblock for block in seq.blocks: t_index_list.append(block.par.Step) self.stream.touchdiffusion_scheduler(t_index_list) def update_denoising_strength(self): amount = parent().par.Denoise mode = parent().par.Denoisemode #self.stream.touchdiffusion_generate_t_index_list(amount, mode) t_index_list = self.stream.touchdiffusion_generate_t_index_list(amount, mode) return t_index_list def generate_t_index_list(self): batchsize = op('parameter1')['Batchsizex',1] t_index_list = [] for i in range(int(batchsize)): t_index_list.append(i) return t_index_list def update_cfg_setting(self): guidance_scale = parent().par.Cfgscale delta = parent().par.Deltamult.val self.stream.touchdiffusion_update_cfg_setting(guidance_scale=guidance_scale, delta=delta) def update_noise(self): seed = parent().par.Seed.val self.stream.touchdiffusion_update_noise(seed=seed) def parexec_onValueChange(self, par, prev): if hasattr(self.stream, 'batch_size'): if par.name == 'Prompt': self.update_prompt() elif par.name == 'Denoise': self.update_denoising_strength() elif par.name == 'Cfgscale': self.update_cfg_setting() elif par.name == 'Seed': self.update_noise() def parexec_onPulse(self, par): if par.name == 'Loadengine': self.activate_stream() elif par.name == 'Refreshenginelist': self.update_engines() if par.name[0:3] == 'Url': self.about(par.name) def fifolog(self, status, message): current_time = datetime.now() formated_time = current_time.strftime("%H:%M:%S") op('fifo1').appendRow([formated_time, status, message]) def about(self, endpoint): if endpoint == 'Urlg': webbrowser.open('https://github.com/olegchomp/TouchDiffusion', new=2) if endpoint == 'Urld': webbrowser.open('https://discord.gg/wNW8xkEjrf', new=2) if endpoint == 'Urlt': webbrowser.open('https://www.youtube.com/vjschool', new=2) if endpoint == 'Urla': webbrowser.open('https://olegcho.mp/', new=2) if endpoint == 'Urldonate': webbrowser.open('https://boosty.to/vjschool/', new=2) class TopCUDAInterface: def __init__(self, width, height, num_comps, dtype): self.mem_shape = CUDAMemoryShape() self.mem_shape.width = width self.mem_shape.height = height self.mem_shape.numComps = num_comps self.mem_shape.dataType = dtype self.bytes_per_comp = np.dtype(dtype).itemsize self.size = width * height * num_comps * self.bytes_per_comp class TopArrayInterface: def __init__(self, top, stream=0): self.top = top mem = top.cudaMemory(stream=stream) self.w, self.h = mem.shape.width, mem.shape.height self.num_comps = mem.shape.numComps self.dtype = mem.shape.dataType shape = (mem.shape.numComps, self.h, self.w) dtype_info = {'descr': [('', ' nul 2>&1 if %errorlevel% neq 0 ( echo Git is not installed or not found in the system PATH. pause exit /b 1 ) else ( echo Git is installed. ) if not exist .venv ( echo Creating .venv directory... %PYTHON_PATH% -m venv ".venv" || ( echo Failed to create virtual environment. pause exit /b 1 ) echo Activating virtual environment... call .venv\Scripts\activate || ( echo Failed to activate virtual environment. pause exit /b 1 ) echo Installing dependencies... python -m pip install --upgrade pip || ( echo Failed to update pip. pause exit /b 1 ) echo Installing dependencies... pip install gradio || ( echo Failed to install gradio. pause exit /b 1 ) if not exist StreamDiffusion ( echo Downloading StreamDiffusion... git clone https://github.com/olegchomp/StreamDiffusion || ( echo Failed to download StreamDiffusion pause exit /b 1 ) ) echo Installation complete. echo Launching WebUI... python StreamDiffusion\webui.py || ( echo No launch file found pause exit /b 1 ) ) else ( echo Activating virtual environment... call .venv\Scripts\activate.bat || ( echo Failed to activate virtual environment. pause exit /b 1 ) if not exist StreamDiffusion ( echo Downloading StreamDiffusion... git clone https://github.com/olegchomp/StreamDiffusion || ( echo Failed to download StreamDiffusion pause exit /b 1 ) ) echo Launching WebUI... python StreamDiffusion\webui.py || ( echo No launch file found pause exit /b 1 ) ) pause