remove old comfyUI
@@ -1,3 +0,0 @@
|
||||
ckpts
|
||||
__pycache__
|
||||
test_result
|
||||
|
Before Width: | Height: | Size: 1.4 MiB |
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Fannovel16
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,189 +0,0 @@
|
||||
# ComfyUI Frame Interpolation (ComfyUI VFI) (WIP)
|
||||
|
||||
A custom node set for Video Frame Interpolation in ComfyUI.
|
||||
**UPDATE** Memory management is improved. Now this extension takes less RAM and VRAM than before.
|
||||
|
||||
## Nodes
|
||||
* KSampler Gradually Adding More Denoise (efficient)
|
||||
* GMFSS Fortuna VFI
|
||||
* IFRNet VFI
|
||||
* IFUnet VFI
|
||||
* M2M VFI
|
||||
* RIFE VFI (4.0 - 4.9) (Note that option `fast_mode` won't do anything from v4.5+ as `contextnet` is removed)
|
||||
* FILM VFI
|
||||
* Sepconv VFI
|
||||
* AMT VFI
|
||||
* Make Interpolation State List
|
||||
* STMFNet VFI (requires at least 4 frames, can only do 2x interpolation for now)
|
||||
* FLAVR VFI (same conditions as STMFNet)
|
||||
|
||||
## Install
|
||||
### ComfyUI Manager
|
||||
Incompatibile issue with it is now fixed
|
||||
|
||||
Following this guide to install this extension
|
||||
|
||||
https://github.com/ltdrdata/ComfyUI-Manager#how-to-use
|
||||
### Command-line
|
||||
#### Windows
|
||||
Run install.bat
|
||||
|
||||
For Window users, if you are having trouble with cupy, please run `install.bat` instead of `install-cupy.py` or `python install.py`.
|
||||
#### Linux
|
||||
Open your shell app and start venv if it is used for ComfyUI. Run:
|
||||
```
|
||||
python install.py
|
||||
```
|
||||
## Support for non-CUDA device (experimental)
|
||||
If you don't have a NVidia card, you can try `taichi` ops backend powered by [Taichi Lang](https://www.taichi-lang.org/)
|
||||
|
||||
On Windows, you can install it by running `install.bat` or `pip install taichi` on Linux
|
||||
|
||||
Then change value of `ops_backend` from `cupy` to `taichi` in `config.yaml`
|
||||
|
||||
If `NotImplementedError` appears, a VFI node in the workflow isn't supported by taichi
|
||||
|
||||
## Usage
|
||||
All VFI nodes can be accessed in **category** `ComfyUI-Frame-Interpolation/VFI` if the installation is successful and require a `IMAGE` containing frames (at least 2, or at least 4 for STMF-Net/FLAVR).
|
||||
|
||||
Regarding STMFNet and FLAVR, if you only have two or three frames, you should use: Load Images -> Other VFI node (FILM is recommended in this case) with `multiplier=4` -> STMFNet VFI/FLAVR VFI
|
||||
|
||||
`clear_cache_after_n_frames` is used to avoid out-of-memory. Decreasing it makes the chance lower but also increases processing time.
|
||||
|
||||
It is recommended to use LoadImages (LoadImagesFromDirectory) from [ComfyUI-Advanced-ControlNet](https://github.com/Kosinkadink/ComfyUI-Advanced-ControlNet/) and [ComfyUI-VideoHelperSuite](https://github.com/Kosinkadink/ComfyUI-VideoHelperSuite) along side with this extension.
|
||||
|
||||
## Example
|
||||
### Simple workflow
|
||||
Workflow metadata isn't embeded
|
||||
Download these two images [anime0.png](./demo_frames/anime0.png) and [anime1.png](./demo_frames/anime0.png) and put them into a folder like `E:\test` in this image.
|
||||

|
||||
|
||||
### Complex workflow
|
||||
It's used in AnimationDiff (can load workflow metadata)
|
||||

|
||||
|
||||
## Credit
|
||||
Big thanks for styler00dollar for making [VSGAN-tensorrt-docker](https://github.com/styler00dollar/VSGAN-tensorrt-docker). About 99% the code of this repo comes from it.
|
||||
|
||||
Citation for each VFI node:
|
||||
### GMFSS Fortuna
|
||||
The All-In-One GMFSS: Dedicated for Anime Video Frame Interpolation
|
||||
|
||||
https://github.com/98mxr/GMFSS_Fortuna
|
||||
|
||||
### IFRNet
|
||||
```bibtex
|
||||
@InProceedings{Kong_2022_CVPR,
|
||||
author = {Kong, Lingtong and Jiang, Boyuan and Luo, Donghao and Chu, Wenqing and Huang, Xiaoming and Tai, Ying and Wang, Chengjie and Yang, Jie},
|
||||
title = {IFRNet: Intermediate Feature Refine Network for Efficient Frame Interpolation},
|
||||
booktitle = {Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)},
|
||||
year = {2022}
|
||||
}
|
||||
```
|
||||
|
||||
### IFUnet
|
||||
RIFE with IFUNet, FusionNet and RefineNet
|
||||
|
||||
https://github.com/98mxr/IFUNet
|
||||
### M2M
|
||||
```bibtex
|
||||
@InProceedings{hu2022m2m,
|
||||
title={Many-to-many Splatting for Efficient Video Frame Interpolation},
|
||||
author={Hu, Ping and Niklaus, Simon and Sclaroff, Stan and Saenko, Kate},
|
||||
journal={CVPR},
|
||||
year={2022}
|
||||
}
|
||||
```
|
||||
|
||||
### RIFE
|
||||
```bibtex
|
||||
@inproceedings{huang2022rife,
|
||||
title={Real-Time Intermediate Flow Estimation for Video Frame Interpolation},
|
||||
author={Huang, Zhewei and Zhang, Tianyuan and Heng, Wen and Shi, Boxin and Zhou, Shuchang},
|
||||
booktitle={Proceedings of the European Conference on Computer Vision (ECCV)},
|
||||
year={2022}
|
||||
}
|
||||
```
|
||||
|
||||
### FILM
|
||||
[Frame interpolation in PyTorch](https://github.com/dajes/frame-interpolation-pytorch)
|
||||
|
||||
```bibtex
|
||||
@inproceedings{reda2022film,
|
||||
title = {FILM: Frame Interpolation for Large Motion},
|
||||
author = {Fitsum Reda and Janne Kontkanen and Eric Tabellion and Deqing Sun and Caroline Pantofaru and Brian Curless},
|
||||
booktitle = {European Conference on Computer Vision (ECCV)},
|
||||
year = {2022}
|
||||
}
|
||||
```
|
||||
|
||||
```bibtex
|
||||
@misc{film-tf,
|
||||
title = {Tensorflow 2 Implementation of "FILM: Frame Interpolation for Large Motion"},
|
||||
author = {Fitsum Reda and Janne Kontkanen and Eric Tabellion and Deqing Sun and Caroline Pantofaru and Brian Curless},
|
||||
year = {2022},
|
||||
publisher = {GitHub},
|
||||
journal = {GitHub repository},
|
||||
howpublished = {\url{https://github.com/google-research/frame-interpolation}}
|
||||
}
|
||||
```
|
||||
|
||||
### Sepconv
|
||||
```bibtex
|
||||
[1] @inproceedings{Niklaus_WACV_2021,
|
||||
author = {Simon Niklaus and Long Mai and Oliver Wang},
|
||||
title = {Revisiting Adaptive Convolutions for Video Frame Interpolation},
|
||||
booktitle = {IEEE Winter Conference on Applications of Computer Vision},
|
||||
year = {2021}
|
||||
}
|
||||
```
|
||||
|
||||
```bibtex
|
||||
[2] @inproceedings{Niklaus_ICCV_2017,
|
||||
author = {Simon Niklaus and Long Mai and Feng Liu},
|
||||
title = {Video Frame Interpolation via Adaptive Separable Convolution},
|
||||
booktitle = {IEEE International Conference on Computer Vision},
|
||||
year = {2017}
|
||||
}
|
||||
```
|
||||
|
||||
```bibtex
|
||||
[3] @inproceedings{Niklaus_CVPR_2017,
|
||||
author = {Simon Niklaus and Long Mai and Feng Liu},
|
||||
title = {Video Frame Interpolation via Adaptive Convolution},
|
||||
booktitle = {IEEE Conference on Computer Vision and Pattern Recognition},
|
||||
year = {2017}
|
||||
}
|
||||
```
|
||||
|
||||
### AMT
|
||||
```bibtex
|
||||
@inproceedings{licvpr23amt,
|
||||
title={AMT: All-Pairs Multi-Field Transforms for Efficient Frame Interpolation},
|
||||
author={Li, Zhen and Zhu, Zuo-Liang and Han, Ling-Hao and Hou, Qibin and Guo, Chun-Le and Cheng, Ming-Ming},
|
||||
booktitle={IEEE Conference on Computer Vision and Pattern Recognition (CVPR)},
|
||||
year={2023}
|
||||
}
|
||||
```
|
||||
|
||||
### ST-MFNet
|
||||
```bibtex
|
||||
@InProceedings{Danier_2022_CVPR,
|
||||
author = {Danier, Duolikun and Zhang, Fan and Bull, David},
|
||||
title = {ST-MFNet: A Spatio-Temporal Multi-Flow Network for Frame Interpolation},
|
||||
booktitle = {Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)},
|
||||
month = {June},
|
||||
year = {2022},
|
||||
pages = {3521-3531}
|
||||
}
|
||||
```
|
||||
|
||||
### FLAVR
|
||||
```bibtex
|
||||
@article{kalluri2021flavr,
|
||||
title={FLAVR: Flow-Agnostic Video Representations for Fast Frame Interpolation},
|
||||
author={Kalluri, Tarun and Pathak, Deepak and Chandraker, Manmohan and Tran, Du},
|
||||
booktitle={arxiv},
|
||||
year={2021}
|
||||
}
|
||||
```
|
||||
@@ -1,41 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
|
||||
|
||||
from .other_nodes import Gradually_More_Denoise_KSampler
|
||||
|
||||
#Some models are commented out because the code is not completed
|
||||
#from vfi_models.eisai import EISAI_VFI
|
||||
from vfi_models.gmfss_fortuna import GMFSS_Fortuna_VFI
|
||||
from vfi_models.ifrnet import IFRNet_VFI
|
||||
from vfi_models.ifunet import IFUnet_VFI
|
||||
from vfi_models.m2m import M2M_VFI
|
||||
from vfi_models.rife import RIFE_VFI
|
||||
from vfi_models.sepconv import SepconvVFI
|
||||
from vfi_models.amt import AMT_VFI
|
||||
from vfi_models.film import FILM_VFI
|
||||
from vfi_models.stmfnet import STMFNet_VFI
|
||||
from vfi_models.flavr import FLAVR_VFI
|
||||
from vfi_models.cain import CAIN_VFI
|
||||
from vfi_utils import MakeInterpolationStateList
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"KSampler Gradually Adding More Denoise (efficient)": Gradually_More_Denoise_KSampler,
|
||||
# "EISAI VFI": EISAI_VFI,
|
||||
"GMFSS Fortuna VFI": GMFSS_Fortuna_VFI,
|
||||
"IFRNet VFI": IFRNet_VFI,
|
||||
"IFUnet VFI": IFUnet_VFI,
|
||||
"M2M VFI": M2M_VFI,
|
||||
"RIFE VFI": RIFE_VFI,
|
||||
"Sepconv VFI": SepconvVFI,
|
||||
"AMT VFI": AMT_VFI,
|
||||
"FILM VFI": FILM_VFI,
|
||||
"Make Interpolation State List": MakeInterpolationStateList,
|
||||
"STMFNet VFI": STMFNet_VFI,
|
||||
"FLAVR VFI": FLAVR_VFI,
|
||||
"CAIN VFI": CAIN_VFI
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"RIFE VFI": "RIFE VFI (recommend rife47 and rife49)"
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
#Plz don't delete this file, just edit it when neccessary.
|
||||
ckpts_path: "./ckpts"
|
||||
ops_backend: "cupy" #Either "taichi" or "cupy"
|
||||
|
Before Width: | Height: | Size: 333 KiB |
|
Before Width: | Height: | Size: 322 KiB |
|
Before Width: | Height: | Size: 127 KiB |
|
Before Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 446 KiB |
|
Before Width: | Height: | Size: 347 KiB |
|
Before Width: | Height: | Size: 349 KiB |
|
Before Width: | Height: | Size: 868 KiB |
|
Before Width: | Height: | Size: 929 KiB |
|
Before Width: | Height: | Size: 178 KiB |
@@ -1,11 +0,0 @@
|
||||
@echo off
|
||||
echo Installing Taichi lang backend...
|
||||
|
||||
if exist "%python_exec%" (
|
||||
%python_exec% -s -m pip install taichi
|
||||
) else (
|
||||
echo Installing with system Python
|
||||
pip install taichi
|
||||
)
|
||||
|
||||
pause
|
||||
@@ -1,16 +0,0 @@
|
||||
@echo off
|
||||
|
||||
set "requirements_txt=%~dp0\requirements-no-cupy.txt"
|
||||
set "python_exec=..\..\..\python_embeded\python.exe"
|
||||
|
||||
echo Installing ComfyUI Frame Interpolation..
|
||||
|
||||
if exist "%python_exec%" (
|
||||
echo Installing with ComfyUI Portable
|
||||
%python_exec% -s install.py
|
||||
) else (
|
||||
echo Installing with system Python
|
||||
python install.py
|
||||
)
|
||||
|
||||
pause
|
||||
@@ -1,59 +0,0 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import platform
|
||||
|
||||
def get_cuda_ver_from_dir(cuda_home):
|
||||
nvrtc = filter(lambda lib_file: "nvrtc-builtins" in lib_file, os.listdir(cuda_home))
|
||||
nvrtc = list(nvrtc)
|
||||
if len(nvrtc) == 0:
|
||||
return
|
||||
nvrtc = nvrtc[0]
|
||||
if ('102' in nvrtc) or ('10.2' in nvrtc):
|
||||
return '102'
|
||||
if '110' in nvrtc or ('11.0' in nvrtc):
|
||||
return '110'
|
||||
if '111' in nvrtc or ('11.1' in nvrtc):
|
||||
return '111'
|
||||
if '11' in nvrtc:
|
||||
return '11x'
|
||||
if '12' in nvrtc:
|
||||
return '12x'
|
||||
|
||||
s_param = '-s' if "python_embeded" in sys.executable else ''
|
||||
|
||||
def get_cuda_home_path():
|
||||
if "CUDA_HOME" in os.environ:
|
||||
return os.environ["CUDA_HOME"]
|
||||
import torch
|
||||
torch_lib_path = Path(torch.__file__).parent / "lib"
|
||||
torch_lib_path = str(torch_lib_path.resolve())
|
||||
if os.path.exists(torch_lib_path):
|
||||
nvrtc = filter(lambda lib_file: "nvrtc-builtins" in lib_file, os.listdir(torch_lib_path))
|
||||
nvrtc = list(nvrtc)
|
||||
return torch_lib_path if len(nvrtc) > 0 else None
|
||||
|
||||
def install_cupy():
|
||||
cuda_home = get_cuda_home_path()
|
||||
try:
|
||||
if cuda_home is not None:
|
||||
os.environ["CUDA_HOME"] = cuda_home
|
||||
os.environ["CUDA_PATH"] = cuda_home
|
||||
import cupy
|
||||
print("CuPy is already installed.")
|
||||
except:
|
||||
print("Uninstall cupy if existed...")
|
||||
os.system(f'"{sys.executable}" {s_param} -m pip uninstall -y cupy-wheel cupy-cuda102 cupy-cuda110 cupy-cuda111 cupy-cuda11x cupy-cuda12x')
|
||||
print("Installing cupy...")
|
||||
cuda_ver = get_cuda_ver_from_dir(cuda_home)
|
||||
cupy_package = f"cupy-cuda{cuda_ver}" if cuda_ver is not None else "cupy-wheel"
|
||||
os.system(f'"{sys.executable}" {s_param} -m pip install {cupy_package}')
|
||||
|
||||
with open(Path(__file__).parent / "requirements-no-cupy.txt", 'r') as f:
|
||||
for package in f.readlines():
|
||||
package = package.strip()
|
||||
print(f"Installing {package}...")
|
||||
os.system(f'"{sys.executable}" {s_param} -m pip install {package}')
|
||||
|
||||
print("Checking cupy...")
|
||||
install_cupy()
|
||||
@@ -1,88 +0,0 @@
|
||||
import latent_preview
|
||||
import comfy
|
||||
import einops
|
||||
import torch
|
||||
|
||||
def common_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent, denoise=1.0, disable_noise=False, start_step=None, last_step=None, force_full_denoise=False):
|
||||
device = comfy.model_management.get_torch_device()
|
||||
latent_image = latent["samples"]
|
||||
|
||||
if disable_noise:
|
||||
noise = torch.zeros(latent_image.size(), dtype=latent_image.dtype, layout=latent_image.layout, device="cpu")
|
||||
else:
|
||||
batch_inds = latent["batch_index"] if "batch_index" in latent else None
|
||||
noise = comfy.sample.prepare_noise(latent_image, seed, batch_inds)
|
||||
|
||||
noise_mask = None
|
||||
if "noise_mask" in latent:
|
||||
noise_mask = latent["noise_mask"]
|
||||
|
||||
preview_format = "JPEG"
|
||||
if preview_format not in ["JPEG", "PNG"]:
|
||||
preview_format = "JPEG"
|
||||
|
||||
previewer = latent_preview.get_previewer(device, model.model.latent_format)
|
||||
|
||||
pbar = comfy.utils.ProgressBar(steps)
|
||||
def callback(step, x0, x, total_steps):
|
||||
preview_bytes = None
|
||||
if previewer:
|
||||
preview_bytes = previewer.decode_latent_to_preview_image(preview_format, x0)
|
||||
pbar.update_absolute(step + 1, total_steps, preview_bytes)
|
||||
|
||||
samples = comfy.sample.sample(model, noise, steps, cfg, sampler_name, scheduler, positive, negative, latent_image,
|
||||
denoise=denoise, disable_noise=disable_noise, start_step=start_step, last_step=last_step,
|
||||
force_full_denoise=force_full_denoise, noise_mask=noise_mask, callback=callback, seed=seed)
|
||||
out = latent.copy()
|
||||
out["samples"] = samples
|
||||
return (out, )
|
||||
|
||||
class Gradually_More_Denoise_KSampler:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":
|
||||
{"model": ("MODEL",),
|
||||
"positive": ("CONDITIONING", ),
|
||||
"negative": ("CONDITIONING", ),
|
||||
"latent_image": ("LATENT", ),
|
||||
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
|
||||
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),
|
||||
"scheduler": (comfy.samplers.KSampler.SCHEDULERS, ),
|
||||
|
||||
"start_denoise": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"denoise_increment": ("FLOAT", {"default": 0.1, "min": 0.0, "max": 1.0, "step": 0.1}),
|
||||
"denoise_increment_steps": ("INT", {"default": 20, "min": 1, "max": 10000})
|
||||
},
|
||||
"optional": { "optional_vae": ("VAE",) }
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MODEL", "CONDITIONING", "CONDITIONING", "LATENT", "VAE", )
|
||||
RETURN_NAMES = ("MODEL", "CONDITIONING+", "CONDITIONING-", "LATENT", "VAE", )
|
||||
OUTPUT_NODE = True
|
||||
FUNCTION = "sample"
|
||||
CATEGORY = "ComfyUI-Frame-Interpolation/others"
|
||||
|
||||
def sample(self, model, positive, negative, latent_image, optional_vae,
|
||||
seed, steps, cfg, sampler_name, scheduler,start_denoise, denoise_increment, denoise_increment_steps):
|
||||
if start_denoise + denoise_increment * denoise_increment_steps > 1.0:
|
||||
raise Exception(f"Max denoise strength can't over 1.0 (start_denoise={start_denoise}, denoise_increment={denoise_increment}, denoise_increment_steps={denoise_increment_steps}")
|
||||
|
||||
copied_latent = latent_image.copy()
|
||||
out_samples = []
|
||||
|
||||
for latent_sample in copied_latent["samples"]:
|
||||
latent = {"samples": einops.rearrange(latent_sample, "c h w -> 1 c h w")}
|
||||
#Latent's shape is NCHW
|
||||
gradually_denoising_samples = [
|
||||
common_ksampler(
|
||||
model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent, denoise=start_denoise + denoise_increment * i
|
||||
)[0]["samples"]
|
||||
for i in range(denoise_increment_steps)
|
||||
]
|
||||
out_samples.extend(gradually_denoising_samples)
|
||||
|
||||
copied_latent["samples"] = torch.cat(out_samples, dim=0)
|
||||
return (model, positive, negative, copied_latent, optional_vae)
|
||||
@@ -1,9 +0,0 @@
|
||||
torch
|
||||
numpy
|
||||
einops
|
||||
opencv-contrib-python
|
||||
kornia
|
||||
scipy
|
||||
Pillow
|
||||
torchvision
|
||||
tqdm
|
||||
@@ -1,10 +0,0 @@
|
||||
torch
|
||||
numpy
|
||||
einops
|
||||
opencv-contrib-python
|
||||
kornia
|
||||
scipy
|
||||
Pillow
|
||||
torchvision
|
||||
tqdm
|
||||
cupy-wheel
|
||||
@@ -1,38 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
|
||||
|
||||
import shutil
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
import PIL
|
||||
import torchvision.transforms.functional as transform
|
||||
from vfi_utils import load_file_from_github_release
|
||||
from vfi_models import gmfss_fortuna, ifrnet, ifunet, m2m, rife, sepconv, amt, xvfi, cain, flavr
|
||||
import numpy as np
|
||||
|
||||
frame_0 = torch.from_numpy(np.array(PIL.Image.open("demo_frames/anime0.png").convert("RGB")).astype(np.float32) / 255.0).unsqueeze(0)
|
||||
frame_1 = torch.from_numpy(np.array(PIL.Image.open("demo_frames/anime1.png").convert("RGB")).astype(np.float32) / 255.0).unsqueeze(0)
|
||||
|
||||
|
||||
if os.path.exists("test_result"):
|
||||
shutil.rmtree("test_result")
|
||||
|
||||
vfi_node_class = gmfss_fortuna.GMFSS_Fortuna_VFI()
|
||||
for i, ckpt_name in enumerate(vfi_node_class.INPUT_TYPES()["required"]["ckpt_name"][0][:2]):
|
||||
result = vfi_node_class.vfi(ckpt_name, torch.cat([
|
||||
frame_0,
|
||||
frame_1,
|
||||
frame_0,
|
||||
frame_1
|
||||
], dim=0).cuda(), multipler=4, batch_size=2)[0]
|
||||
print(result.shape)
|
||||
print(f"Generated {result.size(0)} frames")
|
||||
frames = [PIL.Image.fromarray(np.clip((frame * 255).numpy(), 0, 255).astype(np.uint8)) for frame in result]
|
||||
print(result[0].shape)
|
||||
os.makedirs(f"test_result/video{i}", exist_ok=True)
|
||||
for j, frame in enumerate(frames):
|
||||
frame.save(f"test_result/video{i}/{j}.jpg")
|
||||
frames[0].save(f"test_result/video{i}.gif", save_all=True, append_images=frames[1:], optimize=True, duration=1/3, loop=0)
|
||||
os.startfile(f"test_result{os.path.sep}video{i}.gif")
|
||||
#torchvision.io.video.write_video("test.mp4", einops.rearrange(result, "n c h w -> n h w c").cpu(), fps=1)
|
||||
@@ -1,87 +0,0 @@
|
||||
import pathlib
|
||||
import torch
|
||||
from torch.utils.data import DataLoader
|
||||
import pathlib
|
||||
from vfi_utils import load_file_from_direct_url, preprocess_frames, postprocess_frames, generic_frame_loop, InterpolationStateList
|
||||
import typing
|
||||
from comfy.model_management import get_torch_device
|
||||
from .amt_arch import AMT_S, AMT_L, AMT_G, InputPadder
|
||||
|
||||
#https://github.com/MCG-NKU/AMT/tree/main/cfgs
|
||||
CKPT_CONFIGS = {
|
||||
"amt-s.pth": {
|
||||
"network": AMT_S,
|
||||
"params": { "corr_radius": 3, "corr_lvls": 4, "num_flows": 3 }
|
||||
},
|
||||
"amt-l.pth": {
|
||||
"network": AMT_L,
|
||||
"params": { "corr_radius": 3, "corr_lvls": 4, "num_flows": 5 }
|
||||
},
|
||||
"amt-g.pth": {
|
||||
"network": AMT_G,
|
||||
"params": { "corr_radius": 3, "corr_lvls": 4, "num_flows": 5 }
|
||||
},
|
||||
"gopro_amt-s.pth": {
|
||||
"network": AMT_S,
|
||||
"params": { "corr_radius": 3, "corr_lvls": 4, "num_flows": 3 }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MODEL_TYPE = pathlib.Path(__file__).parent.name
|
||||
|
||||
class AMT_VFI:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"ckpt_name": (list(CKPT_CONFIGS.keys()), ),
|
||||
"frames": ("IMAGE", ),
|
||||
"clear_cache_after_n_frames": ("INT", {"default": 1, "min": 1, "max": 100}),
|
||||
"multiplier": ("INT", {"default": 2, "min": 2, "max": 1000})
|
||||
},
|
||||
"optional": {
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", )
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", )
|
||||
FUNCTION = "vfi"
|
||||
CATEGORY = "ComfyUI-Frame-Interpolation/VFI"
|
||||
|
||||
def vfi(
|
||||
self,
|
||||
ckpt_name: typing.AnyStr,
|
||||
frames: torch.Tensor,
|
||||
clear_cache_after_n_frames: typing.SupportsInt = 1,
|
||||
multiplier: typing.SupportsInt = 2,
|
||||
optional_interpolation_states: InterpolationStateList = None,
|
||||
**kwargs
|
||||
):
|
||||
model_path = load_file_from_direct_url(MODEL_TYPE, f"https://huggingface.co/lalala125/AMT/resolve/main/{ckpt_name}")
|
||||
ckpt_config = CKPT_CONFIGS[ckpt_name]
|
||||
|
||||
interpolation_model = ckpt_config["network"](**ckpt_config["params"])
|
||||
interpolation_model.load_state_dict(torch.load(model_path)["state_dict"])
|
||||
interpolation_model.eval().to(get_torch_device())
|
||||
|
||||
frames = preprocess_frames(frames)
|
||||
padder = InputPadder(frames.shape, 16)
|
||||
frames = padder.pad(frames)
|
||||
|
||||
def return_middle_frame(frame_0, frame_1, timestep, model):
|
||||
return model(
|
||||
frame_0,
|
||||
frame_1,
|
||||
embt=torch.FloatTensor([timestep] * frame_0.shape[0]).view(frame_0.shape[0], 1, 1, 1).to(get_torch_device()),
|
||||
scale_factor=1.0,
|
||||
eval=True
|
||||
)["imgt_pred"]
|
||||
|
||||
args = [interpolation_model]
|
||||
out = generic_frame_loop(frames, clear_cache_after_n_frames, multiplier, return_middle_frame, *args,
|
||||
interpolation_states=optional_interpolation_states, dtype=torch.float32)
|
||||
out = padder.unpad(out)
|
||||
out = postprocess_frames(out)
|
||||
return (out,)
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import torch
|
||||
from torch.utils.data import DataLoader
|
||||
import pathlib
|
||||
from vfi_utils import load_file_from_github_release, preprocess_frames, postprocess_frames, generic_frame_loop, InterpolationStateList
|
||||
import typing
|
||||
from comfy.model_management import get_torch_device
|
||||
|
||||
MODEL_TYPE = pathlib.Path(__file__).parent.name
|
||||
CKPT_NAMES = ["pretrained_cain.pth"]
|
||||
|
||||
|
||||
class CAIN_VFI:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"ckpt_name": (CKPT_NAMES, ),
|
||||
"frames": ("IMAGE", ),
|
||||
"clear_cache_after_n_frames": ("INT", {"default": 10, "min": 1, "max": 1000}),
|
||||
"multiplier": ("INT", {"default": 2, "min": 2, "max": 1000})
|
||||
},
|
||||
"optional": {
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", )
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", )
|
||||
FUNCTION = "vfi"
|
||||
CATEGORY = "ComfyUI-Frame-Interpolation/VFI"
|
||||
|
||||
def vfi(
|
||||
self,
|
||||
ckpt_name: typing.AnyStr,
|
||||
frames: torch.Tensor,
|
||||
clear_cache_after_n_frames: typing.SupportsInt = 1,
|
||||
multiplier: typing.SupportsInt = 2,
|
||||
optional_interpolation_states: InterpolationStateList = None,
|
||||
**kwargs
|
||||
):
|
||||
from .cain_arch import CAIN
|
||||
model_path = load_file_from_github_release(MODEL_TYPE, ckpt_name)
|
||||
sd = torch.load(model_path)["state_dict"]
|
||||
sd = {key.replace('module.', ''): value for key, value in sd.items()}
|
||||
|
||||
|
||||
global interpolation_model
|
||||
interpolation_model = CAIN(depth=3)
|
||||
interpolation_model.load_state_dict(sd)
|
||||
interpolation_model.eval().to(get_torch_device())
|
||||
del sd
|
||||
|
||||
frames = preprocess_frames(frames)
|
||||
|
||||
|
||||
def return_middle_frame(frame_0, frame_1, timestep, model):
|
||||
#CAIN does some direct modifications to input frame tensors so we need to clone them
|
||||
return model(frame_0.detach().clone(), frame_1.detach().clone())[0]
|
||||
|
||||
args = [interpolation_model]
|
||||
out = postprocess_frames(
|
||||
generic_frame_loop(frames, clear_cache_after_n_frames, multiplier, return_middle_frame, *args,
|
||||
interpolation_states=optional_interpolation_states, use_timestep=False, dtype=torch.float32)
|
||||
)
|
||||
return (out,)
|
||||
@@ -1,74 +0,0 @@
|
||||
import math
|
||||
import numpy as np
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from .common import *
|
||||
|
||||
|
||||
class Encoder(nn.Module):
|
||||
def __init__(self, in_channels=3, depth=3):
|
||||
super(Encoder, self).__init__()
|
||||
|
||||
# Shuffle pixels to expand in channel dimension
|
||||
# shuffler_list = [PixelShuffle(0.5) for i in range(depth)]
|
||||
# self.shuffler = nn.Sequential(*shuffler_list)
|
||||
self.shuffler = PixelShuffle(1 / 2**depth)
|
||||
|
||||
relu = nn.LeakyReLU(0.2, True)
|
||||
|
||||
# FF_RCAN or FF_Resblocks
|
||||
self.interpolate = Interpolation(5, 12, in_channels * (4**depth), act=relu)
|
||||
|
||||
def forward(self, x1, x2):
|
||||
"""
|
||||
Encoder: Shuffle-spread --> Feature Fusion --> Return fused features
|
||||
"""
|
||||
feats1 = self.shuffler(x1)
|
||||
feats2 = self.shuffler(x2)
|
||||
|
||||
feats = self.interpolate(feats1, feats2)
|
||||
|
||||
return feats
|
||||
|
||||
|
||||
class Decoder(nn.Module):
|
||||
def __init__(self, depth=3):
|
||||
super(Decoder, self).__init__()
|
||||
|
||||
# shuffler_list = [PixelShuffle(2) for i in range(depth)]
|
||||
# self.shuffler = nn.Sequential(*shuffler_list)
|
||||
self.shuffler = PixelShuffle(2**depth)
|
||||
|
||||
def forward(self, feats):
|
||||
out = self.shuffler(feats)
|
||||
return out
|
||||
|
||||
|
||||
class CAIN(nn.Module):
|
||||
def __init__(self, depth=3):
|
||||
super(CAIN, self).__init__()
|
||||
|
||||
self.encoder = Encoder(in_channels=3, depth=depth)
|
||||
self.decoder = Decoder(depth=depth)
|
||||
|
||||
def forward(self, x1, x2):
|
||||
x1, m1 = sub_mean(x1)
|
||||
x2, m2 = sub_mean(x2)
|
||||
|
||||
if not self.training:
|
||||
paddingInput, paddingOutput = InOutPaddings(x1)
|
||||
x1 = paddingInput(x1)
|
||||
x2 = paddingInput(x2)
|
||||
|
||||
feats = self.encoder(x1, x2)
|
||||
out = self.decoder(feats)
|
||||
|
||||
if not self.training:
|
||||
out = paddingOutput(out)
|
||||
|
||||
mi = (m1 + m2) / 2
|
||||
out += mi
|
||||
|
||||
return out, feats
|
||||
@@ -1,95 +0,0 @@
|
||||
import math
|
||||
import numpy as np
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from .common import *
|
||||
from comfy.model_management import get_torch_device
|
||||
|
||||
class Encoder(nn.Module):
|
||||
def __init__(self, in_channels=3, depth=3, nf_start=32, norm=False):
|
||||
super(Encoder, self).__init__()
|
||||
self.device = get_torch_device()
|
||||
|
||||
nf = nf_start
|
||||
relu = nn.LeakyReLU(negative_slope=0.2, inplace=True)
|
||||
|
||||
self.body = nn.Sequential(
|
||||
ConvNorm(in_channels, nf * 1, 7, stride=1, norm=norm),
|
||||
relu,
|
||||
ConvNorm(nf * 1, nf * 2, 5, stride=2, norm=norm),
|
||||
relu,
|
||||
ConvNorm(nf * 2, nf * 4, 5, stride=2, norm=norm),
|
||||
relu,
|
||||
ConvNorm(nf * 4, nf * 6, 5, stride=2, norm=norm)
|
||||
)
|
||||
|
||||
self.interpolate = Interpolation(5, 12, nf * 6, reduction=16, act=relu)
|
||||
|
||||
def forward(self, x1, x2):
|
||||
"""
|
||||
Encoder: Feature Extraction --> Feature Fusion --> Return
|
||||
"""
|
||||
feats1 = self.body(x1)
|
||||
feats2 = self.body(x2)
|
||||
|
||||
feats = self.interpolate(feats1, feats2)
|
||||
|
||||
return feats
|
||||
|
||||
|
||||
class Decoder(nn.Module):
|
||||
def __init__(self, in_channels=192, out_channels=3, depth=3, norm=False, up_mode='shuffle'):
|
||||
super(Decoder, self).__init__()
|
||||
self.device = get_torch_device()
|
||||
|
||||
relu = nn.LeakyReLU(negative_slope=0.2, inplace=True)
|
||||
|
||||
nf = [in_channels, (in_channels*2)//3, in_channels//3, in_channels//6]
|
||||
#nf = [192, 128, 64, 32]
|
||||
#nf = [186, 124, 62, 31]
|
||||
self.body = nn.Sequential(
|
||||
UpConvNorm(nf[0], nf[1], mode=up_mode, norm=norm),
|
||||
ResBlock(nf[1], nf[1], norm=norm, act=relu),
|
||||
UpConvNorm(nf[1], nf[2], mode=up_mode, norm=norm),
|
||||
ResBlock(nf[2], nf[2], norm=norm, act=relu),
|
||||
UpConvNorm(nf[2], nf[3], mode=up_mode, norm=norm),
|
||||
ResBlock(nf[3], nf[3], norm=norm, act=relu),
|
||||
conv7x7(nf[3], out_channels)
|
||||
)
|
||||
|
||||
def forward(self, feats):
|
||||
out = self.body(feats)
|
||||
#out = self.conv_final(out)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class CAIN_EncDec(nn.Module):
|
||||
def __init__(self, depth=3, n_resblocks=3, start_filts=32, up_mode='shuffle'):
|
||||
super(CAIN_EncDec, self).__init__()
|
||||
self.depth = depth
|
||||
|
||||
self.encoder = Encoder(in_channels=3, depth=depth, norm=False)
|
||||
self.decoder = Decoder(in_channels=start_filts*6, depth=depth, norm=False, up_mode=up_mode)
|
||||
|
||||
def forward(self, x1, x2):
|
||||
x1, m1 = sub_mean(x1)
|
||||
x2, m2 = sub_mean(x2)
|
||||
|
||||
if not self.training:
|
||||
paddingInput, paddingOutput = InOutPaddings(x1)
|
||||
x1 = paddingInput(x1)
|
||||
x2 = paddingInput(x2)
|
||||
|
||||
feats = self.encoder(x1, x2)
|
||||
out = self.decoder(feats)
|
||||
|
||||
if not self.training:
|
||||
out = paddingOutput(out)
|
||||
|
||||
mi = (m1 + m2)/2
|
||||
out += mi
|
||||
|
||||
return out, feats
|
||||
@@ -1,73 +0,0 @@
|
||||
import math
|
||||
import numpy as np
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from .common import *
|
||||
from comfy.model_management import get_torch_device
|
||||
|
||||
class Encoder(nn.Module):
|
||||
def __init__(self, in_channels=3, depth=3):
|
||||
super(Encoder, self).__init__()
|
||||
self.device = get_torch_device()
|
||||
|
||||
self.shuffler = PixelShuffle(1/2**depth)
|
||||
# self.shuffler = nn.Sequential(
|
||||
# PixelShuffle(1/2),
|
||||
# PixelShuffle(1/2),
|
||||
# PixelShuffle(1/2))
|
||||
self.interpolate = Interpolation_res(5, 12, in_channels * (4**depth))
|
||||
|
||||
def forward(self, x1, x2):
|
||||
feats1 = self.shuffler(x1)
|
||||
feats2 = self.shuffler(x2)
|
||||
|
||||
feats = self.interpolate(feats1, feats2)
|
||||
|
||||
return feats
|
||||
|
||||
|
||||
class Decoder(nn.Module):
|
||||
def __init__(self, depth=3):
|
||||
super(Decoder, self).__init__()
|
||||
self.device = get_torch_device()
|
||||
|
||||
self.shuffler = PixelShuffle(2**depth)
|
||||
# self.shuffler = nn.Sequential(
|
||||
# PixelShuffle(2),
|
||||
# PixelShuffle(2),
|
||||
# PixelShuffle(2))
|
||||
|
||||
def forward(self, feats):
|
||||
out = self.shuffler(feats)
|
||||
return out
|
||||
|
||||
|
||||
class CAIN_NoCA(nn.Module):
|
||||
def __init__(self, depth=3):
|
||||
super(CAIN_NoCA, self).__init__()
|
||||
self.depth = depth
|
||||
|
||||
self.encoder = Encoder(in_channels=3, depth=depth)
|
||||
self.decoder = Decoder(depth=depth)
|
||||
|
||||
def forward(self, x1, x2):
|
||||
x1, m1 = sub_mean(x1)
|
||||
x2, m2 = sub_mean(x2)
|
||||
|
||||
if not self.training:
|
||||
paddingInput, paddingOutput = InOutPaddings(x1)
|
||||
x1 = paddingInput(x1)
|
||||
x2 = paddingInput(x2)
|
||||
|
||||
feats = self.encoder(x1, x2)
|
||||
out = self.decoder(feats)
|
||||
|
||||
if not self.training:
|
||||
out = paddingOutput(out)
|
||||
|
||||
mi = (m1 + m2) / 2
|
||||
out += mi
|
||||
|
||||
return out, feats
|
||||
@@ -1,361 +0,0 @@
|
||||
import math
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
def sub_mean(x):
|
||||
mean = x.mean(2, keepdim=True).mean(3, keepdim=True)
|
||||
x -= mean
|
||||
return x, mean
|
||||
|
||||
def InOutPaddings(x):
|
||||
w, h = x.size(3), x.size(2)
|
||||
padding_width, padding_height = 0, 0
|
||||
if w != ((w >> 7) << 7):
|
||||
padding_width = (((w >> 7) + 1) << 7) - w
|
||||
if h != ((h >> 7) << 7):
|
||||
padding_height = (((h >> 7) + 1) << 7) - h
|
||||
paddingInput = nn.ReflectionPad2d(padding=[padding_width // 2, padding_width - padding_width // 2,
|
||||
padding_height // 2, padding_height - padding_height // 2])
|
||||
paddingOutput = nn.ReflectionPad2d(padding=[0 - padding_width // 2, padding_width // 2 - padding_width,
|
||||
0 - padding_height // 2, padding_height // 2 - padding_height])
|
||||
return paddingInput, paddingOutput
|
||||
|
||||
|
||||
class ConvNorm(nn.Module):
|
||||
def __init__(self, in_feat, out_feat, kernel_size, stride=1, norm=False):
|
||||
super(ConvNorm, self).__init__()
|
||||
|
||||
reflection_padding = kernel_size // 2
|
||||
self.reflection_pad = nn.ReflectionPad2d(reflection_padding)
|
||||
self.conv = nn.Conv2d(in_feat, out_feat, stride=stride, kernel_size=kernel_size, bias=True)
|
||||
|
||||
self.norm = norm
|
||||
if norm == 'IN':
|
||||
self.norm = nn.InstanceNorm2d(out_feat, track_running_stats=True)
|
||||
elif norm == 'BN':
|
||||
self.norm = nn.BatchNorm2d(out_feat)
|
||||
|
||||
def forward(self, x):
|
||||
out = self.reflection_pad(x)
|
||||
out = self.conv(out)
|
||||
if self.norm:
|
||||
out = self.norm(out)
|
||||
return out
|
||||
|
||||
|
||||
class UpConvNorm(nn.Module):
|
||||
def __init__(self, in_channels, out_channels, mode='transpose', norm=False):
|
||||
super(UpConvNorm, self).__init__()
|
||||
|
||||
if mode == 'transpose':
|
||||
self.upconv = nn.ConvTranspose2d(in_channels, out_channels, kernel_size=4, stride=2, padding=1)
|
||||
elif mode == 'shuffle':
|
||||
self.upconv = nn.Sequential(
|
||||
ConvNorm(in_channels, 4*out_channels, kernel_size=3, stride=1, norm=norm),
|
||||
PixelShuffle(2))
|
||||
else:
|
||||
# out_channels is always going to be the same as in_channels
|
||||
self.upconv = nn.Sequential(
|
||||
nn.Upsample(mode='bilinear', scale_factor=2, align_corners=False),
|
||||
ConvNorm(in_channels, out_channels, kernel_size=1, stride=1, norm=norm))
|
||||
|
||||
def forward(self, x):
|
||||
out = self.upconv(x)
|
||||
return out
|
||||
|
||||
|
||||
|
||||
class meanShift(nn.Module):
|
||||
def __init__(self, rgbRange, rgbMean, sign, nChannel=3):
|
||||
super(meanShift, self).__init__()
|
||||
if nChannel == 1:
|
||||
l = rgbMean[0] * rgbRange * float(sign)
|
||||
|
||||
self.shifter = nn.Conv2d(1, 1, kernel_size=1, stride=1, padding=0)
|
||||
self.shifter.weight.data = torch.eye(1).view(1, 1, 1, 1)
|
||||
self.shifter.bias.data = torch.Tensor([l])
|
||||
elif nChannel == 3:
|
||||
r = rgbMean[0] * rgbRange * float(sign)
|
||||
g = rgbMean[1] * rgbRange * float(sign)
|
||||
b = rgbMean[2] * rgbRange * float(sign)
|
||||
|
||||
self.shifter = nn.Conv2d(3, 3, kernel_size=1, stride=1, padding=0)
|
||||
self.shifter.weight.data = torch.eye(3).view(3, 3, 1, 1)
|
||||
self.shifter.bias.data = torch.Tensor([r, g, b])
|
||||
else:
|
||||
r = rgbMean[0] * rgbRange * float(sign)
|
||||
g = rgbMean[1] * rgbRange * float(sign)
|
||||
b = rgbMean[2] * rgbRange * float(sign)
|
||||
self.shifter = nn.Conv2d(6, 6, kernel_size=1, stride=1, padding=0)
|
||||
self.shifter.weight.data = torch.eye(6).view(6, 6, 1, 1)
|
||||
self.shifter.bias.data = torch.Tensor([r, g, b, r, g, b])
|
||||
|
||||
# Freeze the meanShift layer
|
||||
for params in self.shifter.parameters():
|
||||
params.requires_grad = False
|
||||
|
||||
def forward(self, x):
|
||||
x = self.shifter(x)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
""" CONV - (BN) - RELU - CONV - (BN) """
|
||||
class ResBlock(nn.Module):
|
||||
def __init__(self, in_feat, out_feat, kernel_size=3, reduction=False, bias=True, # 'reduction' is just for placeholder
|
||||
norm=False, act=nn.ReLU(True), downscale=False):
|
||||
super(ResBlock, self).__init__()
|
||||
|
||||
self.body = nn.Sequential(
|
||||
ConvNorm(in_feat, out_feat, kernel_size=kernel_size, stride=2 if downscale else 1),
|
||||
act,
|
||||
ConvNorm(out_feat, out_feat, kernel_size=kernel_size, stride=1)
|
||||
)
|
||||
|
||||
self.downscale = None
|
||||
if downscale:
|
||||
self.downscale = nn.Conv2d(in_feat, out_feat, kernel_size=1, stride=2)
|
||||
|
||||
def forward(self, x):
|
||||
res = x
|
||||
out = self.body(x)
|
||||
if self.downscale is not None:
|
||||
res = self.downscale(res)
|
||||
out += res
|
||||
|
||||
return out
|
||||
|
||||
|
||||
## Channel Attention (CA) Layer
|
||||
class CALayer(nn.Module):
|
||||
def __init__(self, channel, reduction=16):
|
||||
super(CALayer, self).__init__()
|
||||
# global average pooling: feature --> point
|
||||
self.avg_pool = nn.AdaptiveAvgPool2d(1)
|
||||
# feature channel downscale and upscale --> channel weight
|
||||
self.conv_du = nn.Sequential(
|
||||
nn.Conv2d(channel, channel // reduction, 1, padding=0, bias=True),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.Conv2d(channel // reduction, channel, 1, padding=0, bias=True),
|
||||
nn.Sigmoid()
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
y = self.avg_pool(x)
|
||||
y = self.conv_du(y)
|
||||
return x * y, y
|
||||
|
||||
|
||||
## Residual Channel Attention Block (RCAB)
|
||||
class RCAB(nn.Module):
|
||||
def __init__(self, in_feat, out_feat, kernel_size, reduction, bias=True,
|
||||
norm=False, act=nn.ReLU(True), downscale=False, return_ca=False):
|
||||
super(RCAB, self).__init__()
|
||||
|
||||
self.body = nn.Sequential(
|
||||
ConvNorm(in_feat, out_feat, kernel_size, stride=2 if downscale else 1, norm=norm),
|
||||
act,
|
||||
ConvNorm(out_feat, out_feat, kernel_size, stride=1, norm=norm),
|
||||
CALayer(out_feat, reduction)
|
||||
)
|
||||
self.downscale = downscale
|
||||
if downscale:
|
||||
self.downConv = nn.Conv2d(in_feat, out_feat, kernel_size=3, stride=2, padding=1)
|
||||
self.return_ca = return_ca
|
||||
|
||||
def forward(self, x):
|
||||
res = x
|
||||
out, ca = self.body(x)
|
||||
if self.downscale:
|
||||
res = self.downConv(res)
|
||||
out += res
|
||||
|
||||
if self.return_ca:
|
||||
return out, ca
|
||||
else:
|
||||
return out
|
||||
|
||||
|
||||
## Residual Group (RG)
|
||||
class ResidualGroup(nn.Module):
|
||||
def __init__(self, Block, n_resblocks, n_feat, kernel_size, reduction, act, norm=False):
|
||||
super(ResidualGroup, self).__init__()
|
||||
|
||||
modules_body = [Block(n_feat, n_feat, kernel_size, reduction, bias=True, norm=norm, act=act)
|
||||
for _ in range(n_resblocks)]
|
||||
modules_body.append(ConvNorm(n_feat, n_feat, kernel_size, stride=1, norm=norm))
|
||||
self.body = nn.Sequential(*modules_body)
|
||||
|
||||
def forward(self, x):
|
||||
res = self.body(x)
|
||||
res += x
|
||||
return res
|
||||
|
||||
|
||||
def pixel_shuffle(input, scale_factor):
|
||||
batch_size, channels, in_height, in_width = input.size()
|
||||
|
||||
out_channels = int(int(channels / scale_factor) / scale_factor)
|
||||
out_height = int(in_height * scale_factor)
|
||||
out_width = int(in_width * scale_factor)
|
||||
|
||||
if scale_factor >= 1:
|
||||
input_view = input.contiguous().view(batch_size, out_channels, scale_factor, scale_factor, in_height, in_width)
|
||||
shuffle_out = input_view.permute(0, 1, 4, 2, 5, 3).contiguous()
|
||||
else:
|
||||
block_size = int(1 / scale_factor)
|
||||
input_view = input.contiguous().view(batch_size, channels, out_height, block_size, out_width, block_size)
|
||||
shuffle_out = input_view.permute(0, 1, 3, 5, 2, 4).contiguous()
|
||||
|
||||
return shuffle_out.view(batch_size, out_channels, out_height, out_width)
|
||||
|
||||
|
||||
class PixelShuffle(nn.Module):
|
||||
def __init__(self, scale_factor):
|
||||
super(PixelShuffle, self).__init__()
|
||||
self.scale_factor = scale_factor
|
||||
|
||||
def forward(self, x):
|
||||
return pixel_shuffle(x, self.scale_factor)
|
||||
def extra_repr(self):
|
||||
return 'scale_factor={}'.format(self.scale_factor)
|
||||
|
||||
|
||||
def conv(in_channels, out_channels, kernel_size,
|
||||
stride=1, bias=True, groups=1):
|
||||
return nn.Conv2d(
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size=kernel_size,
|
||||
padding=kernel_size//2,
|
||||
stride=1,
|
||||
bias=bias,
|
||||
groups=groups)
|
||||
|
||||
|
||||
def conv1x1(in_channels, out_channels, stride=1, bias=True, groups=1):
|
||||
return nn.Conv2d(
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size=1,
|
||||
stride=stride,
|
||||
bias=bias,
|
||||
groups=groups)
|
||||
|
||||
def conv3x3(in_channels, out_channels, stride=1,
|
||||
padding=1, bias=True, groups=1):
|
||||
return nn.Conv2d(
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size=3,
|
||||
stride=stride,
|
||||
padding=padding,
|
||||
bias=bias,
|
||||
groups=groups)
|
||||
|
||||
def conv5x5(in_channels, out_channels, stride=1,
|
||||
padding=2, bias=True, groups=1):
|
||||
return nn.Conv2d(
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size=5,
|
||||
stride=stride,
|
||||
padding=padding,
|
||||
bias=bias,
|
||||
groups=groups)
|
||||
|
||||
def conv7x7(in_channels, out_channels, stride=1,
|
||||
padding=3, bias=True, groups=1):
|
||||
return nn.Conv2d(
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size=7,
|
||||
stride=stride,
|
||||
padding=padding,
|
||||
bias=bias,
|
||||
groups=groups)
|
||||
|
||||
def upconv2x2(in_channels, out_channels, mode='shuffle'):
|
||||
if mode == 'transpose':
|
||||
return nn.ConvTranspose2d(
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size=4,
|
||||
stride=2,
|
||||
padding=1)
|
||||
elif mode == 'shuffle':
|
||||
return nn.Sequential(
|
||||
conv3x3(in_channels, 4*out_channels),
|
||||
PixelShuffle(2))
|
||||
else:
|
||||
# out_channels is always going to be the same as in_channels
|
||||
return nn.Sequential(
|
||||
nn.Upsample(mode='bilinear', scale_factor=2, align_corners=False),
|
||||
conv1x1(in_channels, out_channels))
|
||||
|
||||
|
||||
|
||||
class Interpolation(nn.Module):
|
||||
def __init__(self, n_resgroups, n_resblocks, n_feats,
|
||||
reduction=16, act=nn.LeakyReLU(0.2, True), norm=False):
|
||||
super(Interpolation, self).__init__()
|
||||
|
||||
# define modules: head, body, tail
|
||||
self.headConv = conv3x3(n_feats * 2, n_feats)
|
||||
|
||||
modules_body = [
|
||||
ResidualGroup(
|
||||
RCAB,
|
||||
n_resblocks=n_resblocks,
|
||||
n_feat=n_feats,
|
||||
kernel_size=3,
|
||||
reduction=reduction,
|
||||
act=act,
|
||||
norm=norm)
|
||||
for _ in range(n_resgroups)]
|
||||
self.body = nn.Sequential(*modules_body)
|
||||
|
||||
self.tailConv = conv3x3(n_feats, n_feats)
|
||||
|
||||
def forward(self, x0, x1):
|
||||
# Build input tensor
|
||||
x = torch.cat([x0, x1], dim=1)
|
||||
x = self.headConv(x)
|
||||
|
||||
res = self.body(x)
|
||||
res += x
|
||||
|
||||
out = self.tailConv(res)
|
||||
return out
|
||||
|
||||
|
||||
class Interpolation_res(nn.Module):
|
||||
def __init__(self, n_resgroups, n_resblocks, n_feats,
|
||||
act=nn.LeakyReLU(0.2, True), norm=False):
|
||||
super(Interpolation_res, self).__init__()
|
||||
|
||||
# define modules: head, body, tail (reduces concatenated inputs to n_feat)
|
||||
self.headConv = conv3x3(n_feats * 2, n_feats)
|
||||
|
||||
modules_body = [ResidualGroup(ResBlock, n_resblocks=n_resblocks, n_feat=n_feats, kernel_size=3,
|
||||
reduction=0, act=act, norm=norm)
|
||||
for _ in range(n_resgroups)]
|
||||
self.body = nn.Sequential(*modules_body)
|
||||
|
||||
self.tailConv = conv3x3(n_feats, n_feats)
|
||||
|
||||
def forward(self, x0, x1):
|
||||
# Build input tensor
|
||||
x = torch.cat([x0, x1], dim=1)
|
||||
x = self.headConv(x)
|
||||
|
||||
res = x
|
||||
for m in self.body:
|
||||
res = m(res)
|
||||
res += x
|
||||
|
||||
x = self.tailConv(res)
|
||||
|
||||
return x
|
||||
@@ -1,84 +0,0 @@
|
||||
import pathlib
|
||||
from vfi_utils import load_file_from_github_release, preprocess_frames, postprocess_frames, generic_frame_loop, InterpolationStateList
|
||||
import typing
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from comfy.model_management import soft_empty_cache, get_torch_device
|
||||
|
||||
MODEL_TYPE = pathlib.Path(__file__).parent.name
|
||||
MODEL_FILE_NAMES = {
|
||||
"ssl": "eisai_ssl.pt",
|
||||
"dtm": "eisai_dtm.pt",
|
||||
"raft": "eisai_anime_interp_full.ckpt"
|
||||
}
|
||||
|
||||
class EISAI(nn.Module):
|
||||
def __init__(self, model_file_names) -> None:
|
||||
from .eisai_arch import SoftsplatLite, DTM, RAFT
|
||||
super(EISAI, self).__init__()
|
||||
self.raft = RAFT(load_file_from_github_release(MODEL_TYPE, model_file_names["raft"]))
|
||||
self.raft.to(get_torch_device()).eval()
|
||||
|
||||
self.ssl = SoftsplatLite()
|
||||
self.ssl.load_state_dict(torch.load(load_file_from_github_release(MODEL_TYPE, model_file_names["ssl"])))
|
||||
self.ssl.to(get_torch_device()).eval()
|
||||
|
||||
self.dtm = DTM()
|
||||
self.dtm.load_state_dict(torch.load(load_file_from_github_release(MODEL_TYPE, model_file_names["dtm"])))
|
||||
self.dtm.to(get_torch_device()).eval()
|
||||
|
||||
def forward(self, img0, img1, t):
|
||||
with torch.no_grad():
|
||||
flow0, _ = self.raft(img0, img1)
|
||||
flow1, _ = self.raft(img1, img0)
|
||||
x = {
|
||||
"images": torch.stack([img0, img1], dim=1),
|
||||
"flows": torch.stack([flow0, flow1], dim=1),
|
||||
}
|
||||
out_ssl, _ = self.ssl(x, t=t, return_more=True)
|
||||
out_dtm, _ = self.dtm(x, out_ssl, _, return_more=False)
|
||||
return out_dtm[:, :3]
|
||||
|
||||
class EISAI_VFI:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"ckpt_name": (["eisai"], ),
|
||||
"frames": ("IMAGE", ),
|
||||
"clear_cache_after_n_frames": ("INT", {"default": 10, "min": 1, "max": 1000}),
|
||||
"multiplier": ("INT", {"default": 2, "min": 2, "max": 1000}),
|
||||
},
|
||||
"optional": {
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", )
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", )
|
||||
FUNCTION = "vfi"
|
||||
CATEGORY = "ComfyUI-Frame-Interpolation/VFI"
|
||||
|
||||
def vfi(
|
||||
self,
|
||||
ckpt_name: typing.AnyStr,
|
||||
frames: torch.Tensor,
|
||||
clear_cache_after_n_frames = 10,
|
||||
multiplier: typing.SupportsInt = 2,
|
||||
optional_interpolation_states: InterpolationStateList = None,
|
||||
**kwargs
|
||||
):
|
||||
interpolation_model = EISAI(MODEL_FILE_NAMES)
|
||||
interpolation_model.eval().to(get_torch_device())
|
||||
frames = preprocess_frames(frames)
|
||||
|
||||
def return_middle_frame(frame_0, frame_1, timestep, model):
|
||||
return model(frame_0, frame_1, t=timestep)
|
||||
|
||||
scale = 1
|
||||
|
||||
args = [interpolation_model, scale]
|
||||
out = postprocess_frames(
|
||||
generic_frame_loop(frames, clear_cache_after_n_frames, multiplier, return_middle_frame, *args,
|
||||
interpolation_states=optional_interpolation_states, dtype=torch.float32)
|
||||
)
|
||||
return (out,)
|
||||
@@ -1,107 +0,0 @@
|
||||
import torch
|
||||
from comfy.model_management import get_torch_device, soft_empty_cache
|
||||
import bisect
|
||||
import numpy as np
|
||||
import typing
|
||||
from vfi_utils import InterpolationStateList, load_file_from_github_release, preprocess_frames, postprocess_frames
|
||||
import pathlib
|
||||
import gc
|
||||
|
||||
MODEL_TYPE = pathlib.Path(__file__).parent.name
|
||||
DEVICE = get_torch_device()
|
||||
def inference(model, img_batch_1, img_batch_2, inter_frames):
|
||||
results = [
|
||||
img_batch_1,
|
||||
img_batch_2
|
||||
]
|
||||
|
||||
idxes = [0, inter_frames + 1]
|
||||
remains = list(range(1, inter_frames + 1))
|
||||
|
||||
splits = torch.linspace(0, 1, inter_frames + 2)
|
||||
|
||||
for _ in range(len(remains)):
|
||||
starts = splits[idxes[:-1]]
|
||||
ends = splits[idxes[1:]]
|
||||
distances = ((splits[None, remains] - starts[:, None]) / (ends[:, None] - starts[:, None]) - .5).abs()
|
||||
matrix = torch.argmin(distances).item()
|
||||
start_i, step = np.unravel_index(matrix, distances.shape)
|
||||
end_i = start_i + 1
|
||||
|
||||
x0 = results[start_i].to(DEVICE)
|
||||
x1 = results[end_i].to(DEVICE)
|
||||
dt = x0.new_full((1, 1), (splits[remains[step]] - splits[idxes[start_i]])) / (splits[idxes[end_i]] - splits[idxes[start_i]])
|
||||
|
||||
with torch.no_grad():
|
||||
prediction = model(x0, x1, dt)
|
||||
insert_position = bisect.bisect_left(idxes, remains[step])
|
||||
idxes.insert(insert_position, remains[step])
|
||||
results.insert(insert_position, prediction.clamp(0, 1).float())
|
||||
del remains[step]
|
||||
|
||||
return [tensor.flip(0) for tensor in results]
|
||||
|
||||
class FILM_VFI:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"ckpt_name": (["film_net_fp32.pt"], ),
|
||||
"frames": ("IMAGE", ),
|
||||
"clear_cache_after_n_frames": ("INT", {"default": 10, "min": 1, "max": 1000}),
|
||||
"multiplier": ("INT", {"default": 2, "min": 2, "max": 1000}),
|
||||
},
|
||||
"optional": {
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", )
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", )
|
||||
FUNCTION = "vfi"
|
||||
CATEGORY = "ComfyUI-Frame-Interpolation/VFI"
|
||||
|
||||
def vfi(
|
||||
self,
|
||||
ckpt_name: typing.AnyStr,
|
||||
frames: torch.Tensor,
|
||||
clear_cache_after_n_frames = 10,
|
||||
multiplier: typing.SupportsInt = 2,
|
||||
optional_interpolation_states: InterpolationStateList = None,
|
||||
**kwargs
|
||||
):
|
||||
interpolation_states = optional_interpolation_states
|
||||
model_path = load_file_from_github_release(MODEL_TYPE, ckpt_name)
|
||||
model = torch.jit.load(model_path, map_location='cpu')
|
||||
model.eval()
|
||||
model = model.to(DEVICE)
|
||||
dtype = torch.float32
|
||||
|
||||
frames = preprocess_frames(frames)
|
||||
number_of_frames_processed_since_last_cleared_cuda_cache = 0
|
||||
output_frames = []
|
||||
for frame_itr in range(len(frames) - 1): # Skip the final frame since there are no frames after it
|
||||
if interpolation_states is not None and interpolation_states.is_frame_skipped(frame_itr):
|
||||
continue
|
||||
#Ensure that input frames are in fp32 - the same dtype as model
|
||||
frame_0 = frames[frame_itr:frame_itr+1].to(DEVICE).float()
|
||||
frame_1 = frames[frame_itr+1:frame_itr+2].to(DEVICE).float()
|
||||
relust = inference(model, frame_0, frame_1, multiplier - 1)
|
||||
output_frames.extend([frame.detach().cpu().to(dtype=dtype) for frame in relust[:-1]])
|
||||
|
||||
number_of_frames_processed_since_last_cleared_cuda_cache += 1
|
||||
# Try to avoid a memory overflow by clearing cuda cache regularly
|
||||
if number_of_frames_processed_since_last_cleared_cuda_cache >= clear_cache_after_n_frames:
|
||||
print("Comfy-VFI: Clearing cache...")
|
||||
soft_empty_cache()
|
||||
number_of_frames_processed_since_last_cleared_cuda_cache = 0
|
||||
print("Comfy-VFI: Done cache clearing")
|
||||
gc.collect()
|
||||
|
||||
output_frames.append(frames[-1:].to(dtype=dtype)) # Append final frame
|
||||
output_frames = [frame.cpu() for frame in output_frames] #Ensure all frames are in cpu
|
||||
out = torch.cat(output_frames, dim=0)
|
||||
# clear cache for courtesy
|
||||
print("Comfy-VFI: Final clearing cache...")
|
||||
soft_empty_cache()
|
||||
print("Comfy-VFI: Done cache clearing")
|
||||
return (postprocess_frames(out), )
|
||||
@@ -1,788 +0,0 @@
|
||||
"""
|
||||
https://github.com/dajes/frame-interpolation-pytorch/blob/main/feature_extractor.py
|
||||
https://github.com/dajes/frame-interpolation-pytorch/blob/main/fusion.py
|
||||
https://github.com/dajes/frame-interpolation-pytorch/blob/main/interpolator.py
|
||||
https://github.com/dajes/frame-interpolation-pytorch/blob/main/pyramid_flow_estimator.py
|
||||
https://github.com/dajes/frame-interpolation-pytorch/blob/main/util.py
|
||||
"""
|
||||
|
||||
"""PyTorch layer for extracting image features for the film_net interpolator.
|
||||
|
||||
The feature extractor implemented here converts an image pyramid into a pyramid
|
||||
of deep features. The feature pyramid serves a similar purpose as U-Net
|
||||
architecture's encoder, but we use a special cascaded architecture described in
|
||||
Multi-view Image Fusion [1].
|
||||
|
||||
For comprehensiveness, below is a short description of the idea. While the
|
||||
description is a bit involved, the cascaded feature pyramid can be used just
|
||||
like any image feature pyramid.
|
||||
|
||||
Why cascaded architeture?
|
||||
=========================
|
||||
To understand the concept it is worth reviewing a traditional feature pyramid
|
||||
first: *A traditional feature pyramid* as in U-net or in many optical flow
|
||||
networks is built by alternating between convolutions and pooling, starting
|
||||
from the input image.
|
||||
|
||||
It is well known that early features of such architecture correspond to low
|
||||
level concepts such as edges in the image whereas later layers extract
|
||||
semantically higher level concepts such as object classes etc. In other words,
|
||||
the meaning of the filters in each resolution level is different. For problems
|
||||
such as semantic segmentation and many others this is a desirable property.
|
||||
|
||||
However, the asymmetric features preclude sharing weights across resolution
|
||||
levels in the feature extractor itself and in any subsequent neural networks
|
||||
that follow. This can be a downside, since optical flow prediction, for
|
||||
instance is symmetric across resolution levels. The cascaded feature
|
||||
architecture addresses this shortcoming.
|
||||
|
||||
How is it built?
|
||||
================
|
||||
The *cascaded* feature pyramid contains feature vectors that have constant
|
||||
length and meaning on each resolution level, except few of the finest ones. The
|
||||
advantage of this is that the subsequent optical flow layer can learn
|
||||
synergically from many resolutions. This means that coarse level prediction can
|
||||
benefit from finer resolution training examples, which can be useful with
|
||||
moderately sized datasets to avoid overfitting.
|
||||
|
||||
The cascaded feature pyramid is built by extracting shallower subtree pyramids,
|
||||
each one of them similar to the traditional architecture. Each subtree
|
||||
pyramid S_i is extracted starting from each resolution level:
|
||||
|
||||
image resolution 0 -> S_0
|
||||
image resolution 1 -> S_1
|
||||
image resolution 2 -> S_2
|
||||
...
|
||||
|
||||
If we denote the features at level j of subtree i as S_i_j, the cascaded pyramid
|
||||
is constructed by concatenating features as follows (assuming subtree depth=3):
|
||||
|
||||
lvl
|
||||
feat_0 = concat( S_0_0 )
|
||||
feat_1 = concat( S_1_0 S_0_1 )
|
||||
feat_2 = concat( S_2_0 S_1_1 S_0_2 )
|
||||
feat_3 = concat( S_3_0 S_2_1 S_1_2 )
|
||||
feat_4 = concat( S_4_0 S_3_1 S_2_2 )
|
||||
feat_5 = concat( S_5_0 S_4_1 S_3_2 )
|
||||
....
|
||||
|
||||
In above, all levels except feat_0 and feat_1 have the same number of features
|
||||
with similar semantic meaning. This enables training a single optical flow
|
||||
predictor module shared by levels 2,3,4,5... . For more details and evaluation
|
||||
see [1].
|
||||
|
||||
[1] Multi-view Image Fusion, Trinidad et al. 2019
|
||||
"""
|
||||
from typing import List
|
||||
|
||||
import torch
|
||||
from torch import nn
|
||||
from torch.nn import functional as F
|
||||
|
||||
|
||||
class SubTreeExtractor(nn.Module):
|
||||
"""Extracts a hierarchical set of features from an image.
|
||||
|
||||
This is a conventional, hierarchical image feature extractor, that extracts
|
||||
[k, k*2, k*4... ] filters for the image pyramid where k=options.sub_levels.
|
||||
Each level is followed by average pooling.
|
||||
"""
|
||||
|
||||
def __init__(self, in_channels=3, channels=64, n_layers=4):
|
||||
super().__init__()
|
||||
convs = []
|
||||
for i in range(n_layers):
|
||||
convs.append(nn.Sequential(
|
||||
conv(in_channels, (channels << i), 3),
|
||||
conv((channels << i), (channels << i), 3)
|
||||
))
|
||||
in_channels = channels << i
|
||||
self.convs = nn.ModuleList(convs)
|
||||
|
||||
def forward(self, image: torch.Tensor, n: int) -> List[torch.Tensor]:
|
||||
"""Extracts a pyramid of features from the image.
|
||||
|
||||
Args:
|
||||
image: TORCH.Tensor with shape BATCH_SIZE x HEIGHT x WIDTH x CHANNELS.
|
||||
n: number of pyramid levels to extract. This can be less or equal to
|
||||
options.sub_levels given in the __init__.
|
||||
Returns:
|
||||
The pyramid of features, starting from the finest level. Each element
|
||||
contains the output after the last convolution on the corresponding
|
||||
pyramid level.
|
||||
"""
|
||||
head = image
|
||||
pyramid = []
|
||||
for i, layer in enumerate(self.convs):
|
||||
head = layer(head)
|
||||
pyramid.append(head)
|
||||
if i < n - 1:
|
||||
head = F.avg_pool2d(head, kernel_size=2, stride=2)
|
||||
return pyramid
|
||||
|
||||
|
||||
class FeatureExtractor(nn.Module):
|
||||
"""Extracts features from an image pyramid using a cascaded architecture.
|
||||
"""
|
||||
|
||||
def __init__(self, in_channels=3, channels=64, sub_levels=4):
|
||||
super().__init__()
|
||||
self.extract_sublevels = SubTreeExtractor(in_channels, channels, sub_levels)
|
||||
self.sub_levels = sub_levels
|
||||
|
||||
def forward(self, image_pyramid: List[torch.Tensor]) -> List[torch.Tensor]:
|
||||
"""Extracts a cascaded feature pyramid.
|
||||
|
||||
Args:
|
||||
image_pyramid: Image pyramid as a list, starting from the finest level.
|
||||
Returns:
|
||||
A pyramid of cascaded features.
|
||||
"""
|
||||
sub_pyramids: List[List[torch.Tensor]] = []
|
||||
for i in range(len(image_pyramid)):
|
||||
# At each level of the image pyramid, creates a sub_pyramid of features
|
||||
# with 'sub_levels' pyramid levels, re-using the same SubTreeExtractor.
|
||||
# We use the same instance since we want to share the weights.
|
||||
#
|
||||
# However, we cap the depth of the sub_pyramid so we don't create features
|
||||
# that are beyond the coarsest level of the cascaded feature pyramid we
|
||||
# want to generate.
|
||||
capped_sub_levels = min(len(image_pyramid) - i, self.sub_levels)
|
||||
sub_pyramids.append(self.extract_sublevels(image_pyramid[i], capped_sub_levels))
|
||||
# Below we generate the cascades of features on each level of the feature
|
||||
# pyramid. Assuming sub_levels=3, The layout of the features will be
|
||||
# as shown in the example on file documentation above.
|
||||
feature_pyramid: List[torch.Tensor] = []
|
||||
for i in range(len(image_pyramid)):
|
||||
features = sub_pyramids[i][0]
|
||||
for j in range(1, self.sub_levels):
|
||||
if j <= i:
|
||||
features = torch.cat([features, sub_pyramids[i - j][j]], dim=1)
|
||||
feature_pyramid.append(features)
|
||||
return feature_pyramid
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
"""The final fusion stage for the film_net frame interpolator.
|
||||
|
||||
The inputs to this module are the warped input images, image features and
|
||||
flow fields, all aligned to the target frame (often midway point between the
|
||||
two original inputs). The output is the final image. FILM has no explicit
|
||||
occlusion handling -- instead using the abovementioned information this module
|
||||
automatically decides how to best blend the inputs together to produce content
|
||||
in areas where the pixels can only be borrowed from one of the inputs.
|
||||
|
||||
Similarly, this module also decides on how much to blend in each input in case
|
||||
of fractional timestep that is not at the halfway point. For example, if the two
|
||||
inputs images are at t=0 and t=1, and we were to synthesize a frame at t=0.1,
|
||||
it often makes most sense to favor the first input. However, this is not
|
||||
always the case -- in particular in occluded pixels.
|
||||
|
||||
The architecture of the Fusion module follows U-net [1] architecture's decoder
|
||||
side, e.g. each pyramid level consists of concatenation with upsampled coarser
|
||||
level output, and two 3x3 convolutions.
|
||||
|
||||
The upsampling is implemented as 'resize convolution', e.g. nearest neighbor
|
||||
upsampling followed by 2x2 convolution as explained in [2]. The classic U-net
|
||||
uses max-pooling which has a tendency to create checkerboard artifacts.
|
||||
|
||||
[1] Ronneberger et al. U-Net: Convolutional Networks for Biomedical Image
|
||||
Segmentation, 2015, https://arxiv.org/pdf/1505.04597.pdf
|
||||
[2] https://distill.pub/2016/deconv-checkerboard/
|
||||
"""
|
||||
from typing import List
|
||||
|
||||
import torch
|
||||
from torch import nn
|
||||
from torch.nn import functional as F
|
||||
|
||||
|
||||
_NUMBER_OF_COLOR_CHANNELS = 3
|
||||
|
||||
|
||||
def get_channels_at_level(level, filters):
|
||||
n_images = 2
|
||||
channels = _NUMBER_OF_COLOR_CHANNELS
|
||||
flows = 2
|
||||
|
||||
return (sum(filters << i for i in range(level)) + channels + flows) * n_images
|
||||
|
||||
|
||||
class Fusion(nn.Module):
|
||||
"""The decoder."""
|
||||
|
||||
def __init__(self, n_layers=4, specialized_layers=3, filters=64):
|
||||
"""
|
||||
Args:
|
||||
m: specialized levels
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
# The final convolution that outputs RGB:
|
||||
self.output_conv = nn.Conv2d(filters, 3, kernel_size=1)
|
||||
|
||||
# Each item 'convs[i]' will contain the list of convolutions to be applied
|
||||
# for pyramid level 'i'.
|
||||
self.convs = nn.ModuleList()
|
||||
|
||||
# Create the convolutions. Roughly following the feature extractor, we
|
||||
# double the number of filters when the resolution halves, but only up to
|
||||
# the specialized_levels, after which we use the same number of filters on
|
||||
# all levels.
|
||||
#
|
||||
# We create the convs in fine-to-coarse order, so that the array index
|
||||
# for the convs will correspond to our normal indexing (0=finest level).
|
||||
# in_channels: tuple = (128, 202, 256, 522, 512, 1162, 1930, 2442)
|
||||
|
||||
in_channels = get_channels_at_level(n_layers, filters)
|
||||
increase = 0
|
||||
for i in range(n_layers)[::-1]:
|
||||
num_filters = (filters << i) if i < specialized_layers else (filters << specialized_layers)
|
||||
convs = nn.ModuleList([
|
||||
conv(in_channels, num_filters, size=2, activation=None),
|
||||
conv(in_channels + (increase or num_filters), num_filters, size=3),
|
||||
conv(num_filters, num_filters, size=3)]
|
||||
)
|
||||
self.convs.append(convs)
|
||||
in_channels = num_filters
|
||||
increase = get_channels_at_level(i, filters) - num_filters // 2
|
||||
|
||||
def forward(self, pyramid: List[torch.Tensor]) -> torch.Tensor:
|
||||
"""Runs the fusion module.
|
||||
|
||||
Args:
|
||||
pyramid: The input feature pyramid as list of tensors. Each tensor being
|
||||
in (B x H x W x C) format, with finest level tensor first.
|
||||
|
||||
Returns:
|
||||
A batch of RGB images.
|
||||
Raises:
|
||||
ValueError, if len(pyramid) != config.fusion_pyramid_levels as provided in
|
||||
the constructor.
|
||||
"""
|
||||
|
||||
# As a slight difference to a conventional decoder (e.g. U-net), we don't
|
||||
# apply any extra convolutions to the coarsest level, but just pass it
|
||||
# to finer levels for concatenation. This choice has not been thoroughly
|
||||
# evaluated, but is motivated by the educated guess that the fusion part
|
||||
# probably does not need large spatial context, because at this point the
|
||||
# features are spatially aligned by the preceding warp.
|
||||
net = pyramid[-1]
|
||||
|
||||
# Loop starting from the 2nd coarsest level:
|
||||
# for i in reversed(range(0, len(pyramid) - 1)):
|
||||
for k, layers in enumerate(self.convs):
|
||||
i = len(self.convs) - 1 - k
|
||||
# Resize the tensor from coarser level to match for concatenation.
|
||||
level_size = pyramid[i].shape[2:4]
|
||||
net = F.interpolate(net, size=level_size, mode='nearest')
|
||||
net = layers[0](net)
|
||||
net = torch.cat([pyramid[i], net], dim=1)
|
||||
net = layers[1](net)
|
||||
net = layers[2](net)
|
||||
net = self.output_conv(net)
|
||||
return net
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
"""The film_net frame interpolator main model code.
|
||||
|
||||
Basics
|
||||
======
|
||||
The film_net is an end-to-end learned neural frame interpolator implemented as
|
||||
a PyTorch model. It has the following inputs and outputs:
|
||||
|
||||
Inputs:
|
||||
x0: image A.
|
||||
x1: image B.
|
||||
time: desired sub-frame time.
|
||||
|
||||
Outputs:
|
||||
image: the predicted in-between image at the chosen time in range [0, 1].
|
||||
|
||||
Additional outputs include forward and backward warped image pyramids, flow
|
||||
pyramids, etc., that can be visualized for debugging and analysis.
|
||||
|
||||
Note that many training sets only contain triplets with ground truth at
|
||||
time=0.5. If a model has been trained with such training set, it will only work
|
||||
well for synthesizing frames at time=0.5. Such models can only generate more
|
||||
in-between frames using recursion.
|
||||
|
||||
Architecture
|
||||
============
|
||||
The inference consists of three main stages: 1) feature extraction 2) warping
|
||||
3) fusion. On high-level, the architecture has similarities to Context-aware
|
||||
Synthesis for Video Frame Interpolation [1], but the exact architecture is
|
||||
closer to Multi-view Image Fusion [2] with some modifications for the frame
|
||||
interpolation use-case.
|
||||
|
||||
Feature extraction stage employs the cascaded multi-scale architecture described
|
||||
in [2]. The advantage of this architecture is that coarse level flow prediction
|
||||
can be learned from finer resolution image samples. This is especially useful
|
||||
to avoid overfitting with moderately sized datasets.
|
||||
|
||||
The warping stage uses a residual flow prediction idea that is similar to
|
||||
PWC-Net [3], Multi-view Image Fusion [2] and many others.
|
||||
|
||||
The fusion stage is similar to U-Net's decoder where the skip connections are
|
||||
connected to warped image and feature pyramids. This is described in [2].
|
||||
|
||||
Implementation Conventions
|
||||
====================
|
||||
Pyramids
|
||||
--------
|
||||
Throughtout the model, all image and feature pyramids are stored as python lists
|
||||
with finest level first followed by downscaled versions obtained by successively
|
||||
halving the resolution. The depths of all pyramids are determined by
|
||||
options.pyramid_levels. The only exception to this is internal to the feature
|
||||
extractor, where smaller feature pyramids are temporarily constructed with depth
|
||||
options.sub_levels.
|
||||
|
||||
Color ranges & gamma
|
||||
--------------------
|
||||
The model code makes no assumptions on whether the images are in gamma or
|
||||
linearized space or what is the range of RGB color values. So a model can be
|
||||
trained with different choices. This does not mean that all the choices lead to
|
||||
similar results. In practice the model has been proven to work well with RGB
|
||||
scale = [0,1] with gamma-space images (i.e. not linearized).
|
||||
|
||||
[1] Context-aware Synthesis for Video Frame Interpolation, Niklaus and Liu, 2018
|
||||
[2] Multi-view Image Fusion, Trinidad et al, 2019
|
||||
[3] PWC-Net: CNNs for Optical Flow Using Pyramid, Warping, and Cost Volume
|
||||
"""
|
||||
from typing import Dict, List
|
||||
|
||||
import torch
|
||||
from torch import nn
|
||||
|
||||
|
||||
|
||||
class Interpolator(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
pyramid_levels=7,
|
||||
fusion_pyramid_levels=5,
|
||||
specialized_levels=3,
|
||||
sub_levels=4,
|
||||
filters=64,
|
||||
flow_convs=(3, 3, 3, 3),
|
||||
flow_filters=(32, 64, 128, 256),
|
||||
):
|
||||
super().__init__()
|
||||
self.pyramid_levels = pyramid_levels
|
||||
self.fusion_pyramid_levels = fusion_pyramid_levels
|
||||
|
||||
self.extract = FeatureExtractor(3, filters, sub_levels)
|
||||
self.predict_flow = PyramidFlowEstimator(filters, flow_convs, flow_filters)
|
||||
self.fuse = Fusion(sub_levels, specialized_levels, filters)
|
||||
|
||||
def shuffle_images(self, x0, x1):
|
||||
return [
|
||||
build_image_pyramid(x0, self.pyramid_levels),
|
||||
build_image_pyramid(x1, self.pyramid_levels)
|
||||
]
|
||||
|
||||
def debug_forward(self, x0, x1, batch_dt) -> Dict[str, List[torch.Tensor]]:
|
||||
image_pyramids = self.shuffle_images(x0, x1)
|
||||
|
||||
# Siamese feature pyramids:
|
||||
feature_pyramids = [self.extract(image_pyramids[0]), self.extract(image_pyramids[1])]
|
||||
|
||||
# Predict forward flow.
|
||||
forward_residual_flow_pyramid = self.predict_flow(feature_pyramids[0], feature_pyramids[1])
|
||||
|
||||
# Predict backward flow.
|
||||
backward_residual_flow_pyramid = self.predict_flow(feature_pyramids[1], feature_pyramids[0])
|
||||
|
||||
# Concatenate features and images:
|
||||
|
||||
# Note that we keep up to 'fusion_pyramid_levels' levels as only those
|
||||
# are used by the fusion module.
|
||||
|
||||
forward_flow_pyramid = flow_pyramid_synthesis(forward_residual_flow_pyramid)[:self.fusion_pyramid_levels]
|
||||
|
||||
backward_flow_pyramid = flow_pyramid_synthesis(backward_residual_flow_pyramid)[:self.fusion_pyramid_levels]
|
||||
|
||||
# We multiply the flows with t and 1-t to warp to the desired fractional time.
|
||||
#
|
||||
# Note: In film_net we fix time to be 0.5, and recursively invoke the interpo-
|
||||
# lator for multi-frame interpolation. Below, we create a constant tensor of
|
||||
# shape [B]. We use the `time` tensor to infer the batch size.
|
||||
mid_time = torch.full_like(batch_dt, .5)
|
||||
backward_flow = multiply_pyramid(backward_flow_pyramid, mid_time[:, 0])
|
||||
forward_flow = multiply_pyramid(forward_flow_pyramid, 1 - mid_time[:, 0])
|
||||
|
||||
pyramids_to_warp = [
|
||||
concatenate_pyramids(image_pyramids[0][:self.fusion_pyramid_levels],
|
||||
feature_pyramids[0][:self.fusion_pyramid_levels]),
|
||||
concatenate_pyramids(image_pyramids[1][:self.fusion_pyramid_levels],
|
||||
feature_pyramids[1][:self.fusion_pyramid_levels])
|
||||
]
|
||||
|
||||
# Warp features and images using the flow. Note that we use backward warping
|
||||
# and backward flow is used to read from image 0 and forward flow from
|
||||
# image 1.
|
||||
forward_warped_pyramid = pyramid_warp(pyramids_to_warp[0], backward_flow)
|
||||
backward_warped_pyramid = pyramid_warp(pyramids_to_warp[1], forward_flow)
|
||||
|
||||
aligned_pyramid = concatenate_pyramids(forward_warped_pyramid,
|
||||
backward_warped_pyramid)
|
||||
aligned_pyramid = concatenate_pyramids(aligned_pyramid, backward_flow)
|
||||
aligned_pyramid = concatenate_pyramids(aligned_pyramid, forward_flow)
|
||||
|
||||
return {
|
||||
'image': [self.fuse(aligned_pyramid)],
|
||||
'forward_residual_flow_pyramid': forward_residual_flow_pyramid,
|
||||
'backward_residual_flow_pyramid': backward_residual_flow_pyramid,
|
||||
'forward_flow_pyramid': forward_flow_pyramid,
|
||||
'backward_flow_pyramid': backward_flow_pyramid,
|
||||
}
|
||||
|
||||
|
||||
def forward(self, x0, x1, batch_dt) -> torch.Tensor:
|
||||
return self.debug_forward(x0, x1, batch_dt)['image'][0]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
"""PyTorch layer for estimating optical flow by a residual flow pyramid.
|
||||
|
||||
This approach of estimating optical flow between two images can be traced back
|
||||
to [1], but is also used by later neural optical flow computation methods such
|
||||
as SpyNet [2] and PWC-Net [3].
|
||||
|
||||
The basic idea is that the optical flow is first estimated in a coarse
|
||||
resolution, then the flow is upsampled to warp the higher resolution image and
|
||||
then a residual correction is computed and added to the estimated flow. This
|
||||
process is repeated in a pyramid on coarse to fine order to successively
|
||||
increase the resolution of both optical flow and the warped image.
|
||||
|
||||
In here, the optical flow predictor is used as an internal component for the
|
||||
film_net frame interpolator, to warp the two input images into the inbetween,
|
||||
target frame.
|
||||
|
||||
[1] F. Glazer, Hierarchical motion detection. PhD thesis, 1987.
|
||||
[2] A. Ranjan and M. J. Black, Optical Flow Estimation using a Spatial Pyramid
|
||||
Network. 2016
|
||||
[3] D. Sun X. Yang, M-Y. Liu and J. Kautz, PWC-Net: CNNs for Optical Flow Using
|
||||
Pyramid, Warping, and Cost Volume, 2017
|
||||
"""
|
||||
from typing import List
|
||||
|
||||
import torch
|
||||
from torch import nn
|
||||
from torch.nn import functional as F
|
||||
|
||||
|
||||
|
||||
class FlowEstimator(nn.Module):
|
||||
"""Small-receptive field predictor for computing the flow between two images.
|
||||
|
||||
This is used to compute the residual flow fields in PyramidFlowEstimator.
|
||||
|
||||
Note that while the number of 3x3 convolutions & filters to apply is
|
||||
configurable, two extra 1x1 convolutions are appended to extract the flow in
|
||||
the end.
|
||||
|
||||
Attributes:
|
||||
name: The name of the layer
|
||||
num_convs: Number of 3x3 convolutions to apply
|
||||
num_filters: Number of filters in each 3x3 convolution
|
||||
"""
|
||||
|
||||
def __init__(self, in_channels: int, num_convs: int, num_filters: int):
|
||||
super(FlowEstimator, self).__init__()
|
||||
|
||||
self._convs = nn.ModuleList()
|
||||
for i in range(num_convs):
|
||||
self._convs.append(conv(in_channels=in_channels, out_channels=num_filters, size=3))
|
||||
in_channels = num_filters
|
||||
self._convs.append(conv(in_channels, num_filters // 2, size=1))
|
||||
in_channels = num_filters // 2
|
||||
# For the final convolution, we want no activation at all to predict the
|
||||
# optical flow vector values. We have done extensive testing on explicitly
|
||||
# bounding these values using sigmoid, but it turned out that having no
|
||||
# activation gives better results.
|
||||
self._convs.append(conv(in_channels, 2, size=1, activation=None))
|
||||
|
||||
def forward(self, features_a: torch.Tensor, features_b: torch.Tensor) -> torch.Tensor:
|
||||
"""Estimates optical flow between two images.
|
||||
|
||||
Args:
|
||||
features_a: per pixel feature vectors for image A (B x H x W x C)
|
||||
features_b: per pixel feature vectors for image B (B x H x W x C)
|
||||
|
||||
Returns:
|
||||
A tensor with optical flow from A to B
|
||||
"""
|
||||
net = torch.cat([features_a, features_b], dim=1)
|
||||
for conv in self._convs:
|
||||
net = conv(net)
|
||||
return net
|
||||
|
||||
|
||||
class PyramidFlowEstimator(nn.Module):
|
||||
"""Predicts optical flow by coarse-to-fine refinement.
|
||||
"""
|
||||
|
||||
def __init__(self, filters: int = 64,
|
||||
flow_convs: tuple = (3, 3, 3, 3),
|
||||
flow_filters: tuple = (32, 64, 128, 256)):
|
||||
super(PyramidFlowEstimator, self).__init__()
|
||||
|
||||
in_channels = filters << 1
|
||||
predictors = []
|
||||
for i in range(len(flow_convs)):
|
||||
predictors.append(
|
||||
FlowEstimator(
|
||||
in_channels=in_channels,
|
||||
num_convs=flow_convs[i],
|
||||
num_filters=flow_filters[i]))
|
||||
in_channels += filters << (i + 2)
|
||||
self._predictor = predictors[-1]
|
||||
self._predictors = nn.ModuleList(predictors[:-1][::-1])
|
||||
|
||||
def forward(self, feature_pyramid_a: List[torch.Tensor],
|
||||
feature_pyramid_b: List[torch.Tensor]) -> List[torch.Tensor]:
|
||||
"""Estimates residual flow pyramids between two image pyramids.
|
||||
|
||||
Each image pyramid is represented as a list of tensors in fine-to-coarse
|
||||
order. Each individual image is represented as a tensor where each pixel is
|
||||
a vector of image features.
|
||||
|
||||
flow_pyramid_synthesis can be used to convert the residual flow
|
||||
pyramid returned by this method into a flow pyramid, where each level
|
||||
encodes the flow instead of a residual correction.
|
||||
|
||||
Args:
|
||||
feature_pyramid_a: image pyramid as a list in fine-to-coarse order
|
||||
feature_pyramid_b: image pyramid as a list in fine-to-coarse order
|
||||
|
||||
Returns:
|
||||
List of flow tensors, in fine-to-coarse order, each level encoding the
|
||||
difference against the bilinearly upsampled version from the coarser
|
||||
level. The coarsest flow tensor, e.g. the last element in the array is the
|
||||
'DC-term', e.g. not a residual (alternatively you can think of it being a
|
||||
residual against zero).
|
||||
"""
|
||||
levels = len(feature_pyramid_a)
|
||||
v = self._predictor(feature_pyramid_a[-1], feature_pyramid_b[-1])
|
||||
residuals = [v]
|
||||
for i in range(levels - 2, len(self._predictors) - 1, -1):
|
||||
# Upsamples the flow to match the current pyramid level. Also, scales the
|
||||
# magnitude by two to reflect the new size.
|
||||
level_size = feature_pyramid_a[i].shape[2:4]
|
||||
v = F.interpolate(2 * v, size=level_size, mode='bilinear')
|
||||
# Warp feature_pyramid_b[i] image based on the current flow estimate.
|
||||
warped = warp(feature_pyramid_b[i], v)
|
||||
# Estimate the residual flow between pyramid_a[i] and warped image:
|
||||
v_residual = self._predictor(feature_pyramid_a[i], warped)
|
||||
residuals.insert(0, v_residual)
|
||||
v = v_residual + v
|
||||
|
||||
for k, predictor in enumerate(self._predictors):
|
||||
i = len(self._predictors) - 1 - k
|
||||
# Upsamples the flow to match the current pyramid level. Also, scales the
|
||||
# magnitude by two to reflect the new size.
|
||||
level_size = feature_pyramid_a[i].shape[2:4]
|
||||
v = F.interpolate(2 * v, size=level_size, mode='bilinear')
|
||||
# Warp feature_pyramid_b[i] image based on the current flow estimate.
|
||||
warped = warp(feature_pyramid_b[i], v)
|
||||
# Estimate the residual flow between pyramid_a[i] and warped image:
|
||||
v_residual = predictor(feature_pyramid_a[i], warped)
|
||||
residuals.insert(0, v_residual)
|
||||
v = v_residual + v
|
||||
return residuals
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
"""Various utilities used in the film_net frame interpolator model."""
|
||||
from typing import List, Optional
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import torch
|
||||
from torch import nn
|
||||
from torch.nn import functional as F
|
||||
|
||||
|
||||
def pad_batch(batch, align):
|
||||
height, width = batch.shape[1:3]
|
||||
height_to_pad = (align - height % align) if height % align != 0 else 0
|
||||
width_to_pad = (align - width % align) if width % align != 0 else 0
|
||||
|
||||
crop_region = [height_to_pad >> 1, width_to_pad >> 1, height + (height_to_pad >> 1), width + (width_to_pad >> 1)]
|
||||
batch = np.pad(batch, ((0, 0), (height_to_pad >> 1, height_to_pad - (height_to_pad >> 1)),
|
||||
(width_to_pad >> 1, width_to_pad - (width_to_pad >> 1)), (0, 0)), mode='constant')
|
||||
return batch, crop_region
|
||||
|
||||
|
||||
def load_image(path, align=64):
|
||||
image = cv2.cvtColor(cv2.imread(path), cv2.COLOR_BGR2RGB).astype(np.float32) / np.float32(255)
|
||||
image_batch, crop_region = pad_batch(np.expand_dims(image, axis=0), align)
|
||||
return image_batch, crop_region
|
||||
|
||||
|
||||
def build_image_pyramid(image: torch.Tensor, pyramid_levels: int = 3) -> List[torch.Tensor]:
|
||||
"""Builds an image pyramid from a given image.
|
||||
|
||||
The original image is included in the pyramid and the rest are generated by
|
||||
successively halving the resolution.
|
||||
|
||||
Args:
|
||||
image: the input image.
|
||||
options: film_net options object
|
||||
|
||||
Returns:
|
||||
A list of images starting from the finest with options.pyramid_levels items
|
||||
"""
|
||||
|
||||
pyramid = []
|
||||
for i in range(pyramid_levels):
|
||||
pyramid.append(image)
|
||||
if i < pyramid_levels - 1:
|
||||
image = F.avg_pool2d(image, 2, 2)
|
||||
return pyramid
|
||||
|
||||
|
||||
def warp(image: torch.Tensor, flow: torch.Tensor) -> torch.Tensor:
|
||||
"""Backward warps the image using the given flow.
|
||||
|
||||
Specifically, the output pixel in batch b, at position x, y will be computed
|
||||
as follows:
|
||||
(flowed_y, flowed_x) = (y+flow[b, y, x, 1], x+flow[b, y, x, 0])
|
||||
output[b, y, x] = bilinear_lookup(image, b, flowed_y, flowed_x)
|
||||
|
||||
Note that the flow vectors are expected as [x, y], e.g. x in position 0 and
|
||||
y in position 1.
|
||||
|
||||
Args:
|
||||
image: An image with shape BxHxWxC.
|
||||
flow: A flow with shape BxHxWx2, with the two channels denoting the relative
|
||||
offset in order: (dx, dy).
|
||||
Returns:
|
||||
A warped image.
|
||||
"""
|
||||
flow = -flow.flip(1)
|
||||
|
||||
dtype = flow.dtype
|
||||
device = flow.device
|
||||
|
||||
# warped = tfa_image.dense_image_warp(image, flow)
|
||||
# Same as above but with pytorch
|
||||
ls1 = 1 - 1 / flow.shape[3]
|
||||
ls2 = 1 - 1 / flow.shape[2]
|
||||
|
||||
normalized_flow2 = flow.permute(0, 2, 3, 1) / torch.tensor(
|
||||
[flow.shape[2] * .5, flow.shape[3] * .5], dtype=dtype, device=device)[None, None, None]
|
||||
normalized_flow2 = torch.stack([
|
||||
torch.linspace(-ls1, ls1, flow.shape[3], dtype=dtype, device=device)[None, None, :] - normalized_flow2[..., 1],
|
||||
torch.linspace(-ls2, ls2, flow.shape[2], dtype=dtype, device=device)[None, :, None] - normalized_flow2[..., 0],
|
||||
], dim=3)
|
||||
|
||||
warped = F.grid_sample(image, normalized_flow2,
|
||||
mode='bilinear', padding_mode='border', align_corners=False)
|
||||
return warped.reshape(image.shape)
|
||||
|
||||
|
||||
def multiply_pyramid(pyramid: List[torch.Tensor],
|
||||
scalar: torch.Tensor) -> List[torch.Tensor]:
|
||||
"""Multiplies all image batches in the pyramid by a batch of scalars.
|
||||
|
||||
Args:
|
||||
pyramid: Pyramid of image batches.
|
||||
scalar: Batch of scalars.
|
||||
|
||||
Returns:
|
||||
An image pyramid with all images multiplied by the scalar.
|
||||
"""
|
||||
# To multiply each image with its corresponding scalar, we first transpose
|
||||
# the batch of images from BxHxWxC-format to CxHxWxB. This can then be
|
||||
# multiplied with a batch of scalars, then we transpose back to the standard
|
||||
# BxHxWxC form.
|
||||
return [image * scalar for image in pyramid]
|
||||
|
||||
|
||||
def flow_pyramid_synthesis(
|
||||
residual_pyramid: List[torch.Tensor]) -> List[torch.Tensor]:
|
||||
"""Converts a residual flow pyramid into a flow pyramid."""
|
||||
flow = residual_pyramid[-1]
|
||||
flow_pyramid: List[torch.Tensor] = [flow]
|
||||
for residual_flow in residual_pyramid[:-1][::-1]:
|
||||
level_size = residual_flow.shape[2:4]
|
||||
flow = F.interpolate(2 * flow, size=level_size, mode='bilinear')
|
||||
flow = residual_flow + flow
|
||||
flow_pyramid.insert(0, flow)
|
||||
return flow_pyramid
|
||||
|
||||
|
||||
def pyramid_warp(feature_pyramid: List[torch.Tensor],
|
||||
flow_pyramid: List[torch.Tensor]) -> List[torch.Tensor]:
|
||||
"""Warps the feature pyramid using the flow pyramid.
|
||||
|
||||
Args:
|
||||
feature_pyramid: feature pyramid starting from the finest level.
|
||||
flow_pyramid: flow fields, starting from the finest level.
|
||||
|
||||
Returns:
|
||||
Reverse warped feature pyramid.
|
||||
"""
|
||||
warped_feature_pyramid = []
|
||||
for features, flow in zip(feature_pyramid, flow_pyramid):
|
||||
warped_feature_pyramid.append(warp(features, flow))
|
||||
return warped_feature_pyramid
|
||||
|
||||
|
||||
def concatenate_pyramids(pyramid1: List[torch.Tensor],
|
||||
pyramid2: List[torch.Tensor]) -> List[torch.Tensor]:
|
||||
"""Concatenates each pyramid level together in the channel dimension."""
|
||||
result = []
|
||||
for features1, features2 in zip(pyramid1, pyramid2):
|
||||
result.append(torch.cat([features1, features2], dim=1))
|
||||
return result
|
||||
|
||||
|
||||
def conv(in_channels, out_channels, size, activation: Optional[str] = 'relu'):
|
||||
# Since PyTorch doesn't have an in-built activation in Conv2d, we use a
|
||||
# Sequential layer to combine Conv2d and Leaky ReLU in one module.
|
||||
_conv = nn.Conv2d(
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
kernel_size=size,
|
||||
padding='same')
|
||||
if activation is None:
|
||||
return _conv
|
||||
assert activation == 'relu'
|
||||
return nn.Sequential(
|
||||
_conv,
|
||||
nn.LeakyReLU(.2)
|
||||
)
|
||||
@@ -1,115 +0,0 @@
|
||||
import torch
|
||||
from comfy.model_management import get_torch_device, soft_empty_cache
|
||||
import numpy as np
|
||||
import typing
|
||||
from vfi_utils import InterpolationStateList, load_file_from_github_release, preprocess_frames, postprocess_frames, assert_batch_size
|
||||
import pathlib
|
||||
import warnings
|
||||
from .flavr_arch import UNet_3D_3D, InputPadder
|
||||
import gc
|
||||
|
||||
device = get_torch_device()
|
||||
NBR_FRAME = 4
|
||||
|
||||
def build_flavr(model_path):
|
||||
sd = torch.load(model_path)['state_dict']
|
||||
sd = {k.partition("module.")[-1]:v for k,v in sd.items()}
|
||||
|
||||
#Ref: Class UNet_3D_3D
|
||||
model = UNet_3D_3D("unet_18", n_inputs=NBR_FRAME, n_outputs=sd["outconv.1.weight"].shape[0] // 3, joinType="concat" , upmode="transpose")
|
||||
model.load_state_dict(sd)
|
||||
model.to(device).eval()
|
||||
del sd
|
||||
return model
|
||||
|
||||
MODEL_TYPE = pathlib.Path(__file__).parent.name
|
||||
CKPT_NAMES = ["FLAVR_2x.pth", "FLAVR_4x.pth", "FLAVR_8x.pth"]
|
||||
|
||||
class FLAVR_VFI:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"ckpt_name": (CKPT_NAMES, ),
|
||||
"frames": ("IMAGE", ),
|
||||
"clear_cache_after_n_frames": ("INT", {"default": 10, "min": 1, "max": 1000}),
|
||||
"multiplier": ("INT", {"default": 2, "min": 2, "max": 2}), #TODO: Implement recursively invoking interpolator for multi-frame interpolation
|
||||
"duplicate_first_last_frames": ("BOOLEAN", {"default": False})
|
||||
},
|
||||
"optional": {
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", )
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", )
|
||||
FUNCTION = "vfi"
|
||||
CATEGORY = "ComfyUI-Frame-Interpolation/VFI"
|
||||
|
||||
#Reference: https://github.com/danier97/ST-MFNet/blob/main/interpolate_yuv.py#L93
|
||||
def vfi(
|
||||
self,
|
||||
ckpt_name: typing.AnyStr,
|
||||
frames: torch.Tensor,
|
||||
clear_cache_after_n_frames = 10,
|
||||
multiplier: typing.SupportsInt = 2,
|
||||
duplicate_first_last_frames: bool = False,
|
||||
optional_interpolation_states: InterpolationStateList = None,
|
||||
**kwargs
|
||||
):
|
||||
if multiplier != 2:
|
||||
warnings.warn("Currently, FLAVR only supports 2x interpolation. The process will continue but please set multiplier=2 afterward")
|
||||
|
||||
assert_batch_size(frames, batch_size=4, vfi_name="ST-MFNet")
|
||||
interpolation_states = optional_interpolation_states
|
||||
model_path = load_file_from_github_release(MODEL_TYPE, ckpt_name)
|
||||
model = build_flavr(model_path)
|
||||
frames = preprocess_frames(frames)
|
||||
padder = InputPadder(frames.shape, 16)
|
||||
frames = padder.pad(frames)
|
||||
|
||||
number_of_frames_processed_since_last_cleared_cuda_cache = 0
|
||||
output_frames = []
|
||||
for frame_itr in range(len(frames) - 3):
|
||||
#Does skipping frame i+1 make sanse in this case?
|
||||
if interpolation_states is not None and interpolation_states.is_frame_skipped(frame_itr) and interpolation_states.is_frame_skipped(frame_itr + 1):
|
||||
continue
|
||||
|
||||
#Ensure that input frames are in fp32 - the same dtype as model
|
||||
frame0, frame1, frame2, frame3 = (
|
||||
frames[frame_itr:frame_itr+1].float(),
|
||||
frames[frame_itr+1:frame_itr+2].float(),
|
||||
frames[frame_itr+2:frame_itr+3].float(),
|
||||
frames[frame_itr+3:frame_itr+4].float()
|
||||
)
|
||||
new_frame = model([frame0.to(device), frame1.to(device), frame2.to(device), frame3.to(device)])[0].detach().cpu()
|
||||
number_of_frames_processed_since_last_cleared_cuda_cache += 2
|
||||
|
||||
if frame_itr == 0:
|
||||
output_frames.append(frame0)
|
||||
if duplicate_first_last_frames:
|
||||
output_frames.append(frame0) # repeat the first frame
|
||||
output_frames.append(frame1)
|
||||
output_frames.append(new_frame)
|
||||
output_frames.append(frame2)
|
||||
if frame_itr == len(frames) - 4:
|
||||
output_frames.append(frame3)
|
||||
if duplicate_first_last_frames:
|
||||
output_frames.append(frame3) # repeat the last frame
|
||||
|
||||
# Try to avoid a memory overflow by clearing cuda cache regularly
|
||||
if number_of_frames_processed_since_last_cleared_cuda_cache >= clear_cache_after_n_frames:
|
||||
print("Comfy-VFI: Clearing cache...", end = ' ')
|
||||
soft_empty_cache()
|
||||
number_of_frames_processed_since_last_cleared_cuda_cache = 0
|
||||
print("Done cache clearing")
|
||||
gc.collect()
|
||||
|
||||
dtype = torch.float32
|
||||
output_frames = [frame.cpu().to(dtype=dtype) for frame in output_frames] #Ensure all frames are in cpu
|
||||
out = torch.cat(output_frames, dim=0)
|
||||
out = padder.unpad(out)
|
||||
# clear cache for courtesy
|
||||
print("Comfy-VFI: Final clearing cache...", end=' ')
|
||||
soft_empty_cache()
|
||||
print("Done cache clearing")
|
||||
return (postprocess_frames(out), )
|
||||
@@ -1,217 +0,0 @@
|
||||
"""
|
||||
https://github.com/tarun005/FLAVR/blob/main/model/FLAVR_arch.py
|
||||
https://github.com/tarun005/FLAVR/blob/main/model/resnet_3D.py (only SEGating)
|
||||
"""
|
||||
import math
|
||||
import numpy as np
|
||||
import importlib
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
class SEGating(nn.Module):
|
||||
|
||||
def __init__(self , inplanes , reduction=16):
|
||||
|
||||
super().__init__()
|
||||
|
||||
self.pool = nn.AdaptiveAvgPool3d(1)
|
||||
self.attn_layer = nn.Sequential(
|
||||
nn.Conv3d(inplanes , inplanes , kernel_size=1 , stride=1 , bias=True),
|
||||
nn.Sigmoid()
|
||||
)
|
||||
|
||||
def forward(self , x):
|
||||
|
||||
out = self.pool(x)
|
||||
y = self.attn_layer(out)
|
||||
return x * y
|
||||
|
||||
def joinTensors(X1 , X2 , type="concat"):
|
||||
|
||||
if type == "concat":
|
||||
return torch.cat([X1 , X2] , dim=1)
|
||||
elif type == "add":
|
||||
return X1 + X2
|
||||
else:
|
||||
return X1
|
||||
|
||||
|
||||
class Conv_2d(nn.Module):
|
||||
|
||||
def __init__(self, in_ch, out_ch, kernel_size, stride=1, padding=0, bias=False, batchnorm=False):
|
||||
|
||||
super().__init__()
|
||||
self.conv = [nn.Conv2d(in_ch, out_ch, kernel_size=kernel_size, stride=stride, padding=padding, bias=bias)]
|
||||
|
||||
if batchnorm:
|
||||
self.conv += [nn.BatchNorm2d(out_ch)]
|
||||
|
||||
self.conv = nn.Sequential(*self.conv)
|
||||
|
||||
def forward(self, x):
|
||||
|
||||
return self.conv(x)
|
||||
|
||||
class upConv3D(nn.Module):
|
||||
|
||||
def __init__(self, in_ch, out_ch, kernel_size, stride, padding, upmode="transpose" , batchnorm=False):
|
||||
|
||||
super().__init__()
|
||||
|
||||
self.upmode = upmode
|
||||
|
||||
if self.upmode=="transpose":
|
||||
self.upconv = nn.ModuleList(
|
||||
[nn.ConvTranspose3d(in_ch, out_ch, kernel_size=kernel_size, stride=stride, padding=padding),
|
||||
SEGating(out_ch)
|
||||
]
|
||||
)
|
||||
|
||||
else:
|
||||
self.upconv = nn.ModuleList(
|
||||
[nn.Upsample(mode='trilinear', scale_factor=(1,2,2), align_corners=False),
|
||||
nn.Conv3d(in_ch, out_ch , kernel_size=1 , stride=1),
|
||||
SEGating(out_ch)
|
||||
]
|
||||
)
|
||||
|
||||
if batchnorm:
|
||||
self.upconv += [nn.BatchNorm3d(out_ch)]
|
||||
|
||||
self.upconv = nn.Sequential(*self.upconv)
|
||||
|
||||
def forward(self, x):
|
||||
|
||||
return self.upconv(x)
|
||||
|
||||
class Conv_3d(nn.Module):
|
||||
|
||||
def __init__(self, in_ch, out_ch, kernel_size, stride=1, padding=0, bias=True, batchnorm=False):
|
||||
|
||||
super().__init__()
|
||||
self.conv = [nn.Conv3d(in_ch, out_ch, kernel_size=kernel_size, stride=stride, padding=padding, bias=bias),
|
||||
SEGating(out_ch)
|
||||
]
|
||||
|
||||
if batchnorm:
|
||||
self.conv += [nn.BatchNorm3d(out_ch)]
|
||||
|
||||
self.conv = nn.Sequential(*self.conv)
|
||||
|
||||
def forward(self, x):
|
||||
|
||||
return self.conv(x)
|
||||
|
||||
class upConv2D(nn.Module):
|
||||
|
||||
def __init__(self, in_ch, out_ch, kernel_size, stride, padding, upmode="transpose" , batchnorm=False):
|
||||
|
||||
super().__init__()
|
||||
|
||||
self.upmode = upmode
|
||||
|
||||
if self.upmode=="transpose":
|
||||
self.upconv = [nn.ConvTranspose2d(in_ch, out_ch, kernel_size=kernel_size, stride=stride, padding=padding)]
|
||||
|
||||
else:
|
||||
self.upconv = [
|
||||
nn.Upsample(mode='bilinear', scale_factor=2, align_corners=False),
|
||||
nn.Conv2d(in_ch, out_ch , kernel_size=1 , stride=1)
|
||||
]
|
||||
|
||||
if batchnorm:
|
||||
self.upconv += [nn.BatchNorm2d(out_ch)]
|
||||
|
||||
self.upconv = nn.Sequential(*self.upconv)
|
||||
|
||||
def forward(self, x):
|
||||
|
||||
return self.upconv(x)
|
||||
|
||||
|
||||
class UNet_3D_3D(nn.Module):
|
||||
def __init__(self, block , n_inputs, n_outputs, batchnorm=False , joinType="concat" , upmode="transpose"):
|
||||
super().__init__()
|
||||
|
||||
nf = [512 , 256 , 128 , 64]
|
||||
out_channels = 3*n_outputs
|
||||
self.joinType = joinType
|
||||
self.n_outputs = n_outputs
|
||||
|
||||
growth = 2 if joinType == "concat" else 1
|
||||
self.lrelu = nn.LeakyReLU(0.2, True)
|
||||
|
||||
unet_3D = importlib.import_module(".resnet_3D", "models.flavr")
|
||||
if n_outputs > 1:
|
||||
unet_3D.useBias = True
|
||||
self.encoder = getattr(unet_3D , block)(pretrained=False , bn=batchnorm)
|
||||
|
||||
self.decoder = nn.Sequential(
|
||||
Conv_3d(nf[0], nf[1] , kernel_size=3, padding=1, bias=True, batchnorm=batchnorm),
|
||||
upConv3D(nf[1]*growth, nf[2], kernel_size=(3,4,4), stride=(1,2,2), padding=(1,1,1) , upmode=upmode, batchnorm=batchnorm),
|
||||
upConv3D(nf[2]*growth, nf[3], kernel_size=(3,4,4), stride=(1,2,2), padding=(1,1,1) , upmode=upmode, batchnorm=batchnorm),
|
||||
Conv_3d(nf[3]*growth, nf[3] , kernel_size=3, padding=1, bias=True, batchnorm=batchnorm),
|
||||
upConv3D(nf[3]*growth , nf[3], kernel_size=(3,4,4), stride=(1,2,2), padding=(1,1,1) , upmode=upmode, batchnorm=batchnorm)
|
||||
)
|
||||
|
||||
self.feature_fuse = Conv_2d(nf[3]*n_inputs , nf[3] , kernel_size=1 , stride=1, batchnorm=batchnorm)
|
||||
|
||||
self.outconv = nn.Sequential(
|
||||
nn.ReflectionPad2d(3),
|
||||
nn.Conv2d(nf[3], out_channels , kernel_size=7 , stride=1, padding=0)
|
||||
)
|
||||
|
||||
def forward(self, images):
|
||||
|
||||
images = torch.stack(images , dim=2)
|
||||
|
||||
## Batch mean normalization works slightly better than global mean normalization, thanks to https://github.com/myungsub/CAIN
|
||||
mean_ = images.mean(2, keepdim=True).mean(3, keepdim=True).mean(4,keepdim=True)
|
||||
images = images-mean_
|
||||
|
||||
x_0 , x_1 , x_2 , x_3 , x_4 = self.encoder(images)
|
||||
|
||||
dx_3 = self.lrelu(self.decoder[0](x_4))
|
||||
dx_3 = joinTensors(dx_3 , x_3 , type=self.joinType)
|
||||
|
||||
dx_2 = self.lrelu(self.decoder[1](dx_3))
|
||||
dx_2 = joinTensors(dx_2 , x_2 , type=self.joinType)
|
||||
|
||||
dx_1 = self.lrelu(self.decoder[2](dx_2))
|
||||
dx_1 = joinTensors(dx_1 , x_1 , type=self.joinType)
|
||||
|
||||
dx_0 = self.lrelu(self.decoder[3](dx_1))
|
||||
dx_0 = joinTensors(dx_0 , x_0 , type=self.joinType)
|
||||
|
||||
dx_out = self.lrelu(self.decoder[4](dx_0))
|
||||
dx_out = torch.cat(torch.unbind(dx_out , 2) , 1)
|
||||
|
||||
out = self.lrelu(self.feature_fuse(dx_out))
|
||||
out = self.outconv(out)
|
||||
|
||||
out = torch.split(out, dim=1, split_size_or_sections=3)
|
||||
mean_ = mean_.squeeze(2)
|
||||
out = [o+mean_ for o in out]
|
||||
|
||||
return out
|
||||
|
||||
class InputPadder:
|
||||
""" Pads images such that dimensions are divisible by divisor """
|
||||
def __init__(self, dims, divisor=16):
|
||||
self.ht, self.wd = dims[-2:]
|
||||
pad_ht = (((self.ht // divisor) + 1) * divisor - self.ht) % divisor
|
||||
pad_wd = (((self.wd // divisor) + 1) * divisor - self.wd) % divisor
|
||||
self._pad = [pad_wd//2, pad_wd - pad_wd//2, pad_ht//2, pad_ht - pad_ht//2]
|
||||
|
||||
def pad(self, input_tensor):
|
||||
return F.pad(input_tensor, self._pad, mode='replicate')
|
||||
|
||||
def unpad(self, input_tensor):
|
||||
return self._unpad(input_tensor)
|
||||
|
||||
def _unpad(self, x):
|
||||
ht, wd = x.shape[-2:]
|
||||
c = [self._pad[2], ht-self._pad[3], self._pad[0], wd-self._pad[1]]
|
||||
return x[..., c[0]:c[1], c[2]:c[3]]
|
||||
@@ -1,288 +0,0 @@
|
||||
# Modified from https://github.com/pytorch/vision/tree/master/torchvision/models/video
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
__all__ = ['unet_18', 'unet_34']
|
||||
|
||||
useBias = False
|
||||
|
||||
class identity(nn.Module):
|
||||
|
||||
def __init__(self , *args , **kwargs):
|
||||
|
||||
super().__init__()
|
||||
|
||||
def forward(self , x):
|
||||
return x
|
||||
|
||||
class Conv3DSimple(nn.Conv3d):
|
||||
def __init__(self,
|
||||
in_planes,
|
||||
out_planes,
|
||||
midplanes=None,
|
||||
stride=1,
|
||||
padding=1):
|
||||
|
||||
super(Conv3DSimple, self).__init__(
|
||||
in_channels=in_planes,
|
||||
out_channels=out_planes,
|
||||
kernel_size=(3, 3, 3),
|
||||
stride=stride,
|
||||
padding=padding,
|
||||
bias=useBias)
|
||||
|
||||
@staticmethod
|
||||
def get_downsample_stride(stride , temporal_stride):
|
||||
if temporal_stride:
|
||||
return (temporal_stride, stride, stride)
|
||||
else:
|
||||
return (stride , stride , stride)
|
||||
|
||||
class BasicStem(nn.Sequential):
|
||||
"""The default conv-batchnorm-relu stem
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
nn.Conv3d(3, 64, kernel_size=(3, 7, 7), stride=(1, 2, 2),
|
||||
padding=(1, 3, 3), bias=useBias),
|
||||
batchnorm(64),
|
||||
nn.ReLU(inplace=False))
|
||||
|
||||
|
||||
class Conv2Plus1D(nn.Sequential):
|
||||
|
||||
def __init__(self,
|
||||
in_planes,
|
||||
out_planes,
|
||||
midplanes,
|
||||
stride=1,
|
||||
padding=1):
|
||||
if not isinstance(stride , int):
|
||||
temporal_stride , stride , stride = stride
|
||||
else:
|
||||
temporal_stride = stride
|
||||
|
||||
super(Conv2Plus1D, self).__init__(
|
||||
nn.Conv3d(in_planes, midplanes, kernel_size=(1, 3, 3),
|
||||
stride=(1, stride, stride), padding=(0, padding, padding),
|
||||
bias=False),
|
||||
# batchnorm(midplanes),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.Conv3d(midplanes, out_planes, kernel_size=(3, 1, 1),
|
||||
stride=(temporal_stride, 1, 1), padding=(padding, 0, 0),
|
||||
bias=False))
|
||||
|
||||
@staticmethod
|
||||
def get_downsample_stride(stride , temporal_stride):
|
||||
if temporal_stride:
|
||||
return (temporal_stride, stride, stride)
|
||||
else:
|
||||
return (stride , stride , stride)
|
||||
|
||||
class R2Plus1dStem(nn.Sequential):
|
||||
"""R(2+1)D stem is different than the default one as it uses separated 3D convolution
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
nn.Conv3d(3, 45, kernel_size=(1, 7, 7),
|
||||
stride=(1, 2, 2), padding=(0, 3, 3),
|
||||
bias=False),
|
||||
batchnorm(45),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.Conv3d(45, 64, kernel_size=(3, 1, 1),
|
||||
stride=(1, 1, 1), padding=(1, 0, 0),
|
||||
bias=False),
|
||||
batchnorm(64),
|
||||
nn.ReLU(inplace=True))
|
||||
|
||||
|
||||
class SEGating(nn.Module):
|
||||
|
||||
def __init__(self , inplanes , reduction=16):
|
||||
|
||||
super().__init__()
|
||||
|
||||
self.pool = nn.AdaptiveAvgPool3d(1)
|
||||
self.attn_layer = nn.Sequential(
|
||||
nn.Conv3d(inplanes , inplanes , kernel_size=1 , stride=1 , bias=True),
|
||||
nn.Sigmoid()
|
||||
)
|
||||
|
||||
def forward(self , x):
|
||||
|
||||
out = self.pool(x)
|
||||
y = self.attn_layer(out)
|
||||
return x * y
|
||||
|
||||
class BasicBlock(nn.Module):
|
||||
|
||||
expansion = 1
|
||||
|
||||
def __init__(self, inplanes, planes, conv_builder, stride=1, downsample=None):
|
||||
midplanes = (inplanes * planes * 3 * 3 * 3) // (inplanes * 3 * 3 + 3 * planes)
|
||||
|
||||
super(BasicBlock, self).__init__()
|
||||
self.conv1 = nn.Sequential(
|
||||
conv_builder(inplanes, planes, midplanes, stride),
|
||||
batchnorm(planes),
|
||||
nn.ReLU(inplace=True)
|
||||
)
|
||||
self.conv2 = nn.Sequential(
|
||||
conv_builder(planes, planes, midplanes),
|
||||
batchnorm(planes)
|
||||
)
|
||||
self.fg = SEGating(planes) ## Feature Gating
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
self.downsample = downsample
|
||||
self.stride = stride
|
||||
|
||||
def forward(self, x):
|
||||
residual = x
|
||||
out = self.conv1(x)
|
||||
out = self.conv2(out)
|
||||
out = self.fg(out)
|
||||
if self.downsample is not None:
|
||||
residual = self.downsample(x)
|
||||
|
||||
out += residual
|
||||
out = self.relu(out)
|
||||
|
||||
return out
|
||||
|
||||
class VideoResNet(nn.Module):
|
||||
|
||||
def __init__(self, block, conv_makers, layers,
|
||||
stem, zero_init_residual=False):
|
||||
"""Generic resnet video generator.
|
||||
|
||||
Args:
|
||||
block (nn.Module): resnet building block
|
||||
conv_makers (list(functions)): generator function for each layer
|
||||
layers (List[int]): number of blocks per layer
|
||||
stem (nn.Module, optional): Resnet stem, if None, defaults to conv-bn-relu. Defaults to None.
|
||||
"""
|
||||
super(VideoResNet, self).__init__()
|
||||
self.inplanes = 64
|
||||
|
||||
self.stem = stem()
|
||||
|
||||
self.layer1 = self._make_layer(block, conv_makers[0], 64, layers[0], stride=1 )
|
||||
self.layer2 = self._make_layer(block, conv_makers[1], 128, layers[1], stride=2 , temporal_stride=1)
|
||||
self.layer3 = self._make_layer(block, conv_makers[2], 256, layers[2], stride=2 , temporal_stride=1)
|
||||
self.layer4 = self._make_layer(block, conv_makers[3], 512, layers[3], stride=1, temporal_stride=1)
|
||||
|
||||
# init weights
|
||||
self._initialize_weights()
|
||||
|
||||
if zero_init_residual:
|
||||
for m in self.modules():
|
||||
if isinstance(m, Bottleneck):
|
||||
nn.init.constant_(m.bn3.weight, 0)
|
||||
|
||||
def forward(self, x):
|
||||
x_0 = self.stem(x)
|
||||
x_1 = self.layer1(x_0)
|
||||
x_2 = self.layer2(x_1)
|
||||
x_3 = self.layer3(x_2)
|
||||
x_4 = self.layer4(x_3)
|
||||
return x_0 , x_1 , x_2 , x_3 , x_4
|
||||
|
||||
def _make_layer(self, block, conv_builder, planes, blocks, stride=1, temporal_stride=None):
|
||||
downsample = None
|
||||
|
||||
if stride != 1 or self.inplanes != planes * block.expansion:
|
||||
ds_stride = conv_builder.get_downsample_stride(stride , temporal_stride)
|
||||
downsample = nn.Sequential(
|
||||
nn.Conv3d(self.inplanes, planes * block.expansion,
|
||||
kernel_size=1, stride=ds_stride, bias=False),
|
||||
batchnorm(planes * block.expansion)
|
||||
)
|
||||
stride = ds_stride
|
||||
|
||||
layers = []
|
||||
layers.append(block(self.inplanes, planes, conv_builder, stride, downsample ))
|
||||
|
||||
self.inplanes = planes * block.expansion
|
||||
for i in range(1, blocks):
|
||||
layers.append(block(self.inplanes, planes, conv_builder ))
|
||||
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
def _initialize_weights(self):
|
||||
for m in self.modules():
|
||||
if isinstance(m, nn.Conv3d):
|
||||
nn.init.kaiming_normal_(m.weight, mode='fan_out',
|
||||
nonlinearity='relu')
|
||||
if m.bias is not None:
|
||||
nn.init.constant_(m.bias, 0)
|
||||
elif isinstance(m, nn.BatchNorm3d):
|
||||
nn.init.constant_(m.weight, 1)
|
||||
nn.init.constant_(m.bias, 0)
|
||||
elif isinstance(m, nn.Linear):
|
||||
nn.init.normal_(m.weight, 0, 0.01)
|
||||
nn.init.constant_(m.bias, 0)
|
||||
|
||||
|
||||
def _video_resnet(arch, pretrained=False, progress=True, **kwargs):
|
||||
model = VideoResNet(**kwargs)
|
||||
## TODO: Other 3D resnet models, like S3D, r(2+1)D.
|
||||
|
||||
if pretrained:
|
||||
state_dict = load_state_dict_from_url(model_urls[arch],
|
||||
progress=progress)
|
||||
model.load_state_dict(state_dict)
|
||||
return model
|
||||
|
||||
|
||||
def unet_18(pretrained=False, bn=False, progress=True, **kwargs):
|
||||
"""
|
||||
Construct 18 layer Unet3D model as in
|
||||
https://arxiv.org/abs/1711.11248
|
||||
|
||||
Args:
|
||||
pretrained (bool): If True, returns a model pre-trained on Kinetics-400
|
||||
progress (bool): If True, displays a progress bar of the download to stderr
|
||||
|
||||
Returns:
|
||||
nn.Module: R3D-18 encoder
|
||||
"""
|
||||
global batchnorm
|
||||
if bn:
|
||||
batchnorm = nn.BatchNorm3d
|
||||
else:
|
||||
batchnorm = identity
|
||||
|
||||
return _video_resnet('r3d_18',
|
||||
pretrained, progress,
|
||||
block=BasicBlock,
|
||||
conv_makers=[Conv3DSimple] * 4,
|
||||
layers=[2, 2, 2, 2],
|
||||
stem=BasicStem, **kwargs)
|
||||
|
||||
def unet_34(pretrained=False, bn=False, progress=True, **kwargs):
|
||||
"""
|
||||
Construct 34 layer Unet3D model as in
|
||||
https://arxiv.org/abs/1711.11248
|
||||
|
||||
Args:
|
||||
pretrained (bool): If True, returns a model pre-trained on Kinetics-400
|
||||
progress (bool): If True, displays a progress bar of the download to stderr
|
||||
|
||||
Returns:
|
||||
nn.Module: R3D-18 encoder
|
||||
"""
|
||||
global batchnorm
|
||||
# bn = False
|
||||
if bn:
|
||||
batchnorm = nn.BatchNorm3d
|
||||
else:
|
||||
batchnorm = identity
|
||||
|
||||
|
||||
return _video_resnet('r3d_34',
|
||||
pretrained, progress,
|
||||
block=BasicBlock,
|
||||
conv_makers=[Conv3DSimple] * 4,
|
||||
layers=[3, 4, 6, 3],
|
||||
stem=BasicStem, **kwargs)
|
||||
@@ -1,24 +0,0 @@
|
||||
import itertools
|
||||
import numpy as np
|
||||
import vapoursynth as vs
|
||||
from .GMFSS_Fortuna_arch import Model_inference
|
||||
import torch
|
||||
import traceback
|
||||
|
||||
|
||||
class GMFSS_Fortuna:
|
||||
def __init__(self):
|
||||
self.cache = False
|
||||
self.amount_input_img = 2
|
||||
|
||||
torch.set_grad_enabled(False)
|
||||
torch.backends.cudnn.enabled = True
|
||||
torch.backends.cudnn.benchmark = True
|
||||
|
||||
self.model = Model_inference()
|
||||
self.model.eval()
|
||||
|
||||
def execute(self, I0, I1, timestep):
|
||||
with torch.inference_mode():
|
||||
middle = self.model(I0, I1, timestep).cpu()
|
||||
return middle
|
||||
@@ -1,23 +0,0 @@
|
||||
import itertools
|
||||
import numpy as np
|
||||
import vapoursynth as vs
|
||||
from .GMFSS_Fortuna_union_arch import Model_inference
|
||||
import torch
|
||||
|
||||
|
||||
class GMFSS_Fortuna_union:
|
||||
def __init__(self):
|
||||
self.cache = False
|
||||
self.amount_input_img = 2
|
||||
|
||||
torch.set_grad_enabled(False)
|
||||
torch.backends.cudnn.enabled = True
|
||||
torch.backends.cudnn.benchmark = True
|
||||
|
||||
self.model = Model_inference()
|
||||
self.model.eval()
|
||||
|
||||
def execute(self, I0, I1, timestep):
|
||||
with torch.inference_mode():
|
||||
middle = self.model(I0, I1, timestep).cpu()
|
||||
return middle
|
||||
@@ -1,143 +0,0 @@
|
||||
import pathlib
|
||||
from vfi_utils import load_file_from_github_release, preprocess_frames, postprocess_frames, generic_frame_loop, InterpolationStateList
|
||||
import typing
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from comfy.model_management import get_torch_device
|
||||
|
||||
|
||||
GLOBAL_MODEL_TYPE = pathlib.Path(__file__).parent.name
|
||||
CKPTS_PATH_CONFIG = {
|
||||
"GMFSS_fortuna_union": {
|
||||
"ifnet": ("rife", "rife46.pth"),
|
||||
"flownet": (GLOBAL_MODEL_TYPE, "GMFSS_fortuna_flownet.pkl"),
|
||||
"metricnet": (GLOBAL_MODEL_TYPE, "GMFSS_fortuna_union_metric.pkl"),
|
||||
"feat_ext": (GLOBAL_MODEL_TYPE, "GMFSS_fortuna_union_feat.pkl"),
|
||||
"fusionnet": (GLOBAL_MODEL_TYPE, "GMFSS_fortuna_union_fusionnet.pkl")
|
||||
},
|
||||
"GMFSS_fortuna": {
|
||||
"flownet": (GLOBAL_MODEL_TYPE, "GMFSS_fortuna_flownet.pkl"),
|
||||
"metricnet": (GLOBAL_MODEL_TYPE, "GMFSS_fortuna_metric.pkl"),
|
||||
"feat_ext": (GLOBAL_MODEL_TYPE, "GMFSS_fortuna_feat.pkl"),
|
||||
"fusionnet": (GLOBAL_MODEL_TYPE, "GMFSS_fortuna_fusionnet.pkl")
|
||||
}
|
||||
}
|
||||
|
||||
class CommonModelInference(nn.Module):
|
||||
def __init__(self, model_type):
|
||||
super(CommonModelInference, self).__init__()
|
||||
from .GMFSS_Fortuna_arch import Model as GMFSS
|
||||
from .GMFSS_Fortuna_union_arch import Model as GMFSS_Union
|
||||
self.model = GMFSS_Union() if "union" in model_type else GMFSS()
|
||||
self.model.eval()
|
||||
self.model.device()
|
||||
_model_path_config = CKPTS_PATH_CONFIG[model_type]
|
||||
self.model.load_model({
|
||||
key: load_file_from_github_release(*_model_path_config[key])
|
||||
for key in _model_path_config
|
||||
})
|
||||
|
||||
def forward(self, I0, I1, timestep, scale=1.0):
|
||||
n, c, h, w = I0.shape
|
||||
tmp = max(64, int(64 / scale))
|
||||
ph = ((h - 1) // tmp + 1) * tmp
|
||||
pw = ((w - 1) // tmp + 1) * tmp
|
||||
padding = (0, pw - w, 0, ph - h)
|
||||
I0 = F.pad(I0, padding)
|
||||
I1 = F.pad(I1, padding)
|
||||
(
|
||||
flow01,
|
||||
flow10,
|
||||
metric0,
|
||||
metric1,
|
||||
feat11,
|
||||
feat12,
|
||||
feat13,
|
||||
feat21,
|
||||
feat22,
|
||||
feat23,
|
||||
) = self.model.reuse(I0, I1, scale)
|
||||
|
||||
output = self.model.inference(
|
||||
I0,
|
||||
I1,
|
||||
flow01,
|
||||
flow10,
|
||||
metric0,
|
||||
metric1,
|
||||
feat11,
|
||||
feat12,
|
||||
feat13,
|
||||
feat21,
|
||||
feat22,
|
||||
feat23,
|
||||
timestep,
|
||||
)
|
||||
return output[:, :, :h, :w]
|
||||
|
||||
class GMFSS_Fortuna_VFI:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"ckpt_name": (list(CKPTS_PATH_CONFIG.keys()), ),
|
||||
"frames": ("IMAGE", ),
|
||||
"clear_cache_after_n_frames": ("INT", {"default": 10, "min": 1, "max": 1000}),
|
||||
"multiplier": ("INT", {"default": 2, "min": 2, "max": 1000}),
|
||||
},
|
||||
"optional": {
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", )
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", )
|
||||
FUNCTION = "vfi"
|
||||
CATEGORY = "ComfyUI-Frame-Interpolation/VFI"
|
||||
|
||||
def vfi(
|
||||
self,
|
||||
ckpt_name: typing.AnyStr,
|
||||
frames: torch.Tensor,
|
||||
clear_cache_after_n_frames = 10,
|
||||
multiplier: typing.SupportsInt = 2,
|
||||
optional_interpolation_states: InterpolationStateList = None,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Perform video frame interpolation using a given checkpoint model.
|
||||
|
||||
Args:
|
||||
ckpt_name (str): The name of the checkpoint model to use.
|
||||
frames (torch.Tensor): A tensor containing input video frames.
|
||||
clear_cache_after_n_frames (int, optional): The number of frames to process before clearing CUDA cache
|
||||
to prevent memory overflow. Defaults to 10. Lower numbers are safer but mean more processing time.
|
||||
How high you should set it depends on how many input frames there are, input resolution (after upscaling),
|
||||
how many times you want to multiply them, and how long you're willing to wait for the process to complete.
|
||||
multiplier (int, optional): The multiplier for each input frame. 60 input frames * 2 = 120 output frames. Defaults to 2.
|
||||
|
||||
Returns:
|
||||
tuple: A tuple containing the output interpolated frames.
|
||||
|
||||
Note:
|
||||
This method interpolates frames in a video sequence using a specified checkpoint model.
|
||||
It processes each frame sequentially, generating interpolated frames between them.
|
||||
|
||||
To prevent memory overflow, it clears the CUDA cache after processing a specified number of frames.
|
||||
"""
|
||||
|
||||
interpolation_model = CommonModelInference(model_type=ckpt_name)
|
||||
interpolation_model.eval().to(get_torch_device())
|
||||
frames = preprocess_frames(frames)
|
||||
|
||||
def return_middle_frame(frame_0, frame_1, timestep, model, scale):
|
||||
return model(frame_0, frame_1, timestep, scale)
|
||||
|
||||
scale = 1
|
||||
|
||||
args = [interpolation_model, scale]
|
||||
out = postprocess_frames(
|
||||
generic_frame_loop(frames, clear_cache_after_n_frames, multiplier, return_middle_frame, *args,
|
||||
interpolation_states=optional_interpolation_states, dtype=torch.float32)
|
||||
)
|
||||
return (out,)
|
||||
@@ -1,293 +0,0 @@
|
||||
# https://github.com/ltkong218/IFRNet/blob/main/models/IFRNet_L.py
|
||||
# https://github.com/ltkong218/IFRNet/blob/main/utils.py
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from comfy.model_management import get_torch_device
|
||||
|
||||
|
||||
def warp(img, flow):
|
||||
B, _, H, W = flow.shape
|
||||
xx = torch.linspace(-1.0, 1.0, W).view(1, 1, 1, W).expand(B, -1, H, -1)
|
||||
yy = torch.linspace(-1.0, 1.0, H).view(1, 1, H, 1).expand(B, -1, -1, W)
|
||||
grid = torch.cat([xx, yy], 1).to(img)
|
||||
flow_ = torch.cat(
|
||||
[
|
||||
flow[:, 0:1, :, :] / ((W - 1.0) / 2.0),
|
||||
flow[:, 1:2, :, :] / ((H - 1.0) / 2.0),
|
||||
],
|
||||
1,
|
||||
)
|
||||
grid_ = (grid + flow_).permute(0, 2, 3, 1)
|
||||
output = F.grid_sample(
|
||||
input=img,
|
||||
grid=grid_,
|
||||
mode="bilinear",
|
||||
padding_mode="border",
|
||||
align_corners=True,
|
||||
)
|
||||
return output
|
||||
|
||||
|
||||
def get_robust_weight(flow_pred, flow_gt, beta):
|
||||
epe = ((flow_pred.detach() - flow_gt) ** 2).sum(dim=1, keepdim=True) ** 0.5
|
||||
robust_weight = torch.exp(-beta * epe)
|
||||
return robust_weight
|
||||
|
||||
|
||||
def resize(x, scale_factor):
|
||||
return F.interpolate(
|
||||
x, scale_factor=scale_factor, mode="bilinear", align_corners=False
|
||||
)
|
||||
|
||||
|
||||
def convrelu(
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size=3,
|
||||
stride=1,
|
||||
padding=1,
|
||||
dilation=1,
|
||||
groups=1,
|
||||
bias=True,
|
||||
):
|
||||
return nn.Sequential(
|
||||
nn.Conv2d(
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size,
|
||||
stride,
|
||||
padding,
|
||||
dilation,
|
||||
groups,
|
||||
bias=bias,
|
||||
),
|
||||
nn.PReLU(out_channels),
|
||||
)
|
||||
|
||||
|
||||
class ResBlock(nn.Module):
|
||||
def __init__(self, in_channels, side_channels, bias=True):
|
||||
super(ResBlock, self).__init__()
|
||||
self.side_channels = side_channels
|
||||
self.conv1 = nn.Sequential(
|
||||
nn.Conv2d(
|
||||
in_channels, in_channels, kernel_size=3, stride=1, padding=1, bias=bias
|
||||
),
|
||||
nn.PReLU(in_channels),
|
||||
)
|
||||
self.conv2 = nn.Sequential(
|
||||
nn.Conv2d(
|
||||
side_channels,
|
||||
side_channels,
|
||||
kernel_size=3,
|
||||
stride=1,
|
||||
padding=1,
|
||||
bias=bias,
|
||||
),
|
||||
nn.PReLU(side_channels),
|
||||
)
|
||||
self.conv3 = nn.Sequential(
|
||||
nn.Conv2d(
|
||||
in_channels, in_channels, kernel_size=3, stride=1, padding=1, bias=bias
|
||||
),
|
||||
nn.PReLU(in_channels),
|
||||
)
|
||||
self.conv4 = nn.Sequential(
|
||||
nn.Conv2d(
|
||||
side_channels,
|
||||
side_channels,
|
||||
kernel_size=3,
|
||||
stride=1,
|
||||
padding=1,
|
||||
bias=bias,
|
||||
),
|
||||
nn.PReLU(side_channels),
|
||||
)
|
||||
self.conv5 = nn.Conv2d(
|
||||
in_channels, in_channels, kernel_size=3, stride=1, padding=1, bias=bias
|
||||
)
|
||||
self.prelu = nn.PReLU(in_channels)
|
||||
|
||||
def forward(self, x):
|
||||
out = self.conv1(x)
|
||||
out[:, -self.side_channels :, :, :] = self.conv2(
|
||||
out[:, -self.side_channels :, :, :]
|
||||
)
|
||||
out = self.conv3(out)
|
||||
out[:, -self.side_channels :, :, :] = self.conv4(
|
||||
out[:, -self.side_channels :, :, :]
|
||||
)
|
||||
out = self.prelu(x + self.conv5(out))
|
||||
return out
|
||||
|
||||
|
||||
class Encoder(nn.Module):
|
||||
def __init__(self):
|
||||
super(Encoder, self).__init__()
|
||||
self.pyramid1 = nn.Sequential(
|
||||
convrelu(3, 64, 7, 2, 3), convrelu(64, 64, 3, 1, 1)
|
||||
)
|
||||
self.pyramid2 = nn.Sequential(
|
||||
convrelu(64, 96, 3, 2, 1), convrelu(96, 96, 3, 1, 1)
|
||||
)
|
||||
self.pyramid3 = nn.Sequential(
|
||||
convrelu(96, 144, 3, 2, 1), convrelu(144, 144, 3, 1, 1)
|
||||
)
|
||||
self.pyramid4 = nn.Sequential(
|
||||
convrelu(144, 192, 3, 2, 1), convrelu(192, 192, 3, 1, 1)
|
||||
)
|
||||
|
||||
def forward(self, img):
|
||||
f1 = self.pyramid1(img)
|
||||
f2 = self.pyramid2(f1)
|
||||
f3 = self.pyramid3(f2)
|
||||
f4 = self.pyramid4(f3)
|
||||
return f1, f2, f3, f4
|
||||
|
||||
|
||||
class Decoder4(nn.Module):
|
||||
def __init__(self):
|
||||
super(Decoder4, self).__init__()
|
||||
self.convblock = nn.Sequential(
|
||||
convrelu(384 + 1, 384),
|
||||
ResBlock(384, 64),
|
||||
nn.ConvTranspose2d(384, 148, 4, 2, 1, bias=True),
|
||||
)
|
||||
|
||||
def forward(self, f0, f1, embt):
|
||||
b, c, h, w = f0.shape
|
||||
embt = embt.repeat(1, 1, h, w)
|
||||
f_in = torch.cat([f0, f1, embt], 1)
|
||||
f_out = self.convblock(f_in)
|
||||
return f_out
|
||||
|
||||
|
||||
class Decoder3(nn.Module):
|
||||
def __init__(self):
|
||||
super(Decoder3, self).__init__()
|
||||
self.convblock = nn.Sequential(
|
||||
convrelu(436, 432),
|
||||
ResBlock(432, 64),
|
||||
nn.ConvTranspose2d(432, 100, 4, 2, 1, bias=True),
|
||||
)
|
||||
|
||||
def forward(self, ft_, f0, f1, up_flow0, up_flow1):
|
||||
f0_warp = warp(f0, up_flow0)
|
||||
f1_warp = warp(f1, up_flow1)
|
||||
f_in = torch.cat([ft_, f0_warp, f1_warp, up_flow0, up_flow1], 1)
|
||||
f_out = self.convblock(f_in)
|
||||
return f_out
|
||||
|
||||
|
||||
class Decoder2(nn.Module):
|
||||
def __init__(self):
|
||||
super(Decoder2, self).__init__()
|
||||
self.convblock = nn.Sequential(
|
||||
convrelu(292, 288),
|
||||
ResBlock(288, 64),
|
||||
nn.ConvTranspose2d(288, 68, 4, 2, 1, bias=True),
|
||||
)
|
||||
|
||||
def forward(self, ft_, f0, f1, up_flow0, up_flow1):
|
||||
f0_warp = warp(f0, up_flow0)
|
||||
f1_warp = warp(f1, up_flow1)
|
||||
f_in = torch.cat([ft_, f0_warp, f1_warp, up_flow0, up_flow1], 1)
|
||||
f_out = self.convblock(f_in)
|
||||
return f_out
|
||||
|
||||
|
||||
class Decoder1(nn.Module):
|
||||
def __init__(self):
|
||||
super(Decoder1, self).__init__()
|
||||
self.convblock = nn.Sequential(
|
||||
convrelu(196, 192),
|
||||
ResBlock(192, 64),
|
||||
nn.ConvTranspose2d(192, 8, 4, 2, 1, bias=True),
|
||||
)
|
||||
|
||||
def forward(self, ft_, f0, f1, up_flow0, up_flow1):
|
||||
f0_warp = warp(f0, up_flow0)
|
||||
f1_warp = warp(f1, up_flow1)
|
||||
f_in = torch.cat([ft_, f0_warp, f1_warp, up_flow0, up_flow1], 1)
|
||||
f_out = self.convblock(f_in)
|
||||
return f_out
|
||||
|
||||
|
||||
class IRFNet_L(nn.Module):
|
||||
def __init__(self):
|
||||
super(IRFNet_L, self).__init__()
|
||||
self.encoder = Encoder()
|
||||
self.decoder4 = Decoder4()
|
||||
self.decoder3 = Decoder3()
|
||||
self.decoder2 = Decoder2()
|
||||
self.decoder1 = Decoder1()
|
||||
|
||||
def forward(self, img0, img1, scale_factor=1.0, timestep=0.5):
|
||||
# emb1 = torch.tensor(1/2).view(1, 1, 1, 1).float()
|
||||
# emb2 = torch.tensor(2/2).view(1, 1, 1, 1).float()
|
||||
# embt = torch.cat([emb1, emb2], 0)
|
||||
n, c, h, w = img0.shape
|
||||
|
||||
ph = ((h - 1) // 64 + 1) * 64
|
||||
pw = ((w - 1) // 64 + 1) * 64
|
||||
padding = (0, pw - w, 0, ph - h)
|
||||
img0 = F.pad(img0, padding)
|
||||
img1 = F.pad(img1, padding)
|
||||
|
||||
#Support multiple batches
|
||||
embt = torch.tensor([timestep] * n).view(n, 1, 1, 1).float().to(get_torch_device())
|
||||
if "HalfTensor" in str(img0.type()):
|
||||
embt = embt.half()
|
||||
|
||||
mean_ = (
|
||||
torch.cat([img0, img1], 2)
|
||||
.mean(1, keepdim=True)
|
||||
.mean(2, keepdim=True)
|
||||
.mean(3, keepdim=True)
|
||||
)
|
||||
img0 = img0 - mean_
|
||||
img1 = img1 - mean_
|
||||
|
||||
img0_ = resize(img0, scale_factor=scale_factor)
|
||||
img1_ = resize(img1, scale_factor=scale_factor)
|
||||
|
||||
f0_1, f0_2, f0_3, f0_4 = self.encoder(img0_)
|
||||
f1_1, f1_2, f1_3, f1_4 = self.encoder(img1_)
|
||||
|
||||
out4 = self.decoder4(f0_4, f1_4, embt)
|
||||
up_flow0_4 = out4[:, 0:2]
|
||||
up_flow1_4 = out4[:, 2:4]
|
||||
ft_3_ = out4[:, 4:]
|
||||
|
||||
out3 = self.decoder3(ft_3_, f0_3, f1_3, up_flow0_4, up_flow1_4)
|
||||
up_flow0_3 = out3[:, 0:2] + 2.0 * resize(up_flow0_4, scale_factor=2.0)
|
||||
up_flow1_3 = out3[:, 2:4] + 2.0 * resize(up_flow1_4, scale_factor=2.0)
|
||||
ft_2_ = out3[:, 4:]
|
||||
|
||||
out2 = self.decoder2(ft_2_, f0_2, f1_2, up_flow0_3, up_flow1_3)
|
||||
up_flow0_2 = out2[:, 0:2] + 2.0 * resize(up_flow0_3, scale_factor=2.0)
|
||||
up_flow1_2 = out2[:, 2:4] + 2.0 * resize(up_flow1_3, scale_factor=2.0)
|
||||
ft_1_ = out2[:, 4:]
|
||||
|
||||
out1 = self.decoder1(ft_1_, f0_1, f1_1, up_flow0_2, up_flow1_2)
|
||||
up_flow0_1 = out1[:, 0:2] + 2.0 * resize(up_flow0_2, scale_factor=2.0)
|
||||
up_flow1_1 = out1[:, 2:4] + 2.0 * resize(up_flow1_2, scale_factor=2.0)
|
||||
up_mask_1 = torch.sigmoid(out1[:, 4:5])
|
||||
up_res_1 = out1[:, 5:]
|
||||
|
||||
up_flow0_1 = resize(up_flow0_1, scale_factor=(1.0 / scale_factor)) * (
|
||||
1.0 / scale_factor
|
||||
)
|
||||
up_flow1_1 = resize(up_flow1_1, scale_factor=(1.0 / scale_factor)) * (
|
||||
1.0 / scale_factor
|
||||
)
|
||||
up_mask_1 = resize(up_mask_1, scale_factor=(1.0 / scale_factor))
|
||||
up_res_1 = resize(up_res_1, scale_factor=(1.0 / scale_factor))
|
||||
|
||||
img0_warp = warp(img0, up_flow0_1)
|
||||
img1_warp = warp(img1, up_flow1_1)
|
||||
imgt_merge = up_mask_1 * img0_warp + (1 - up_mask_1) * img1_warp + mean_
|
||||
imgt_pred = imgt_merge + up_res_1
|
||||
imgt_pred = torch.clamp(imgt_pred, 0, 1)
|
||||
return imgt_pred[:, :, :h, :w]
|
||||
@@ -1,293 +0,0 @@
|
||||
# https://github.com/ltkong218/IFRNet/blob/main/models/IFRNet_S.py
|
||||
# https://github.com/ltkong218/IFRNet/blob/main/utils.py
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from comfy.model_management import get_torch_device
|
||||
|
||||
|
||||
def warp(img, flow):
|
||||
B, _, H, W = flow.shape
|
||||
xx = torch.linspace(-1.0, 1.0, W).view(1, 1, 1, W).expand(B, -1, H, -1)
|
||||
yy = torch.linspace(-1.0, 1.0, H).view(1, 1, H, 1).expand(B, -1, -1, W)
|
||||
grid = torch.cat([xx, yy], 1).to(img)
|
||||
flow_ = torch.cat(
|
||||
[
|
||||
flow[:, 0:1, :, :] / ((W - 1.0) / 2.0),
|
||||
flow[:, 1:2, :, :] / ((H - 1.0) / 2.0),
|
||||
],
|
||||
1,
|
||||
)
|
||||
grid_ = (grid + flow_).permute(0, 2, 3, 1)
|
||||
output = F.grid_sample(
|
||||
input=img,
|
||||
grid=grid_,
|
||||
mode="bilinear",
|
||||
padding_mode="border",
|
||||
align_corners=True,
|
||||
)
|
||||
return output
|
||||
|
||||
|
||||
def get_robust_weight(flow_pred, flow_gt, beta):
|
||||
epe = ((flow_pred.detach() - flow_gt) ** 2).sum(dim=1, keepdim=True) ** 0.5
|
||||
robust_weight = torch.exp(-beta * epe)
|
||||
return robust_weight
|
||||
|
||||
|
||||
def resize(x, scale_factor):
|
||||
return F.interpolate(
|
||||
x, scale_factor=scale_factor, mode="bilinear", align_corners=False
|
||||
)
|
||||
|
||||
|
||||
def convrelu(
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size=3,
|
||||
stride=1,
|
||||
padding=1,
|
||||
dilation=1,
|
||||
groups=1,
|
||||
bias=True,
|
||||
):
|
||||
return nn.Sequential(
|
||||
nn.Conv2d(
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size,
|
||||
stride,
|
||||
padding,
|
||||
dilation,
|
||||
groups,
|
||||
bias=bias,
|
||||
),
|
||||
nn.PReLU(out_channels),
|
||||
)
|
||||
|
||||
|
||||
class ResBlock(nn.Module):
|
||||
def __init__(self, in_channels, side_channels, bias=True):
|
||||
super(ResBlock, self).__init__()
|
||||
self.side_channels = side_channels
|
||||
self.conv1 = nn.Sequential(
|
||||
nn.Conv2d(
|
||||
in_channels, in_channels, kernel_size=3, stride=1, padding=1, bias=bias
|
||||
),
|
||||
nn.PReLU(in_channels),
|
||||
)
|
||||
self.conv2 = nn.Sequential(
|
||||
nn.Conv2d(
|
||||
side_channels,
|
||||
side_channels,
|
||||
kernel_size=3,
|
||||
stride=1,
|
||||
padding=1,
|
||||
bias=bias,
|
||||
),
|
||||
nn.PReLU(side_channels),
|
||||
)
|
||||
self.conv3 = nn.Sequential(
|
||||
nn.Conv2d(
|
||||
in_channels, in_channels, kernel_size=3, stride=1, padding=1, bias=bias
|
||||
),
|
||||
nn.PReLU(in_channels),
|
||||
)
|
||||
self.conv4 = nn.Sequential(
|
||||
nn.Conv2d(
|
||||
side_channels,
|
||||
side_channels,
|
||||
kernel_size=3,
|
||||
stride=1,
|
||||
padding=1,
|
||||
bias=bias,
|
||||
),
|
||||
nn.PReLU(side_channels),
|
||||
)
|
||||
self.conv5 = nn.Conv2d(
|
||||
in_channels, in_channels, kernel_size=3, stride=1, padding=1, bias=bias
|
||||
)
|
||||
self.prelu = nn.PReLU(in_channels)
|
||||
|
||||
def forward(self, x):
|
||||
out = self.conv1(x)
|
||||
out[:, -self.side_channels :, :, :] = self.conv2(
|
||||
out[:, -self.side_channels :, :, :]
|
||||
)
|
||||
out = self.conv3(out)
|
||||
out[:, -self.side_channels :, :, :] = self.conv4(
|
||||
out[:, -self.side_channels :, :, :]
|
||||
)
|
||||
out = self.prelu(x + self.conv5(out))
|
||||
return out
|
||||
|
||||
|
||||
class Encoder(nn.Module):
|
||||
def __init__(self):
|
||||
super(Encoder, self).__init__()
|
||||
self.pyramid1 = nn.Sequential(
|
||||
convrelu(3, 24, 3, 2, 1), convrelu(24, 24, 3, 1, 1)
|
||||
)
|
||||
self.pyramid2 = nn.Sequential(
|
||||
convrelu(24, 36, 3, 2, 1), convrelu(36, 36, 3, 1, 1)
|
||||
)
|
||||
self.pyramid3 = nn.Sequential(
|
||||
convrelu(36, 54, 3, 2, 1), convrelu(54, 54, 3, 1, 1)
|
||||
)
|
||||
self.pyramid4 = nn.Sequential(
|
||||
convrelu(54, 72, 3, 2, 1), convrelu(72, 72, 3, 1, 1)
|
||||
)
|
||||
|
||||
def forward(self, img):
|
||||
f1 = self.pyramid1(img)
|
||||
f2 = self.pyramid2(f1)
|
||||
f3 = self.pyramid3(f2)
|
||||
f4 = self.pyramid4(f3)
|
||||
return f1, f2, f3, f4
|
||||
|
||||
|
||||
class Decoder4(nn.Module):
|
||||
def __init__(self):
|
||||
super(Decoder4, self).__init__()
|
||||
self.convblock = nn.Sequential(
|
||||
convrelu(144 + 1, 144),
|
||||
ResBlock(144, 24),
|
||||
nn.ConvTranspose2d(144, 58, 4, 2, 1, bias=True),
|
||||
)
|
||||
|
||||
def forward(self, f0, f1, embt):
|
||||
b, c, h, w = f0.shape
|
||||
embt = embt.repeat(1, 1, h, w)
|
||||
f_in = torch.cat([f0, f1, embt], 1)
|
||||
f_out = self.convblock(f_in)
|
||||
return f_out
|
||||
|
||||
|
||||
class Decoder3(nn.Module):
|
||||
def __init__(self):
|
||||
super(Decoder3, self).__init__()
|
||||
self.convblock = nn.Sequential(
|
||||
convrelu(166, 162),
|
||||
ResBlock(162, 24),
|
||||
nn.ConvTranspose2d(162, 40, 4, 2, 1, bias=True),
|
||||
)
|
||||
|
||||
def forward(self, ft_, f0, f1, up_flow0, up_flow1):
|
||||
f0_warp = warp(f0, up_flow0)
|
||||
f1_warp = warp(f1, up_flow1)
|
||||
f_in = torch.cat([ft_, f0_warp, f1_warp, up_flow0, up_flow1], 1)
|
||||
f_out = self.convblock(f_in)
|
||||
return f_out
|
||||
|
||||
|
||||
class Decoder2(nn.Module):
|
||||
def __init__(self):
|
||||
super(Decoder2, self).__init__()
|
||||
self.convblock = nn.Sequential(
|
||||
convrelu(112, 108),
|
||||
ResBlock(108, 24),
|
||||
nn.ConvTranspose2d(108, 28, 4, 2, 1, bias=True),
|
||||
)
|
||||
|
||||
def forward(self, ft_, f0, f1, up_flow0, up_flow1):
|
||||
f0_warp = warp(f0, up_flow0)
|
||||
f1_warp = warp(f1, up_flow1)
|
||||
f_in = torch.cat([ft_, f0_warp, f1_warp, up_flow0, up_flow1], 1)
|
||||
f_out = self.convblock(f_in)
|
||||
return f_out
|
||||
|
||||
|
||||
class Decoder1(nn.Module):
|
||||
def __init__(self):
|
||||
super(Decoder1, self).__init__()
|
||||
self.convblock = nn.Sequential(
|
||||
convrelu(76, 72),
|
||||
ResBlock(72, 24),
|
||||
nn.ConvTranspose2d(72, 8, 4, 2, 1, bias=True),
|
||||
)
|
||||
|
||||
def forward(self, ft_, f0, f1, up_flow0, up_flow1):
|
||||
f0_warp = warp(f0, up_flow0)
|
||||
f1_warp = warp(f1, up_flow1)
|
||||
f_in = torch.cat([ft_, f0_warp, f1_warp, up_flow0, up_flow1], 1)
|
||||
f_out = self.convblock(f_in)
|
||||
return f_out
|
||||
|
||||
|
||||
class IRFNet_S(nn.Module):
|
||||
def __init__(self):
|
||||
super(IRFNet_S, self).__init__()
|
||||
self.encoder = Encoder()
|
||||
self.decoder4 = Decoder4()
|
||||
self.decoder3 = Decoder3()
|
||||
self.decoder2 = Decoder2()
|
||||
self.decoder1 = Decoder1()
|
||||
|
||||
def forward(self, img0, img1, scale_factor=1.0, timestep=0.5):
|
||||
# emb1 = torch.tensor(1/2).view(1, 1, 1, 1).float()
|
||||
# emb2 = torch.tensor(2/2).view(1, 1, 1, 1).float()
|
||||
# embt = torch.cat([emb1, emb2], 0)
|
||||
n, c, h, w = img0.shape
|
||||
|
||||
ph = ((h - 1) // 64 + 1) * 64
|
||||
pw = ((w - 1) // 64 + 1) * 64
|
||||
padding = (0, pw - w, 0, ph - h)
|
||||
img0 = F.pad(img0, padding)
|
||||
img1 = F.pad(img1, padding)
|
||||
|
||||
#Support multiple batches
|
||||
embt = torch.tensor([timestep] * n).view(n, 1, 1, 1).float().to(get_torch_device())
|
||||
if "HalfTensor" in str(img0.type()):
|
||||
embt = embt.half()
|
||||
|
||||
mean_ = (
|
||||
torch.cat([img0, img1], 2)
|
||||
.mean(1, keepdim=True)
|
||||
.mean(2, keepdim=True)
|
||||
.mean(3, keepdim=True)
|
||||
)
|
||||
img0 = img0 - mean_
|
||||
img1 = img1 - mean_
|
||||
|
||||
img0_ = resize(img0, scale_factor=scale_factor)
|
||||
img1_ = resize(img1, scale_factor=scale_factor)
|
||||
|
||||
f0_1, f0_2, f0_3, f0_4 = self.encoder(img0_)
|
||||
f1_1, f1_2, f1_3, f1_4 = self.encoder(img1_)
|
||||
|
||||
out4 = self.decoder4(f0_4, f1_4, embt)
|
||||
up_flow0_4 = out4[:, 0:2]
|
||||
up_flow1_4 = out4[:, 2:4]
|
||||
ft_3_ = out4[:, 4:]
|
||||
|
||||
out3 = self.decoder3(ft_3_, f0_3, f1_3, up_flow0_4, up_flow1_4)
|
||||
up_flow0_3 = out3[:, 0:2] + 2.0 * resize(up_flow0_4, scale_factor=2.0)
|
||||
up_flow1_3 = out3[:, 2:4] + 2.0 * resize(up_flow1_4, scale_factor=2.0)
|
||||
ft_2_ = out3[:, 4:]
|
||||
|
||||
out2 = self.decoder2(ft_2_, f0_2, f1_2, up_flow0_3, up_flow1_3)
|
||||
up_flow0_2 = out2[:, 0:2] + 2.0 * resize(up_flow0_3, scale_factor=2.0)
|
||||
up_flow1_2 = out2[:, 2:4] + 2.0 * resize(up_flow1_3, scale_factor=2.0)
|
||||
ft_1_ = out2[:, 4:]
|
||||
|
||||
out1 = self.decoder1(ft_1_, f0_1, f1_1, up_flow0_2, up_flow1_2)
|
||||
up_flow0_1 = out1[:, 0:2] + 2.0 * resize(up_flow0_2, scale_factor=2.0)
|
||||
up_flow1_1 = out1[:, 2:4] + 2.0 * resize(up_flow1_2, scale_factor=2.0)
|
||||
up_mask_1 = torch.sigmoid(out1[:, 4:5])
|
||||
up_res_1 = out1[:, 5:]
|
||||
|
||||
up_flow0_1 = resize(up_flow0_1, scale_factor=(1.0 / scale_factor)) * (
|
||||
1.0 / scale_factor
|
||||
)
|
||||
up_flow1_1 = resize(up_flow1_1, scale_factor=(1.0 / scale_factor)) * (
|
||||
1.0 / scale_factor
|
||||
)
|
||||
up_mask_1 = resize(up_mask_1, scale_factor=(1.0 / scale_factor))
|
||||
up_res_1 = resize(up_res_1, scale_factor=(1.0 / scale_factor))
|
||||
|
||||
img0_warp = warp(img0, up_flow0_1)
|
||||
img1_warp = warp(img1, up_flow1_1)
|
||||
imgt_merge = up_mask_1 * img0_warp + (1 - up_mask_1) * img1_warp + mean_
|
||||
imgt_pred = imgt_merge + up_res_1
|
||||
imgt_pred = torch.clamp(imgt_pred, 0, 1)
|
||||
return imgt_pred[:, :, :h, :w]
|
||||
@@ -1,57 +0,0 @@
|
||||
import torch
|
||||
import pathlib
|
||||
from vfi_utils import load_file_from_github_release, preprocess_frames, postprocess_frames
|
||||
import typing
|
||||
from comfy.model_management import get_torch_device
|
||||
from vfi_utils import generic_frame_loop, InterpolationStateList
|
||||
|
||||
MODEL_TYPE = pathlib.Path(__file__).parent.name
|
||||
CKPT_NAMES = ["IFRNet_S_Vimeo90K.pth", "IFRNet_L_Vimeo90K.pth"]
|
||||
|
||||
class IFRNet_VFI:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"ckpt_name": (CKPT_NAMES, ),
|
||||
"frames": ("IMAGE", ),
|
||||
"clear_cache_after_n_frames": ("INT", {"default": 10, "min": 1, "max": 1000}),
|
||||
"multiplier": ("INT", {"default": 2, "min": 2, "max": 1000}),
|
||||
"scale_factor": ([0.25, 0.5, 1.0, 2.0, 4.0], {"default": 1.0}),
|
||||
},
|
||||
"optional": {
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", )
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", )
|
||||
FUNCTION = "vfi"
|
||||
CATEGORY = "ComfyUI-Frame-Interpolation/VFI"
|
||||
|
||||
def vfi(
|
||||
self,
|
||||
ckpt_name: typing.AnyStr,
|
||||
frames: torch.Tensor,
|
||||
clear_cache_after_n_frames: typing.SupportsInt = 1,
|
||||
multiplier: typing.SupportsInt = 2,
|
||||
scale_factor: typing.SupportsFloat = 1.0,
|
||||
optional_interpolation_states: InterpolationStateList = None,
|
||||
**kwargs
|
||||
):
|
||||
from .IFRNet_S_arch import IRFNet_S
|
||||
from .IFRNet_L_arch import IRFNet_L
|
||||
model_path = load_file_from_github_release(MODEL_TYPE, ckpt_name)
|
||||
interpolation_model = IRFNet_S() if 'S' in ckpt_name else IRFNet_L()
|
||||
interpolation_model.load_state_dict(torch.load(model_path))
|
||||
interpolation_model.eval().to(get_torch_device())
|
||||
frames = preprocess_frames(frames)
|
||||
|
||||
def return_middle_frame(frame_0, frame_1, timestep, model, scale_factor):
|
||||
return model(frame_0, frame_1, timestep, scale_factor)
|
||||
|
||||
args = [interpolation_model, scale_factor]
|
||||
out = postprocess_frames(
|
||||
generic_frame_loop(frames, clear_cache_after_n_frames, multiplier, return_middle_frame, *args,
|
||||
interpolation_states=optional_interpolation_states, dtype=torch.float32)
|
||||
)
|
||||
return (out,)
|
||||
@@ -1,766 +0,0 @@
|
||||
"""
|
||||
https://github.com/98mxr/IFUNet/blob/main/model/IFUNet.py
|
||||
https://github.com/98mxr/IFUNet/blob/main/model/cbam.py
|
||||
https://github.com/98mxr/IFUNet/blob/main/model/warplayer.py
|
||||
https://github.com/98mxr/IFUNet/blob/5be535c8cff66d6fa1967252685719df4c0620e4/model/RIFE.py
|
||||
https://github.com/98mxr/IFUNet/blob/main/model/rrdb.py
|
||||
https://github.com/98mxr/IFUNet/blob/main/model/ResynNet.py
|
||||
"""
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from comfy.model_management import get_torch_device
|
||||
|
||||
backwarp_tenGrid = {}
|
||||
device = get_torch_device()
|
||||
|
||||
|
||||
def conv(in_planes, out_planes, kernel_size=3, stride=1, padding=1, dilation=1):
|
||||
return nn.Sequential(
|
||||
nn.Conv2d(
|
||||
in_planes,
|
||||
out_planes,
|
||||
kernel_size=kernel_size,
|
||||
stride=stride,
|
||||
padding=padding,
|
||||
dilation=dilation,
|
||||
bias=True,
|
||||
),
|
||||
nn.PReLU(out_planes),
|
||||
)
|
||||
|
||||
|
||||
def conv_bn(in_planes, out_planes, kernel_size=3, stride=1, padding=1, dilation=1):
|
||||
return nn.Sequential(
|
||||
nn.Conv2d(
|
||||
in_planes,
|
||||
out_planes,
|
||||
kernel_size=kernel_size,
|
||||
stride=stride,
|
||||
padding=padding,
|
||||
dilation=dilation,
|
||||
bias=False,
|
||||
),
|
||||
nn.BatchNorm2d(out_planes),
|
||||
nn.PReLU(out_planes),
|
||||
)
|
||||
|
||||
|
||||
class DegCNN(nn.Module):
|
||||
def __init__(self):
|
||||
super(DegCNN, self).__init__()
|
||||
self.conv0 = conv(3, 32, 3, 2, 1)
|
||||
self.conv1 = conv(32, 32, 3, 2, 1)
|
||||
self.conv2 = conv(32, 32, 3, 2, 1)
|
||||
self.conv3 = conv(32, 32, 3, 2, 1)
|
||||
self.deconv = nn.Sequential(
|
||||
nn.Dropout2d(0.95),
|
||||
nn.ConvTranspose2d(4 * 32, 32, 4, 2, 1),
|
||||
nn.PReLU(32),
|
||||
nn.Conv2d(32, 3, 3, 1, 1),
|
||||
nn.Sigmoid(),
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
f0 = self.conv0(x)
|
||||
f1 = self.conv1(f0)
|
||||
f2 = self.conv2(f1)
|
||||
f3 = self.conv3(f2)
|
||||
f1 = F.interpolate(f1, scale_factor=2.0, mode="bilinear", align_corners=False)
|
||||
f2 = F.interpolate(f2, scale_factor=4.0, mode="bilinear", align_corners=False)
|
||||
f3 = F.interpolate(f3, scale_factor=8.0, mode="bilinear", align_corners=False)
|
||||
return self.deconv(torch.cat((f0, f1, f2, f3), 1))
|
||||
|
||||
|
||||
class FlowBlock(nn.Module):
|
||||
def __init__(self, in_planes, c=64):
|
||||
super(FlowBlock, self).__init__()
|
||||
self.conv0 = nn.Sequential(
|
||||
conv_bn(in_planes, c // 2, 3, 2, 1),
|
||||
conv_bn(c // 2, c, 3, 2, 1),
|
||||
conv_bn(c, 2 * c, 3, 2, 1),
|
||||
)
|
||||
self.convblock = nn.Sequential(
|
||||
conv_bn(2 * c, 2 * c),
|
||||
conv_bn(2 * c, 2 * c),
|
||||
conv_bn(2 * c, 2 * c),
|
||||
conv_bn(2 * c, 2 * c),
|
||||
conv_bn(2 * c, 2 * c),
|
||||
conv_bn(2 * c, 2 * c),
|
||||
)
|
||||
self.lastconv = nn.ConvTranspose2d(2 * c, 4, 4, 2, 1)
|
||||
|
||||
def forward(self, x, flow, scale=1):
|
||||
x = F.interpolate(
|
||||
x, scale_factor=1.0 / scale, mode="bilinear", align_corners=False
|
||||
)
|
||||
if flow is not None:
|
||||
flow = (
|
||||
F.interpolate(
|
||||
flow, scale_factor=1.0 / scale, mode="bilinear", align_corners=False
|
||||
)
|
||||
* 1.0
|
||||
/ scale
|
||||
)
|
||||
x = torch.cat((x, flow), 1)
|
||||
feat = self.conv0(x)
|
||||
feat = self.convblock(feat) + feat
|
||||
tmp = self.lastconv(feat)
|
||||
tmp = F.interpolate(
|
||||
tmp, scale_factor=scale * 4, mode="bilinear", align_corners=False
|
||||
)
|
||||
flow = tmp[:, :2] * scale * 4
|
||||
mask = tmp[:, 2:3]
|
||||
return flow, mask
|
||||
|
||||
|
||||
class ResynNet(nn.Module):
|
||||
def __init__(self):
|
||||
super(ResynNet, self).__init__()
|
||||
self.block0 = FlowBlock(6, c=128)
|
||||
self.block1 = FlowBlock(12, c=128)
|
||||
self.block2 = FlowBlock(12, c=128)
|
||||
self.degrad = DegCNN()
|
||||
# Contextual Refinement context + decode
|
||||
self.context0 = nn.Sequential(
|
||||
conv(3, 16, 3, 2, 1),
|
||||
conv(16, 32, 3, 2, 1),
|
||||
)
|
||||
self.context1 = nn.Sequential(
|
||||
conv(3, 16, 3, 2, 1),
|
||||
conv(16, 32, 3, 2, 1),
|
||||
)
|
||||
self.decode = nn.Sequential(
|
||||
nn.ConvTranspose2d(64, 32, 4, 2, 1),
|
||||
nn.ConvTranspose2d(32, 3, 4, 2, 1),
|
||||
nn.Tanh(),
|
||||
)
|
||||
|
||||
def calflow(self, img0, lowres, scale):
|
||||
flow = None
|
||||
stu = [self.block0, self.block1, self.block2]
|
||||
for i in range(3):
|
||||
if flow is not None:
|
||||
flow_d, mask_d = stu[i](
|
||||
torch.cat((img0, lowres, warped_img0, mask), 1),
|
||||
flow,
|
||||
scale=scale[i],
|
||||
)
|
||||
flow = flow + flow_d
|
||||
mask = mask + mask_d
|
||||
else:
|
||||
flow, mask = stu[i](torch.cat((img0, lowres), 1), None, scale=scale[i])
|
||||
warped_img0 = warp(img0, flow)
|
||||
flow_down = (
|
||||
F.interpolate(flow, scale_factor=0.25, mode="bilinear", align_corners=False)
|
||||
* 0.25
|
||||
)
|
||||
c0 = warp(self.context0(img0), flow_down)
|
||||
c1 = self.context1(warped_img0)
|
||||
warped_img0 = warped_img0 + self.decode(torch.cat((c0, c1), 1))
|
||||
return flow, mask, torch.clamp(warped_img0, 0, 1)
|
||||
|
||||
def forward(
|
||||
self, x, deg=None, gt=None, scale=[4, 2, 1], training=False, blend=True
|
||||
):
|
||||
if training:
|
||||
deg = self.degrad(gt)
|
||||
loss_cons = (gt - deg).abs().mean()
|
||||
else:
|
||||
loss_cons = torch.tensor([0])
|
||||
img_list = []
|
||||
N = x.shape[1] // 3
|
||||
for i in range(N):
|
||||
img_list.append(x[:, i * 3 : i * 3 + 3])
|
||||
warped_list = []
|
||||
merged = []
|
||||
mask_list = []
|
||||
flow_list = []
|
||||
for i in range(N):
|
||||
f, m, img = self.calflow(img_list[i], deg.detach(), scale)
|
||||
mask_list.append(m)
|
||||
warped_list.append(img)
|
||||
flow_list.append(f)
|
||||
if blend:
|
||||
N += 1
|
||||
mask_list.append(m * 0)
|
||||
warped_list.append(deg)
|
||||
mask = F.softmax(torch.clamp(torch.cat(mask_list, 1), -4, 4), dim=1)
|
||||
merged = 0
|
||||
for i in range(N):
|
||||
merged += warped_list[i] * mask[:, i : i + 1]
|
||||
return merged, loss_cons
|
||||
|
||||
|
||||
def make_layer(basic_block, num_basic_block, **kwarg):
|
||||
"""Make layers by stacking the same blocks.
|
||||
Args:
|
||||
basic_block (nn.module): nn.module class for basic block.
|
||||
num_basic_block (int): number of blocks.
|
||||
Returns:
|
||||
nn.Sequential: Stacked blocks in nn.Sequential.
|
||||
"""
|
||||
layers = []
|
||||
for _ in range(num_basic_block):
|
||||
layers.append(basic_block(**kwarg))
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
|
||||
class ResidualDenseBlock(nn.Module):
|
||||
"""Residual Dense Block.
|
||||
|
||||
Used in RRDB block in ESRGAN.
|
||||
|
||||
Args:
|
||||
num_feat (int): Channel number of intermediate features.
|
||||
num_grow_ch (int): Channels for each growth.
|
||||
"""
|
||||
|
||||
def __init__(self, num_feat=64, num_grow_ch=32):
|
||||
super(ResidualDenseBlock, self).__init__()
|
||||
self.conv1 = nn.Conv2d(num_feat, num_grow_ch, 3, 1, 1)
|
||||
self.conv2 = nn.Conv2d(num_feat + num_grow_ch, num_grow_ch, 3, 1, 1)
|
||||
self.conv3 = nn.Conv2d(num_feat + 2 * num_grow_ch, num_grow_ch, 3, 1, 1)
|
||||
self.conv4 = nn.Conv2d(num_feat + 3 * num_grow_ch, num_grow_ch, 3, 1, 1)
|
||||
self.conv5 = nn.Conv2d(num_feat + 4 * num_grow_ch, num_feat, 3, 1, 1)
|
||||
|
||||
self.lrelu = nn.LeakyReLU(negative_slope=0.2, inplace=True)
|
||||
|
||||
# initialization
|
||||
# default_init_weights([self.conv1, self.conv2, self.conv3, self.conv4, self.conv5], 0.1)
|
||||
# 只能先取消,default_init_weights来自basicsr.arch_util
|
||||
|
||||
def forward(self, x):
|
||||
x1 = self.lrelu(self.conv1(x))
|
||||
x2 = self.lrelu(self.conv2(torch.cat((x, x1), 1)))
|
||||
x3 = self.lrelu(self.conv3(torch.cat((x, x1, x2), 1)))
|
||||
x4 = self.lrelu(self.conv4(torch.cat((x, x1, x2, x3), 1)))
|
||||
x5 = self.conv5(torch.cat((x, x1, x2, x3, x4), 1))
|
||||
# Emperically, we use 0.2 to scale the residual for better performance
|
||||
# 原作者这么说我就这么听着吧
|
||||
return x5 * 0.2 + x
|
||||
|
||||
|
||||
class RRDB(nn.Module):
|
||||
"""Residual in Residual Dense Block.
|
||||
|
||||
Used in RRDB-Net in ESRGAN.
|
||||
|
||||
Args:
|
||||
num_feat (int): Channel number of intermediate features.
|
||||
num_grow_ch (int): Channels for each growth.
|
||||
"""
|
||||
|
||||
def __init__(self, num_feat, num_grow_ch=32):
|
||||
super(RRDB, self).__init__()
|
||||
self.rdb1 = ResidualDenseBlock(num_feat, num_grow_ch)
|
||||
self.rdb2 = ResidualDenseBlock(num_feat, num_grow_ch)
|
||||
self.rdb3 = ResidualDenseBlock(num_feat, num_grow_ch)
|
||||
|
||||
def forward(self, x):
|
||||
out = self.rdb1(x)
|
||||
out = self.rdb2(out)
|
||||
out = self.rdb3(out)
|
||||
# Emperically, we use 0.2 to scale the residual for better performance
|
||||
# 原作者这么说我就这么听着吧
|
||||
return out * 0.2 + x
|
||||
|
||||
|
||||
class RRDBNet(nn.Module):
|
||||
"""Networks consisting of Residual in Residual Dense Block, which is used
|
||||
in ESRGAN.
|
||||
|
||||
ESRGAN: Enhanced Super-Resolution Generative Adversarial Networks.
|
||||
|
||||
We extend ESRGAN for scale x2 and scale x1.
|
||||
Note: This is one option for scale 1, scale 2 in RRDBNet.
|
||||
We first employ the pixel-unshuffle (an inverse operation of pixelshuffle to reduce the spatial size
|
||||
and enlarge the channel size before feeding inputs into the main ESRGAN architecture.
|
||||
|
||||
Args:
|
||||
num_in_ch (int): Channel number of inputs.
|
||||
num_out_ch (int): Channel number of outputs.
|
||||
num_feat (int): Channel number of intermediate features.
|
||||
Default: 64
|
||||
num_block (int): Block number in the trunk network. Defaults: 23
|
||||
num_grow_ch (int): Channels for each growth. Default: 32.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, num_in_ch=16, num_out_ch=1, num_feat=64, num_block=6, num_grow_ch=32
|
||||
):
|
||||
super(RRDBNet, self).__init__()
|
||||
self.conv_first = nn.Conv2d(num_in_ch, num_feat, 3, 1, 1)
|
||||
self.body = make_layer(
|
||||
RRDB, num_block, num_feat=num_feat, num_grow_ch=num_grow_ch
|
||||
)
|
||||
self.conv_body = nn.Conv2d(num_feat, num_feat, 3, 1, 1)
|
||||
# upsample
|
||||
self.conv_up1 = nn.Conv2d(num_feat, num_feat, 3, 1, 1)
|
||||
self.conv_up2 = nn.Conv2d(num_feat, num_feat, 3, 1, 1)
|
||||
self.conv_hr = nn.Conv2d(num_feat, num_feat, 3, 1, 1)
|
||||
self.conv_last = nn.Conv2d(num_feat, num_out_ch, 3, 1, 1)
|
||||
|
||||
self.lrelu = nn.LeakyReLU(negative_slope=0.2, inplace=True)
|
||||
|
||||
def forward(self, img0, img1, warped_img0, warped_img1, flow):
|
||||
x = torch.cat((img0, img1, warped_img0, warped_img1), 1)
|
||||
x = F.interpolate(x, scale_factor=0.25, mode="bilinear", align_corners=False)
|
||||
flow = (
|
||||
F.interpolate(flow, scale_factor=0.25, mode="bilinear", align_corners=False)
|
||||
* 0.25
|
||||
)
|
||||
feat = torch.cat((x, flow), 1)
|
||||
|
||||
feat = self.conv_first(feat)
|
||||
body_feat = self.conv_body(self.body(feat))
|
||||
feat = feat + body_feat
|
||||
# upsample,充分利用四倍放大
|
||||
feat = self.lrelu(
|
||||
self.conv_up1(F.interpolate(feat, scale_factor=2.0, mode="nearest"))
|
||||
)
|
||||
feat = self.lrelu(
|
||||
self.conv_up2(F.interpolate(feat, scale_factor=2.0, mode="nearest"))
|
||||
)
|
||||
out = self.conv_last(self.lrelu(self.conv_hr(feat)))
|
||||
|
||||
out = torch.sigmoid(out)
|
||||
return out
|
||||
|
||||
|
||||
def warp(tenInput, tenFlow):
|
||||
k = (str(tenFlow.device), str(tenFlow.size()))
|
||||
if k not in backwarp_tenGrid:
|
||||
tenHorizontal = (
|
||||
torch.linspace(-1.0, 1.0, tenFlow.shape[3], device=device)
|
||||
.view(1, 1, 1, tenFlow.shape[3])
|
||||
.expand(tenFlow.shape[0], -1, tenFlow.shape[2], -1)
|
||||
)
|
||||
tenVertical = (
|
||||
torch.linspace(-1.0, 1.0, tenFlow.shape[2], device=device)
|
||||
.view(1, 1, tenFlow.shape[2], 1)
|
||||
.expand(tenFlow.shape[0], -1, -1, tenFlow.shape[3])
|
||||
)
|
||||
backwarp_tenGrid[k] = torch.cat([tenHorizontal, tenVertical], 1).to(device)
|
||||
|
||||
tenFlow = torch.cat(
|
||||
[
|
||||
tenFlow[:, 0:1, :, :] / ((tenInput.shape[3] - 1.0) / 2.0),
|
||||
tenFlow[:, 1:2, :, :] / ((tenInput.shape[2] - 1.0) / 2.0),
|
||||
],
|
||||
1,
|
||||
)
|
||||
|
||||
g = (backwarp_tenGrid[k] + tenFlow).permute(0, 2, 3, 1)
|
||||
return torch.nn.functional.grid_sample(
|
||||
input=tenInput,
|
||||
grid=g,
|
||||
mode="bilinear",
|
||||
padding_mode="border",
|
||||
align_corners=True,
|
||||
)
|
||||
|
||||
|
||||
class BasicConv(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
in_planes,
|
||||
out_planes,
|
||||
kernel_size,
|
||||
stride=1,
|
||||
padding=0,
|
||||
dilation=1,
|
||||
groups=1,
|
||||
relu=True,
|
||||
bn=True,
|
||||
bias=False,
|
||||
):
|
||||
super(BasicConv, self).__init__()
|
||||
self.out_channels = out_planes
|
||||
self.conv = nn.Conv2d(
|
||||
in_planes,
|
||||
out_planes,
|
||||
kernel_size=kernel_size,
|
||||
stride=stride,
|
||||
padding=padding,
|
||||
dilation=dilation,
|
||||
groups=groups,
|
||||
bias=bias,
|
||||
)
|
||||
self.bn = (
|
||||
nn.BatchNorm2d(out_planes, eps=1e-5, momentum=0.01, affine=True)
|
||||
if bn
|
||||
else None
|
||||
)
|
||||
self.relu = nn.ReLU() if relu else None
|
||||
|
||||
def forward(self, x):
|
||||
x = self.conv(x)
|
||||
if self.bn is not None:
|
||||
x = self.bn(x)
|
||||
if self.relu is not None:
|
||||
x = self.relu(x)
|
||||
return x
|
||||
|
||||
|
||||
class Flatten(nn.Module):
|
||||
def forward(self, x):
|
||||
return x.view(x.size(0), -1)
|
||||
|
||||
|
||||
class ChannelGate(nn.Module):
|
||||
def __init__(self, gate_channels, reduction_ratio=16, pool_types=["avg", "max"]):
|
||||
super(ChannelGate, self).__init__()
|
||||
self.gate_channels = gate_channels
|
||||
self.mlp = nn.Sequential(
|
||||
Flatten(),
|
||||
nn.Linear(gate_channels, gate_channels // reduction_ratio),
|
||||
nn.ReLU(),
|
||||
nn.Linear(gate_channels // reduction_ratio, gate_channels),
|
||||
)
|
||||
self.pool_types = pool_types
|
||||
|
||||
def forward(self, x):
|
||||
channel_att_sum = None
|
||||
for pool_type in self.pool_types:
|
||||
if pool_type == "avg":
|
||||
avg_pool = F.avg_pool2d(
|
||||
x, (x.size(2), x.size(3)), stride=(x.size(2), x.size(3))
|
||||
)
|
||||
channel_att_raw = self.mlp(avg_pool)
|
||||
elif pool_type == "max":
|
||||
max_pool = F.max_pool2d(
|
||||
x, (x.size(2), x.size(3)), stride=(x.size(2), x.size(3))
|
||||
)
|
||||
channel_att_raw = self.mlp(max_pool)
|
||||
elif pool_type == "lp":
|
||||
lp_pool = F.lp_pool2d(
|
||||
x, 2, (x.size(2), x.size(3)), stride=(x.size(2), x.size(3))
|
||||
)
|
||||
channel_att_raw = self.mlp(lp_pool)
|
||||
elif pool_type == "lse":
|
||||
# LSE pool only
|
||||
lse_pool = logsumexp_2d(x)
|
||||
channel_att_raw = self.mlp(lse_pool)
|
||||
|
||||
if channel_att_sum is None:
|
||||
channel_att_sum = channel_att_raw
|
||||
else:
|
||||
channel_att_sum = channel_att_sum + channel_att_raw
|
||||
|
||||
scale = F.sigmoid(channel_att_sum).unsqueeze(2).unsqueeze(3).expand_as(x)
|
||||
return x * scale
|
||||
|
||||
|
||||
def logsumexp_2d(tensor):
|
||||
tensor_flatten = tensor.view(tensor.size(0), tensor.size(1), -1)
|
||||
s, _ = torch.max(tensor_flatten, dim=2, keepdim=True)
|
||||
outputs = s + (tensor_flatten - s).exp().sum(dim=2, keepdim=True).log()
|
||||
return outputs
|
||||
|
||||
|
||||
class ChannelPool(nn.Module):
|
||||
def forward(self, x):
|
||||
return torch.cat(
|
||||
(torch.max(x, 1)[0].unsqueeze(1), torch.mean(x, 1).unsqueeze(1)), dim=1
|
||||
)
|
||||
|
||||
|
||||
class SpatialGate(nn.Module):
|
||||
def __init__(self):
|
||||
super(SpatialGate, self).__init__()
|
||||
kernel_size = 7
|
||||
self.compress = ChannelPool()
|
||||
self.spatial = BasicConv(
|
||||
2, 1, kernel_size, stride=1, padding=(kernel_size - 1) // 2, relu=False
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
x_compress = self.compress(x)
|
||||
x_out = self.spatial(x_compress)
|
||||
scale = F.sigmoid(x_out) # broadcasting
|
||||
return x * scale
|
||||
|
||||
|
||||
class CBAM(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
gate_channels,
|
||||
reduction_ratio=16,
|
||||
pool_types=["avg", "max"],
|
||||
no_spatial=False,
|
||||
):
|
||||
super(CBAM, self).__init__()
|
||||
self.ChannelGate = ChannelGate(gate_channels, reduction_ratio, pool_types)
|
||||
self.no_spatial = no_spatial
|
||||
if not no_spatial:
|
||||
self.SpatialGate = SpatialGate()
|
||||
|
||||
def forward(self, x):
|
||||
x_out = self.ChannelGate(x)
|
||||
if not self.no_spatial:
|
||||
x_out = self.SpatialGate(x_out)
|
||||
return x_out
|
||||
|
||||
|
||||
def conv(in_planes, out_planes, kernel_size=3, stride=1, padding=1, dilation=1):
|
||||
return nn.Sequential(
|
||||
nn.Conv2d(
|
||||
in_planes,
|
||||
out_planes,
|
||||
kernel_size=kernel_size,
|
||||
stride=stride,
|
||||
padding=padding,
|
||||
dilation=dilation,
|
||||
bias=True,
|
||||
),
|
||||
nn.PReLU(out_planes),
|
||||
)
|
||||
|
||||
|
||||
class UNetConv(nn.Module):
|
||||
def __init__(self, in_planes, out_planes, att=True):
|
||||
super(UNetConv, self).__init__()
|
||||
self.conv1 = conv(in_planes, out_planes, 3, 2, 1)
|
||||
self.conv2 = conv(out_planes, out_planes, 3, 1, 1)
|
||||
|
||||
if att:
|
||||
self.cbam = CBAM(out_planes, 16) # 这一步导致了通道数最低为128
|
||||
else:
|
||||
self.cbam = None
|
||||
|
||||
def forward(self, x):
|
||||
x = self.conv1(x)
|
||||
x = self.conv2(x)
|
||||
if self.cbam is not None:
|
||||
x = self.cbam(x)
|
||||
return x
|
||||
|
||||
|
||||
class UpConv(nn.Module):
|
||||
def __init__(self, in_planes, out_planes, att=True):
|
||||
super(UpConv, self).__init__()
|
||||
self.deconv = nn.Sequential(
|
||||
nn.ConvTranspose2d(in_planes, in_planes // 2, 4, 2, 1),
|
||||
nn.PReLU(in_planes // 2),
|
||||
)
|
||||
|
||||
# 也许不需要这么卷积,我不确定
|
||||
self.conv1 = conv(in_planes, in_planes // 2, 3, 1, 1)
|
||||
self.conv2 = conv(in_planes // 2, out_planes, 3, 1, 1)
|
||||
|
||||
if att:
|
||||
self.cbam = CBAM(out_planes, 16)
|
||||
else:
|
||||
self.cbam = None
|
||||
|
||||
def forward(self, x1, x2):
|
||||
x1 = self.deconv(x1)
|
||||
y = self.conv1(torch.cat((x1, x2), 1))
|
||||
y = self.conv2(y)
|
||||
if self.cbam is not None:
|
||||
y = self.cbam(y)
|
||||
return y
|
||||
|
||||
|
||||
class FeatureNet(nn.Module):
|
||||
def __init__(self, in_planes, out_planes):
|
||||
super(FeatureNet, self).__init__()
|
||||
# 处理IFBlock0时通道数问题
|
||||
self.conv0 = conv(7, in_planes, 1, 1, 0)
|
||||
|
||||
self.conv1 = UNetConv(in_planes, out_planes // 8, att=False)
|
||||
self.conv2 = UNetConv(out_planes // 8, out_planes // 4, att=True)
|
||||
self.conv3 = UNetConv(out_planes // 4, out_planes // 2, att=True)
|
||||
self.conv4 = UNetConv(out_planes // 2, out_planes, att=True)
|
||||
self.conv5 = UNetConv(out_planes, 2 * out_planes, att=True)
|
||||
|
||||
self.deconv5 = UpConv(2 * out_planes, out_planes, att=True)
|
||||
self.deconv4 = UpConv(out_planes, out_planes // 2, att=False)
|
||||
self.deconv3 = UpConv(out_planes // 2, out_planes // 4, att=False)
|
||||
|
||||
def forward(self, x, level=0):
|
||||
if x.shape[1] != 17:
|
||||
x = self.conv0(x)
|
||||
x2 = self.conv1(x)
|
||||
x4 = self.conv2(x2)
|
||||
x8 = self.conv3(x4)
|
||||
x16 = self.conv4(x8)
|
||||
x32 = self.conv5(x16)
|
||||
y = self.deconv5(x32, x16) # 匹配IFBlock0通道和尺寸
|
||||
|
||||
# “早退机制”以期待用同一个UNet提取特征,不确定是否对训练产生影响
|
||||
if level != 0:
|
||||
y = self.deconv4(y, x8) # 匹配IFBlock1通道和尺寸
|
||||
if level == 2:
|
||||
y = self.deconv3(y, x4) # 匹配IFBlock2通道和尺寸
|
||||
return y
|
||||
|
||||
|
||||
class IFBlock(nn.Module):
|
||||
def __init__(self, c=64, level=0):
|
||||
super(IFBlock, self).__init__()
|
||||
self.convblock = nn.Sequential(
|
||||
conv(c, c),
|
||||
conv(c, c),
|
||||
conv(c, c),
|
||||
conv(c, c),
|
||||
conv(c, c),
|
||||
conv(c, c),
|
||||
)
|
||||
self.flowconv = nn.Conv2d(c, 4, 3, 1, 1)
|
||||
self.maskconvx16 = nn.Conv2d(c, 16 * 16 * 9, 1, 1, 0)
|
||||
self.maskconvx8 = nn.Conv2d(c, 8 * 8 * 9, 1, 1, 0)
|
||||
self.maskconvx4 = nn.Conv2d(c, 4 * 4 * 9, 1, 1, 0)
|
||||
|
||||
self.level = level
|
||||
assert self.level in [4, 8, 16], "Bitch"
|
||||
|
||||
def mask_conv(self, x):
|
||||
if self.level == 4:
|
||||
return self.maskconvx4(x)
|
||||
if self.level == 8:
|
||||
return self.maskconvx8(x)
|
||||
if self.level == 16:
|
||||
return self.maskconvx16(x)
|
||||
|
||||
def upsample_flow(self, flow, mask):
|
||||
# 俺寻思俺懂了
|
||||
N, _, H, W = flow.shape
|
||||
mask = mask.view(N, 1, 9, self.level, self.level, H, W)
|
||||
mask = torch.softmax(mask, dim=2)
|
||||
|
||||
up_flow = F.unfold(self.level * flow, [3, 3], padding=1)
|
||||
up_flow = up_flow.view(N, 4, 9, 1, 1, H, W)
|
||||
|
||||
up_flow = torch.sum(mask * up_flow, dim=2)
|
||||
up_flow = up_flow.permute(0, 1, 4, 2, 5, 3)
|
||||
return up_flow.reshape(N, 4, self.level * H, self.level * W)
|
||||
|
||||
def forward(self, x, scale):
|
||||
x = self.convblock(x) + x # 类似ResNet的f(x) + x
|
||||
tmp = self.flowconv(x)
|
||||
up_mask = self.mask_conv(x)
|
||||
flow_up = self.upsample_flow(tmp, up_mask)
|
||||
flow = (
|
||||
F.interpolate(
|
||||
flow_up, scale_factor=scale, mode="bilinear", align_corners=False
|
||||
)
|
||||
* scale
|
||||
)
|
||||
return flow
|
||||
|
||||
|
||||
class IFUNet(nn.Module):
|
||||
def __init__(self):
|
||||
super(IFUNet, self).__init__()
|
||||
# block0通道数必须为128的整倍数
|
||||
self.fmap = FeatureNet(in_planes=17, out_planes=256)
|
||||
self.block0 = IFBlock(c=256, level=16)
|
||||
self.block1 = IFBlock(c=128, level=8)
|
||||
self.block2 = IFBlock(c=64, level=4)
|
||||
|
||||
def forward(self, x, scale=1.0, timestep=0.5, ensemble=True):
|
||||
channel = x.shape[1] // 2
|
||||
img0 = x[:, :channel]
|
||||
img1 = x[:, channel:]
|
||||
if not torch.is_tensor(timestep):
|
||||
timestep = (x[:, :1].clone() * 0 + 1) * timestep
|
||||
else:
|
||||
timestep = timestep.repeat(1, 1, img0.shape[2], img0.shape[3])
|
||||
warped_img0 = img0
|
||||
warped_img1 = img1
|
||||
flow = None
|
||||
block = [self.block0, self.block1, self.block2]
|
||||
for i in range(3):
|
||||
if flow != None:
|
||||
x = torch.cat((img0, img1, timestep, warped_img0, warped_img1), 1)
|
||||
flowtmp = flow
|
||||
if scale != 1:
|
||||
x = F.interpolate(
|
||||
x, scale_factor=scale, mode="bilinear", align_corners=False
|
||||
)
|
||||
flowtmp = (
|
||||
F.interpolate(
|
||||
flow,
|
||||
scale_factor=scale,
|
||||
mode="bilinear",
|
||||
align_corners=False,
|
||||
)
|
||||
* scale
|
||||
)
|
||||
x = torch.cat((x, flowtmp), 1)
|
||||
# 期待UNet能提取到特征,不再需要ensemble
|
||||
Fmap = self.fmap(x, level=i)
|
||||
flow_d = block[i](Fmap, scale=1.0 / scale)
|
||||
flow = flow + flow_d
|
||||
|
||||
if ensemble:
|
||||
x = torch.cat(
|
||||
(img1, img0, 1 - timestep, warped_img0, warped_img1), 1
|
||||
)
|
||||
flowtmp = flow
|
||||
if scale != 1:
|
||||
x = F.interpolate(
|
||||
x, scale_factor=scale, mode="bilinear", align_corners=False
|
||||
)
|
||||
flowtmp = (
|
||||
F.interpolate(
|
||||
flow,
|
||||
scale_factor=scale,
|
||||
mode="bilinear",
|
||||
align_corners=False,
|
||||
)
|
||||
* scale
|
||||
)
|
||||
x = torch.cat((x, flowtmp), 1)
|
||||
# 期待UNet能提取到特征,不再需要ensemble
|
||||
Fmap = self.fmap(x, level=i)
|
||||
flow_d = block[i](Fmap, scale=1.0 / scale)
|
||||
flow2 = flow + flow_d
|
||||
flow = (flow + flow2) / 2
|
||||
else:
|
||||
x = torch.cat((img0, img1, timestep), 1)
|
||||
if scale != 1:
|
||||
x = F.interpolate(
|
||||
x, scale_factor=scale, mode="bilinear", align_corners=False
|
||||
)
|
||||
Fmap = self.fmap(x, level=i)
|
||||
flow = block[i](Fmap, scale=1.0 / scale)
|
||||
|
||||
if ensemble:
|
||||
x = torch.cat((img1, img0, 1 - timestep), 1)
|
||||
if scale != 1:
|
||||
x = F.interpolate(
|
||||
x, scale_factor=scale, mode="bilinear", align_corners=False
|
||||
)
|
||||
Fmap = self.fmap(x, level=i)
|
||||
flow2 = block[i](Fmap, scale=1.0 / scale)
|
||||
flow = (flow + flow2) / 2
|
||||
|
||||
warped_img0 = warp(img0, flow[:, :2])
|
||||
warped_img1 = warp(img1, flow[:, 2:4])
|
||||
return flow, warped_img0, warped_img1
|
||||
|
||||
|
||||
class IFUNetModel(nn.Module):
|
||||
def __init__(self, local_rank=-1):
|
||||
super(IFUNetModel, self).__init__()
|
||||
self.flownet = IFUNet()
|
||||
self.fusionnet = RRDBNet()
|
||||
self.refinenet = ResynNet()
|
||||
|
||||
def forward(self, img0, img1, timestep=0.5, scale=1.0, ensemble=False):
|
||||
n, c, h, w = img0.shape
|
||||
ph = ((h - 1) // 64 + 1) * 64
|
||||
pw = ((w - 1) // 64 + 1) * 64
|
||||
padding = (0, pw - w, 0, ph - h)
|
||||
img0 = F.pad(img0, padding)
|
||||
img1 = F.pad(img1, padding)
|
||||
|
||||
imgs = torch.cat((img0, img1), 1)
|
||||
flow, warped_img0, warped_img1 = self.flownet(imgs, scale, timestep, ensemble)
|
||||
mask = self.fusionnet(img0, img1, warped_img0, warped_img1, flow)
|
||||
merged = warped_img0 * mask + warped_img1 * (1 - mask)
|
||||
merged, _ = self.refinenet(imgs, deg=merged, scale=[4, 2, 1])
|
||||
return merged[:, :, :h, :w]
|
||||
@@ -1,59 +0,0 @@
|
||||
import torch
|
||||
from torch.utils.data import DataLoader
|
||||
import pathlib
|
||||
from vfi_utils import load_file_from_github_release, preprocess_frames, postprocess_frames, generic_frame_loop, InterpolationStateList
|
||||
import typing
|
||||
from comfy.model_management import get_torch_device
|
||||
|
||||
MODEL_TYPE = pathlib.Path(__file__).parent.name
|
||||
CKPT_NAMES = ["IFUNet.pth"]
|
||||
|
||||
class IFUnet_VFI:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"ckpt_name": (CKPT_NAMES, ),
|
||||
"frames": ("IMAGE", ),
|
||||
"clear_cache_after_n_frames": ("INT", {"default": 10, "min": 1, "max": 1000}),
|
||||
"multiplier": ("INT", {"default": 2, "min": 2, "max": 1000}),
|
||||
"scale_factor": ("FLOAT", {"default": 1.0, "min": 0.1, "max": 100, "step": 0.1}),
|
||||
"ensemble": ("BOOLEAN", {"default":True})
|
||||
},
|
||||
"optional": {
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", )
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", )
|
||||
FUNCTION = "vfi"
|
||||
CATEGORY = "ComfyUI-Frame-Interpolation/VFI"
|
||||
|
||||
def vfi(
|
||||
self,
|
||||
ckpt_name: typing.AnyStr,
|
||||
frames: torch.Tensor,
|
||||
clear_cache_after_n_frames: typing.SupportsInt = 1,
|
||||
multiplier: typing.SupportsInt = 2,
|
||||
scale_factor: typing.SupportsFloat = 1.0,
|
||||
ensemble: bool = True,
|
||||
optional_interpolation_states: InterpolationStateList = None,
|
||||
**kwargs
|
||||
):
|
||||
from .IFUNet_arch import IFUNetModel
|
||||
model_path = load_file_from_github_release(MODEL_TYPE, ckpt_name)
|
||||
interpolation_model = IFUNetModel()
|
||||
interpolation_model.load_state_dict(torch.load(model_path))
|
||||
interpolation_model.eval().to(get_torch_device())
|
||||
frames = preprocess_frames(frames)
|
||||
|
||||
def return_middle_frame(frame_0, frame_1, timestep, model, scale_factor, ensemble):
|
||||
return model(frame_0, frame_1, timestep=timestep, scale=scale_factor, ensemble=ensemble)
|
||||
|
||||
args = [interpolation_model, scale_factor, ensemble]
|
||||
out = postprocess_frames(
|
||||
generic_frame_loop(frames, clear_cache_after_n_frames, multiplier, return_middle_frame, *args,
|
||||
interpolation_states=optional_interpolation_states, dtype=torch.float32)
|
||||
)
|
||||
return (out,)
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import pathlib
|
||||
import torch
|
||||
from torch.utils.data import DataLoader
|
||||
import pathlib
|
||||
from vfi_utils import load_file_from_github_release, preprocess_frames, postprocess_frames
|
||||
import typing
|
||||
from comfy.model_management import get_torch_device
|
||||
from vfi_utils import InterpolationStateList, generic_frame_loop
|
||||
|
||||
MODEL_TYPE = pathlib.Path(__file__).parent.name
|
||||
CKPT_NAMES = ["M2M.pth"]
|
||||
|
||||
|
||||
class M2M_VFI:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"ckpt_name": (CKPT_NAMES, ),
|
||||
"frames": ("IMAGE", ),
|
||||
"clear_cache_after_n_frames": ("INT", {"default": 10, "min": 1, "max": 1000}),
|
||||
"multiplier": ("INT", {"default": 2, "min": 2, "max": 1000}),
|
||||
},
|
||||
"optional": {
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", )
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", )
|
||||
FUNCTION = "vfi"
|
||||
CATEGORY = "ComfyUI-Frame-Interpolation/VFI"
|
||||
|
||||
def vfi(
|
||||
self,
|
||||
ckpt_name: typing.AnyStr,
|
||||
frames: torch.Tensor,
|
||||
clear_cache_after_n_frames: typing.SupportsInt = 1,
|
||||
multiplier: typing.SupportsInt = 2,
|
||||
optional_interpolation_states: InterpolationStateList = None,
|
||||
**kwargs
|
||||
):
|
||||
from .M2M_arch import M2M_PWC
|
||||
model_path = load_file_from_github_release(MODEL_TYPE, ckpt_name)
|
||||
interpolation_model = M2M_PWC()
|
||||
interpolation_model.load_state_dict(torch.load(model_path))
|
||||
interpolation_model.eval().to(get_torch_device())
|
||||
frames = preprocess_frames(frames)
|
||||
|
||||
def return_middle_frame(frame_0, frame_1, int_timestep, model):
|
||||
tenSteps = [
|
||||
torch.FloatTensor([int_timestep] * len(frame_0)).view(len(frame_0), 1, 1, 1).to(get_torch_device())
|
||||
]
|
||||
return model(frame_0, frame_1, tenSteps)[0]
|
||||
|
||||
args = [interpolation_model]
|
||||
out = postprocess_frames(
|
||||
generic_frame_loop(frames, clear_cache_after_n_frames, multiplier, return_middle_frame, *args,
|
||||
interpolation_states=optional_interpolation_states, dtype=torch.float32)
|
||||
)
|
||||
return (out,)
|
||||
@@ -1,22 +0,0 @@
|
||||
import torch.multiprocessing as mp
|
||||
|
||||
if mp.current_process().name == "MainProcess":
|
||||
import yaml
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
config_path = Path(Path(__file__).parent.parent.parent.resolve(), "config.yaml")
|
||||
|
||||
if os.path.exists(config_path):
|
||||
config = yaml.load(open(config_path, "r"), Loader=yaml.FullLoader)
|
||||
ops_backend = config["ops_backend"]
|
||||
else:
|
||||
ops_backend = "taichi"
|
||||
|
||||
assert ops_backend in ["taichi", "cupy"]
|
||||
|
||||
if ops_backend == "taichi":
|
||||
from .taichi_ops import softsplat, ModuleSoftsplat, FunctionSoftsplat, softsplat_func, costvol_func, sepconv_func, init, batch_edt, FunctionAdaCoF, ModuleCorrelation, FunctionCorrelation, _FunctionCorrelation
|
||||
else:
|
||||
from .cupy_ops import softsplat, ModuleSoftsplat, FunctionSoftsplat, softsplat_func, costvol_func, sepconv_func, init, batch_edt, FunctionAdaCoF, ModuleCorrelation, FunctionCorrelation, _FunctionCorrelation
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
from .costvol import *
|
||||
from .sepconv import *
|
||||
from .softsplat import *
|
||||
from .adacof import *
|
||||
from .correlation import *
|
||||
from comfy.model_management import is_nvidia, get_torch_device_name, get_torch_device
|
||||
|
||||
def init():
|
||||
if not is_nvidia():
|
||||
raise NotImplementedError(f"CuPy ops backend only support CUDA device but found {get_torch_device_name(get_torch_device())} instead. Try Taichi ops backend by editing config.yaml")
|
||||
return
|
||||
@@ -1,491 +0,0 @@
|
||||
import torch
|
||||
from .utils import cuda_kernel, cuda_launch, cuda_int32
|
||||
import math
|
||||
|
||||
kernel_AdaCoF_updateOutput = """
|
||||
extern "C" __global__ void kernel_AdaCoF_updateOutput(
|
||||
const int n,
|
||||
const float* input,
|
||||
const float* weight,
|
||||
const float* offset_i,
|
||||
const float* offset_j,
|
||||
float* output
|
||||
) { for (int intIndex = (blockIdx.x * blockDim.x) + threadIdx.x; intIndex < n; intIndex += blockDim.x * gridDim.x) {
|
||||
float dblOutput = 0.0;
|
||||
|
||||
const int intSample = ( intIndex / SIZE_3(output) / SIZE_2(output) / SIZE_1(output) ) % SIZE_0(output);
|
||||
const int c = ( intIndex / SIZE_3(output) / SIZE_2(output) ) % SIZE_1(output);
|
||||
const int i = ( intIndex / SIZE_3(output) ) % SIZE_2(output);
|
||||
const int j = ( intIndex ) % SIZE_3(output);
|
||||
|
||||
for (int k = 0; k < F_SIZE; k += 1) {
|
||||
for (int l = 0; l < F_SIZE; l += 1) {
|
||||
float w = VALUE_4(weight, intSample, k*F_SIZE+l, i, j);
|
||||
float alpha = VALUE_4(offset_i, intSample, k*F_SIZE+l, i, j);
|
||||
float beta = VALUE_4(offset_j, intSample, k*F_SIZE+l, i, j);
|
||||
int A = (int) alpha;
|
||||
int B = (int) beta;
|
||||
|
||||
int i_k_A = i+k*DILATION+A;
|
||||
if(i_k_A < 0)
|
||||
i_k_A = 0;
|
||||
if(i_k_A > SIZE_2(input) - 1)
|
||||
i_k_A = SIZE_2(input) - 1;
|
||||
|
||||
int j_l_B = j+l*DILATION+B;
|
||||
if(j_l_B < 0)
|
||||
j_l_B = 0;
|
||||
if(j_l_B > SIZE_3(input) - 1)
|
||||
j_l_B = SIZE_3(input) - 1;
|
||||
|
||||
int i_k_A_1 = i+k*DILATION+A+1;
|
||||
if(i_k_A_1 < 0)
|
||||
i_k_A_1 = 0;
|
||||
if(i_k_A_1 > SIZE_2(input) - 1)
|
||||
i_k_A_1 = SIZE_2(input) - 1;
|
||||
|
||||
int j_l_B_1 = j+l*DILATION+B+1;
|
||||
if(j_l_B_1 < 0)
|
||||
j_l_B_1 = 0;
|
||||
if(j_l_B_1 > SIZE_3(input) - 1)
|
||||
j_l_B_1 = SIZE_3(input) - 1;
|
||||
|
||||
dblOutput += w * (
|
||||
VALUE_4(input, intSample, c, i_k_A, j_l_B)*(1-(alpha-(float)A))*(1-(beta-(float)B)) +
|
||||
VALUE_4(input, intSample, c, i_k_A_1, j_l_B)*(alpha-(float)A)*(1-(beta-(float)B)) +
|
||||
VALUE_4(input, intSample, c, i_k_A, j_l_B_1)*(1-(alpha-(float)A))*(beta-(float)B) +
|
||||
VALUE_4(input, intSample, c, i_k_A_1, j_l_B_1)*(alpha-(float)A)*(beta-(float)B)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
output[intIndex] = dblOutput;
|
||||
} }
|
||||
"""
|
||||
|
||||
kernel_AdaCoF_updateGradWeight = """
|
||||
extern "C" __global__ void kernel_AdaCoF_updateGradWeight(
|
||||
const int n,
|
||||
const float* gradLoss,
|
||||
const float* input,
|
||||
const float* offset_i,
|
||||
const float* offset_j,
|
||||
float* gradWeight
|
||||
) { for (int intIndex = (blockIdx.x * blockDim.x) + threadIdx.x; intIndex < n; intIndex += blockDim.x * gridDim.x) {
|
||||
float floatOutput = 0.0;
|
||||
|
||||
const int intSample = ( intIndex / SIZE_3(gradWeight) / SIZE_2(gradWeight) / SIZE_1(gradWeight) ) % SIZE_0(gradWeight);
|
||||
const int intDepth = ( intIndex / SIZE_3(gradWeight) / SIZE_2(gradWeight) ) % SIZE_1(gradWeight);
|
||||
const int i = ( intIndex / SIZE_3(gradWeight) ) % SIZE_2(gradWeight);
|
||||
const int j = ( intIndex ) % SIZE_3(gradWeight);
|
||||
|
||||
int k = intDepth / F_SIZE;
|
||||
int l = intDepth % F_SIZE;
|
||||
|
||||
for (int c = 0; c < 3; c++)
|
||||
{
|
||||
float delta = VALUE_4(gradLoss, intSample, c, i, j);
|
||||
float alpha = VALUE_4(offset_i, intSample, k*F_SIZE+l, i, j);
|
||||
float beta = VALUE_4(offset_j, intSample, k*F_SIZE+l, i, j);
|
||||
int A = (int) alpha;
|
||||
int B = (int) beta;
|
||||
|
||||
int i_k_A = i+k*DILATION+A;
|
||||
if(i_k_A < 0)
|
||||
i_k_A = 0;
|
||||
if(i_k_A > SIZE_2(input) - 1)
|
||||
i_k_A = SIZE_2(input) - 1;
|
||||
|
||||
int j_l_B = j+l*DILATION+B;
|
||||
if(j_l_B < 0)
|
||||
j_l_B = 0;
|
||||
if(j_l_B > SIZE_3(input) - 1)
|
||||
j_l_B = SIZE_3(input) - 1;
|
||||
|
||||
int i_k_A_1 = i+k*DILATION+A+1;
|
||||
if(i_k_A_1 < 0)
|
||||
i_k_A_1 = 0;
|
||||
if(i_k_A_1 > SIZE_2(input) - 1)
|
||||
i_k_A_1 = SIZE_2(input) - 1;
|
||||
|
||||
int j_l_B_1 = j+l*DILATION+B+1;
|
||||
if(j_l_B_1 < 0)
|
||||
j_l_B_1 = 0;
|
||||
if(j_l_B_1 > SIZE_3(input) - 1)
|
||||
j_l_B_1 = SIZE_3(input) - 1;
|
||||
|
||||
floatOutput += delta * (
|
||||
VALUE_4(input, intSample, c, i_k_A, j_l_B)*(1-(alpha-(float)A))*(1-(beta-(float)B)) +
|
||||
VALUE_4(input, intSample, c, i_k_A_1, j_l_B)*(alpha-(float)A)*(1-(beta-(float)B)) +
|
||||
VALUE_4(input, intSample, c, i_k_A, j_l_B_1)*(1-(alpha-(float)A))*(beta-(float)B) +
|
||||
VALUE_4(input, intSample, c, i_k_A_1, j_l_B_1)*(alpha-(float)A)*(beta-(float)B)
|
||||
);
|
||||
}
|
||||
|
||||
gradWeight[intIndex] = floatOutput;
|
||||
} }
|
||||
"""
|
||||
|
||||
kernel_AdaCoF_updateGradAlpha = """
|
||||
extern "C" __global__ void kernel_AdaCoF_updateGradAlpha(
|
||||
const int n,
|
||||
const float* gradLoss,
|
||||
const float* input,
|
||||
const float* weight,
|
||||
const float* offset_i,
|
||||
const float* offset_j,
|
||||
float* gradOffset_i
|
||||
) { for (int intIndex = (blockIdx.x * blockDim.x) + threadIdx.x; intIndex < n; intIndex += blockDim.x * gridDim.x) {
|
||||
float floatOutput = 0.0;
|
||||
|
||||
const int intSample = ( intIndex / SIZE_3(gradOffset_i) / SIZE_2(gradOffset_i) / SIZE_1(gradOffset_i) ) % SIZE_0(gradOffset_i);
|
||||
const int intDepth = ( intIndex / SIZE_3(gradOffset_i) / SIZE_2(gradOffset_i) ) % SIZE_1(gradOffset_i);
|
||||
const int i = ( intIndex / SIZE_3(gradOffset_i) ) % SIZE_2(gradOffset_i);
|
||||
const int j = ( intIndex ) % SIZE_3(gradOffset_i);
|
||||
|
||||
int k = intDepth / F_SIZE;
|
||||
int l = intDepth % F_SIZE;
|
||||
|
||||
for (int c = 0; c < 3; c++)
|
||||
{
|
||||
float delta = VALUE_4(gradLoss, intSample, c, i, j);
|
||||
float w = VALUE_4(weight, intSample, k*F_SIZE+l, i, j);
|
||||
float alpha = VALUE_4(offset_i, intSample, k*F_SIZE+l, i, j);
|
||||
float beta = VALUE_4(offset_j, intSample, k*F_SIZE+l, i, j);
|
||||
int A = (int) alpha;
|
||||
int B = (int) beta;
|
||||
|
||||
int i_k_A = i+k*DILATION+A;
|
||||
if(i_k_A < 0)
|
||||
i_k_A = 0;
|
||||
if(i_k_A > SIZE_2(input) - 1)
|
||||
i_k_A = SIZE_2(input) - 1;
|
||||
|
||||
int j_l_B = j+l*DILATION+B;
|
||||
if(j_l_B < 0)
|
||||
j_l_B = 0;
|
||||
if(j_l_B > SIZE_3(input) - 1)
|
||||
j_l_B = SIZE_3(input) - 1;
|
||||
|
||||
int i_k_A_1 = i+k*DILATION+A+1;
|
||||
if(i_k_A_1 < 0)
|
||||
i_k_A_1 = 0;
|
||||
if(i_k_A_1 > SIZE_2(input) - 1)
|
||||
i_k_A_1 = SIZE_2(input) - 1;
|
||||
|
||||
int j_l_B_1 = j+l*DILATION+B+1;
|
||||
if(j_l_B_1 < 0)
|
||||
j_l_B_1 = 0;
|
||||
if(j_l_B_1 > SIZE_3(input) - 1)
|
||||
j_l_B_1 = SIZE_3(input) - 1;
|
||||
|
||||
floatOutput += delta * w * (
|
||||
- VALUE_4(input, intSample, c, i_k_A, j_l_B)*(1-(beta-(float)B)) +
|
||||
VALUE_4(input, intSample, c, i_k_A_1, j_l_B)*(1-(beta-(float)B)) -
|
||||
VALUE_4(input, intSample, c, i_k_A, j_l_B_1)*(beta-(float)B) +
|
||||
VALUE_4(input, intSample, c, i_k_A_1, j_l_B_1)*(beta-(float)B)
|
||||
);
|
||||
}
|
||||
|
||||
gradOffset_i[intIndex] = floatOutput;
|
||||
} }
|
||||
"""
|
||||
|
||||
kernel_AdaCoF_updateGradBeta = """
|
||||
extern "C" __global__ void kernel_AdaCoF_updateGradBeta(
|
||||
const int n,
|
||||
const float* gradLoss,
|
||||
const float* input,
|
||||
const float* weight,
|
||||
const float* offset_i,
|
||||
const float* offset_j,
|
||||
float* gradOffset_j
|
||||
) { for (int intIndex = (blockIdx.x * blockDim.x) + threadIdx.x; intIndex < n; intIndex += blockDim.x * gridDim.x) {
|
||||
float floatOutput = 0.0;
|
||||
|
||||
const int intSample = ( intIndex / SIZE_3(gradOffset_j) / SIZE_2(gradOffset_j) / SIZE_1(gradOffset_j) ) % SIZE_0(gradOffset_j);
|
||||
const int intDepth = ( intIndex / SIZE_3(gradOffset_j) / SIZE_2(gradOffset_j) ) % SIZE_1(gradOffset_j);
|
||||
const int i = ( intIndex / SIZE_3(gradOffset_j) ) % SIZE_2(gradOffset_j);
|
||||
const int j = ( intIndex ) % SIZE_3(gradOffset_j);
|
||||
|
||||
int k = intDepth / F_SIZE;
|
||||
int l = intDepth % F_SIZE;
|
||||
|
||||
for (int c = 0; c < 3; c++)
|
||||
{
|
||||
float delta = VALUE_4(gradLoss, intSample, c, i, j);
|
||||
float w = VALUE_4(weight, intSample, k*F_SIZE+l, i, j);
|
||||
float alpha = VALUE_4(offset_i, intSample, k*F_SIZE+l, i, j);
|
||||
float beta = VALUE_4(offset_j, intSample, k*F_SIZE+l, i, j);
|
||||
int A = (int) alpha;
|
||||
int B = (int) beta;
|
||||
|
||||
int i_k_A = i+k*DILATION+A;
|
||||
if(i_k_A < 0)
|
||||
i_k_A = 0;
|
||||
if(i_k_A > SIZE_2(input) - 1)
|
||||
i_k_A = SIZE_2(input) - 1;
|
||||
|
||||
int j_l_B = j+l*DILATION+B;
|
||||
if(j_l_B < 0)
|
||||
j_l_B = 0;
|
||||
if(j_l_B > SIZE_3(input) - 1)
|
||||
j_l_B = SIZE_3(input) - 1;
|
||||
|
||||
int i_k_A_1 = i+k*DILATION+A+1;
|
||||
if(i_k_A_1 < 0)
|
||||
i_k_A_1 = 0;
|
||||
if(i_k_A_1 > SIZE_2(input) - 1)
|
||||
i_k_A_1 = SIZE_2(input) - 1;
|
||||
|
||||
int j_l_B_1 = j+l*DILATION+B+1;
|
||||
if(j_l_B_1 < 0)
|
||||
j_l_B_1 = 0;
|
||||
if(j_l_B_1 > SIZE_3(input) - 1)
|
||||
j_l_B_1 = SIZE_3(input) - 1;
|
||||
|
||||
floatOutput += delta * w * (
|
||||
- VALUE_4(input, intSample, c, i_k_A, j_l_B)*(1-(alpha-(float)A)) -
|
||||
VALUE_4(input, intSample, c, i_k_A_1, j_l_B)*(alpha-(float)A) +
|
||||
VALUE_4(input, intSample, c, i_k_A, j_l_B_1)*(1-(alpha-(float)A)) +
|
||||
VALUE_4(input, intSample, c, i_k_A_1, j_l_B_1)*(alpha-(float)A)
|
||||
);
|
||||
}
|
||||
|
||||
gradOffset_j[intIndex] = floatOutput;
|
||||
} }
|
||||
"""
|
||||
|
||||
class FunctionAdaCoF(torch.autograd.Function):
|
||||
# end
|
||||
@staticmethod
|
||||
def forward(ctx, input, weight, offset_i, offset_j, dilation):
|
||||
ctx.save_for_backward(input, weight, offset_i, offset_j)
|
||||
ctx.dilation = dilation
|
||||
|
||||
intSample = input.size(0)
|
||||
intInputDepth = input.size(1)
|
||||
intInputHeight = input.size(2)
|
||||
intInputWidth = input.size(3)
|
||||
intFilterSize = int(math.sqrt(weight.size(1)))
|
||||
intOutputHeight = weight.size(2)
|
||||
intOutputWidth = weight.size(3)
|
||||
|
||||
assert (
|
||||
intInputHeight - ((intFilterSize - 1) * dilation + 1) == intOutputHeight - 1
|
||||
)
|
||||
assert (
|
||||
intInputWidth - ((intFilterSize - 1) * dilation + 1) == intOutputWidth - 1
|
||||
)
|
||||
|
||||
assert input.is_contiguous() == True
|
||||
assert weight.is_contiguous() == True
|
||||
assert offset_i.is_contiguous() == True
|
||||
assert offset_j.is_contiguous() == True
|
||||
|
||||
output = input.new_zeros(
|
||||
intSample, intInputDepth, intOutputHeight, intOutputWidth
|
||||
)
|
||||
|
||||
if input.is_cuda == True:
|
||||
|
||||
class Stream:
|
||||
ptr = torch.cuda.current_stream().cuda_stream
|
||||
|
||||
# end
|
||||
|
||||
n = output.nelement()
|
||||
cuda_launch(
|
||||
cuda_kernel(
|
||||
"kernel_AdaCoF_updateOutput",
|
||||
kernel_AdaCoF_updateOutput,
|
||||
{
|
||||
"input": input,
|
||||
"weight": weight,
|
||||
"offset_i": offset_i,
|
||||
"offset_j": offset_j,
|
||||
"output": output,
|
||||
},
|
||||
F_SIZE=str(intFilterSize),
|
||||
DILATION=str(dilation)
|
||||
),
|
||||
)(
|
||||
grid=tuple([int((n + 512 - 1) / 512), 1, 1]),
|
||||
block=tuple([512, 1, 1]),
|
||||
args=[
|
||||
n,
|
||||
input.data_ptr(),
|
||||
weight.data_ptr(),
|
||||
offset_i.data_ptr(),
|
||||
offset_j.data_ptr(),
|
||||
output.data_ptr(),
|
||||
],
|
||||
stream=Stream,
|
||||
)
|
||||
|
||||
elif input.is_cuda == False:
|
||||
raise NotImplementedError()
|
||||
|
||||
# end
|
||||
|
||||
return output
|
||||
|
||||
# end
|
||||
@staticmethod
|
||||
def backward(ctx, gradOutput):
|
||||
input, weight, offset_i, offset_j = ctx.saved_tensors
|
||||
dilation = ctx.dilation
|
||||
|
||||
intSample = input.size(0)
|
||||
intInputDepth = input.size(1)
|
||||
intInputHeight = input.size(2)
|
||||
intInputWidth = input.size(3)
|
||||
intFilterSize = int(math.sqrt(weight.size(1)))
|
||||
intOutputHeight = weight.size(2)
|
||||
intOutputWidth = weight.size(3)
|
||||
|
||||
assert (
|
||||
intInputHeight - ((intFilterSize - 1) * dilation + 1) == intOutputHeight - 1
|
||||
)
|
||||
assert (
|
||||
intInputWidth - ((intFilterSize - 1) * dilation + 1) == intOutputWidth - 1
|
||||
)
|
||||
|
||||
assert gradOutput.is_contiguous() == True
|
||||
|
||||
gradInput = (
|
||||
input.new_zeros(intSample, intInputDepth, intInputHeight, intInputWidth)
|
||||
if ctx.needs_input_grad[0] == True
|
||||
else None
|
||||
)
|
||||
gradWeight = (
|
||||
input.new_zeros(
|
||||
intSample, intFilterSize**2, intOutputHeight, intOutputWidth
|
||||
)
|
||||
if ctx.needs_input_grad[1] == True
|
||||
else None
|
||||
)
|
||||
gradOffset_i = (
|
||||
input.new_zeros(
|
||||
intSample, intFilterSize**2, intOutputHeight, intOutputWidth
|
||||
)
|
||||
if ctx.needs_input_grad[2] == True
|
||||
else None
|
||||
)
|
||||
gradOffset_j = (
|
||||
input.new_zeros(
|
||||
intSample, intFilterSize**2, intOutputHeight, intOutputWidth
|
||||
)
|
||||
if ctx.needs_input_grad[2] == True
|
||||
else None
|
||||
)
|
||||
|
||||
if input.is_cuda == True:
|
||||
|
||||
class Stream:
|
||||
ptr = torch.cuda.current_stream().cuda_stream
|
||||
|
||||
# end
|
||||
|
||||
# weight grad
|
||||
n_w = gradWeight.nelement()
|
||||
cuda_launch(
|
||||
cuda_kernel(
|
||||
"kernel_AdaCoF_updateGradWeight",
|
||||
kernel_AdaCoF_updateGradWeight,
|
||||
{
|
||||
"gradLoss": gradOutput,
|
||||
"input": input,
|
||||
"offset_i": offset_i,
|
||||
"offset_j": offset_j,
|
||||
"gradWeight": gradWeight,
|
||||
},
|
||||
F_SIZE=str(intFilterSize),
|
||||
DILATION=str(dilation)
|
||||
),
|
||||
)(
|
||||
grid=tuple([int((n_w + 512 - 1) / 512), 1, 1]),
|
||||
block=tuple([512, 1, 1]),
|
||||
args=[
|
||||
n_w,
|
||||
gradOutput.data_ptr(),
|
||||
input.data_ptr(),
|
||||
offset_i.data_ptr(),
|
||||
offset_j.data_ptr(),
|
||||
gradWeight.data_ptr(),
|
||||
],
|
||||
stream=Stream,
|
||||
)
|
||||
|
||||
# alpha grad
|
||||
n_i = gradOffset_i.nelement()
|
||||
cuda_launch(
|
||||
cuda_kernel(
|
||||
"kernel_AdaCoF_updateGradAlpha",
|
||||
kernel_AdaCoF_updateGradAlpha,
|
||||
{
|
||||
"gradLoss": gradOutput,
|
||||
"input": input,
|
||||
"weight": weight,
|
||||
"offset_i": offset_i,
|
||||
"offset_j": offset_j,
|
||||
"gradOffset_i": gradOffset_i,
|
||||
},
|
||||
F_SIZE=str(intFilterSize),
|
||||
DILATION=str(dilation)
|
||||
),
|
||||
)(
|
||||
grid=tuple([int((n_i + 512 - 1) / 512), 1, 1]),
|
||||
block=tuple([512, 1, 1]),
|
||||
args=[
|
||||
n_i,
|
||||
gradOutput.data_ptr(),
|
||||
input.data_ptr(),
|
||||
weight.data_ptr(),
|
||||
offset_i.data_ptr(),
|
||||
offset_j.data_ptr(),
|
||||
gradOffset_i.data_ptr(),
|
||||
],
|
||||
stream=Stream,
|
||||
)
|
||||
|
||||
# beta grad
|
||||
n_j = gradOffset_j.nelement()
|
||||
cuda_launch(
|
||||
cuda_kernel(
|
||||
"kernel_AdaCoF_updateGradBeta",
|
||||
kernel_AdaCoF_updateGradBeta,
|
||||
{
|
||||
"gradLoss": gradOutput,
|
||||
"input": input,
|
||||
"weight": weight,
|
||||
"offset_i": offset_i,
|
||||
"offset_j": offset_j,
|
||||
"gradOffset_j": gradOffset_j,
|
||||
},
|
||||
F_SIZE=str(intFilterSize),
|
||||
DILATION=str(dilation)
|
||||
),
|
||||
)(
|
||||
grid=tuple([int((n_j + 512 - 1) / 512), 1, 1]),
|
||||
block=tuple([512, 1, 1]),
|
||||
args=[
|
||||
n_j,
|
||||
gradOutput.data_ptr(),
|
||||
input.data_ptr(),
|
||||
weight.data_ptr(),
|
||||
offset_i.data_ptr(),
|
||||
offset_j.data_ptr(),
|
||||
gradOffset_j.data_ptr(),
|
||||
],
|
||||
stream=Stream,
|
||||
)
|
||||
|
||||
elif input.is_cuda == False:
|
||||
raise NotImplementedError()
|
||||
|
||||
# end
|
||||
|
||||
return gradInput, gradWeight, gradOffset_i, gradOffset_j, None
|
||||
|
||||
__all__ = ["FunctionAdaCoF"]
|
||||
@@ -1,119 +0,0 @@
|
||||
############### DISTANCE TRANSFORM ###############
|
||||
# img tensor: (bs,h,w) or (bs,1,h,w)
|
||||
# returns same shape
|
||||
# expects white lines, black whitespace
|
||||
# defaults to diameter if empty image
|
||||
from .utils import cuda_kernel, cuda_launch, cuda_int32, cuda_float32
|
||||
import torch
|
||||
|
||||
_batch_edt_kernel = (
|
||||
"kernel_dt",
|
||||
"""
|
||||
extern "C" __global__ void kernel_dt(
|
||||
const int bs,
|
||||
const int h,
|
||||
const int w,
|
||||
const float diam2,
|
||||
float* data,
|
||||
float* output
|
||||
) {
|
||||
int idx = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (idx >= bs*h*w) {
|
||||
return;
|
||||
}
|
||||
int pb = idx / (h*w);
|
||||
int pi = (idx - h*w*pb) / w;
|
||||
int pj = (idx - h*w*pb - w*pi);
|
||||
|
||||
float cost;
|
||||
float mincost = diam2;
|
||||
for (int j = 0; j < w; j++) {
|
||||
cost = data[h*w*pb + w*pi + j] + (pj-j)*(pj-j);
|
||||
if (cost < mincost) {
|
||||
mincost = cost;
|
||||
}
|
||||
}
|
||||
output[idx] = mincost;
|
||||
return;
|
||||
}
|
||||
""",
|
||||
)
|
||||
_batch_edt = None
|
||||
|
||||
|
||||
def batch_edt(img, block=1024):
|
||||
# must initialize cuda/cupy after forking
|
||||
global _batch_edt
|
||||
if _batch_edt is None:
|
||||
_batch_edt = cuda_launch(*_batch_edt_kernel)
|
||||
|
||||
# bookkeeppingg
|
||||
if len(img.shape) == 4:
|
||||
assert img.shape[1] == 1
|
||||
img = img.squeeze(1)
|
||||
expand = True
|
||||
else:
|
||||
expand = False
|
||||
bs, h, w = img.shape
|
||||
diam2 = h**2 + w**2
|
||||
odtype = img.dtype
|
||||
grid = (img.nelement() + block - 1) // block
|
||||
|
||||
# cupy implementation
|
||||
if img.is_cuda:
|
||||
# first pass, y-axis
|
||||
data = ((1 - img.type(torch.float32)) * diam2).contiguous()
|
||||
intermed = torch.zeros_like(data)
|
||||
_batch_edt(
|
||||
grid=(grid, 1, 1),
|
||||
block=(block, 1, 1), # < 1024
|
||||
args=[
|
||||
cuda_int32(bs),
|
||||
cuda_int32(h),
|
||||
cuda_int32(w),
|
||||
cuda_float32(diam2),
|
||||
data.data_ptr(),
|
||||
intermed.data_ptr(),
|
||||
],
|
||||
)
|
||||
|
||||
# second pass, x-axis
|
||||
intermed = intermed.permute(0, 2, 1).contiguous()
|
||||
out = torch.zeros_like(intermed)
|
||||
_batch_edt(
|
||||
grid=(grid, 1, 1),
|
||||
block=(block, 1, 1),
|
||||
args=[
|
||||
cuda_int32(bs),
|
||||
cuda_int32(w),
|
||||
cuda_int32(h),
|
||||
cuda_float32(diam2),
|
||||
intermed.data_ptr(),
|
||||
out.data_ptr(),
|
||||
],
|
||||
)
|
||||
ans = out.permute(0, 2, 1).sqrt()
|
||||
ans = ans.type(odtype) if odtype != ans.dtype else ans
|
||||
|
||||
# default to scipy cpu implementation
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
""" sums = img.sum(dim=(1, 2))
|
||||
ans = torch.tensor(
|
||||
np.stack(
|
||||
[
|
||||
scipy.ndimage.morphology.distance_transform_edt(i)
|
||||
if s != 0
|
||||
else np.ones_like(i) # change scipy behavior for empty image
|
||||
* np.sqrt(diam2)
|
||||
for i, s in zip(1 - img, sums)
|
||||
]
|
||||
),
|
||||
dtype=odtype,
|
||||
) """
|
||||
|
||||
if expand:
|
||||
ans = ans.unsqueeze(1)
|
||||
return ans
|
||||
|
||||
__all__ = ["batch_edt"]
|
||||
@@ -1,413 +0,0 @@
|
||||
import torch
|
||||
from .utils import cuda_kernel, cuda_launch, cuda_int32
|
||||
|
||||
kernel_Correlation_rearrange = """
|
||||
extern "C" __global__ void kernel_Correlation_rearrange(
|
||||
const int n,
|
||||
const float* input,
|
||||
float* output
|
||||
) {
|
||||
int intIndex = (blockIdx.x * blockDim.x) + threadIdx.x;
|
||||
|
||||
if (intIndex >= n) {
|
||||
return;
|
||||
}
|
||||
|
||||
int intSample = blockIdx.z;
|
||||
int intChannel = blockIdx.y;
|
||||
|
||||
float fltValue = input[(((intSample * SIZE_1(input)) + intChannel) * SIZE_2(input) * SIZE_3(input)) + intIndex];
|
||||
|
||||
__syncthreads();
|
||||
|
||||
int intPaddedY = (intIndex / SIZE_3(input)) + 4;
|
||||
int intPaddedX = (intIndex % SIZE_3(input)) + 4;
|
||||
int intRearrange = ((SIZE_3(input) + 8) * intPaddedY) + intPaddedX;
|
||||
|
||||
output[(((intSample * SIZE_1(output) * SIZE_2(output)) + intRearrange) * SIZE_1(input)) + intChannel] = fltValue;
|
||||
}
|
||||
"""
|
||||
|
||||
kernel_Correlation_updateOutput = """
|
||||
extern "C" __global__ void kernel_Correlation_updateOutput(
|
||||
const int n,
|
||||
const float* rbot0,
|
||||
const float* rbot1,
|
||||
float* top
|
||||
) {
|
||||
extern __shared__ char patch_data_char[];
|
||||
|
||||
float *patch_data = (float *)patch_data_char;
|
||||
|
||||
// First (upper left) position of kernel upper-left corner in current center position of neighborhood in image 1
|
||||
int x1 = blockIdx.x + 4;
|
||||
int y1 = blockIdx.y + 4;
|
||||
int item = blockIdx.z;
|
||||
int ch_off = threadIdx.x;
|
||||
|
||||
// Load 3D patch into shared shared memory
|
||||
for (int j = 0; j < 1; j++) { // HEIGHT
|
||||
for (int i = 0; i < 1; i++) { // WIDTH
|
||||
int ji_off = (j + i) * SIZE_3(rbot0);
|
||||
for (int ch = ch_off; ch < SIZE_3(rbot0); ch += 32) { // CHANNELS
|
||||
int idx1 = ((item * SIZE_1(rbot0) + y1+j) * SIZE_2(rbot0) + x1+i) * SIZE_3(rbot0) + ch;
|
||||
int idxPatchData = ji_off + ch;
|
||||
patch_data[idxPatchData] = rbot0[idx1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
|
||||
__shared__ float sum[32];
|
||||
|
||||
// Compute correlation
|
||||
for (int top_channel = 0; top_channel < SIZE_1(top); top_channel++) {
|
||||
sum[ch_off] = 0;
|
||||
|
||||
int s2o = top_channel % 9 - 4;
|
||||
int s2p = top_channel / 9 - 4;
|
||||
|
||||
for (int j = 0; j < 1; j++) { // HEIGHT
|
||||
for (int i = 0; i < 1; i++) { // WIDTH
|
||||
int ji_off = (j + i) * SIZE_3(rbot0);
|
||||
for (int ch = ch_off; ch < SIZE_3(rbot0); ch += 32) { // CHANNELS
|
||||
int x2 = x1 + s2o;
|
||||
int y2 = y1 + s2p;
|
||||
|
||||
int idxPatchData = ji_off + ch;
|
||||
int idx2 = ((item * SIZE_1(rbot0) + y2+j) * SIZE_2(rbot0) + x2+i) * SIZE_3(rbot0) + ch;
|
||||
|
||||
sum[ch_off] += patch_data[idxPatchData] * rbot1[idx2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
|
||||
if (ch_off == 0) {
|
||||
float total_sum = 0;
|
||||
for (int idx = 0; idx < 32; idx++) {
|
||||
total_sum += sum[idx];
|
||||
}
|
||||
const int sumelems = SIZE_3(rbot0);
|
||||
const int index = ((top_channel*SIZE_2(top) + blockIdx.y)*SIZE_3(top))+blockIdx.x;
|
||||
top[index + item*SIZE_1(top)*SIZE_2(top)*SIZE_3(top)] = total_sum / (float)sumelems;
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
kernel_Correlation_updateGradFirst = """
|
||||
#define ROUND_OFF 50000
|
||||
|
||||
extern "C" __global__ void kernel_Correlation_updateGradFirst(
|
||||
const int n,
|
||||
const int intSample,
|
||||
const float* rbot0,
|
||||
const float* rbot1,
|
||||
const float* gradOutput,
|
||||
float* gradFirst,
|
||||
float* gradSecond
|
||||
) { for (int intIndex = (blockIdx.x * blockDim.x) + threadIdx.x; intIndex < n; intIndex += blockDim.x * gridDim.x) {
|
||||
int n = intIndex % SIZE_1(gradFirst); // channels
|
||||
int l = (intIndex / SIZE_1(gradFirst)) % SIZE_3(gradFirst) + 4; // w-pos
|
||||
int m = (intIndex / SIZE_1(gradFirst) / SIZE_3(gradFirst)) % SIZE_2(gradFirst) + 4; // h-pos
|
||||
|
||||
// round_off is a trick to enable integer division with ceil, even for negative numbers
|
||||
// We use a large offset, for the inner part not to become negative.
|
||||
const int round_off = ROUND_OFF;
|
||||
const int round_off_s1 = round_off;
|
||||
|
||||
// We add round_off before_s1 the int division and subtract round_off after it, to ensure the formula matches ceil behavior:
|
||||
int xmin = (l - 4 + round_off_s1 - 1) + 1 - round_off; // ceil (l - 4)
|
||||
int ymin = (m - 4 + round_off_s1 - 1) + 1 - round_off; // ceil (l - 4)
|
||||
|
||||
// Same here:
|
||||
int xmax = (l - 4 + round_off_s1) - round_off; // floor (l - 4)
|
||||
int ymax = (m - 4 + round_off_s1) - round_off; // floor (m - 4)
|
||||
|
||||
float sum = 0;
|
||||
if (xmax>=0 && ymax>=0 && (xmin<=SIZE_3(gradOutput)-1) && (ymin<=SIZE_2(gradOutput)-1)) {
|
||||
xmin = max(0,xmin);
|
||||
xmax = min(SIZE_3(gradOutput)-1,xmax);
|
||||
|
||||
ymin = max(0,ymin);
|
||||
ymax = min(SIZE_2(gradOutput)-1,ymax);
|
||||
|
||||
for (int p = -4; p <= 4; p++) {
|
||||
for (int o = -4; o <= 4; o++) {
|
||||
// Get rbot1 data:
|
||||
int s2o = o;
|
||||
int s2p = p;
|
||||
int idxbot1 = ((intSample * SIZE_1(rbot0) + (m+s2p)) * SIZE_2(rbot0) + (l+s2o)) * SIZE_3(rbot0) + n;
|
||||
float bot1tmp = rbot1[idxbot1]; // rbot1[l+s2o,m+s2p,n]
|
||||
|
||||
// Index offset for gradOutput in following loops:
|
||||
int op = (p+4) * 9 + (o+4); // index[o,p]
|
||||
int idxopoffset = (intSample * SIZE_1(gradOutput) + op);
|
||||
|
||||
for (int y = ymin; y <= ymax; y++) {
|
||||
for (int x = xmin; x <= xmax; x++) {
|
||||
int idxgradOutput = (idxopoffset * SIZE_2(gradOutput) + y) * SIZE_3(gradOutput) + x; // gradOutput[x,y,o,p]
|
||||
sum += gradOutput[idxgradOutput] * bot1tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const int sumelems = SIZE_1(gradFirst);
|
||||
const int bot0index = ((n * SIZE_2(gradFirst)) + (m-4)) * SIZE_3(gradFirst) + (l-4);
|
||||
gradFirst[bot0index + intSample*SIZE_1(gradFirst)*SIZE_2(gradFirst)*SIZE_3(gradFirst)] = sum / (float)sumelems;
|
||||
} }
|
||||
"""
|
||||
|
||||
kernel_Correlation_updateGradSecond = """
|
||||
#define ROUND_OFF 50000
|
||||
|
||||
extern "C" __global__ void kernel_Correlation_updateGradSecond(
|
||||
const int n,
|
||||
const int intSample,
|
||||
const float* rbot0,
|
||||
const float* rbot1,
|
||||
const float* gradOutput,
|
||||
float* gradFirst,
|
||||
float* gradSecond
|
||||
) { for (int intIndex = (blockIdx.x * blockDim.x) + threadIdx.x; intIndex < n; intIndex += blockDim.x * gridDim.x) {
|
||||
int n = intIndex % SIZE_1(gradSecond); // channels
|
||||
int l = (intIndex / SIZE_1(gradSecond)) % SIZE_3(gradSecond) + 4; // w-pos
|
||||
int m = (intIndex / SIZE_1(gradSecond) / SIZE_3(gradSecond)) % SIZE_2(gradSecond) + 4; // h-pos
|
||||
|
||||
// round_off is a trick to enable integer division with ceil, even for negative numbers
|
||||
// We use a large offset, for the inner part not to become negative.
|
||||
const int round_off = ROUND_OFF;
|
||||
const int round_off_s1 = round_off;
|
||||
|
||||
float sum = 0;
|
||||
for (int p = -4; p <= 4; p++) {
|
||||
for (int o = -4; o <= 4; o++) {
|
||||
int s2o = o;
|
||||
int s2p = p;
|
||||
|
||||
//Get X,Y ranges and clamp
|
||||
// We add round_off before_s1 the int division and subtract round_off after it, to ensure the formula matches ceil behavior:
|
||||
int xmin = (l - 4 - s2o + round_off_s1 - 1) + 1 - round_off; // ceil (l - 4 - s2o)
|
||||
int ymin = (m - 4 - s2p + round_off_s1 - 1) + 1 - round_off; // ceil (l - 4 - s2o)
|
||||
|
||||
// Same here:
|
||||
int xmax = (l - 4 - s2o + round_off_s1) - round_off; // floor (l - 4 - s2o)
|
||||
int ymax = (m - 4 - s2p + round_off_s1) - round_off; // floor (m - 4 - s2p)
|
||||
|
||||
if (xmax>=0 && ymax>=0 && (xmin<=SIZE_3(gradOutput)-1) && (ymin<=SIZE_2(gradOutput)-1)) {
|
||||
xmin = max(0,xmin);
|
||||
xmax = min(SIZE_3(gradOutput)-1,xmax);
|
||||
|
||||
ymin = max(0,ymin);
|
||||
ymax = min(SIZE_2(gradOutput)-1,ymax);
|
||||
|
||||
// Get rbot0 data:
|
||||
int idxbot0 = ((intSample * SIZE_1(rbot0) + (m-s2p)) * SIZE_2(rbot0) + (l-s2o)) * SIZE_3(rbot0) + n;
|
||||
float bot0tmp = rbot0[idxbot0]; // rbot1[l+s2o,m+s2p,n]
|
||||
|
||||
// Index offset for gradOutput in following loops:
|
||||
int op = (p+4) * 9 + (o+4); // index[o,p]
|
||||
int idxopoffset = (intSample * SIZE_1(gradOutput) + op);
|
||||
|
||||
for (int y = ymin; y <= ymax; y++) {
|
||||
for (int x = xmin; x <= xmax; x++) {
|
||||
int idxgradOutput = (idxopoffset * SIZE_2(gradOutput) + y) * SIZE_3(gradOutput) + x; // gradOutput[x,y,o,p]
|
||||
sum += gradOutput[idxgradOutput] * bot0tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const int sumelems = SIZE_1(gradSecond);
|
||||
const int bot1index = ((n * SIZE_2(gradSecond)) + (m-4)) * SIZE_3(gradSecond) + (l-4);
|
||||
gradSecond[bot1index + intSample*SIZE_1(gradSecond)*SIZE_2(gradSecond)*SIZE_3(gradSecond)] = sum / (float)sumelems;
|
||||
} }
|
||||
"""
|
||||
|
||||
|
||||
class _FunctionCorrelation(torch.autograd.Function):
|
||||
@staticmethod
|
||||
def forward(self, first, second):
|
||||
rbot0 = first.new_zeros(
|
||||
[first.shape[0], first.shape[2] + 8, first.shape[3] + 8, first.shape[1]]
|
||||
)
|
||||
rbot1 = first.new_zeros(
|
||||
[first.shape[0], first.shape[2] + 8, first.shape[3] + 8, first.shape[1]]
|
||||
)
|
||||
|
||||
self.save_for_backward(first, second, rbot0, rbot1)
|
||||
|
||||
first = first.contiguous()
|
||||
assert first.is_cuda == True
|
||||
second = second.contiguous()
|
||||
assert second.is_cuda == True
|
||||
|
||||
output = first.new_zeros([first.shape[0], 81, first.shape[2], first.shape[3]])
|
||||
|
||||
if first.is_cuda == True:
|
||||
n = first.shape[2] * first.shape[3]
|
||||
cuda_launch(
|
||||
cuda_kernel(
|
||||
"kernel_Correlation_rearrange", kernel_Correlation_rearrange, {"input": first, "output": rbot0}
|
||||
),
|
||||
)(
|
||||
grid=tuple([int((n + 16 - 1) / 16), first.shape[1], first.shape[0]]),
|
||||
block=tuple([16, 1, 1]),
|
||||
args=[n, first.data_ptr(), rbot0.data_ptr()],
|
||||
)
|
||||
|
||||
n = second.shape[2] * second.shape[3]
|
||||
cuda_launch(
|
||||
cuda_kernel(
|
||||
"kernel_Correlation_rearrange", kernel_Correlation_rearrange, {"input": second, "output": rbot1}
|
||||
),
|
||||
)(
|
||||
grid=tuple([int((n + 16 - 1) / 16), second.shape[1], second.shape[0]]),
|
||||
block=tuple([16, 1, 1]),
|
||||
args=[n, second.data_ptr(), rbot1.data_ptr()],
|
||||
)
|
||||
|
||||
n = output.shape[1] * output.shape[2] * output.shape[3]
|
||||
cuda_launch(
|
||||
cuda_kernel(
|
||||
"kernel_Correlation_updateOutput",
|
||||
kernel_Correlation_updateOutput,
|
||||
{"rbot0": rbot0, "rbot1": rbot1, "top": output},
|
||||
),
|
||||
)(
|
||||
grid=tuple([output.shape[3], output.shape[2], output.shape[0]]),
|
||||
block=tuple([32, 1, 1]),
|
||||
shared_mem=first.shape[1] * 4,
|
||||
args=[n, rbot0.data_ptr(), rbot1.data_ptr(), output.data_ptr()],
|
||||
)
|
||||
|
||||
elif first.is_cuda == False:
|
||||
raise NotImplementedError()
|
||||
|
||||
# end
|
||||
|
||||
return output
|
||||
|
||||
# end
|
||||
|
||||
@staticmethod
|
||||
def backward(self, gradOutput):
|
||||
first, second, rbot0, rbot1 = self.saved_tensors
|
||||
|
||||
gradOutput = gradOutput.contiguous()
|
||||
assert gradOutput.is_cuda == True
|
||||
|
||||
gradFirst = (
|
||||
first.new_zeros(
|
||||
[first.shape[0], first.shape[1], first.shape[2], first.shape[3]]
|
||||
)
|
||||
if self.needs_input_grad[0] == True
|
||||
else None
|
||||
)
|
||||
gradSecond = (
|
||||
first.new_zeros(
|
||||
[first.shape[0], first.shape[1], first.shape[2], first.shape[3]]
|
||||
)
|
||||
if self.needs_input_grad[1] == True
|
||||
else None
|
||||
)
|
||||
|
||||
if first.is_cuda == True:
|
||||
if gradFirst is not None:
|
||||
for intSample in range(first.shape[0]):
|
||||
n = first.shape[1] * first.shape[2] * first.shape[3]
|
||||
cuda_launch(
|
||||
cuda_kernel(
|
||||
"kernel_Correlation_updateGradFirst",
|
||||
kernel_Correlation_updateGradFirst,
|
||||
{
|
||||
"rbot0": rbot0,
|
||||
"rbot1": rbot1,
|
||||
"gradOutput": gradOutput,
|
||||
"gradFirst": gradFirst,
|
||||
"gradSecond": None,
|
||||
},
|
||||
),
|
||||
)(
|
||||
grid=tuple([int((n + 512 - 1) / 512), 1, 1]),
|
||||
block=tuple([512, 1, 1]),
|
||||
args=[
|
||||
n,
|
||||
intSample,
|
||||
rbot0.data_ptr(),
|
||||
rbot1.data_ptr(),
|
||||
gradOutput.data_ptr(),
|
||||
gradFirst.data_ptr(),
|
||||
None,
|
||||
],
|
||||
)
|
||||
# end
|
||||
# end
|
||||
|
||||
if gradSecond is not None:
|
||||
for intSample in range(first.shape[0]):
|
||||
n = first.shape[1] * first.shape[2] * first.shape[3]
|
||||
cuda_launch(
|
||||
cuda_kernel(
|
||||
"kernel_Correlation_updateGradSecond",
|
||||
kernel_Correlation_updateGradSecond,
|
||||
{
|
||||
"rbot0": rbot0,
|
||||
"rbot1": rbot1,
|
||||
"gradOutput": gradOutput,
|
||||
"gradFirst": None,
|
||||
"gradSecond": gradSecond,
|
||||
},
|
||||
),
|
||||
)(
|
||||
grid=tuple([int((n + 512 - 1) / 512), 1, 1]),
|
||||
block=tuple([512, 1, 1]),
|
||||
args=[
|
||||
n,
|
||||
intSample,
|
||||
rbot0.data_ptr(),
|
||||
rbot1.data_ptr(),
|
||||
gradOutput.data_ptr(),
|
||||
None,
|
||||
gradSecond.data_ptr(),
|
||||
],
|
||||
)
|
||||
# end
|
||||
# end
|
||||
|
||||
elif first.is_cuda == False:
|
||||
raise NotImplementedError()
|
||||
|
||||
# end
|
||||
|
||||
return gradFirst, gradSecond
|
||||
|
||||
# end
|
||||
|
||||
|
||||
# end
|
||||
|
||||
|
||||
def FunctionCorrelation(tenFirst, tenSecond):
|
||||
return _FunctionCorrelation.apply(tenFirst, tenSecond)
|
||||
|
||||
|
||||
# end
|
||||
|
||||
|
||||
class ModuleCorrelation(torch.nn.Module):
|
||||
def __init__(self):
|
||||
super(ModuleCorrelation, self).__init__()
|
||||
|
||||
# end
|
||||
|
||||
def forward(self, tenFirst, tenSecond):
|
||||
return _FunctionCorrelation.apply(tenFirst, tenSecond)
|
||||
|
||||
# end
|
||||
|
||||
__all__ = ["_FunctionCorrelation", "FunctionCorrelation", "ModuleCorrelation"]
|
||||
@@ -1,317 +0,0 @@
|
||||
from .utils import cuda_kernel, cuda_launch, cuda_int32
|
||||
import torch, collections
|
||||
|
||||
costvol_out = """
|
||||
extern "C" __global__ void __launch_bounds__(512) costvol_out(
|
||||
const int n,
|
||||
const {{type}}* __restrict__ tenOne,
|
||||
const {{type}}* __restrict__ tenTwo,
|
||||
{{type}}* __restrict__ tenOut
|
||||
) { for (int intIndex = (blockIdx.x * blockDim.x) + threadIdx.x; intIndex < n; intIndex += blockDim.x * gridDim.x) {
|
||||
const int intN = ( intIndex / SIZE_3(tenOut) / SIZE_2(tenOut) ) % SIZE_0(tenOut);
|
||||
const int intC = -1;
|
||||
const int intY = ( intIndex / SIZE_3(tenOut) ) % SIZE_2(tenOut);
|
||||
const int intX = ( intIndex ) % SIZE_3(tenOut);
|
||||
|
||||
{{type}} fltOne[{{intChans}}];
|
||||
|
||||
for (int intValue = 0; intValue < SIZE_1(tenOne); intValue += 1) {
|
||||
fltOne[intValue] = VALUE_4(tenOne, intN, intValue, intY, intX);
|
||||
}
|
||||
|
||||
int intOffset = OFFSET_4(tenOut, intN, 0, intY, intX);
|
||||
|
||||
for (int intOy = intY - 4; intOy <= intY + 4; intOy += 1) {
|
||||
for (int intOx = intX - 4; intOx <= intX + 4; intOx += 1) {
|
||||
{{type}} fltValue = 0.0f;
|
||||
|
||||
if ((intOy >= 0) && (intOy < SIZE_2(tenOut)) && (intOx >= 0) && (intOx < SIZE_3(tenOut))) {
|
||||
for (int intValue = 0; intValue < SIZE_1(tenOne); intValue += 1) {
|
||||
fltValue += abs(fltOne[intValue] - VALUE_4(tenTwo, intN, intValue, intOy, intOx));
|
||||
}
|
||||
} else {
|
||||
for (int intValue = 0; intValue < SIZE_1(tenOne); intValue += 1) {
|
||||
fltValue += abs(fltOne[intValue]);
|
||||
}
|
||||
}
|
||||
|
||||
tenOut[intOffset] = fltValue / SIZE_1(tenOne);
|
||||
intOffset += SIZE_2(tenOut) * SIZE_3(tenOut);
|
||||
}
|
||||
}
|
||||
} }
|
||||
"""
|
||||
|
||||
costvol_onegrad = """
|
||||
extern "C" __global__ void __launch_bounds__(512) costvol_onegrad(
|
||||
const int n,
|
||||
const {{type}}* __restrict__ tenOne,
|
||||
const {{type}}* __restrict__ tenTwo,
|
||||
const {{type}}* __restrict__ tenOutgrad,
|
||||
{{type}}* __restrict__ tenOnegrad,
|
||||
{{type}}* __restrict__ tenTwograd
|
||||
) { for (int intIndex = (blockIdx.x * blockDim.x) + threadIdx.x; intIndex < n; intIndex += blockDim.x * gridDim.x) {
|
||||
const int intN = ( intIndex / SIZE_3(tenOnegrad) / SIZE_2(tenOnegrad) ) % SIZE_0(tenOnegrad);
|
||||
const int intC = -1;
|
||||
const int intY = ( intIndex / SIZE_3(tenOnegrad) ) % SIZE_2(tenOnegrad);
|
||||
const int intX = ( intIndex ) % SIZE_3(tenOnegrad);
|
||||
|
||||
{{type}} fltOne[{{intChans}}];
|
||||
|
||||
for (int intValue = 0; intValue < SIZE_1(tenOne); intValue += 1) {
|
||||
fltOne[intValue] = VALUE_4(tenOne, intN, intValue, intY, intX);
|
||||
}
|
||||
|
||||
int intOffset = OFFSET_4(tenOutgrad, intN, 0, intY, intX);
|
||||
|
||||
for (int intOy = intY - 4; intOy <= intY + 4; intOy += 1) {
|
||||
for (int intOx = intX - 4; intOx <= intX + 4; intOx += 1) {
|
||||
if ((intOy >= 0) && (intOy < SIZE_2(tenOutgrad)) && (intOx >= 0) && (intOx < SIZE_3(tenOutgrad))) {
|
||||
for (int intValue = 0; intValue < SIZE_1(tenOne); intValue += 1) {
|
||||
if (fltOne[intValue] - VALUE_4(tenTwo, intN, intValue, intOy, intOx) >= 0.0f) {
|
||||
tenOnegrad[OFFSET_4(tenOnegrad, intN, intValue, intY, intX)] += +tenOutgrad[intOffset] / SIZE_1(tenOne);
|
||||
} else {
|
||||
tenOnegrad[OFFSET_4(tenOnegrad, intN, intValue, intY, intX)] += -tenOutgrad[intOffset] / SIZE_1(tenOne);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int intValue = 0; intValue < SIZE_1(tenOne); intValue += 1) {
|
||||
if (fltOne[intValue] >= 0.0f) {
|
||||
tenOnegrad[OFFSET_4(tenOnegrad, intN, intValue, intY, intX)] += +tenOutgrad[intOffset] / SIZE_1(tenOne);
|
||||
} else {
|
||||
tenOnegrad[OFFSET_4(tenOnegrad, intN, intValue, intY, intX)] += -tenOutgrad[intOffset] / SIZE_1(tenOne);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
intOffset += SIZE_2(tenOutgrad) * SIZE_3(tenOutgrad);
|
||||
}
|
||||
}
|
||||
} }
|
||||
"""
|
||||
|
||||
costvol_twograd = """
|
||||
extern "C" __global__ void __launch_bounds__(512) costvol_twograd(
|
||||
const int n,
|
||||
const {{type}}* __restrict__ tenOne,
|
||||
const {{type}}* __restrict__ tenTwo,
|
||||
const {{type}}* __restrict__ tenOutgrad,
|
||||
{{type}}* __restrict__ tenOnegrad,
|
||||
{{type}}* __restrict__ tenTwograd
|
||||
) { for (int intIndex = (blockIdx.x * blockDim.x) + threadIdx.x; intIndex < n; intIndex += blockDim.x * gridDim.x) {
|
||||
const int intN = ( intIndex / SIZE_3(tenTwograd) / SIZE_2(tenTwograd) ) % SIZE_0(tenTwograd);
|
||||
const int intC = -1;
|
||||
const int intY = ( intIndex / SIZE_3(tenTwograd) ) % SIZE_2(tenTwograd);
|
||||
const int intX = ( intIndex ) % SIZE_3(tenTwograd);
|
||||
|
||||
{{type}} fltOne[{{intChans}}];
|
||||
|
||||
for (int intValue = 0; intValue < SIZE_1(tenOne); intValue += 1) {
|
||||
fltOne[intValue] = VALUE_4(tenOne, intN, intValue, intY, intX);
|
||||
}
|
||||
|
||||
int intOffset = OFFSET_4(tenOutgrad, intN, 0, intY, intX);
|
||||
|
||||
for (int intOy = intY - 4; intOy <= intY + 4; intOy += 1) {
|
||||
for (int intOx = intX - 4; intOx <= intX + 4; intOx += 1) {
|
||||
if ((intOy >= 0) && (intOy < SIZE_2(tenOutgrad)) && (intOx >= 0) && (intOx < SIZE_3(tenOutgrad))) {
|
||||
for (int intValue = 0; intValue < SIZE_1(tenOne); intValue += 1) {
|
||||
if (fltOne[intValue] - VALUE_4(tenTwo, intN, intValue, intOy, intOx) >= 0.0f) {
|
||||
atomicAdd(&tenTwograd[OFFSET_4(tenTwograd, intN, intValue, intOy, intOx)], -tenOutgrad[intOffset] / SIZE_1(tenOne));
|
||||
} else {
|
||||
atomicAdd(&tenTwograd[OFFSET_4(tenTwograd, intN, intValue, intOy, intOx)], +tenOutgrad[intOffset] / SIZE_1(tenOne));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// ...
|
||||
}
|
||||
|
||||
intOffset += SIZE_2(tenOutgrad) * SIZE_3(tenOutgrad);
|
||||
}
|
||||
}
|
||||
} }
|
||||
"""
|
||||
|
||||
class costvol_func(torch.autograd.Function):
|
||||
@staticmethod
|
||||
@torch.cuda.amp.custom_fwd(cast_inputs=torch.float32)
|
||||
def forward(self, tenOne, tenTwo):
|
||||
tenOut = tenOne.new_empty(
|
||||
[tenOne.shape[0], 81, tenOne.shape[2], tenOne.shape[3]]
|
||||
)
|
||||
|
||||
cuda_launch(
|
||||
cuda_kernel(
|
||||
"costvol_out",
|
||||
costvol_out,
|
||||
{
|
||||
"intChans": tenOne.shape[1],
|
||||
"tenOne": tenOne,
|
||||
"tenTwo": tenTwo,
|
||||
"tenOut": tenOut,
|
||||
},
|
||||
)
|
||||
)(
|
||||
grid=tuple(
|
||||
[
|
||||
int(
|
||||
(
|
||||
(tenOut.shape[0] * tenOut.shape[2] * tenOut.shape[3])
|
||||
+ 512
|
||||
- 1
|
||||
)
|
||||
/ 512
|
||||
),
|
||||
1,
|
||||
1,
|
||||
]
|
||||
),
|
||||
block=tuple([512, 1, 1]),
|
||||
args=[
|
||||
cuda_int32(tenOut.shape[0] * tenOut.shape[2] * tenOut.shape[3]),
|
||||
tenOne.data_ptr(),
|
||||
tenTwo.data_ptr(),
|
||||
tenOut.data_ptr(),
|
||||
],
|
||||
stream=collections.namedtuple("Stream", "ptr")(
|
||||
torch.cuda.current_stream().cuda_stream
|
||||
),
|
||||
)
|
||||
|
||||
self.save_for_backward(tenOne, tenTwo)
|
||||
|
||||
return tenOut
|
||||
|
||||
# end
|
||||
|
||||
@staticmethod
|
||||
@torch.cuda.amp.custom_bwd
|
||||
def backward(self, tenOutgrad):
|
||||
tenOne, tenTwo = self.saved_tensors
|
||||
|
||||
tenOutgrad = tenOutgrad.contiguous()
|
||||
assert tenOutgrad.is_cuda == True
|
||||
|
||||
tenOnegrad = (
|
||||
tenOne.new_zeros(
|
||||
[tenOne.shape[0], tenOne.shape[1], tenOne.shape[2], tenOne.shape[3]]
|
||||
)
|
||||
if self.needs_input_grad[0] == True
|
||||
else None
|
||||
)
|
||||
tenTwograd = (
|
||||
tenTwo.new_zeros(
|
||||
[tenTwo.shape[0], tenTwo.shape[1], tenTwo.shape[2], tenTwo.shape[3]]
|
||||
)
|
||||
if self.needs_input_grad[1] == True
|
||||
else None
|
||||
)
|
||||
|
||||
if tenOnegrad is not None:
|
||||
cuda_launch(
|
||||
cuda_kernel(
|
||||
"costvol_onegrad",
|
||||
costvol_onegrad,
|
||||
{
|
||||
"intChans": tenOne.shape[1],
|
||||
"tenOne": tenOne,
|
||||
"tenTwo": tenTwo,
|
||||
"tenOutgrad": tenOutgrad,
|
||||
"tenOnegrad": tenOnegrad,
|
||||
"tenTwograd": tenTwograd,
|
||||
},
|
||||
)
|
||||
)(
|
||||
grid=tuple(
|
||||
[
|
||||
int(
|
||||
(
|
||||
(
|
||||
tenOnegrad.shape[0]
|
||||
* tenOnegrad.shape[2]
|
||||
* tenOnegrad.shape[3]
|
||||
)
|
||||
+ 512
|
||||
- 1
|
||||
)
|
||||
/ 512
|
||||
),
|
||||
1,
|
||||
1,
|
||||
]
|
||||
),
|
||||
block=tuple([512, 1, 1]),
|
||||
args=[
|
||||
cuda_int32(
|
||||
tenOnegrad.shape[0] * tenOnegrad.shape[2] * tenOnegrad.shape[3]
|
||||
),
|
||||
tenOne.data_ptr(),
|
||||
tenTwo.data_ptr(),
|
||||
tenOutgrad.data_ptr(),
|
||||
tenOnegrad.data_ptr(),
|
||||
tenTwograd.data_ptr(),
|
||||
],
|
||||
stream=collections.namedtuple("Stream", "ptr")(
|
||||
torch.cuda.current_stream().cuda_stream
|
||||
),
|
||||
)
|
||||
# end
|
||||
|
||||
if tenTwograd is not None:
|
||||
cuda_launch(
|
||||
cuda_kernel(
|
||||
"costvol_twograd",
|
||||
costvol_twograd,
|
||||
{
|
||||
"intChans": tenOne.shape[1],
|
||||
"tenOne": tenOne,
|
||||
"tenTwo": tenTwo,
|
||||
"tenOutgrad": tenOutgrad,
|
||||
"tenOnegrad": tenOnegrad,
|
||||
"tenTwograd": tenTwograd,
|
||||
},
|
||||
)
|
||||
)(
|
||||
grid=tuple(
|
||||
[
|
||||
int(
|
||||
(
|
||||
(
|
||||
tenTwograd.shape[0]
|
||||
* tenTwograd.shape[2]
|
||||
* tenTwograd.shape[3]
|
||||
)
|
||||
+ 512
|
||||
- 1
|
||||
)
|
||||
/ 512
|
||||
),
|
||||
1,
|
||||
1,
|
||||
]
|
||||
),
|
||||
block=tuple([512, 1, 1]),
|
||||
args=[
|
||||
cuda_int32(
|
||||
tenTwograd.shape[0] * tenTwograd.shape[2] * tenTwograd.shape[3]
|
||||
),
|
||||
tenOne.data_ptr(),
|
||||
tenTwo.data_ptr(),
|
||||
tenOutgrad.data_ptr(),
|
||||
tenOnegrad.data_ptr(),
|
||||
tenTwograd.data_ptr(),
|
||||
],
|
||||
stream=collections.namedtuple("Stream", "ptr")(
|
||||
torch.cuda.current_stream().cuda_stream
|
||||
),
|
||||
)
|
||||
# end
|
||||
|
||||
return tenOnegrad, tenTwograd, None, None
|
||||
|
||||
# end
|
||||
|
||||
|
||||
# end
|
||||
|
||||
__all__ = ["costvol_func"]
|
||||
@@ -1,332 +0,0 @@
|
||||
import torch
|
||||
from .utils import cuda_launch, cuda_kernel, cuda_int32
|
||||
|
||||
sepconv_vergrad = """
|
||||
extern "C" __global__ void __launch_bounds__(512) sepconv_vergrad(
|
||||
const int n,
|
||||
const {{type}}* __restrict__ tenIn,
|
||||
const {{type}}* __restrict__ tenVer,
|
||||
const {{type}}* __restrict__ tenHor,
|
||||
const {{type}}* __restrict__ tenOutgrad,
|
||||
{{type}}* __restrict__ tenIngrad,
|
||||
{{type}}* __restrict__ tenVergrad,
|
||||
{{type}}* __restrict__ tenHorgrad
|
||||
) { for (int intIndex = (blockIdx.x * blockDim.x) + threadIdx.x; intIndex < n; intIndex += blockDim.x * gridDim.x) {
|
||||
const int intN = ( intIndex / SIZE_3(tenVergrad) / SIZE_2(tenVergrad) / SIZE_1(tenVergrad) ) % SIZE_0(tenVergrad);
|
||||
const int intC = ( intIndex / SIZE_3(tenVergrad) / SIZE_2(tenVergrad) ) % SIZE_1(tenVergrad);
|
||||
const int intY = ( intIndex / SIZE_3(tenVergrad) ) % SIZE_2(tenVergrad);
|
||||
const int intX = ( intIndex ) % SIZE_3(tenVergrad);
|
||||
|
||||
{{type}} fltVergrad = 0.0;
|
||||
|
||||
{{type}} fltKahanc = 0.0;
|
||||
{{type}} fltKahany = 0.0;
|
||||
{{type}} fltKahant = 0.0;
|
||||
|
||||
for (int intI = 0; intI < SIZE_1(tenIn); intI += 1) {
|
||||
for (int intFx = 0; intFx < SIZE_1(tenHor); intFx += 1) {
|
||||
fltKahany = VALUE_4(tenHor, intN, intFx, intY, intX) * VALUE_4(tenIn, intN, intI, intY + intC, intX + intFx) * VALUE_4(tenOutgrad, intN, intI, intY, intX);
|
||||
fltKahany = fltKahany - fltKahanc;
|
||||
fltKahant = fltVergrad + fltKahany;
|
||||
fltKahanc = (fltKahant - fltVergrad) - fltKahany;
|
||||
fltVergrad = fltKahant;
|
||||
}
|
||||
}
|
||||
|
||||
tenVergrad[intIndex] = fltVergrad;
|
||||
} }
|
||||
"""
|
||||
|
||||
sepconv_ingrad = """
|
||||
extern "C" __global__ void __launch_bounds__(512) sepconv_ingrad(
|
||||
const int n,
|
||||
const {{type}}* __restrict__ tenIn,
|
||||
const {{type}}* __restrict__ tenVer,
|
||||
const {{type}}* __restrict__ tenHor,
|
||||
const {{type}}* __restrict__ tenOutgrad,
|
||||
{{type}}* __restrict__ tenIngrad,
|
||||
{{type}}* __restrict__ tenVergrad,
|
||||
{{type}}* __restrict__ tenHorgrad
|
||||
) { for (int intIndex = (blockIdx.x * blockDim.x) + threadIdx.x; intIndex < n; intIndex += blockDim.x * gridDim.x) {
|
||||
const int intN = ( intIndex / SIZE_3(tenIngrad) / SIZE_2(tenIngrad) / SIZE_1(tenIngrad) ) % SIZE_0(tenIngrad);
|
||||
const int intC = ( intIndex / SIZE_3(tenIngrad) / SIZE_2(tenIngrad) ) % SIZE_1(tenIngrad);
|
||||
const int intY = ( intIndex / SIZE_3(tenIngrad) ) % SIZE_2(tenIngrad);
|
||||
const int intX = ( intIndex ) % SIZE_3(tenIngrad);
|
||||
|
||||
{{type}} fltIngrad = 0.0;
|
||||
|
||||
{{type}} fltKahanc = 0.0;
|
||||
{{type}} fltKahany = 0.0;
|
||||
{{type}} fltKahant = 0.0;
|
||||
|
||||
for (int intFy = 0; intFy < SIZE_1(tenVer); intFy += 1) {
|
||||
int intKy = intY + intFy - (SIZE_1(tenVer) - 1);
|
||||
|
||||
if (intKy < 0) { continue; }
|
||||
if (intKy >= SIZE_2(tenVer)) { continue; }
|
||||
|
||||
for (int intFx = 0; intFx < SIZE_1(tenHor); intFx += 1) {
|
||||
int intKx = intX + intFx - (SIZE_1(tenHor) - 1);
|
||||
|
||||
if (intKx < 0) { continue; }
|
||||
if (intKx >= SIZE_3(tenHor)) { continue; }
|
||||
|
||||
fltKahany = VALUE_4(tenVer, intN, (SIZE_1(tenVer) - 1) - intFy, intKy, intKx) * VALUE_4(tenHor, intN, (SIZE_1(tenHor) - 1) - intFx, intKy, intKx) * VALUE_4(tenOutgrad, intN, intC, intKy, intKx);
|
||||
fltKahany = fltKahany - fltKahanc;
|
||||
fltKahant = fltIngrad + fltKahany;
|
||||
fltKahanc = (fltKahant - fltIngrad) - fltKahany;
|
||||
fltIngrad = fltKahant;
|
||||
}
|
||||
}
|
||||
|
||||
tenIngrad[intIndex] = fltIngrad;
|
||||
} }
|
||||
"""
|
||||
|
||||
sepconv_out = """
|
||||
extern "C" __global__ void __launch_bounds__(512) sepconv_out(
|
||||
const int n,
|
||||
const {{type}}* __restrict__ tenIn,
|
||||
const {{type}}* __restrict__ tenVer,
|
||||
const {{type}}* __restrict__ tenHor,
|
||||
{{type}}* __restrict__ tenOut
|
||||
) { for (int intIndex = (blockIdx.x * blockDim.x) + threadIdx.x; intIndex < n; intIndex += blockDim.x * gridDim.x) {
|
||||
const int intN = ( intIndex / SIZE_3(tenOut) / SIZE_2(tenOut) / SIZE_1(tenOut) ) % SIZE_0(tenOut);
|
||||
const int intC = ( intIndex / SIZE_3(tenOut) / SIZE_2(tenOut) ) % SIZE_1(tenOut);
|
||||
const int intY = ( intIndex / SIZE_3(tenOut) ) % SIZE_2(tenOut);
|
||||
const int intX = ( intIndex ) % SIZE_3(tenOut);
|
||||
|
||||
{{type}} fltOut = 0.0;
|
||||
|
||||
{{type}} fltKahanc = 0.0;
|
||||
{{type}} fltKahany = 0.0;
|
||||
{{type}} fltKahant = 0.0;
|
||||
|
||||
for (int intFy = 0; intFy < SIZE_1(tenVer); intFy += 1) {
|
||||
for (int intFx = 0; intFx < SIZE_1(tenHor); intFx += 1) {
|
||||
fltKahany = VALUE_4(tenIn, intN, intC, intY + intFy, intX + intFx) * VALUE_4(tenVer, intN, intFy, intY, intX) * VALUE_4(tenHor, intN, intFx, intY, intX);
|
||||
fltKahany = fltKahany - fltKahanc;
|
||||
fltKahant = fltOut + fltKahany;
|
||||
fltKahanc = (fltKahant - fltOut) - fltKahany;
|
||||
fltOut = fltKahant;
|
||||
}
|
||||
}
|
||||
|
||||
tenOut[intIndex] = fltOut;
|
||||
} }
|
||||
"""
|
||||
|
||||
sepconv_horgrad = """
|
||||
extern "C" __global__ void __launch_bounds__(512) sepconv_horgrad(
|
||||
const int n,
|
||||
const {{type}}* __restrict__ tenIn,
|
||||
const {{type}}* __restrict__ tenVer,
|
||||
const {{type}}* __restrict__ tenHor,
|
||||
const {{type}}* __restrict__ tenOutgrad,
|
||||
{{type}}* __restrict__ tenIngrad,
|
||||
{{type}}* __restrict__ tenVergrad,
|
||||
{{type}}* __restrict__ tenHorgrad
|
||||
) { for (int intIndex = (blockIdx.x * blockDim.x) + threadIdx.x; intIndex < n; intIndex += blockDim.x * gridDim.x) {
|
||||
const int intN = ( intIndex / SIZE_3(tenHorgrad) / SIZE_2(tenHorgrad) / SIZE_1(tenHorgrad) ) % SIZE_0(tenHorgrad);
|
||||
const int intC = ( intIndex / SIZE_3(tenHorgrad) / SIZE_2(tenHorgrad) ) % SIZE_1(tenHorgrad);
|
||||
const int intY = ( intIndex / SIZE_3(tenHorgrad) ) % SIZE_2(tenHorgrad);
|
||||
const int intX = ( intIndex ) % SIZE_3(tenHorgrad);
|
||||
|
||||
{{type}} fltHorgrad = 0.0;
|
||||
|
||||
{{type}} fltKahanc = 0.0;
|
||||
{{type}} fltKahany = 0.0;
|
||||
{{type}} fltKahant = 0.0;
|
||||
|
||||
for (int intI = 0; intI < SIZE_1(tenIn); intI += 1) {
|
||||
for (int intFy = 0; intFy < SIZE_1(tenVer); intFy += 1) {
|
||||
fltKahany = VALUE_4(tenVer, intN, intFy, intY, intX) * VALUE_4(tenIn, intN, intI, intY + intFy, intX + intC) * VALUE_4(tenOutgrad, intN, intI, intY, intX);
|
||||
fltKahany = fltKahany - fltKahanc;
|
||||
fltKahant = fltHorgrad + fltKahany;
|
||||
fltKahanc = (fltKahant - fltHorgrad) - fltKahany;
|
||||
fltHorgrad = fltKahant;
|
||||
}
|
||||
}
|
||||
|
||||
tenHorgrad[intIndex] = fltHorgrad;
|
||||
} }
|
||||
"""
|
||||
|
||||
class sepconv_func(torch.autograd.Function):
|
||||
@staticmethod
|
||||
@torch.cuda.amp.custom_fwd(cast_inputs=torch.float32)
|
||||
def forward(self, tenIn, tenVer, tenHor):
|
||||
tenOut = tenIn.new_empty(
|
||||
[
|
||||
tenIn.shape[0],
|
||||
tenIn.shape[1],
|
||||
tenVer.shape[2] and tenHor.shape[2],
|
||||
tenVer.shape[3] and tenHor.shape[3],
|
||||
]
|
||||
)
|
||||
|
||||
if tenIn.is_cuda == True:
|
||||
cuda_launch(
|
||||
cuda_kernel(
|
||||
"sepconv_out",
|
||||
sepconv_out,
|
||||
{
|
||||
"tenIn": tenIn,
|
||||
"tenVer": tenVer,
|
||||
"tenHor": tenHor,
|
||||
"tenOut": tenOut,
|
||||
},
|
||||
)
|
||||
)(
|
||||
grid=tuple([int((tenOut.nelement() + 512 - 1) / 512), 1, 1]),
|
||||
block=tuple([512, 1, 1]),
|
||||
args=[
|
||||
cuda_int32(tenOut.nelement()),
|
||||
tenIn.data_ptr(),
|
||||
tenVer.data_ptr(),
|
||||
tenHor.data_ptr(),
|
||||
tenOut.data_ptr(),
|
||||
],
|
||||
)
|
||||
|
||||
elif tenIn.is_cuda != True:
|
||||
assert False
|
||||
|
||||
# end
|
||||
|
||||
self.save_for_backward(tenIn, tenVer, tenHor)
|
||||
|
||||
return tenOut
|
||||
|
||||
# end
|
||||
|
||||
@staticmethod
|
||||
@torch.cuda.amp.custom_bwd
|
||||
def backward(self, tenOutgrad):
|
||||
tenIn, tenVer, tenHor = self.saved_tensors
|
||||
|
||||
tenOutgrad = tenOutgrad.contiguous()
|
||||
assert tenOutgrad.is_cuda == True
|
||||
|
||||
tenIngrad = (
|
||||
tenIn.new_empty(
|
||||
[tenIn.shape[0], tenIn.shape[1], tenIn.shape[2], tenIn.shape[3]]
|
||||
)
|
||||
if self.needs_input_grad[0] == True
|
||||
else None
|
||||
)
|
||||
tenVergrad = (
|
||||
tenVer.new_empty(
|
||||
[tenVer.shape[0], tenVer.shape[1], tenVer.shape[2], tenVer.shape[3]]
|
||||
)
|
||||
if self.needs_input_grad[1] == True
|
||||
else None
|
||||
)
|
||||
tenHorgrad = (
|
||||
tenHor.new_empty(
|
||||
[tenHor.shape[0], tenHor.shape[1], tenHor.shape[2], tenHor.shape[3]]
|
||||
)
|
||||
if self.needs_input_grad[2] == True
|
||||
else None
|
||||
)
|
||||
|
||||
if tenIngrad is not None:
|
||||
cuda_launch(
|
||||
cuda_kernel(
|
||||
"sepconv_ingrad",
|
||||
sepconv_ingrad,
|
||||
{
|
||||
"tenIn": tenIn,
|
||||
"tenVer": tenVer,
|
||||
"tenHor": tenHor,
|
||||
"tenOutgrad": tenOutgrad,
|
||||
"tenIngrad": tenIngrad,
|
||||
"tenVergrad": tenVergrad,
|
||||
"tenHorgrad": tenHorgrad,
|
||||
},
|
||||
)
|
||||
)(
|
||||
grid=tuple([int((tenIngrad.nelement() + 512 - 1) / 512), 1, 1]),
|
||||
block=tuple([512, 1, 1]),
|
||||
args=[
|
||||
cuda_int32(tenIngrad.nelement()),
|
||||
tenIn.data_ptr(),
|
||||
tenVer.data_ptr(),
|
||||
tenHor.data_ptr(),
|
||||
tenOutgrad.data_ptr(),
|
||||
tenIngrad.data_ptr(),
|
||||
None,
|
||||
None,
|
||||
],
|
||||
)
|
||||
# end
|
||||
|
||||
if tenVergrad is not None:
|
||||
cuda_launch(
|
||||
cuda_kernel(
|
||||
"sepconv_vergrad",
|
||||
sepconv_vergrad,
|
||||
{
|
||||
"tenIn": tenIn,
|
||||
"tenVer": tenVer,
|
||||
"tenHor": tenHor,
|
||||
"tenOutgrad": tenOutgrad,
|
||||
"tenIngrad": tenIngrad,
|
||||
"tenVergrad": tenVergrad,
|
||||
"tenHorgrad": tenHorgrad,
|
||||
},
|
||||
)
|
||||
)(
|
||||
grid=tuple([int((tenVergrad.nelement() + 512 - 1) / 512), 1, 1]),
|
||||
block=tuple([512, 1, 1]),
|
||||
args=[
|
||||
cuda_int32(tenVergrad.nelement()),
|
||||
tenIn.data_ptr(),
|
||||
tenVer.data_ptr(),
|
||||
tenHor.data_ptr(),
|
||||
tenOutgrad.data_ptr(),
|
||||
None,
|
||||
tenVergrad.data_ptr(),
|
||||
None,
|
||||
],
|
||||
)
|
||||
# end
|
||||
|
||||
if tenHorgrad is not None:
|
||||
cuda_launch(
|
||||
cuda_kernel(
|
||||
"sepconv_horgrad",
|
||||
sepconv_horgrad,
|
||||
{
|
||||
"tenIn": tenIn,
|
||||
"tenVer": tenVer,
|
||||
"tenHor": tenHor,
|
||||
"tenOutgrad": tenOutgrad,
|
||||
"tenIngrad": tenIngrad,
|
||||
"tenVergrad": tenVergrad,
|
||||
"tenHorgrad": tenHorgrad,
|
||||
},
|
||||
)
|
||||
)(
|
||||
grid=tuple([int((tenHorgrad.nelement() + 512 - 1) / 512), 1, 1]),
|
||||
block=tuple([512, 1, 1]),
|
||||
args=[
|
||||
cuda_int32(tenHorgrad.nelement()),
|
||||
tenIn.data_ptr(),
|
||||
tenVer.data_ptr(),
|
||||
tenHor.data_ptr(),
|
||||
tenOutgrad.data_ptr(),
|
||||
None,
|
||||
None,
|
||||
tenHorgrad.data_ptr(),
|
||||
],
|
||||
)
|
||||
# end
|
||||
|
||||
return tenIngrad, tenVergrad, tenHorgrad
|
||||
|
||||
# end
|
||||
|
||||
|
||||
# end
|
||||
__all__ = ["sepconv_func"]
|
||||
@@ -1,440 +0,0 @@
|
||||
import torch
|
||||
from .utils import cuda_launch, cuda_kernel, cuda_int32
|
||||
import cupy
|
||||
import collections
|
||||
|
||||
softsplat_flowgrad = """
|
||||
extern "C" __global__ void __launch_bounds__(512) softsplat_flowgrad(
|
||||
const int n,
|
||||
const {{type}}* __restrict__ tenIn,
|
||||
const {{type}}* __restrict__ tenFlow,
|
||||
const {{type}}* __restrict__ tenOutgrad,
|
||||
{{type}}* __restrict__ tenIngrad,
|
||||
{{type}}* __restrict__ tenFlowgrad
|
||||
) { for (int intIndex = (blockIdx.x * blockDim.x) + threadIdx.x; intIndex < n; intIndex += blockDim.x * gridDim.x) {
|
||||
const int intN = ( intIndex / SIZE_3(tenFlowgrad) / SIZE_2(tenFlowgrad) / SIZE_1(tenFlowgrad) ) % SIZE_0(tenFlowgrad);
|
||||
const int intC = ( intIndex / SIZE_3(tenFlowgrad) / SIZE_2(tenFlowgrad) ) % SIZE_1(tenFlowgrad);
|
||||
const int intY = ( intIndex / SIZE_3(tenFlowgrad) ) % SIZE_2(tenFlowgrad);
|
||||
const int intX = ( intIndex ) % SIZE_3(tenFlowgrad);
|
||||
|
||||
assert(SIZE_1(tenFlow) == 2);
|
||||
|
||||
{{type}} fltFlowgrad = 0.0f;
|
||||
|
||||
{{type}} fltX = ({{type}}) (intX) + VALUE_4(tenFlow, intN, 0, intY, intX);
|
||||
{{type}} fltY = ({{type}}) (intY) + VALUE_4(tenFlow, intN, 1, intY, intX);
|
||||
|
||||
if (isfinite(fltX) == false) { return; }
|
||||
if (isfinite(fltY) == false) { return; }
|
||||
|
||||
int intNorthwestX = (int) (floor(fltX));
|
||||
int intNorthwestY = (int) (floor(fltY));
|
||||
int intNortheastX = intNorthwestX + 1;
|
||||
int intNortheastY = intNorthwestY;
|
||||
int intSouthwestX = intNorthwestX;
|
||||
int intSouthwestY = intNorthwestY + 1;
|
||||
int intSoutheastX = intNorthwestX + 1;
|
||||
int intSoutheastY = intNorthwestY + 1;
|
||||
|
||||
{{type}} fltNorthwest = 0.0f;
|
||||
{{type}} fltNortheast = 0.0f;
|
||||
{{type}} fltSouthwest = 0.0f;
|
||||
{{type}} fltSoutheast = 0.0f;
|
||||
|
||||
if (intC == 0) {
|
||||
fltNorthwest = (({{type}}) (-1.0f)) * (({{type}}) (intSoutheastY) - fltY);
|
||||
fltNortheast = (({{type}}) (+1.0f)) * (({{type}}) (intSouthwestY) - fltY);
|
||||
fltSouthwest = (({{type}}) (-1.0f)) * (fltY - ({{type}}) (intNortheastY));
|
||||
fltSoutheast = (({{type}}) (+1.0f)) * (fltY - ({{type}}) (intNorthwestY));
|
||||
|
||||
} else if (intC == 1) {
|
||||
fltNorthwest = (({{type}}) (intSoutheastX) - fltX) * (({{type}}) (-1.0f));
|
||||
fltNortheast = (fltX - ({{type}}) (intSouthwestX)) * (({{type}}) (-1.0f));
|
||||
fltSouthwest = (({{type}}) (intNortheastX) - fltX) * (({{type}}) (+1.0f));
|
||||
fltSoutheast = (fltX - ({{type}}) (intNorthwestX)) * (({{type}}) (+1.0f));
|
||||
|
||||
}
|
||||
|
||||
for (int intChannel = 0; intChannel < SIZE_1(tenOutgrad); intChannel += 1) {
|
||||
{{type}} fltIn = VALUE_4(tenIn, intN, intChannel, intY, intX);
|
||||
|
||||
if ((intNorthwestX >= 0) && (intNorthwestX < SIZE_3(tenOutgrad)) && (intNorthwestY >= 0) && (intNorthwestY < SIZE_2(tenOutgrad))) {
|
||||
fltFlowgrad += VALUE_4(tenOutgrad, intN, intChannel, intNorthwestY, intNorthwestX) * fltIn * fltNorthwest;
|
||||
}
|
||||
|
||||
if ((intNortheastX >= 0) && (intNortheastX < SIZE_3(tenOutgrad)) && (intNortheastY >= 0) && (intNortheastY < SIZE_2(tenOutgrad))) {
|
||||
fltFlowgrad += VALUE_4(tenOutgrad, intN, intChannel, intNortheastY, intNortheastX) * fltIn * fltNortheast;
|
||||
}
|
||||
|
||||
if ((intSouthwestX >= 0) && (intSouthwestX < SIZE_3(tenOutgrad)) && (intSouthwestY >= 0) && (intSouthwestY < SIZE_2(tenOutgrad))) {
|
||||
fltFlowgrad += VALUE_4(tenOutgrad, intN, intChannel, intSouthwestY, intSouthwestX) * fltIn * fltSouthwest;
|
||||
}
|
||||
|
||||
if ((intSoutheastX >= 0) && (intSoutheastX < SIZE_3(tenOutgrad)) && (intSoutheastY >= 0) && (intSoutheastY < SIZE_2(tenOutgrad))) {
|
||||
fltFlowgrad += VALUE_4(tenOutgrad, intN, intChannel, intSoutheastY, intSoutheastX) * fltIn * fltSoutheast;
|
||||
}
|
||||
}
|
||||
|
||||
tenFlowgrad[intIndex] = fltFlowgrad;
|
||||
} }
|
||||
"""
|
||||
|
||||
softsplat_ingrad = """
|
||||
extern "C" __global__ void __launch_bounds__(512) softsplat_ingrad(
|
||||
const int n,
|
||||
const {{type}}* __restrict__ tenIn,
|
||||
const {{type}}* __restrict__ tenFlow,
|
||||
const {{type}}* __restrict__ tenOutgrad,
|
||||
{{type}}* __restrict__ tenIngrad,
|
||||
{{type}}* __restrict__ tenFlowgrad
|
||||
) { for (int intIndex = (blockIdx.x * blockDim.x) + threadIdx.x; intIndex < n; intIndex += blockDim.x * gridDim.x) {
|
||||
const int intN = ( intIndex / SIZE_3(tenIngrad) / SIZE_2(tenIngrad) / SIZE_1(tenIngrad) ) % SIZE_0(tenIngrad);
|
||||
const int intC = ( intIndex / SIZE_3(tenIngrad) / SIZE_2(tenIngrad) ) % SIZE_1(tenIngrad);
|
||||
const int intY = ( intIndex / SIZE_3(tenIngrad) ) % SIZE_2(tenIngrad);
|
||||
const int intX = ( intIndex ) % SIZE_3(tenIngrad);
|
||||
|
||||
assert(SIZE_1(tenFlow) == 2);
|
||||
|
||||
{{type}} fltIngrad = 0.0f;
|
||||
|
||||
{{type}} fltX = ({{type}}) (intX) + VALUE_4(tenFlow, intN, 0, intY, intX);
|
||||
{{type}} fltY = ({{type}}) (intY) + VALUE_4(tenFlow, intN, 1, intY, intX);
|
||||
|
||||
if (isfinite(fltX) == false) { return; }
|
||||
if (isfinite(fltY) == false) { return; }
|
||||
|
||||
int intNorthwestX = (int) (floor(fltX));
|
||||
int intNorthwestY = (int) (floor(fltY));
|
||||
int intNortheastX = intNorthwestX + 1;
|
||||
int intNortheastY = intNorthwestY;
|
||||
int intSouthwestX = intNorthwestX;
|
||||
int intSouthwestY = intNorthwestY + 1;
|
||||
int intSoutheastX = intNorthwestX + 1;
|
||||
int intSoutheastY = intNorthwestY + 1;
|
||||
|
||||
{{type}} fltNorthwest = (({{type}}) (intSoutheastX) - fltX) * (({{type}}) (intSoutheastY) - fltY);
|
||||
{{type}} fltNortheast = (fltX - ({{type}}) (intSouthwestX)) * (({{type}}) (intSouthwestY) - fltY);
|
||||
{{type}} fltSouthwest = (({{type}}) (intNortheastX) - fltX) * (fltY - ({{type}}) (intNortheastY));
|
||||
{{type}} fltSoutheast = (fltX - ({{type}}) (intNorthwestX)) * (fltY - ({{type}}) (intNorthwestY));
|
||||
|
||||
if ((intNorthwestX >= 0) && (intNorthwestX < SIZE_3(tenOutgrad)) && (intNorthwestY >= 0) && (intNorthwestY < SIZE_2(tenOutgrad))) {
|
||||
fltIngrad += VALUE_4(tenOutgrad, intN, intC, intNorthwestY, intNorthwestX) * fltNorthwest;
|
||||
}
|
||||
|
||||
if ((intNortheastX >= 0) && (intNortheastX < SIZE_3(tenOutgrad)) && (intNortheastY >= 0) && (intNortheastY < SIZE_2(tenOutgrad))) {
|
||||
fltIngrad += VALUE_4(tenOutgrad, intN, intC, intNortheastY, intNortheastX) * fltNortheast;
|
||||
}
|
||||
|
||||
if ((intSouthwestX >= 0) && (intSouthwestX < SIZE_3(tenOutgrad)) && (intSouthwestY >= 0) && (intSouthwestY < SIZE_2(tenOutgrad))) {
|
||||
fltIngrad += VALUE_4(tenOutgrad, intN, intC, intSouthwestY, intSouthwestX) * fltSouthwest;
|
||||
}
|
||||
|
||||
if ((intSoutheastX >= 0) && (intSoutheastX < SIZE_3(tenOutgrad)) && (intSoutheastY >= 0) && (intSoutheastY < SIZE_2(tenOutgrad))) {
|
||||
fltIngrad += VALUE_4(tenOutgrad, intN, intC, intSoutheastY, intSoutheastX) * fltSoutheast;
|
||||
}
|
||||
|
||||
tenIngrad[intIndex] = fltIngrad;
|
||||
} }
|
||||
"""
|
||||
|
||||
softsplat_out = """
|
||||
extern "C" __global__ void __launch_bounds__(512) softsplat_out(
|
||||
const int n,
|
||||
const {{type}}* __restrict__ tenIn,
|
||||
const {{type}}* __restrict__ tenFlow,
|
||||
{{type}}* __restrict__ tenOut
|
||||
) { for (int intIndex = (blockIdx.x * blockDim.x) + threadIdx.x; intIndex < n; intIndex += blockDim.x * gridDim.x) {
|
||||
const int intN = ( intIndex / SIZE_3(tenOut) / SIZE_2(tenOut) / SIZE_1(tenOut) ) % SIZE_0(tenOut);
|
||||
const int intC = ( intIndex / SIZE_3(tenOut) / SIZE_2(tenOut) ) % SIZE_1(tenOut);
|
||||
const int intY = ( intIndex / SIZE_3(tenOut) ) % SIZE_2(tenOut);
|
||||
const int intX = ( intIndex ) % SIZE_3(tenOut);
|
||||
|
||||
assert(SIZE_1(tenFlow) == 2);
|
||||
|
||||
{{type}} fltX = ({{type}}) (intX) + VALUE_4(tenFlow, intN, 0, intY, intX);
|
||||
{{type}} fltY = ({{type}}) (intY) + VALUE_4(tenFlow, intN, 1, intY, intX);
|
||||
|
||||
if (isfinite(fltX) == false) { return; }
|
||||
if (isfinite(fltY) == false) { return; }
|
||||
|
||||
{{type}} fltIn = VALUE_4(tenIn, intN, intC, intY, intX);
|
||||
|
||||
int intNorthwestX = (int) (floor(fltX));
|
||||
int intNorthwestY = (int) (floor(fltY));
|
||||
int intNortheastX = intNorthwestX + 1;
|
||||
int intNortheastY = intNorthwestY;
|
||||
int intSouthwestX = intNorthwestX;
|
||||
int intSouthwestY = intNorthwestY + 1;
|
||||
int intSoutheastX = intNorthwestX + 1;
|
||||
int intSoutheastY = intNorthwestY + 1;
|
||||
|
||||
{{type}} fltNorthwest = (({{type}}) (intSoutheastX) - fltX) * (({{type}}) (intSoutheastY) - fltY);
|
||||
{{type}} fltNortheast = (fltX - ({{type}}) (intSouthwestX)) * (({{type}}) (intSouthwestY) - fltY);
|
||||
{{type}} fltSouthwest = (({{type}}) (intNortheastX) - fltX) * (fltY - ({{type}}) (intNortheastY));
|
||||
{{type}} fltSoutheast = (fltX - ({{type}}) (intNorthwestX)) * (fltY - ({{type}}) (intNorthwestY));
|
||||
|
||||
if ((intNorthwestX >= 0) && (intNorthwestX < SIZE_3(tenOut)) && (intNorthwestY >= 0) && (intNorthwestY < SIZE_2(tenOut))) {
|
||||
atomicAdd(&tenOut[OFFSET_4(tenOut, intN, intC, intNorthwestY, intNorthwestX)], fltIn * fltNorthwest);
|
||||
}
|
||||
|
||||
if ((intNortheastX >= 0) && (intNortheastX < SIZE_3(tenOut)) && (intNortheastY >= 0) && (intNortheastY < SIZE_2(tenOut))) {
|
||||
atomicAdd(&tenOut[OFFSET_4(tenOut, intN, intC, intNortheastY, intNortheastX)], fltIn * fltNortheast);
|
||||
}
|
||||
|
||||
if ((intSouthwestX >= 0) && (intSouthwestX < SIZE_3(tenOut)) && (intSouthwestY >= 0) && (intSouthwestY < SIZE_2(tenOut))) {
|
||||
atomicAdd(&tenOut[OFFSET_4(tenOut, intN, intC, intSouthwestY, intSouthwestX)], fltIn * fltSouthwest);
|
||||
}
|
||||
|
||||
if ((intSoutheastX >= 0) && (intSoutheastX < SIZE_3(tenOut)) && (intSoutheastY >= 0) && (intSoutheastY < SIZE_2(tenOut))) {
|
||||
atomicAdd(&tenOut[OFFSET_4(tenOut, intN, intC, intSoutheastY, intSoutheastX)], fltIn * fltSoutheast);
|
||||
}
|
||||
} }
|
||||
"""
|
||||
|
||||
|
||||
# end
|
||||
|
||||
class softsplat_func(torch.autograd.Function):
|
||||
@staticmethod
|
||||
@torch.cuda.amp.custom_fwd(cast_inputs=torch.float32)
|
||||
def forward(self, tenIn, tenFlow):
|
||||
tenOut = tenIn.new_zeros(
|
||||
[tenIn.shape[0], tenIn.shape[1], tenIn.shape[2], tenIn.shape[3]]
|
||||
)
|
||||
|
||||
if tenIn.is_cuda == True:
|
||||
cuda_launch(
|
||||
cuda_kernel(
|
||||
"softsplat_out",
|
||||
softsplat_out,
|
||||
{"tenIn": tenIn, "tenFlow": tenFlow, "tenOut": tenOut},
|
||||
)
|
||||
)(
|
||||
grid=tuple([int((tenOut.nelement() + 512 - 1) / 512), 1, 1]),
|
||||
block=tuple([512, 1, 1]),
|
||||
args=[
|
||||
cuda_int32(tenOut.nelement()),
|
||||
tenIn.data_ptr(),
|
||||
tenFlow.data_ptr(),
|
||||
tenOut.data_ptr(),
|
||||
],
|
||||
stream=collections.namedtuple("Stream", "ptr")(
|
||||
torch.cuda.current_stream().cuda_stream
|
||||
),
|
||||
)
|
||||
|
||||
elif tenIn.is_cuda != True:
|
||||
assert False
|
||||
|
||||
# end
|
||||
|
||||
self.save_for_backward(tenIn, tenFlow)
|
||||
|
||||
return tenOut
|
||||
|
||||
# end
|
||||
|
||||
@staticmethod
|
||||
@torch.cuda.amp.custom_bwd
|
||||
def backward(self, tenOutgrad):
|
||||
tenIn, tenFlow = self.saved_tensors
|
||||
|
||||
tenOutgrad = tenOutgrad.contiguous()
|
||||
assert tenOutgrad.is_cuda == True
|
||||
|
||||
tenIngrad = (
|
||||
tenIn.new_zeros(
|
||||
[tenIn.shape[0], tenIn.shape[1], tenIn.shape[2], tenIn.shape[3]]
|
||||
)
|
||||
if self.needs_input_grad[0] == True
|
||||
else None
|
||||
)
|
||||
tenFlowgrad = (
|
||||
tenFlow.new_zeros(
|
||||
[tenFlow.shape[0], tenFlow.shape[1], tenFlow.shape[2], tenFlow.shape[3]]
|
||||
)
|
||||
if self.needs_input_grad[1] == True
|
||||
else None
|
||||
)
|
||||
|
||||
if tenIngrad is not None:
|
||||
cuda_launch(
|
||||
cuda_kernel(
|
||||
"softsplat_ingrad",
|
||||
softsplat_ingrad,
|
||||
{
|
||||
"tenIn": tenIn,
|
||||
"tenFlow": tenFlow,
|
||||
"tenOutgrad": tenOutgrad,
|
||||
"tenIngrad": tenIngrad,
|
||||
"tenFlowgrad": tenFlowgrad,
|
||||
},
|
||||
)
|
||||
)(
|
||||
grid=tuple([int((tenIngrad.nelement() + 512 - 1) / 512), 1, 1]),
|
||||
block=tuple([512, 1, 1]),
|
||||
args=[
|
||||
cuda_int32(tenIngrad.nelement()),
|
||||
tenIn.data_ptr(),
|
||||
tenFlow.data_ptr(),
|
||||
tenOutgrad.data_ptr(),
|
||||
tenIngrad.data_ptr(),
|
||||
None,
|
||||
],
|
||||
stream=collections.namedtuple("Stream", "ptr")(
|
||||
torch.cuda.current_stream().cuda_stream
|
||||
),
|
||||
)
|
||||
# end
|
||||
|
||||
if tenFlowgrad is not None:
|
||||
cuda_launch(
|
||||
cuda_kernel(
|
||||
"softsplat_flowgrad",
|
||||
softsplat_flowgrad,
|
||||
{
|
||||
"tenIn": tenIn,
|
||||
"tenFlow": tenFlow,
|
||||
"tenOutgrad": tenOutgrad,
|
||||
"tenIngrad": tenIngrad,
|
||||
"tenFlowgrad": tenFlowgrad,
|
||||
},
|
||||
)
|
||||
)(
|
||||
grid=tuple([int((tenFlowgrad.nelement() + 512 - 1) / 512), 1, 1]),
|
||||
block=tuple([512, 1, 1]),
|
||||
args=[
|
||||
cuda_int32(tenFlowgrad.nelement()),
|
||||
tenIn.data_ptr(),
|
||||
tenFlow.data_ptr(),
|
||||
tenOutgrad.data_ptr(),
|
||||
None,
|
||||
tenFlowgrad.data_ptr(),
|
||||
],
|
||||
stream=collections.namedtuple("Stream", "ptr")(
|
||||
torch.cuda.current_stream().cuda_stream
|
||||
),
|
||||
)
|
||||
# end
|
||||
|
||||
return tenIngrad, tenFlowgrad
|
||||
|
||||
# end
|
||||
|
||||
|
||||
def FunctionSoftsplat(tenInput, tenFlow, tenMetric, strType):
|
||||
assert tenMetric is None or tenMetric.shape[1] == 1
|
||||
assert strType in ["summation", "average", "linear", "softmax"]
|
||||
|
||||
if strType == "average":
|
||||
tenInput = torch.cat(
|
||||
[
|
||||
tenInput,
|
||||
tenInput.new_ones(
|
||||
tenInput.shape[0], 1, tenInput.shape[2], tenInput.shape[3]
|
||||
),
|
||||
],
|
||||
1,
|
||||
)
|
||||
|
||||
elif strType == "linear":
|
||||
tenInput = torch.cat([tenInput * tenMetric, tenMetric], 1)
|
||||
|
||||
elif strType == "softmax":
|
||||
tenInput = torch.cat([tenInput * tenMetric.exp(), tenMetric.exp()], 1)
|
||||
|
||||
# end
|
||||
|
||||
tenOutput = softsplat_func.apply(tenInput, tenFlow)
|
||||
|
||||
if strType != "summation":
|
||||
tenNormalize = tenOutput[:, -1:, :, :]
|
||||
|
||||
tenNormalize[tenNormalize == 0.0] = 1.0
|
||||
|
||||
tenOutput = tenOutput[:, :-1, :, :] / tenNormalize
|
||||
# end
|
||||
|
||||
return tenOutput
|
||||
|
||||
|
||||
# end
|
||||
|
||||
|
||||
class ModuleSoftsplat(torch.nn.Module):
|
||||
def __init__(self, strType):
|
||||
super().__init__()
|
||||
|
||||
self.strType = strType
|
||||
|
||||
# end
|
||||
|
||||
def forward(self, tenInput, tenFlow, tenMetric):
|
||||
return FunctionSoftsplat(tenInput, tenFlow, tenMetric, self.strType)
|
||||
|
||||
# end
|
||||
|
||||
|
||||
# end
|
||||
|
||||
|
||||
|
||||
def softsplat(
|
||||
tenIn: torch.Tensor, tenFlow: torch.Tensor, tenMetric: torch.Tensor, strMode: str
|
||||
):
|
||||
assert strMode.split("-")[0] in ["sum", "avg", "linear", "soft"]
|
||||
|
||||
if strMode == "sum":
|
||||
assert tenMetric is None
|
||||
if strMode == "avg":
|
||||
assert tenMetric is None
|
||||
if strMode.split("-")[0] == "linear":
|
||||
assert tenMetric is not None
|
||||
if strMode.split("-")[0] == "soft":
|
||||
assert tenMetric is not None
|
||||
|
||||
if strMode == "avg":
|
||||
tenIn = torch.cat(
|
||||
[
|
||||
tenIn,
|
||||
tenIn.new_ones([tenIn.shape[0], 1, tenIn.shape[2], tenIn.shape[3]]),
|
||||
],
|
||||
1,
|
||||
)
|
||||
|
||||
elif strMode.split("-")[0] == "linear":
|
||||
tenIn = torch.cat([tenIn * tenMetric, tenMetric], 1)
|
||||
|
||||
elif strMode.split("-")[0] == "soft":
|
||||
tenIn = torch.cat([tenIn * tenMetric.exp(), tenMetric.exp()], 1)
|
||||
|
||||
# end
|
||||
|
||||
tenOut = softsplat_func.apply(tenIn, tenFlow)
|
||||
|
||||
if strMode.split("-")[0] in ["avg", "linear", "soft"]:
|
||||
tenNormalize = tenOut[:, -1:, :, :]
|
||||
|
||||
if len(strMode.split("-")) == 1:
|
||||
tenNormalize = tenNormalize + 0.0000001
|
||||
|
||||
elif strMode.split("-")[1] == "addeps":
|
||||
tenNormalize = tenNormalize + 0.0000001
|
||||
|
||||
elif strMode.split("-")[1] == "zeroeps":
|
||||
tenNormalize[tenNormalize == 0.0] = 1.0
|
||||
|
||||
elif strMode.split("-")[1] == "clipeps":
|
||||
tenNormalize = tenNormalize.clip(0.0000001, None)
|
||||
|
||||
# end
|
||||
|
||||
tenOut = tenOut[:, :-1, :, :] / tenNormalize
|
||||
# end
|
||||
|
||||
return tenOut
|
||||
|
||||
|
||||
# end
|
||||
|
||||
__all__ = ["FunctionSoftsplat", "ModuleSoftsplat", "softsplat", "softsplat_func"]
|
||||
@@ -1,242 +0,0 @@
|
||||
import cupy
|
||||
import os
|
||||
import re
|
||||
import torch
|
||||
import typing
|
||||
from pathlib import Path
|
||||
import platform
|
||||
|
||||
##########################################################
|
||||
|
||||
|
||||
objCudacache = {}
|
||||
|
||||
|
||||
def cuda_int32(intIn: int):
|
||||
return cupy.int32(intIn)
|
||||
|
||||
|
||||
# end
|
||||
|
||||
|
||||
def cuda_float32(fltIn: float):
|
||||
return cupy.float32(fltIn)
|
||||
|
||||
|
||||
# end
|
||||
|
||||
|
||||
def cuda_kernel(strFunction: str, strKernel: str, objVariables: typing.Dict, **replace_kwargs):
|
||||
if "device" not in objCudacache:
|
||||
objCudacache["device"] = torch.cuda.get_device_name()
|
||||
# end
|
||||
|
||||
strKey = strFunction
|
||||
|
||||
for strVariable in objVariables:
|
||||
objValue = objVariables[strVariable]
|
||||
|
||||
strKey += strVariable
|
||||
|
||||
if objValue is None:
|
||||
continue
|
||||
|
||||
elif type(objValue) == int:
|
||||
strKey += str(objValue)
|
||||
|
||||
elif type(objValue) == float:
|
||||
strKey += str(objValue)
|
||||
|
||||
elif type(objValue) == bool:
|
||||
strKey += str(objValue)
|
||||
|
||||
elif type(objValue) == str:
|
||||
strKey += objValue
|
||||
|
||||
elif type(objValue) == torch.Tensor:
|
||||
strKey += str(objValue.dtype)
|
||||
strKey += str(objValue.shape)
|
||||
strKey += str(objValue.stride())
|
||||
|
||||
elif True:
|
||||
print(strVariable, type(objValue))
|
||||
assert False
|
||||
|
||||
# end
|
||||
# end
|
||||
|
||||
strKey += objCudacache["device"]
|
||||
|
||||
if strKey not in objCudacache:
|
||||
for strVariable in objVariables:
|
||||
objValue = objVariables[strVariable]
|
||||
|
||||
if objValue is None:
|
||||
continue
|
||||
|
||||
elif type(objValue) == int:
|
||||
strKernel = strKernel.replace("{{" + strVariable + "}}", str(objValue))
|
||||
|
||||
elif type(objValue) == float:
|
||||
strKernel = strKernel.replace("{{" + strVariable + "}}", str(objValue))
|
||||
|
||||
elif type(objValue) == bool:
|
||||
strKernel = strKernel.replace("{{" + strVariable + "}}", str(objValue))
|
||||
|
||||
elif type(objValue) == str:
|
||||
strKernel = strKernel.replace("{{" + strVariable + "}}", objValue)
|
||||
|
||||
elif type(objValue) == torch.Tensor and objValue.dtype == torch.uint8:
|
||||
strKernel = strKernel.replace("{{type}}", "unsigned char")
|
||||
|
||||
elif type(objValue) == torch.Tensor and objValue.dtype == torch.float16:
|
||||
strKernel = strKernel.replace("{{type}}", "half")
|
||||
|
||||
elif type(objValue) == torch.Tensor and objValue.dtype == torch.float32:
|
||||
strKernel = strKernel.replace("{{type}}", "float")
|
||||
|
||||
elif type(objValue) == torch.Tensor and objValue.dtype == torch.float64:
|
||||
strKernel = strKernel.replace("{{type}}", "double")
|
||||
|
||||
elif type(objValue) == torch.Tensor and objValue.dtype == torch.int32:
|
||||
strKernel = strKernel.replace("{{type}}", "int")
|
||||
|
||||
elif type(objValue) == torch.Tensor and objValue.dtype == torch.int64:
|
||||
strKernel = strKernel.replace("{{type}}", "long")
|
||||
|
||||
elif type(objValue) == torch.Tensor:
|
||||
print(strVariable, objValue.dtype)
|
||||
assert False
|
||||
|
||||
elif True:
|
||||
print(strVariable, type(objValue))
|
||||
assert False
|
||||
|
||||
# end
|
||||
# end
|
||||
|
||||
while True:
|
||||
objMatch = re.search("(SIZE_)([0-4])(\()([^\)]*)(\))", strKernel)
|
||||
|
||||
if objMatch is None:
|
||||
break
|
||||
# end
|
||||
|
||||
intArg = int(objMatch.group(2))
|
||||
|
||||
strTensor = objMatch.group(4)
|
||||
intSizes = objVariables[strTensor].size()
|
||||
|
||||
strKernel = strKernel.replace(objMatch.group(), str(intSizes[intArg]))
|
||||
# end
|
||||
|
||||
while True:
|
||||
objMatch = re.search("(OFFSET_)([0-4])(\()([^\)]+)(\))", strKernel)
|
||||
|
||||
if objMatch is None:
|
||||
break
|
||||
# end
|
||||
|
||||
intArgs = int(objMatch.group(2))
|
||||
strArgs = objMatch.group(4).split(",")
|
||||
|
||||
strTensor = strArgs[0]
|
||||
intStrides = objVariables[strTensor].stride()
|
||||
strIndex = [
|
||||
"(("
|
||||
+ strArgs[intArg + 1].replace("{", "(").replace("}", ")").strip()
|
||||
+ ")*"
|
||||
+ str(intStrides[intArg])
|
||||
+ ")"
|
||||
for intArg in range(intArgs)
|
||||
]
|
||||
|
||||
strKernel = strKernel.replace(
|
||||
objMatch.group(0), "(" + str.join("+", strIndex) + ")"
|
||||
)
|
||||
# end
|
||||
|
||||
while True:
|
||||
objMatch = re.search("(VALUE_)([0-4])(\()", strKernel)
|
||||
|
||||
if objMatch is None:
|
||||
break
|
||||
# end
|
||||
|
||||
intStart = objMatch.span()[1]
|
||||
intStop = objMatch.span()[1]
|
||||
intParentheses = 1
|
||||
|
||||
while True:
|
||||
intParentheses += 1 if strKernel[intStop] == "(" else 0
|
||||
intParentheses -= 1 if strKernel[intStop] == ")" else 0
|
||||
|
||||
if intParentheses == 0:
|
||||
break
|
||||
# end
|
||||
|
||||
intStop += 1
|
||||
# end
|
||||
|
||||
intArgs = int(objMatch.group(2))
|
||||
strArgs = strKernel[intStart:intStop].split(",")
|
||||
|
||||
assert intArgs == len(strArgs) - 1
|
||||
|
||||
strTensor = strArgs[0]
|
||||
intStrides = objVariables[strTensor].stride()
|
||||
|
||||
strIndex = []
|
||||
|
||||
for intArg in range(intArgs):
|
||||
strIndex.append(
|
||||
"(("
|
||||
+ strArgs[intArg + 1].replace("{", "(").replace("}", ")").strip()
|
||||
+ ")*"
|
||||
+ str(intStrides[intArg])
|
||||
+ ")"
|
||||
)
|
||||
# end
|
||||
|
||||
strKernel = strKernel.replace(
|
||||
"VALUE_" + str(intArgs) + "(" + strKernel[intStart:intStop] + ")",
|
||||
strTensor + "[" + str.join("+", strIndex) + "]",
|
||||
)
|
||||
# end
|
||||
|
||||
for replace_key, value in replace_kwargs.items():
|
||||
strKernel = strKernel.replace(replace_key, value)
|
||||
|
||||
objCudacache[strKey] = {"strFunction": strFunction, "strKernel": strKernel}
|
||||
# end
|
||||
|
||||
return strKey
|
||||
|
||||
|
||||
# end
|
||||
def get_cuda_home_path():
|
||||
if "CUDA_HOME" in os.environ:
|
||||
return os.environ["CUDA_HOME"]
|
||||
import torch
|
||||
torch_lib_path = Path(torch.__file__).parent / "lib"
|
||||
torch_lib_path = str(torch_lib_path.resolve())
|
||||
if os.path.exists(torch_lib_path):
|
||||
nvrtc = filter(lambda lib_file: "nvrtc-builtins" in lib_file, os.listdir(torch_lib_path))
|
||||
nvrtc = list(nvrtc)
|
||||
return torch_lib_path if len(nvrtc) > 0 else None
|
||||
|
||||
@cupy.memoize(for_each_device=True)
|
||||
def cuda_launch(strKey: str):
|
||||
if True:#"CUDA_HOME" not in os.environ:
|
||||
cuda_home = get_cuda_home_path()
|
||||
if cuda_home is not None:
|
||||
os.environ["CUDA_HOME"] = cuda_home
|
||||
os.environ["CUDA_PATH"] = cuda_home
|
||||
else:
|
||||
os.environ["CUDA_HOME"] = "/usr/local/cuda/"
|
||||
os.environ["CUDA_PATH"] = "/usr/local/cuda/"
|
||||
# print(objCudacache[strKey]['strKernel'])
|
||||
# return cupy.cuda.compile_with_cache(objCudacache[strKey]['strKernel'], tuple(['-I ' + os.environ['CUDA_HOME'], '-I ' + os.environ['CUDA_HOME'] + '/include'])).get_function(objCudacache[strKey]['strFunction'])
|
||||
return cupy.RawModule(code=objCudacache[strKey]["strKernel"]).get_function(
|
||||
objCudacache[strKey]["strFunction"]
|
||||
)
|
||||
@@ -1,150 +0,0 @@
|
||||
import comfy.model_management as model_management
|
||||
import torch
|
||||
import torch.multiprocessing as mp
|
||||
from .worker_process import f
|
||||
from .utils import to_shared_memory
|
||||
|
||||
parent_conn, child_conn, process = None, None, None
|
||||
device = model_management.get_torch_device()
|
||||
|
||||
def req_to_taichi_process(op_name, *tensors):
|
||||
global parent_conn, child_conn, process
|
||||
if parent_conn is None:
|
||||
mp.set_start_method('spawn', force=True)
|
||||
parent_conn, child_conn = mp.Pipe()
|
||||
process = mp.Process(target=f, args=(child_conn, device))
|
||||
process.start()
|
||||
|
||||
tensors = to_shared_memory(tensors)
|
||||
parent_conn.send((op_name, tensors))
|
||||
result = parent_conn.recv()
|
||||
del tensors
|
||||
|
||||
if type(result) not in [tuple, list]:
|
||||
raise Exception(result)
|
||||
|
||||
return [tensor.to(device) for tensor in result]
|
||||
|
||||
def softsplat(
|
||||
tenIn: torch.Tensor, tenFlow: torch.Tensor, tenMetric: torch.Tensor, strMode: str
|
||||
):
|
||||
assert strMode.split("-")[0] in ["sum", "avg", "linear", "soft"]
|
||||
|
||||
if strMode == "sum":
|
||||
assert tenMetric is None
|
||||
if strMode == "avg":
|
||||
assert tenMetric is None
|
||||
if strMode.split("-")[0] == "linear":
|
||||
assert tenMetric is not None
|
||||
if strMode.split("-")[0] == "soft":
|
||||
assert tenMetric is not None
|
||||
|
||||
if strMode == "avg":
|
||||
tenIn = torch.cat(
|
||||
[
|
||||
tenIn,
|
||||
tenIn.new_ones([tenIn.shape[0], 1, tenIn.shape[2], tenIn.shape[3]]),
|
||||
],
|
||||
1,
|
||||
)
|
||||
|
||||
elif strMode.split("-")[0] == "linear":
|
||||
tenIn = torch.cat([tenIn * tenMetric, tenMetric], 1)
|
||||
|
||||
elif strMode.split("-")[0] == "soft":
|
||||
tenIn = torch.cat([tenIn * tenMetric.exp(), tenMetric.exp()], 1)
|
||||
|
||||
# end
|
||||
|
||||
tenOut = req_to_taichi_process("softsplat_out", tenIn, tenFlow)[0]
|
||||
|
||||
if strMode.split("-")[0] in ["avg", "linear", "soft"]:
|
||||
tenNormalize = tenOut[:, -1:, :, :]
|
||||
|
||||
if len(strMode.split("-")) == 1:
|
||||
tenNormalize = tenNormalize + 0.0000001
|
||||
|
||||
elif strMode.split("-")[1] == "addeps":
|
||||
tenNormalize = tenNormalize + 0.0000001
|
||||
|
||||
elif strMode.split("-")[1] == "zeroeps":
|
||||
tenNormalize[tenNormalize == 0.0] = 1.0
|
||||
|
||||
elif strMode.split("-")[1] == "clipeps":
|
||||
tenNormalize = tenNormalize.clip(0.0000001, None)
|
||||
|
||||
# end
|
||||
|
||||
tenOut = tenOut[:, :-1, :, :] / tenNormalize
|
||||
# end
|
||||
|
||||
return tenOut
|
||||
|
||||
def FunctionSoftsplat(tenInput, tenFlow, tenMetric, strType):
|
||||
assert tenMetric is None or tenMetric.shape[1] == 1
|
||||
assert strType in ["summation", "average", "linear", "softmax"]
|
||||
|
||||
if strType == "average":
|
||||
tenInput = torch.cat(
|
||||
[
|
||||
tenInput,
|
||||
tenInput.new_ones(
|
||||
tenInput.shape[0], 1, tenInput.shape[2], tenInput.shape[3]
|
||||
),
|
||||
],
|
||||
1,
|
||||
)
|
||||
|
||||
elif strType == "linear":
|
||||
tenInput = torch.cat([tenInput * tenMetric, tenMetric], 1)
|
||||
|
||||
elif strType == "softmax":
|
||||
tenInput = torch.cat([tenInput * tenMetric.exp(), tenMetric.exp()], 1)
|
||||
|
||||
# end
|
||||
|
||||
tenOutput = req_to_taichi_process("softsplat_out", tenInput, tenFlow)[0]
|
||||
|
||||
if strType != "summation":
|
||||
tenNormalize = tenOutput[:, -1:, :, :]
|
||||
|
||||
tenNormalize[tenNormalize == 0.0] = 1.0
|
||||
|
||||
tenOutput = tenOutput[:, :-1, :, :] / tenNormalize
|
||||
# end
|
||||
|
||||
return tenOutput
|
||||
|
||||
|
||||
# end
|
||||
|
||||
|
||||
class ModuleSoftsplat(torch.nn.Module):
|
||||
def __init__(self, strType):
|
||||
super(self).__init__()
|
||||
|
||||
self.strType = strType
|
||||
|
||||
# end
|
||||
|
||||
def forward(self, tenInput, tenFlow, tenMetric):
|
||||
return FunctionSoftsplat(tenInput, tenFlow, tenMetric, self.strType)
|
||||
|
||||
def softsplat_func(tenIn, tenFlow):
|
||||
return req_to_taichi_process("softsplat_out", tenIn, tenFlow)[0]
|
||||
|
||||
class costvol_func:
|
||||
@staticmethod
|
||||
def apply(tenOne, tenTwo):
|
||||
return req_to_taichi_process("costvol_out", tenOne, tenTwo)[0]
|
||||
|
||||
class sepconv_func:
|
||||
@staticmethod
|
||||
def apply(tenIn, tenVer, tenHor):
|
||||
return req_to_taichi_process("sepconv_out", tenIn, tenVer, tenHor)[0]
|
||||
|
||||
def init():
|
||||
one_sample = torch.ones(1, 3, 16, 16, dtype=torch.float32, device=device)
|
||||
softsplat_func(one_sample, one_sample)
|
||||
costvol_func.apply(one_sample, one_sample)
|
||||
sepconv_func.apply(one_sample, one_sample, one_sample)
|
||||
@@ -1,6 +0,0 @@
|
||||
import torch
|
||||
class FunctionAdaCoF(torch.autograd.Function):
|
||||
# end
|
||||
@staticmethod
|
||||
def forward(ctx, input, weight, offset_i, offset_j, dilation):
|
||||
raise NotImplementedError()
|
||||
@@ -1,2 +0,0 @@
|
||||
def batch_edt(img, block=1024):
|
||||
raise NotImplementedError()
|
||||
@@ -1,15 +0,0 @@
|
||||
import torch
|
||||
|
||||
class _FunctionCorrelation(torch.autograd.Function):
|
||||
@staticmethod
|
||||
def forward(self, first, second):
|
||||
raise NotImplementedError()
|
||||
|
||||
def FunctionCorrelation(tenFirst, tenSecond):
|
||||
raise NotImplementedError()
|
||||
return _FunctionCorrelation.apply(tenFirst, tenSecond)
|
||||
|
||||
class ModuleCorrelation(torch.nn.Module):
|
||||
def __init__(self):
|
||||
raise NotImplementedError()
|
||||
super(ModuleCorrelation, self).__init__()
|
||||
@@ -1,26 +0,0 @@
|
||||
import taichi as ti
|
||||
import taichi.math as tm
|
||||
|
||||
""" @ti.kernel
|
||||
def costvol_out(tenOne: ti.types.ndarray(), tltOne: ti.types.ndarray(), tenTwo: ti.types.ndarray(), tenOut: ti.types.ndarray()):
|
||||
N, C, H, W = tenOut.shape
|
||||
for i, ch, y, x in ti.ndrange(N, C, H, W):
|
||||
for intValue in range(tenOne.shape[1]):
|
||||
tltOne[intValue] = tenOne[i, intValue, y, x]
|
||||
|
||||
tenOut_ch = 0
|
||||
for intOy in range(y - 4, y + 4 + 1):
|
||||
for intOx in range(x - 4, x + 4 + 1):
|
||||
point = tm.ivec2(intOx, intOy)
|
||||
fltValue = 0.0
|
||||
for intValue in range(ch):
|
||||
if (point.y >= 0) and (point.y < H) and (point.x >= 0) and (point.x < W):
|
||||
fltValue += ti.abs(tltOne[intValue] - tenTwo[i, intValue, point.y, point.x])
|
||||
else:
|
||||
fltValue += ti.abs(tltOne[intValue])
|
||||
|
||||
tenOut[i, tenOut_ch, y, x] = fltValue / tenOne.shape[1]
|
||||
tenOut_ch += 1 """
|
||||
|
||||
def worker_interface(op_name, tensors):
|
||||
raise NotImplementedError(op_name)
|
||||
@@ -1,126 +0,0 @@
|
||||
#Seperate taichi kernels to another file so that comfy.model_management won't be called in the new process
|
||||
|
||||
import taichi as ti
|
||||
import taichi.math as tm
|
||||
|
||||
@ti.func
|
||||
def put_to_tenOut(tenOut: ti.types.ndarray(), fltIn: ti.i32, flt: ti.i32, pos:tm.uvec2, i:ti.i32, ch:ti.i32):
|
||||
N, C, H, W = tenOut.shape
|
||||
if (pos.x >= 0) and (pos.x < W) and (pos.y >= 0) and (pos.y < H):
|
||||
tenOut[i, ch, pos.y, pos.x] += fltIn * flt
|
||||
@ti.kernel
|
||||
def softsplat_out(tenIn: ti.types.ndarray(), tenFlow: ti.types.ndarray(), tenOut: ti.types.ndarray()):
|
||||
N, C, H, W = tenIn.shape
|
||||
for i, ch, y, x in ti.ndrange(N, C, H, W):
|
||||
fltX = x + tenFlow[i, 0, y, x]
|
||||
fltY = y + tenFlow[i, 1, y, x]
|
||||
fltIn = tenIn[i, ch, y, x]
|
||||
|
||||
northWest = tm.ivec2(ti.floor(fltX), ti.floor(fltY))
|
||||
northEast = northWest + [1, 0]
|
||||
southWest = northWest + [0, 1]
|
||||
southEast = northWest + [1, 1]
|
||||
|
||||
fltNorthwest = (southEast.x - fltX) * (southEast.y - fltY)
|
||||
fltNortheast = (fltX - southWest.x) * (southWest.y - fltY)
|
||||
fltSouthwest = (northEast.x - fltX) * (fltY - northEast.y)
|
||||
fltSoutheast = (fltX - northWest.x) * (fltY - northWest.y)
|
||||
|
||||
put_to_tenOut(tenOut, fltIn, fltNorthwest, northWest, i, ch)
|
||||
put_to_tenOut(tenOut, fltIn, fltNortheast, northEast, i, ch)
|
||||
put_to_tenOut(tenOut, fltIn, fltSouthwest, southWest, i, ch)
|
||||
put_to_tenOut(tenOut, fltIn, fltSoutheast, southEast, i, ch)
|
||||
|
||||
@ti.func
|
||||
def add_to_fltFlowgrad(fltFlowgrad, tenOutgrad, fltIn, flt, pos, i, ch):
|
||||
N, C, H, W = tenOutgrad.shape
|
||||
if (pos.x >= 0) and (pos.x < W) and (pos.y >= 0) and (pos.y < H):
|
||||
fltFlowgrad += tenOutgrad[i, ch, pos.y, pos.x] * fltIn * flt
|
||||
|
||||
@ti.kernel
|
||||
def softsplat_flowgrad(
|
||||
tenIn: ti.types.ndarray(),
|
||||
tenFlow: ti.types.ndarray(),
|
||||
tenOutgrad: ti.types.ndarray(),
|
||||
tenIngrad: ti.types.ndarray(),
|
||||
tenFlowgrad: ti.types.ndarray()
|
||||
):
|
||||
N, C, H, W = tenFlowgrad.shape
|
||||
for i, ch, y, x in ti.ndrange(N, C, H, W):
|
||||
fltFlowgrad = 0.0
|
||||
fltX = x + tenFlow[i, 0, y, x]
|
||||
fltY = y + tenFlow[i, 1, y, x]
|
||||
|
||||
northWest = tm.vec2(ti.floor(fltX, dtype=ti.i32), ti.floor(fltY, dtype=ti.i32))
|
||||
northEast = tm.vec2(northWest.x + 1, northWest.y)
|
||||
southWest = tm.vec2(northWest.x, northWest.y + 1)
|
||||
southEast = tm.vec2(northWest.x + 1, northWest.y + 1)
|
||||
|
||||
if ch == 0:
|
||||
fltNorthwest = -1.0 * (southEast.y - fltY)
|
||||
fltNortheast = +1.0 * (southWest.y - fltY)
|
||||
fltSouthwest = -1.0 * (fltY - northEast.y)
|
||||
fltSoutheast = +1.0 * (fltY - northWest.y)
|
||||
|
||||
elif ch == 1:
|
||||
fltNorthwest = -1.0 * (southEast.x - fltX)
|
||||
fltNortheast = -1.0 * (fltX - southWest.x)
|
||||
fltSouthwest = +1.0 * (northEast.x - fltX)
|
||||
fltSoutheast = +1.0 * (fltX - northWest.x)
|
||||
|
||||
for outgrad_ch in ti.ndrange(tenOutgrad.shape[1]):
|
||||
fltIn = tenIn[i, outgrad_ch, y, x]
|
||||
add_to_fltFlowgrad(fltFlowgrad, tenOutgrad, fltIn, fltNorthwest, northWest, i, outgrad_ch)
|
||||
add_to_fltFlowgrad(fltFlowgrad, tenOutgrad, fltIn, fltNortheast, northEast, i, outgrad_ch)
|
||||
add_to_fltFlowgrad(fltFlowgrad, tenOutgrad, fltIn, fltSouthwest, southWest, i, outgrad_ch)
|
||||
add_to_fltFlowgrad(fltFlowgrad, tenOutgrad, fltIn, fltSoutheast, southEast, i, outgrad_ch)
|
||||
|
||||
tenFlowgrad[i] = fltFlowgrad #Is 'i' the same as intIndex?
|
||||
|
||||
@ti.func
|
||||
def add_to_fltIngrad(fltIngrad, tenOutgrad, flt, pos, i, ch):
|
||||
N, C, H, W = tenOutgrad.shape
|
||||
if (pos.x >= 0) and (pos.x < W) and (pos.y >= 0) and (pos.y < H):
|
||||
fltIngrad += tenOutgrad[i, ch, pos.y, pos.x] * flt
|
||||
@ti.kernel
|
||||
def softsplat_ingrad(
|
||||
tenIn: ti.types.ndarray(),
|
||||
tenFlow: ti.types.ndarray(),
|
||||
tenOutgrad: ti.types.ndarray(),
|
||||
tenIngrad: ti.types.ndarray(),
|
||||
tenFlowgrad: ti.types.ndarray()
|
||||
):
|
||||
N, C, H, W = tenIngrad.shape
|
||||
for i, ch, y, x in ti.ndrange(N, C, H, W):
|
||||
fltIngrad = 0.0
|
||||
fltX = x + tenFlow[i, 0, y, x]
|
||||
fltY = y + tenFlow[i, 1, y, x]
|
||||
|
||||
northWest = tm.vec2(ti.floor(fltX, dtype=ti.i32), ti.floor(fltY, dtype=ti.i32))
|
||||
northEast = tm.vec2(northWest.x + 1, northWest.y)
|
||||
southWest = tm.vec2(northWest.x, northWest.y + 1)
|
||||
southEast = tm.vec2(northWest.x + 1, northWest.y + 1)
|
||||
|
||||
fltNorthwest = (southEast.x - fltX) * (southEast.y - fltY)
|
||||
fltNortheast = (fltX - southWest.x) * (southWest.y - fltY)
|
||||
fltSouthwest = (northEast.x - fltX) * (fltY - northEast.y)
|
||||
fltSoutheast = (fltX - northWest.x) * (fltY - northWest.y)
|
||||
|
||||
add_to_fltIngrad(fltIngrad, tenOutgrad, fltNorthwest, northWest, i, ch)
|
||||
add_to_fltIngrad(fltIngrad, tenOutgrad, fltNortheast, northEast, i, ch)
|
||||
add_to_fltIngrad(fltIngrad, tenOutgrad, fltSouthwest, southWest, i, ch)
|
||||
add_to_fltIngrad(fltIngrad, tenOutgrad, fltSoutheast, southEast, i, ch)
|
||||
tenIngrad[i] = fltIngrad
|
||||
|
||||
# end
|
||||
|
||||
def worker_interface(op_name, tensors):
|
||||
if op_name == "softsplat_out":
|
||||
tenIn, tenFlow = tensors
|
||||
tenOut = tenIn.new_zeros(tenIn.shape)
|
||||
softsplat_out(tenIn, tenFlow, tenOut)
|
||||
return (tenOut, )
|
||||
|
||||
raise NotImplementedError(op_name)
|
||||
|
||||
__all__ = ["worker_interface"]
|
||||
@@ -1,39 +0,0 @@
|
||||
import taichi as ti
|
||||
import taichi.math as tm
|
||||
from functools import reduce
|
||||
|
||||
@ti.kernel
|
||||
def sepconv_out(tenIn: ti.types.ndarray(), tenVer: ti.types.ndarray(), tenHor: ti.types.ndarray(), tenOut: ti.types.ndarray()):
|
||||
N, C, H, W = tenIn.shape
|
||||
intIndex = 0
|
||||
for i, ch, y, x in ti.ndrange(N, C, H, W):
|
||||
fltOut, fltKahanc, fltKahany, fltKahant = 0.0, 0.0, 0.0, 0.0
|
||||
for intFy, intFx in ti.ndrange(tenVer.shape[1], tenHor.shape[1]):
|
||||
fltKahany = tenIn[i, ch, y + intFy, x + intFx] * tenVer[i, intFy, y, x] * tenHor[i, intFx, y, x]
|
||||
fltKahany = fltKahany - fltKahanc
|
||||
fltKahant = fltOut + fltKahany
|
||||
fltKahanc = (fltKahant - fltOut) - fltKahany
|
||||
fltOut = fltKahant
|
||||
tenOut[intIndex] = fltOut
|
||||
intIndex += 1
|
||||
|
||||
|
||||
def worker_interface(op_name, tensors):
|
||||
if op_name == "sepconv_out":
|
||||
tenIn, tenVer, tenHor = tensors
|
||||
real_tenOut_shape = [
|
||||
tenIn.shape[0],
|
||||
tenIn.shape[1],
|
||||
tenVer.shape[2] and tenHor.shape[2],
|
||||
tenVer.shape[3] and tenHor.shape[3],
|
||||
]
|
||||
tenOut = tenIn.new_zeros([
|
||||
int(reduce(lambda a, b: a * b, real_tenOut_shape))
|
||||
])
|
||||
sepconv_out(tenIn, tenVer, tenHor, tenOut)
|
||||
tenOut = tenOut.view(*real_tenOut_shape)
|
||||
return (tenOut, )
|
||||
|
||||
raise NotImplementedError(op_name)
|
||||
|
||||
__all__ = ["worker_interface"]
|
||||
@@ -1,11 +0,0 @@
|
||||
import platform
|
||||
import torch
|
||||
def to_shared_memory(tensors: tuple[torch.Tensor]):
|
||||
return [tensor.cpu() for tensor in tensors if tensor is not None]
|
||||
""" if platform.system() == "Windows":
|
||||
return [tensor.cpu() for tensor in tensors if tensor is not None]
|
||||
|
||||
return [tensor.share_memory_() for tensor in tensors if tensor is not None] """
|
||||
|
||||
def to_device(tensors: tuple[torch.Tensor], device: torch.device):
|
||||
return [tensor.to(device) for tensor in tensors if tensor is not None]
|
||||
@@ -1,26 +0,0 @@
|
||||
import torch.multiprocessing as mp
|
||||
import torch
|
||||
from .raw_softsplat import worker_interface as raw_softsplat
|
||||
from .costvol import worker_interface as costvol
|
||||
from .sepconv import worker_interface as sepconv
|
||||
from .utils import to_shared_memory, to_device
|
||||
import taichi as ti
|
||||
import traceback
|
||||
|
||||
def f(child_conn, device: torch.DeviceObjType):
|
||||
ti.init(arch=ti.gpu)
|
||||
while True:
|
||||
op_name, tensors = child_conn.recv()
|
||||
tensors = to_device(tensors, device)
|
||||
try:
|
||||
if "softsplat" in op_name:
|
||||
result = raw_softsplat(op_name, tensors)
|
||||
elif "costvol" in op_name:
|
||||
result = costvol(op_name, tensors)
|
||||
elif "sepconv" in op_name:
|
||||
result = sepconv(op_name, tensors)
|
||||
else:
|
||||
raise NotImplementedError(op_name)
|
||||
child_conn.send(to_shared_memory(result))
|
||||
except:
|
||||
child_conn.send(traceback.format_exc())
|
||||
@@ -1,107 +0,0 @@
|
||||
import torch
|
||||
from torch.utils.data import DataLoader
|
||||
import pathlib
|
||||
from vfi_utils import load_file_from_github_release, preprocess_frames, postprocess_frames, generic_frame_loop, InterpolationStateList
|
||||
import typing
|
||||
from comfy.model_management import get_torch_device
|
||||
import re
|
||||
from functools import cmp_to_key
|
||||
from packaging import version
|
||||
|
||||
MODEL_TYPE = pathlib.Path(__file__).parent.name
|
||||
CKPT_NAME_VER_DICT = {
|
||||
"rife40.pth": "4.0",
|
||||
"rife41.pth": "4.0",
|
||||
"rife42.pth": "4.2",
|
||||
"rife43.pth": "4.3",
|
||||
"rife44.pth": "4.3",
|
||||
"rife45.pth": "4.5",
|
||||
"rife46.pth": "4.6",
|
||||
"rife47.pth": "4.7",
|
||||
"rife48.pth": "4.7",
|
||||
"rife49.pth": "4.7",
|
||||
"sudo_rife4_269.662_testV1_scale1.pth": "4.0"
|
||||
#Arch 4.10 doesn't work due to state dict mismatch
|
||||
#TODO: Investigating and fix it
|
||||
#"rife410.pth": "4.10",
|
||||
#"rife411.pth": "4.10",
|
||||
#"rife412.pth": "4.10"
|
||||
}
|
||||
|
||||
class RIFE_VFI:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"ckpt_name": (
|
||||
sorted(list(CKPT_NAME_VER_DICT.keys()), key=lambda ckpt_name: version.parse(CKPT_NAME_VER_DICT[ckpt_name])),
|
||||
{"default": "rife47.pth"}
|
||||
),
|
||||
"frames": ("IMAGE", ),
|
||||
"clear_cache_after_n_frames": ("INT", {"default": 10, "min": 1, "max": 1000}),
|
||||
"multiplier": ("INT", {"default": 2, "min": 1}),
|
||||
"fast_mode": ("BOOLEAN", {"default":True}),
|
||||
"ensemble": ("BOOLEAN", {"default":True}),
|
||||
"scale_factor": ([0.25, 0.5, 1.0, 2.0, 4.0], {"default": 1.0})
|
||||
},
|
||||
"optional": {
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", )
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", )
|
||||
FUNCTION = "vfi"
|
||||
CATEGORY = "ComfyUI-Frame-Interpolation/VFI"
|
||||
|
||||
def vfi(
|
||||
self,
|
||||
ckpt_name: typing.AnyStr,
|
||||
frames: torch.Tensor,
|
||||
clear_cache_after_n_frames = 10,
|
||||
multiplier: typing.SupportsInt = 2,
|
||||
fast_mode = False,
|
||||
ensemble = False,
|
||||
scale_factor = 1.0,
|
||||
optional_interpolation_states: InterpolationStateList = None,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Perform video frame interpolation using a given checkpoint model.
|
||||
|
||||
Args:
|
||||
ckpt_name (str): The name of the checkpoint model to use.
|
||||
frames (torch.Tensor): A tensor containing input video frames.
|
||||
clear_cache_after_n_frames (int, optional): The number of frames to process before clearing CUDA cache
|
||||
to prevent memory overflow. Defaults to 10. Lower numbers are safer but mean more processing time.
|
||||
How high you should set it depends on how many input frames there are, input resolution (after upscaling),
|
||||
how many times you want to multiply them, and how long you're willing to wait for the process to complete.
|
||||
multiplier (int, optional): The multiplier for each input frame. 60 input frames * 2 = 120 output frames. Defaults to 2.
|
||||
|
||||
Returns:
|
||||
tuple: A tuple containing the output interpolated frames.
|
||||
|
||||
Note:
|
||||
This method interpolates frames in a video sequence using a specified checkpoint model.
|
||||
It processes each frame sequentially, generating interpolated frames between them.
|
||||
|
||||
To prevent memory overflow, it clears the CUDA cache after processing a specified number of frames.
|
||||
"""
|
||||
from .rife_arch import IFNet
|
||||
model_path = load_file_from_github_release(MODEL_TYPE, ckpt_name)
|
||||
arch_ver = CKPT_NAME_VER_DICT[ckpt_name]
|
||||
interpolation_model = IFNet(arch_ver=arch_ver)
|
||||
interpolation_model.load_state_dict(torch.load(model_path))
|
||||
interpolation_model.eval().to(get_torch_device())
|
||||
frames = preprocess_frames(frames)
|
||||
|
||||
def return_middle_frame(frame_0, frame_1, timestep, model, scale_list, in_fast_mode, in_ensemble):
|
||||
return model(frame_0, frame_1, timestep, scale_list, in_fast_mode, in_ensemble)
|
||||
|
||||
scale_list = [8 / scale_factor, 4 / scale_factor, 2 / scale_factor, 1 / scale_factor]
|
||||
|
||||
args = [interpolation_model, scale_list, fast_mode, ensemble]
|
||||
out = postprocess_frames(
|
||||
generic_frame_loop(frames, clear_cache_after_n_frames, multiplier, return_middle_frame, *args,
|
||||
interpolation_states=optional_interpolation_states, dtype=torch.float32)
|
||||
)
|
||||
return (out,)
|
||||
@@ -1,581 +0,0 @@
|
||||
"""
|
||||
26-Dez-21
|
||||
https://github.com/hzwer/Practical-RIFE
|
||||
https://github.com/hzwer/Practical-RIFE/blob/main/model/warplayer.py
|
||||
https://github.com/HolyWu/vs-rife/blob/master/vsrife/__init__.py
|
||||
"""
|
||||
from torch.nn.parallel import DistributedDataParallel as DDP
|
||||
from torch.optim import AdamW
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
import torch.nn as nn
|
||||
import torch.optim as optim
|
||||
import warnings
|
||||
from comfy.model_management import get_torch_device
|
||||
|
||||
device = get_torch_device()
|
||||
backwarp_tenGrid = {}
|
||||
|
||||
|
||||
class ResConv(nn.Module):
|
||||
def __init__(self, c, dilation=1):
|
||||
super(ResConv, self).__init__()
|
||||
self.conv = nn.Conv2d(c, c, 3, 1, dilation, dilation=dilation, groups=1)
|
||||
self.beta = nn.Parameter(torch.ones((1, c, 1, 1)), requires_grad=True)
|
||||
self.relu = nn.LeakyReLU(0.2, True)
|
||||
|
||||
def forward(self, x):
|
||||
return self.relu(self.conv(x) * self.beta + x)
|
||||
|
||||
|
||||
def warp(tenInput, tenFlow):
|
||||
k = (str(tenFlow.device), str(tenFlow.size()))
|
||||
if k not in backwarp_tenGrid:
|
||||
tenHorizontal = (
|
||||
torch.linspace(-1.0, 1.0, tenFlow.shape[3], device=device)
|
||||
.view(1, 1, 1, tenFlow.shape[3])
|
||||
.expand(tenFlow.shape[0], -1, tenFlow.shape[2], -1)
|
||||
)
|
||||
tenVertical = (
|
||||
torch.linspace(-1.0, 1.0, tenFlow.shape[2], device=device)
|
||||
.view(1, 1, tenFlow.shape[2], 1)
|
||||
.expand(tenFlow.shape[0], -1, -1, tenFlow.shape[3])
|
||||
)
|
||||
backwarp_tenGrid[k] = torch.cat([tenHorizontal, tenVertical], 1).to(device)
|
||||
|
||||
tenFlow = torch.cat(
|
||||
[
|
||||
tenFlow[:, 0:1, :, :] / ((tenInput.shape[3] - 1.0) / 2.0),
|
||||
tenFlow[:, 1:2, :, :] / ((tenInput.shape[2] - 1.0) / 2.0),
|
||||
],
|
||||
1,
|
||||
)
|
||||
|
||||
g = (backwarp_tenGrid[k] + tenFlow).permute(0, 2, 3, 1)
|
||||
|
||||
if tenInput.type() == "torch.cuda.HalfTensor":
|
||||
g = g.half()
|
||||
|
||||
return torch.nn.functional.grid_sample(
|
||||
input=tenInput,
|
||||
grid=g,
|
||||
mode="bilinear",
|
||||
padding_mode="border",
|
||||
align_corners=True,
|
||||
)
|
||||
|
||||
|
||||
def conv(
|
||||
in_planes,
|
||||
out_planes,
|
||||
kernel_size=3,
|
||||
stride=1,
|
||||
padding=1,
|
||||
dilation=1,
|
||||
arch_ver="4.0",
|
||||
):
|
||||
if arch_ver == "4.0":
|
||||
return nn.Sequential(
|
||||
nn.Conv2d(
|
||||
in_planes,
|
||||
out_planes,
|
||||
kernel_size=kernel_size,
|
||||
stride=stride,
|
||||
padding=padding,
|
||||
dilation=dilation,
|
||||
bias=True,
|
||||
),
|
||||
nn.PReLU(out_planes),
|
||||
)
|
||||
if arch_ver in ["4.2", "4.3", "4.5", "4.6", "4.7", "4.10"]:
|
||||
return nn.Sequential(
|
||||
nn.Conv2d(
|
||||
in_planes,
|
||||
out_planes,
|
||||
kernel_size=kernel_size,
|
||||
stride=stride,
|
||||
padding=padding,
|
||||
dilation=dilation,
|
||||
bias=True,
|
||||
),
|
||||
nn.LeakyReLU(0.2, True),
|
||||
)
|
||||
|
||||
|
||||
def conv_woact(in_planes, out_planes, kernel_size=3, stride=1, padding=1, dilation=1):
|
||||
return nn.Sequential(
|
||||
nn.Conv2d(
|
||||
in_planes,
|
||||
out_planes,
|
||||
kernel_size=kernel_size,
|
||||
stride=stride,
|
||||
padding=padding,
|
||||
dilation=dilation,
|
||||
bias=True,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def conv_woact(in_planes, out_planes, kernel_size=3, stride=1, padding=1, dilation=1):
|
||||
return nn.Sequential(
|
||||
nn.Conv2d(
|
||||
in_planes,
|
||||
out_planes,
|
||||
kernel_size=kernel_size,
|
||||
stride=stride,
|
||||
padding=padding,
|
||||
dilation=dilation,
|
||||
bias=True,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def deconv(in_planes, out_planes, kernel_size=4, stride=2, padding=1, arch_ver="4.0"):
|
||||
if arch_ver == "4.0":
|
||||
return nn.Sequential(
|
||||
torch.nn.ConvTranspose2d(
|
||||
in_channels=in_planes,
|
||||
out_channels=out_planes,
|
||||
kernel_size=4,
|
||||
stride=2,
|
||||
padding=1,
|
||||
bias=True,
|
||||
),
|
||||
nn.PReLU(out_planes),
|
||||
)
|
||||
if arch_ver in ["4.2", "4.3", "4.5", "4.6", "4.7", "4.10"]:
|
||||
return nn.Sequential(
|
||||
torch.nn.ConvTranspose2d(
|
||||
in_channels=in_planes,
|
||||
out_channels=out_planes,
|
||||
kernel_size=4,
|
||||
stride=2,
|
||||
padding=1,
|
||||
bias=True,
|
||||
),
|
||||
nn.LeakyReLU(0.2, True),
|
||||
)
|
||||
|
||||
|
||||
class Conv2(nn.Module):
|
||||
def __init__(self, in_planes, out_planes, stride=2, arch_ver="4.0"):
|
||||
super(Conv2, self).__init__()
|
||||
self.conv1 = conv(in_planes, out_planes, 3, stride, 1, arch_ver=arch_ver)
|
||||
self.conv2 = conv(out_planes, out_planes, 3, 1, 1, arch_ver=arch_ver)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.conv1(x)
|
||||
x = self.conv2(x)
|
||||
return x
|
||||
|
||||
|
||||
class IFBlock(nn.Module):
|
||||
def __init__(self, in_planes, c=64, arch_ver="4.0"):
|
||||
super(IFBlock, self).__init__()
|
||||
self.arch_ver = arch_ver
|
||||
self.conv0 = nn.Sequential(
|
||||
conv(in_planes, c // 2, 3, 2, 1, arch_ver=arch_ver),
|
||||
conv(c // 2, c, 3, 2, 1, arch_ver=arch_ver),
|
||||
)
|
||||
self.arch_ver = arch_ver
|
||||
|
||||
if arch_ver in ["4.0", "4.2", "4.3"]:
|
||||
self.convblock = nn.Sequential(
|
||||
conv(c, c, arch_ver=arch_ver),
|
||||
conv(c, c, arch_ver=arch_ver),
|
||||
conv(c, c, arch_ver=arch_ver),
|
||||
conv(c, c, arch_ver=arch_ver),
|
||||
conv(c, c, arch_ver=arch_ver),
|
||||
conv(c, c, arch_ver=arch_ver),
|
||||
conv(c, c, arch_ver=arch_ver),
|
||||
conv(c, c, arch_ver=arch_ver),
|
||||
)
|
||||
self.lastconv = nn.ConvTranspose2d(c, 5, 4, 2, 1)
|
||||
|
||||
if arch_ver in ["4.5", "4.6", "4.7", "4.10"]:
|
||||
self.convblock = nn.Sequential(
|
||||
ResConv(c),
|
||||
ResConv(c),
|
||||
ResConv(c),
|
||||
ResConv(c),
|
||||
ResConv(c),
|
||||
ResConv(c),
|
||||
ResConv(c),
|
||||
ResConv(c),
|
||||
)
|
||||
if arch_ver == "4.5":
|
||||
self.lastconv = nn.Sequential(
|
||||
nn.ConvTranspose2d(c, 4 * 5, 4, 2, 1), nn.PixelShuffle(2)
|
||||
)
|
||||
if arch_ver in ["4.6", "4.7", "4.10"]:
|
||||
self.lastconv = nn.Sequential(
|
||||
nn.ConvTranspose2d(c, 4 * 6, 4, 2, 1), nn.PixelShuffle(2)
|
||||
)
|
||||
|
||||
def forward(self, x, flow=None, scale=1):
|
||||
x = F.interpolate(
|
||||
x, scale_factor=1.0 / scale, mode="bilinear", align_corners=False
|
||||
)
|
||||
if flow is not None:
|
||||
flow = (
|
||||
F.interpolate(
|
||||
flow, scale_factor=1.0 / scale, mode="bilinear", align_corners=False
|
||||
)
|
||||
* 1.0
|
||||
/ scale
|
||||
)
|
||||
x = torch.cat((x, flow), 1)
|
||||
feat = self.conv0(x)
|
||||
if self.arch_ver == "4.0":
|
||||
feat = self.convblock(feat) + feat
|
||||
if self.arch_ver in ["4.2", "4.3", "4.5", "4.6", "4.7", "4.10"]:
|
||||
feat = self.convblock(feat)
|
||||
|
||||
tmp = self.lastconv(feat)
|
||||
if self.arch_ver in ["4.0", "4.2", "4.3"]:
|
||||
tmp = F.interpolate(
|
||||
tmp, scale_factor=scale * 2, mode="bilinear", align_corners=False
|
||||
)
|
||||
flow = tmp[:, :4] * scale * 2
|
||||
if self.arch_ver in ["4.5", "4.6", "4.7", "4.10"]:
|
||||
tmp = F.interpolate(
|
||||
tmp, scale_factor=scale, mode="bilinear", align_corners=False
|
||||
)
|
||||
flow = tmp[:, :4] * scale
|
||||
mask = tmp[:, 4:5]
|
||||
return flow, mask
|
||||
|
||||
|
||||
class Contextnet(nn.Module):
|
||||
def __init__(self, arch_ver="4.0"):
|
||||
super(Contextnet, self).__init__()
|
||||
c = 16
|
||||
self.conv1 = Conv2(3, c, arch_ver=arch_ver)
|
||||
self.conv2 = Conv2(c, 2 * c, arch_ver=arch_ver)
|
||||
self.conv3 = Conv2(2 * c, 4 * c, arch_ver=arch_ver)
|
||||
self.conv4 = Conv2(4 * c, 8 * c, arch_ver=arch_ver)
|
||||
|
||||
def forward(self, x, flow):
|
||||
x = self.conv1(x)
|
||||
flow = (
|
||||
F.interpolate(flow, scale_factor=0.5, mode="bilinear", align_corners=False)
|
||||
* 0.5
|
||||
)
|
||||
f1 = warp(x, flow)
|
||||
x = self.conv2(x)
|
||||
flow = (
|
||||
F.interpolate(flow, scale_factor=0.5, mode="bilinear", align_corners=False)
|
||||
* 0.5
|
||||
)
|
||||
f2 = warp(x, flow)
|
||||
x = self.conv3(x)
|
||||
flow = (
|
||||
F.interpolate(flow, scale_factor=0.5, mode="bilinear", align_corners=False)
|
||||
* 0.5
|
||||
)
|
||||
f3 = warp(x, flow)
|
||||
x = self.conv4(x)
|
||||
flow = (
|
||||
F.interpolate(flow, scale_factor=0.5, mode="bilinear", align_corners=False)
|
||||
* 0.5
|
||||
)
|
||||
f4 = warp(x, flow)
|
||||
return [f1, f2, f3, f4]
|
||||
|
||||
|
||||
class Unet(nn.Module):
|
||||
def __init__(self, arch_ver="4.0"):
|
||||
super(Unet, self).__init__()
|
||||
c = 16
|
||||
self.down0 = Conv2(17, 2 * c, arch_ver=arch_ver)
|
||||
self.down1 = Conv2(4 * c, 4 * c, arch_ver=arch_ver)
|
||||
self.down2 = Conv2(8 * c, 8 * c, arch_ver=arch_ver)
|
||||
self.down3 = Conv2(16 * c, 16 * c, arch_ver=arch_ver)
|
||||
self.up0 = deconv(32 * c, 8 * c, arch_ver=arch_ver)
|
||||
self.up1 = deconv(16 * c, 4 * c, arch_ver=arch_ver)
|
||||
self.up2 = deconv(8 * c, 2 * c, arch_ver=arch_ver)
|
||||
self.up3 = deconv(4 * c, c, arch_ver=arch_ver)
|
||||
self.conv = nn.Conv2d(c, 3, 3, 1, 1)
|
||||
|
||||
def forward(self, img0, img1, warped_img0, warped_img1, mask, flow, c0, c1):
|
||||
s0 = self.down0(
|
||||
torch.cat((img0, img1, warped_img0, warped_img1, mask, flow), 1)
|
||||
)
|
||||
s1 = self.down1(torch.cat((s0, c0[0], c1[0]), 1))
|
||||
s2 = self.down2(torch.cat((s1, c0[1], c1[1]), 1))
|
||||
s3 = self.down3(torch.cat((s2, c0[2], c1[2]), 1))
|
||||
x = self.up0(torch.cat((s3, c0[3], c1[3]), 1))
|
||||
x = self.up1(torch.cat((x, s2), 1))
|
||||
x = self.up2(torch.cat((x, s1), 1))
|
||||
x = self.up3(torch.cat((x, s0), 1))
|
||||
x = self.conv(x)
|
||||
return torch.sigmoid(x)
|
||||
|
||||
|
||||
"""
|
||||
currently supports 4.0-4.12
|
||||
|
||||
4.0: 4.0, 4.1
|
||||
4.2: 4.2
|
||||
4.3: 4.3, 4.4
|
||||
4.5: 4.5
|
||||
4.6: 4.6
|
||||
4.7: 4.7, 4.8, 4.9
|
||||
4.10: 4.10 4.11 4.12
|
||||
"""
|
||||
|
||||
|
||||
class IFNet(nn.Module):
|
||||
def __init__(self, arch_ver="4.0"):
|
||||
super(IFNet, self).__init__()
|
||||
self.arch_ver = arch_ver
|
||||
if arch_ver in ["4.0", "4.2", "4.3", "4.5", "4.6"]:
|
||||
self.block0 = IFBlock(7, c=192, arch_ver=arch_ver)
|
||||
self.block1 = IFBlock(8 + 4, c=128, arch_ver=arch_ver)
|
||||
self.block2 = IFBlock(8 + 4, c=96, arch_ver=arch_ver)
|
||||
self.block3 = IFBlock(8 + 4, c=64, arch_ver=arch_ver)
|
||||
if arch_ver in ["4.7"]:
|
||||
self.block0 = IFBlock(7 + 8, c=192, arch_ver=arch_ver)
|
||||
self.block1 = IFBlock(8 + 4 + 8, c=128, arch_ver=arch_ver)
|
||||
self.block2 = IFBlock(8 + 4 + 8, c=96, arch_ver=arch_ver)
|
||||
self.block3 = IFBlock(8 + 4 + 8, c=64, arch_ver=arch_ver)
|
||||
self.encode = nn.Sequential(
|
||||
nn.Conv2d(3, 16, 3, 2, 1), nn.ConvTranspose2d(16, 4, 4, 2, 1)
|
||||
)
|
||||
if arch_ver in ["4.10"]:
|
||||
self.block0 = IFBlock(7 + 16, c=192)
|
||||
self.block1 = IFBlock(8 + 4 + 16, c=128)
|
||||
self.block2 = IFBlock(8 + 4 + 16, c=96)
|
||||
self.block3 = IFBlock(8 + 4 + 16, c=64)
|
||||
self.encode = nn.Sequential(
|
||||
nn.Conv2d(3, 32, 3, 2, 1),
|
||||
nn.LeakyReLU(0.2, True),
|
||||
nn.Conv2d(32, 32, 3, 1, 1),
|
||||
nn.LeakyReLU(0.2, True),
|
||||
nn.Conv2d(32, 32, 3, 1, 1),
|
||||
nn.LeakyReLU(0.2, True),
|
||||
nn.ConvTranspose2d(32, 8, 4, 2, 1),
|
||||
)
|
||||
|
||||
if arch_ver in ["4.0", "4.2", "4.3"]:
|
||||
self.contextnet = Contextnet(arch_ver=arch_ver)
|
||||
self.unet = Unet(arch_ver=arch_ver)
|
||||
self.arch_ver = arch_ver
|
||||
|
||||
def forward(
|
||||
self,
|
||||
img0,
|
||||
img1,
|
||||
timestep=0.5,
|
||||
scale_list=[8, 4, 2, 1],
|
||||
training=True,
|
||||
fastmode=True,
|
||||
ensemble=False,
|
||||
return_flow=False,
|
||||
):
|
||||
img0 = torch.clamp(img0, 0, 1)
|
||||
img1 = torch.clamp(img1, 0, 1)
|
||||
|
||||
n, c, h, w = img0.shape
|
||||
ph = ((h - 1) // 64 + 1) * 64
|
||||
pw = ((w - 1) // 64 + 1) * 64
|
||||
padding = (0, pw - w, 0, ph - h)
|
||||
img0 = F.pad(img0, padding)
|
||||
img1 = F.pad(img1, padding)
|
||||
x = torch.cat((img0, img1), 1)
|
||||
|
||||
if training == False:
|
||||
channel = x.shape[1] // 2
|
||||
img0 = x[:, :channel]
|
||||
img1 = x[:, channel:]
|
||||
if not torch.is_tensor(timestep):
|
||||
timestep = (x[:, :1].clone() * 0 + 1) * timestep
|
||||
else:
|
||||
timestep = timestep.repeat(1, 1, img0.shape[2], img0.shape[3])
|
||||
|
||||
flow_list = []
|
||||
merged = []
|
||||
mask_list = []
|
||||
|
||||
if self.arch_ver in ["4.7", "4.10"]:
|
||||
f0 = self.encode(img0[:, :3])
|
||||
f1 = self.encode(img1[:, :3])
|
||||
|
||||
warped_img0 = img0
|
||||
warped_img1 = img1
|
||||
flow = None
|
||||
mask = None
|
||||
block = [self.block0, self.block1, self.block2, self.block3]
|
||||
|
||||
for i in range(4):
|
||||
if flow is None:
|
||||
# 4.0-4.6
|
||||
if self.arch_ver in ["4.0", "4.2", "4.3", "4.5", "4.6"]:
|
||||
flow, mask = block[i](
|
||||
torch.cat((img0[:, :3], img1[:, :3], timestep), 1),
|
||||
None,
|
||||
scale=scale_list[i],
|
||||
)
|
||||
if ensemble:
|
||||
f1, m1 = block[i](
|
||||
torch.cat((img1[:, :3], img0[:, :3], 1 - timestep), 1),
|
||||
None,
|
||||
scale=scale_list[i],
|
||||
)
|
||||
flow = (flow + torch.cat((f1[:, 2:4], f1[:, :2]), 1)) / 2
|
||||
mask = (mask + (-m1)) / 2
|
||||
|
||||
# 4.7+
|
||||
if self.arch_ver in ["4.7", "4.10"]:
|
||||
flow, mask = block[i](
|
||||
torch.cat((img0[:, :3], img1[:, :3], f0, f1, timestep), 1),
|
||||
None,
|
||||
scale=scale_list[i],
|
||||
)
|
||||
|
||||
if ensemble:
|
||||
f_, m_ = block[i](
|
||||
torch.cat(
|
||||
(img1[:, :3], img0[:, :3], f1, f0, 1 - timestep), 1
|
||||
),
|
||||
None,
|
||||
scale=scale_list[i],
|
||||
)
|
||||
flow = (flow + torch.cat((f_[:, 2:4], f_[:, :2]), 1)) / 2
|
||||
mask = (mask + (-m_)) / 2
|
||||
|
||||
else:
|
||||
# 4.0-4.6
|
||||
if self.arch_ver in ["4.0", "4.2", "4.3", "4.5", "4.6"]:
|
||||
f0, m0 = block[i](
|
||||
torch.cat(
|
||||
(warped_img0[:, :3], warped_img1[:, :3], timestep, mask), 1
|
||||
),
|
||||
flow,
|
||||
scale=scale_list[i],
|
||||
)
|
||||
|
||||
if self.arch_ver in ["4.0"]:
|
||||
if (
|
||||
i == 1
|
||||
and f0[:, :2].abs().max() > 32
|
||||
and f0[:, 2:4].abs().max() > 32
|
||||
and not training
|
||||
):
|
||||
for k in range(4):
|
||||
scale_list[k] *= 2
|
||||
flow, mask = block[0](
|
||||
torch.cat((img0[:, :3], img1[:, :3], timestep), 1),
|
||||
None,
|
||||
scale=scale_list[0],
|
||||
)
|
||||
warped_img0 = warp(img0, flow[:, :2])
|
||||
warped_img1 = warp(img1, flow[:, 2:4])
|
||||
f0, m0 = block[i](
|
||||
torch.cat(
|
||||
(
|
||||
warped_img0[:, :3],
|
||||
warped_img1[:, :3],
|
||||
timestep,
|
||||
mask,
|
||||
),
|
||||
1,
|
||||
),
|
||||
flow,
|
||||
scale=scale_list[i],
|
||||
)
|
||||
|
||||
# 4.7+
|
||||
if self.arch_ver in ["4.7", "4.10"]:
|
||||
fd, m0 = block[i](
|
||||
torch.cat(
|
||||
(
|
||||
warped_img0[:, :3],
|
||||
warped_img1[:, :3],
|
||||
warp(f0, flow[:, :2]),
|
||||
warp(f1, flow[:, 2:4]),
|
||||
timestep,
|
||||
mask,
|
||||
),
|
||||
1,
|
||||
),
|
||||
flow,
|
||||
scale=scale_list[i],
|
||||
)
|
||||
flow = flow + fd
|
||||
|
||||
# 4.0-4.6 ensemble
|
||||
if ensemble and self.arch_ver in [
|
||||
"4.0",
|
||||
"4.2",
|
||||
"4.3",
|
||||
"4.5",
|
||||
"4.6",
|
||||
]:
|
||||
f1, m1 = block[i](
|
||||
torch.cat(
|
||||
(
|
||||
warped_img1[:, :3],
|
||||
warped_img0[:, :3],
|
||||
1 - timestep,
|
||||
-mask,
|
||||
),
|
||||
1,
|
||||
),
|
||||
torch.cat((flow[:, 2:4], flow[:, :2]), 1),
|
||||
scale=scale_list[i],
|
||||
)
|
||||
f0 = (f0 + torch.cat((f1[:, 2:4], f1[:, :2]), 1)) / 2
|
||||
m0 = (m0 + (-m1)) / 2
|
||||
|
||||
# 4.7+ ensemble
|
||||
if ensemble and self.arch_ver in ["4.7", "4.10"]:
|
||||
wf0 = warp(f0, flow[:, :2])
|
||||
wf1 = warp(f1, flow[:, 2:4])
|
||||
|
||||
f_, m_ = block[i](
|
||||
torch.cat(
|
||||
(
|
||||
warped_img1[:, :3],
|
||||
warped_img0[:, :3],
|
||||
wf1,
|
||||
wf0,
|
||||
1 - timestep,
|
||||
-mask,
|
||||
),
|
||||
1,
|
||||
),
|
||||
torch.cat((flow[:, 2:4], flow[:, :2]), 1),
|
||||
scale=scale_list[i],
|
||||
)
|
||||
fd = (fd + torch.cat((f_[:, 2:4], f_[:, :2]), 1)) / 2
|
||||
mask = (m0 + (-m_)) / 2
|
||||
|
||||
if self.arch_ver in ["4.0", "4.2", "4.3", "4.5", "4.6"]:
|
||||
flow = flow + f0
|
||||
mask = mask + m0
|
||||
|
||||
if not ensemble and self.arch_ver in ["4.7", "4.10"]:
|
||||
mask = m0
|
||||
|
||||
mask_list.append(mask)
|
||||
flow_list.append(flow)
|
||||
warped_img0 = warp(img0, flow[:, :2])
|
||||
warped_img1 = warp(img1, flow[:, 2:4])
|
||||
merged.append((warped_img0, warped_img1))
|
||||
|
||||
if self.arch_ver in ["4.0", "4.1", "4.2", "4.3", "4.4", "4.5", "4.6"]:
|
||||
mask_list[3] = torch.sigmoid(mask_list[3])
|
||||
merged[3] = merged[3][0] * mask_list[3] + merged[3][1] * (1 - mask_list[3])
|
||||
|
||||
if self.arch_ver in ["4.7", "4.10"]:
|
||||
mask = torch.sigmoid(mask)
|
||||
merged[3] = warped_img0 * mask + warped_img1 * (1 - mask)
|
||||
|
||||
if not fastmode and self.arch_ver in ["4.0", "4.2", "4.3"]:
|
||||
c0 = self.contextnet(img0, flow[:, :2])
|
||||
c1 = self.contextnet(img1, flow[:, 2:4])
|
||||
tmp = self.unet(img0, img1, warped_img0, warped_img1, mask, flow, c0, c1)
|
||||
res = tmp[:, :3] * 2 - 1
|
||||
merged[3] = torch.clamp(merged[3] + res, 0, 1)
|
||||
return merged[3][:, :, :h, :w]
|
||||
@@ -1,56 +0,0 @@
|
||||
import torch
|
||||
from torch.utils.data import DataLoader
|
||||
import pathlib
|
||||
from vfi_utils import load_file_from_github_release, preprocess_frames, postprocess_frames
|
||||
import typing
|
||||
from comfy.model_management import soft_empty_cache, get_torch_device
|
||||
from vfi_utils import InterpolationStateList, generic_frame_loop
|
||||
|
||||
MODEL_TYPE = pathlib.Path(__file__).parent.name
|
||||
CKPT_NAMES = ["sepconv.pth"]
|
||||
|
||||
|
||||
class SepconvVFI:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"ckpt_name": (CKPT_NAMES, ),
|
||||
"frames": ("IMAGE", ),
|
||||
"clear_cache_after_n_frames": ("INT", {"default": 10, "min": 1, "max": 1000}),
|
||||
"multiplier": ("INT", {"default": 2, "min": 2, "max": 1000})
|
||||
},
|
||||
"optional": {
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", )
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", )
|
||||
FUNCTION = "vfi"
|
||||
CATEGORY = "ComfyUI-Frame-Interpolation/VFI"
|
||||
|
||||
def vfi(
|
||||
self,
|
||||
ckpt_name: typing.AnyStr,
|
||||
frames: torch.Tensor,
|
||||
clear_cache_after_n_frames = 10,
|
||||
multiplier: typing.SupportsInt = 2,
|
||||
optional_interpolation_states: InterpolationStateList = None,
|
||||
**kwargs
|
||||
):
|
||||
from .sepconv_enhanced import Network
|
||||
model_path = load_file_from_github_release(MODEL_TYPE, ckpt_name)
|
||||
interpolation_model = Network()
|
||||
interpolation_model.load_state_dict(torch.load(model_path))
|
||||
interpolation_model.eval().to(get_torch_device())
|
||||
frames = preprocess_frames(frames)
|
||||
|
||||
def return_middle_frame(frame_0, frame_1, timestep, model):
|
||||
return model(frame_0, frame_1)
|
||||
|
||||
args = [interpolation_model]
|
||||
out = postprocess_frames(
|
||||
generic_frame_loop(frames, clear_cache_after_n_frames, multiplier, return_middle_frame, *args,
|
||||
interpolation_states=optional_interpolation_states, use_timestep=False, dtype=torch.float32)
|
||||
)
|
||||
return (out,)
|
||||
@@ -1,748 +0,0 @@
|
||||
"""
|
||||
23-nov-21
|
||||
https://github.com/sniklaus/revisiting-sepconv/blob/fea509d98157170df1fb35bf615bd41d98858e1a/run.py
|
||||
https://github.com/sniklaus/revisiting-sepconv/blob/fea509d98157170df1fb35bf615bd41d98858e1a/sepconv/sepconv.py
|
||||
Deleted stuffs about arguments_strModel and getopt
|
||||
"""
|
||||
#!/usr/bin/env python
|
||||
import torch
|
||||
import typing
|
||||
from comfy.model_management import get_torch_device
|
||||
|
||||
##########################################################
|
||||
from vfi_models.ops import sepconv_func
|
||||
##########################################################
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
import torch
|
||||
|
||||
import math
|
||||
import numpy
|
||||
import os
|
||||
import PIL
|
||||
import PIL.Image
|
||||
import sys
|
||||
import typing
|
||||
|
||||
##########################################################
|
||||
|
||||
assert (
|
||||
int(str("").join(torch.__version__.split(".")[0:2])) >= 13
|
||||
) # requires at least pytorch version 1.3.0
|
||||
|
||||
torch.set_grad_enabled(
|
||||
False
|
||||
) # make sure to not compute gradients for computational performance
|
||||
|
||||
torch.backends.cudnn.enabled = (
|
||||
True # make sure to use cudnn for computational performance
|
||||
)
|
||||
|
||||
##########################################################
|
||||
|
||||
##########################################################
|
||||
|
||||
|
||||
class Basic(torch.nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
strType: str,
|
||||
intChans: typing.List[int],
|
||||
objScratch: typing.Optional[typing.Dict] = None,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
self.strType = strType
|
||||
self.netEvenize = None
|
||||
self.netMain = None
|
||||
self.netShortcut = None
|
||||
|
||||
intIn = intChans[0]
|
||||
intOut = intChans[-1]
|
||||
netMain = []
|
||||
intChans = intChans.copy()
|
||||
fltStride = 1.0
|
||||
|
||||
for intPart, strPart in enumerate(self.strType.split("+")[0].split("-")):
|
||||
if strPart.startswith("conv") == True:
|
||||
intKsize = 3
|
||||
intPad = 1
|
||||
strPad = "zeros"
|
||||
|
||||
if "(" in strPart:
|
||||
intKsize = int(strPart.split("(")[1].split(")")[0].split(",")[0])
|
||||
intPad = int(math.floor(0.5 * (intKsize - 1)))
|
||||
|
||||
if "replpad" in strPart.split("(")[1].split(")")[0].split(","):
|
||||
strPad = "replicate"
|
||||
if "reflpad" in strPart.split("(")[1].split(")")[0].split(","):
|
||||
strPad = "reflect"
|
||||
# end
|
||||
|
||||
if "nopad" in self.strType.split("+"):
|
||||
intPad = 0
|
||||
# end
|
||||
|
||||
netMain += [
|
||||
torch.nn.Conv2d(
|
||||
in_channels=intChans[0],
|
||||
out_channels=intChans[1],
|
||||
kernel_size=intKsize,
|
||||
stride=1,
|
||||
padding=intPad,
|
||||
padding_mode=strPad,
|
||||
bias="nobias" not in self.strType.split("+"),
|
||||
)
|
||||
]
|
||||
intChans = intChans[1:]
|
||||
fltStride *= 1.0
|
||||
|
||||
elif strPart.startswith("sconv") == True:
|
||||
intKsize = 3
|
||||
intPad = 1
|
||||
strPad = "zeros"
|
||||
|
||||
if "(" in strPart:
|
||||
intKsize = int(strPart.split("(")[1].split(")")[0].split(",")[0])
|
||||
intPad = int(math.floor(0.5 * (intKsize - 1)))
|
||||
|
||||
if "replpad" in strPart.split("(")[1].split(")")[0].split(","):
|
||||
strPad = "replicate"
|
||||
if "reflpad" in strPart.split("(")[1].split(")")[0].split(","):
|
||||
strPad = "reflect"
|
||||
# end
|
||||
|
||||
if "nopad" in self.strType.split("+"):
|
||||
intPad = 0
|
||||
# end
|
||||
|
||||
netMain += [
|
||||
torch.nn.Conv2d(
|
||||
in_channels=intChans[0],
|
||||
out_channels=intChans[1],
|
||||
kernel_size=intKsize,
|
||||
stride=2,
|
||||
padding=intPad,
|
||||
padding_mode=strPad,
|
||||
bias="nobias" not in self.strType.split("+"),
|
||||
)
|
||||
]
|
||||
intChans = intChans[1:]
|
||||
fltStride *= 2.0
|
||||
|
||||
elif strPart.startswith("up") == True:
|
||||
|
||||
class Up(torch.nn.Module):
|
||||
def __init__(self, strType):
|
||||
super().__init__()
|
||||
|
||||
self.strType = strType
|
||||
|
||||
# end
|
||||
|
||||
def forward(self, tenIn: torch.Tensor) -> torch.Tensor:
|
||||
if self.strType == "nearest":
|
||||
return torch.nn.functional.interpolate(
|
||||
input=tenIn,
|
||||
scale_factor=2.0,
|
||||
mode="nearest",
|
||||
align_corners=False,
|
||||
)
|
||||
|
||||
elif self.strType == "bilinear":
|
||||
return torch.nn.functional.interpolate(
|
||||
input=tenIn,
|
||||
scale_factor=2.0,
|
||||
mode="bilinear",
|
||||
align_corners=False,
|
||||
)
|
||||
|
||||
elif self.strType == "pyramid":
|
||||
return pyramid(tenIn, None, "up")
|
||||
|
||||
elif self.strType == "shuffle":
|
||||
return torch.nn.functional.pixel_shuffle(
|
||||
tenIn, upscale_factor=2
|
||||
) # https://github.com/pytorch/pytorch/issues/62854
|
||||
|
||||
# end
|
||||
|
||||
assert False # to make torchscript happy
|
||||
|
||||
# end
|
||||
|
||||
# end
|
||||
|
||||
strType = "bilinear"
|
||||
|
||||
if "(" in strPart:
|
||||
if "nearest" in strPart.split("(")[1].split(")")[0].split(","):
|
||||
strType = "nearest"
|
||||
if "pyramid" in strPart.split("(")[1].split(")")[0].split(","):
|
||||
strType = "pyramid"
|
||||
if "shuffle" in strPart.split("(")[1].split(")")[0].split(","):
|
||||
strType = "shuffle"
|
||||
# end
|
||||
|
||||
netMain += [Up(strType)]
|
||||
fltStride *= 0.5
|
||||
|
||||
elif strPart.startswith("prelu") == True:
|
||||
netMain += [
|
||||
torch.nn.PReLU(
|
||||
num_parameters=1,
|
||||
init=float(strPart.split("(")[1].split(")")[0].split(",")[0]),
|
||||
)
|
||||
]
|
||||
fltStride *= 1.0
|
||||
|
||||
elif True:
|
||||
assert False
|
||||
|
||||
# end
|
||||
# end
|
||||
|
||||
self.netMain = torch.nn.Sequential(*netMain)
|
||||
|
||||
for strPart in self.strType.split("+")[1:]:
|
||||
if strPart.startswith("skip") == True:
|
||||
if intIn == intOut and fltStride == 1.0:
|
||||
self.netShortcut = torch.nn.Identity()
|
||||
|
||||
elif intIn != intOut and fltStride == 1.0:
|
||||
self.netShortcut = torch.nn.Conv2d(
|
||||
in_channels=intIn,
|
||||
out_channels=intOut,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
bias="nobias" not in self.strType.split("+"),
|
||||
)
|
||||
|
||||
elif intIn == intOut and fltStride != 1.0:
|
||||
|
||||
class Down(torch.nn.Module):
|
||||
def __init__(self, fltScale):
|
||||
super().__init__()
|
||||
|
||||
self.fltScale = fltScale
|
||||
|
||||
# end
|
||||
|
||||
def forward(self, tenIn: torch.Tensor) -> torch.Tensor:
|
||||
return torch.nn.functional.interpolate(
|
||||
input=tenIn,
|
||||
scale_factor=self.fltScale,
|
||||
mode="bilinear",
|
||||
align_corners=False,
|
||||
)
|
||||
|
||||
# end
|
||||
|
||||
# end
|
||||
|
||||
self.netShortcut = Down(1.0 / fltStride)
|
||||
|
||||
elif intIn != intOut and fltStride != 1.0:
|
||||
|
||||
class Down(torch.nn.Module):
|
||||
def __init__(self, fltScale):
|
||||
super().__init__()
|
||||
|
||||
self.fltScale = fltScale
|
||||
|
||||
# end
|
||||
|
||||
def forward(self, tenIn: torch.Tensor) -> torch.Tensor:
|
||||
return torch.nn.functional.interpolate(
|
||||
input=tenIn,
|
||||
scale_factor=self.fltScale,
|
||||
mode="bilinear",
|
||||
align_corners=False,
|
||||
)
|
||||
|
||||
# end
|
||||
|
||||
# end
|
||||
|
||||
self.netShortcut = torch.nn.Sequential(
|
||||
Down(1.0 / fltStride),
|
||||
torch.nn.Conv2d(
|
||||
in_channels=intIn,
|
||||
out_channels=intOut,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
padding=0,
|
||||
bias="nobias" not in self.strType.split("+"),
|
||||
),
|
||||
)
|
||||
|
||||
# end
|
||||
|
||||
elif strPart.startswith("...") == True:
|
||||
pass
|
||||
|
||||
# end
|
||||
# end
|
||||
|
||||
assert len(intChans) == 1
|
||||
|
||||
# end
|
||||
|
||||
def forward(self, tenIn: torch.Tensor) -> torch.Tensor:
|
||||
if self.netEvenize is not None:
|
||||
tenIn = self.netEvenize(tenIn)
|
||||
# end
|
||||
|
||||
tenOut = self.netMain(tenIn)
|
||||
|
||||
if self.netShortcut is not None:
|
||||
tenOut = tenOut + self.netShortcut(tenIn)
|
||||
# end
|
||||
|
||||
return tenOut
|
||||
|
||||
# end
|
||||
|
||||
|
||||
# end
|
||||
|
||||
|
||||
class Encode(torch.nn.Module):
|
||||
objScratch: typing.Dict[str, typing.List[int]] = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
intIns: typing.List[int],
|
||||
intOuts: typing.List[int],
|
||||
strHor: str,
|
||||
strVer: str,
|
||||
objScratch: typing.Dict[str, typing.List[int]],
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
assert len(intIns) == len(intOuts)
|
||||
assert len(intOuts) == len(intIns)
|
||||
|
||||
self.intRows = len(intIns) and len(intOuts)
|
||||
self.intIns = intIns.copy()
|
||||
self.intOuts = intOuts.copy()
|
||||
self.strHor = strHor
|
||||
self.strVer = strVer
|
||||
self.objScratch = objScratch
|
||||
|
||||
self.netHor = torch.nn.ModuleList()
|
||||
self.netVer = torch.nn.ModuleList()
|
||||
|
||||
for intRow in range(self.intRows):
|
||||
netHor = torch.nn.Identity()
|
||||
netVer = torch.nn.Identity()
|
||||
|
||||
if self.intOuts[intRow] != 0:
|
||||
if self.intIns[intRow] != 0:
|
||||
netHor = Basic(
|
||||
self.strHor,
|
||||
[
|
||||
self.intIns[intRow],
|
||||
self.intOuts[intRow],
|
||||
self.intOuts[intRow],
|
||||
],
|
||||
objScratch,
|
||||
)
|
||||
# end
|
||||
|
||||
if intRow != 0:
|
||||
netVer = Basic(
|
||||
self.strVer,
|
||||
[
|
||||
self.intOuts[intRow - 1],
|
||||
self.intOuts[intRow],
|
||||
self.intOuts[intRow],
|
||||
],
|
||||
objScratch,
|
||||
)
|
||||
# end
|
||||
# end
|
||||
|
||||
self.netHor.append(netHor)
|
||||
self.netVer.append(netVer)
|
||||
# end
|
||||
|
||||
# end
|
||||
|
||||
def forward(self, tenIns: typing.List[torch.Tensor]) -> typing.List[torch.Tensor]:
|
||||
intRow = 0
|
||||
for netHor in self.netHor:
|
||||
if self.intOuts[intRow] != 0:
|
||||
if self.intIns[intRow] != 0:
|
||||
tenIns[intRow] = netHor(tenIns[intRow])
|
||||
# end
|
||||
# end
|
||||
intRow += 1
|
||||
# end
|
||||
|
||||
intRow = 0
|
||||
for netVer in self.netVer:
|
||||
if self.intOuts[intRow] != 0:
|
||||
if intRow != 0:
|
||||
tenIns[intRow] = tenIns[intRow] + netVer(tenIns[intRow - 1])
|
||||
# end
|
||||
# end
|
||||
intRow += 1
|
||||
# end
|
||||
|
||||
for intRow, tenIn in enumerate(tenIns):
|
||||
self.objScratch["levelshape" + str(intRow)] = tenIn.shape
|
||||
# end
|
||||
|
||||
return tenIns
|
||||
|
||||
# end
|
||||
|
||||
|
||||
# end
|
||||
|
||||
|
||||
class Decode(torch.nn.Module):
|
||||
objScratch: typing.Dict[str, typing.List[int]] = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
intIns: typing.List[int],
|
||||
intOuts: typing.List[int],
|
||||
strHor: str,
|
||||
strVer: str,
|
||||
objScratch: typing.Dict[str, typing.List[int]],
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
assert len(intIns) == len(intOuts)
|
||||
assert len(intOuts) == len(intIns)
|
||||
|
||||
self.intRows = len(intIns) and len(intOuts)
|
||||
self.intIns = intIns.copy()
|
||||
self.intOuts = intOuts.copy()
|
||||
self.strHor = strHor
|
||||
self.strVer = strVer
|
||||
self.objScratch = objScratch
|
||||
|
||||
self.netHor = torch.nn.ModuleList()
|
||||
self.netVer = torch.nn.ModuleList()
|
||||
|
||||
for intRow in range(self.intRows - 1, -1, -1):
|
||||
netHor = torch.nn.Identity()
|
||||
netVer = torch.nn.Identity()
|
||||
|
||||
if self.intOuts[intRow] != 0:
|
||||
if self.intIns[intRow] != 0:
|
||||
netHor = Basic(
|
||||
self.strHor,
|
||||
[
|
||||
self.intIns[intRow],
|
||||
self.intOuts[intRow],
|
||||
self.intOuts[intRow],
|
||||
],
|
||||
objScratch,
|
||||
)
|
||||
# end
|
||||
|
||||
if intRow != self.intRows - 1:
|
||||
netVer = Basic(
|
||||
self.strVer,
|
||||
[
|
||||
self.intOuts[intRow + 1],
|
||||
self.intOuts[intRow],
|
||||
self.intOuts[intRow],
|
||||
],
|
||||
objScratch,
|
||||
)
|
||||
# end
|
||||
# end
|
||||
|
||||
self.netHor.append(netHor)
|
||||
self.netVer.append(netVer)
|
||||
# end
|
||||
|
||||
# end
|
||||
|
||||
def forward(self, tenIns: typing.List[torch.Tensor]) -> typing.List[torch.Tensor]:
|
||||
intRow = self.intRows - 1
|
||||
for netHor in self.netHor:
|
||||
if self.intOuts[intRow] != 0:
|
||||
if self.intIns[intRow] != 0:
|
||||
tenIns[intRow] = netHor(tenIns[intRow])
|
||||
# end
|
||||
# end
|
||||
intRow -= 1
|
||||
# end
|
||||
|
||||
intRow = self.intRows - 1
|
||||
for netVer in self.netVer:
|
||||
if self.intOuts[intRow] != 0:
|
||||
if intRow != self.intRows - 1:
|
||||
tenVer = netVer(tenIns[intRow + 1])
|
||||
|
||||
if "levelshape" + str(intRow) in self.objScratch:
|
||||
if (
|
||||
tenVer.shape[2]
|
||||
== self.objScratch["levelshape" + str(intRow)][2] + 1
|
||||
):
|
||||
tenVer = torch.nn.functional.pad(
|
||||
input=tenVer,
|
||||
pad=[0, 0, 0, -1],
|
||||
mode="constant",
|
||||
value=0.0,
|
||||
)
|
||||
if (
|
||||
tenVer.shape[3]
|
||||
== self.objScratch["levelshape" + str(intRow)][3] + 1
|
||||
):
|
||||
tenVer = torch.nn.functional.pad(
|
||||
input=tenVer,
|
||||
pad=[0, -1, 0, 0],
|
||||
mode="constant",
|
||||
value=0.0,
|
||||
)
|
||||
# end
|
||||
|
||||
tenIns[intRow] = tenIns[intRow] + tenVer
|
||||
# end
|
||||
# end
|
||||
intRow -= 1
|
||||
# end
|
||||
|
||||
return tenIns
|
||||
|
||||
# end
|
||||
|
||||
|
||||
# end
|
||||
|
||||
##########################################################
|
||||
|
||||
|
||||
class Network(torch.nn.Module):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.intEncdec = [1, 1]
|
||||
self.intChannels = [32, 64, 128, 256, 512]
|
||||
|
||||
self.objScratch = {}
|
||||
|
||||
self.netInput = torch.nn.Conv2d(
|
||||
in_channels=3,
|
||||
out_channels=int(round(0.5 * self.intChannels[0])),
|
||||
kernel_size=3,
|
||||
stride=1,
|
||||
padding=1,
|
||||
padding_mode="zeros",
|
||||
)
|
||||
|
||||
self.netEncode = torch.nn.Sequential(
|
||||
*(
|
||||
[
|
||||
Encode(
|
||||
[0] * len(self.intChannels),
|
||||
self.intChannels,
|
||||
"prelu(0.25)-conv(3)-prelu(0.25)-conv(3)+skip",
|
||||
"prelu(0.25)-sconv(3)-prelu(0.25)-conv(3)",
|
||||
self.objScratch,
|
||||
)
|
||||
]
|
||||
+ [
|
||||
Encode(
|
||||
self.intChannels,
|
||||
self.intChannels,
|
||||
"prelu(0.25)-conv(3)-prelu(0.25)-conv(3)+skip",
|
||||
"prelu(0.25)-sconv(3)-prelu(0.25)-conv(3)",
|
||||
self.objScratch,
|
||||
)
|
||||
for intEncdec in range(1, self.intEncdec[0])
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
self.netDecode = torch.nn.Sequential(
|
||||
*(
|
||||
[
|
||||
Decode(
|
||||
[0] + self.intChannels[1:],
|
||||
[0] + self.intChannels[1:],
|
||||
"prelu(0.25)-conv(3)-prelu(0.25)-conv(3)+skip",
|
||||
"prelu(0.25)-up(bilinear)-conv(3)-prelu(0.25)-conv(3)",
|
||||
self.objScratch,
|
||||
)
|
||||
for intEncdec in range(0, self.intEncdec[1])
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
self.netVerone = Basic(
|
||||
"up(bilinear)-conv(3)-prelu(0.25)-conv(3)",
|
||||
[self.intChannels[1], self.intChannels[1], 51],
|
||||
)
|
||||
self.netVertwo = Basic(
|
||||
"up(bilinear)-conv(3)-prelu(0.25)-conv(3)",
|
||||
[self.intChannels[1], self.intChannels[1], 51],
|
||||
)
|
||||
self.netHorone = Basic(
|
||||
"up(bilinear)-conv(3)-prelu(0.25)-conv(3)",
|
||||
[self.intChannels[1], self.intChannels[1], 51],
|
||||
)
|
||||
self.netHortwo = Basic(
|
||||
"up(bilinear)-conv(3)-prelu(0.25)-conv(3)",
|
||||
[self.intChannels[1], self.intChannels[1], 51],
|
||||
)
|
||||
|
||||
# self.load_state_dict(torch.hub.load_state_dict_from_url(url='http://content.sniklaus.com/resepconv/network-' + arguments_strModel + '.pytorch', file_name='resepconv-' + arguments_strModel))
|
||||
|
||||
# end
|
||||
|
||||
def forward(self, x1, x2):
|
||||
# padding if needed
|
||||
intWidth = x1.shape[3]
|
||||
intHeight = x1.shape[2]
|
||||
|
||||
intPadr = (2 - (intWidth % 2)) % 2
|
||||
intPadb = (2 - (intHeight % 2)) % 2
|
||||
|
||||
tenOne = torch.nn.functional.pad(
|
||||
input=x1, pad=[0, intPadr, 0, intPadb], mode="replicate"
|
||||
)
|
||||
tenTwo = torch.nn.functional.pad(
|
||||
input=x2, pad=[0, intPadr, 0, intPadb], mode="replicate"
|
||||
)
|
||||
####
|
||||
|
||||
tenSeq = [tenOne, tenTwo]
|
||||
|
||||
with torch.set_grad_enabled(False):
|
||||
tenStack = torch.stack(tenSeq, 1)
|
||||
tenMean = (
|
||||
tenStack.view(tenStack.shape[0], -1)
|
||||
.mean(1, True)
|
||||
.view(tenStack.shape[0], 1, 1, 1)
|
||||
)
|
||||
tenStd = (
|
||||
tenStack.view(tenStack.shape[0], -1)
|
||||
.std(1, True)
|
||||
.view(tenStack.shape[0], 1, 1, 1)
|
||||
)
|
||||
tenSeq = [
|
||||
(tenFrame - tenMean) / (tenStd + 0.0000001) for tenFrame in tenSeq
|
||||
]
|
||||
tenSeq = [tenFrame.detach() for tenFrame in tenSeq]
|
||||
# end
|
||||
|
||||
tenOut = self.netDecode(
|
||||
self.netEncode(
|
||||
[torch.cat([self.netInput(tenSeq[0]), self.netInput(tenSeq[1])], 1)]
|
||||
+ ([0.0] * (len(self.intChannels) - 1))
|
||||
)
|
||||
)[1]
|
||||
|
||||
tenOne = torch.nn.functional.pad(
|
||||
input=tenOne,
|
||||
pad=[
|
||||
int(math.floor(0.5 * 51)),
|
||||
int(math.floor(0.5 * 51)),
|
||||
int(math.floor(0.5 * 51)),
|
||||
int(math.floor(0.5 * 51)),
|
||||
],
|
||||
mode="replicate",
|
||||
)
|
||||
tenTwo = torch.nn.functional.pad(
|
||||
input=tenTwo,
|
||||
pad=[
|
||||
int(math.floor(0.5 * 51)),
|
||||
int(math.floor(0.5 * 51)),
|
||||
int(math.floor(0.5 * 51)),
|
||||
int(math.floor(0.5 * 51)),
|
||||
],
|
||||
mode="replicate",
|
||||
)
|
||||
|
||||
tenOne = torch.cat(
|
||||
[
|
||||
tenOne,
|
||||
tenOne.new_ones([tenOne.shape[0], 1, tenOne.shape[2], tenOne.shape[3]]),
|
||||
],
|
||||
1,
|
||||
).detach()
|
||||
tenTwo = torch.cat(
|
||||
[
|
||||
tenTwo,
|
||||
tenTwo.new_ones([tenTwo.shape[0], 1, tenTwo.shape[2], tenTwo.shape[3]]),
|
||||
],
|
||||
1,
|
||||
).detach()
|
||||
|
||||
tenVerone = self.netVerone(tenOut)
|
||||
tenVertwo = self.netVertwo(tenOut)
|
||||
tenHorone = self.netHorone(tenOut)
|
||||
tenHortwo = self.netHortwo(tenOut)
|
||||
|
||||
tenOut = sepconv_func.apply(tenOne, tenVerone, tenHorone) + sepconv_func.apply(
|
||||
tenTwo, tenVertwo, tenHortwo
|
||||
)
|
||||
|
||||
tenNormalize = tenOut[:, -1:, :, :]
|
||||
tenNormalize[tenNormalize.abs() < 0.01] = 1.0
|
||||
tenOut = tenOut[:, :-1, :, :] / tenNormalize
|
||||
|
||||
# crop if needed
|
||||
return tenOut[:, :, :intHeight, :intWidth]
|
||||
|
||||
# end
|
||||
|
||||
|
||||
# end
|
||||
|
||||
netNetwork = None
|
||||
|
||||
##########################################################
|
||||
|
||||
|
||||
def estimate(tenOne, tenTwo):
|
||||
global netNetwork
|
||||
|
||||
if netNetwork is None:
|
||||
netNetwork = Network().to(get_torch_device()).eval()
|
||||
# end
|
||||
|
||||
assert tenOne.shape[1] == tenTwo.shape[1]
|
||||
assert tenOne.shape[2] == tenTwo.shape[2]
|
||||
|
||||
intWidth = tenOne.shape[2]
|
||||
intHeight = tenOne.shape[1]
|
||||
|
||||
assert (
|
||||
intWidth <= 1280
|
||||
) # while our approach works with larger images, we do not recommend it unless you are aware of the implications
|
||||
assert (
|
||||
intHeight <= 720
|
||||
) # while our approach works with larger images, we do not recommend it unless you are aware of the implications
|
||||
|
||||
tenPreprocessedOne = tenOne.to(get_torch_device()).view(1, 3, intHeight, intWidth)
|
||||
tenPreprocessedTwo = tenTwo.to(get_torch_device()).view(1, 3, intHeight, intWidth)
|
||||
|
||||
intPadr = (2 - (intWidth % 2)) % 2
|
||||
intPadb = (2 - (intHeight % 2)) % 2
|
||||
|
||||
tenPreprocessedOne = torch.nn.functional.pad(
|
||||
input=tenPreprocessedOne, pad=[0, intPadr, 0, intPadb], mode="replicate"
|
||||
)
|
||||
tenPreprocessedTwo = torch.nn.functional.pad(
|
||||
input=tenPreprocessedTwo, pad=[0, intPadr, 0, intPadb], mode="replicate"
|
||||
)
|
||||
|
||||
return netNetwork([tenPreprocessedOne, tenPreprocessedTwo])[
|
||||
0, :, :intHeight, :intWidth
|
||||
].cpu()
|
||||
|
||||
|
||||
# end
|
||||
@@ -1,100 +0,0 @@
|
||||
import torch
|
||||
from comfy.model_management import get_torch_device, soft_empty_cache
|
||||
import numpy as np
|
||||
import typing
|
||||
from vfi_utils import InterpolationStateList, load_file_from_github_release, preprocess_frames, postprocess_frames, assert_batch_size
|
||||
import pathlib
|
||||
import warnings
|
||||
import gc
|
||||
|
||||
MODEL_TYPE = pathlib.Path(__file__).parent.name
|
||||
device = get_torch_device()
|
||||
|
||||
class STMFNet_VFI:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"ckpt_name": (["stmfnet.pth"], ),
|
||||
"frames": ("IMAGE", ),
|
||||
"clear_cache_after_n_frames": ("INT", {"default": 10, "min": 1, "max": 1000}),
|
||||
"multiplier": ("INT", {"default": 2, "min": 2, "max": 2}), #TODO: Implement recursively invoking interpolator for multi-frame interpolation
|
||||
"duplicate_first_last_frames": ("BOOLEAN", {"default": False})
|
||||
},
|
||||
"optional": {
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", )
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", )
|
||||
FUNCTION = "vfi"
|
||||
CATEGORY = "ComfyUI-Frame-Interpolation/VFI"
|
||||
|
||||
#Reference: https://github.com/danier97/ST-MFNet/blob/main/interpolate_yuv.py#L93
|
||||
def vfi(
|
||||
self,
|
||||
ckpt_name: typing.AnyStr,
|
||||
frames: torch.Tensor,
|
||||
clear_cache_after_n_frames = 10,
|
||||
multiplier: typing.SupportsInt = 2,
|
||||
duplicate_first_last_frames: bool = False,
|
||||
optional_interpolation_states: InterpolationStateList = None,
|
||||
**kwargs
|
||||
):
|
||||
from .stmfnet_arch import STMFNet_Model
|
||||
if multiplier != 2:
|
||||
warnings.warn("Currently, ST-MFNet only supports 2x interpolation. The process will continue but please set multiplier=2 afterward")
|
||||
|
||||
assert_batch_size(frames, batch_size=4, vfi_name="ST-MFNet")
|
||||
interpolation_states = optional_interpolation_states
|
||||
model_path = load_file_from_github_release(MODEL_TYPE, ckpt_name)
|
||||
model = STMFNet_Model()
|
||||
model.load_state_dict(torch.load(model_path))
|
||||
model = model.eval().to(device)
|
||||
|
||||
frames = preprocess_frames(frames)
|
||||
number_of_frames_processed_since_last_cleared_cuda_cache = 0
|
||||
output_frames = []
|
||||
for frame_itr in range(len(frames) - 3):
|
||||
#Does skipping frame i+1 make sanse in this case?
|
||||
if interpolation_states is not None and interpolation_states.is_frame_skipped(frame_itr) and interpolation_states.is_frame_skipped(frame_itr + 1):
|
||||
continue
|
||||
|
||||
#Ensure that input frames are in fp32 - the same dtype as model
|
||||
frame0, frame1, frame2, frame3 = (
|
||||
frames[frame_itr:frame_itr+1].float(),
|
||||
frames[frame_itr+1:frame_itr+2].float(),
|
||||
frames[frame_itr+2:frame_itr+3].float(),
|
||||
frames[frame_itr+3:frame_itr+4].float()
|
||||
)
|
||||
new_frame = model(frame0.to(device), frame1.to(device), frame2.to(device), frame3.to(device)).detach().cpu()
|
||||
number_of_frames_processed_since_last_cleared_cuda_cache += 2
|
||||
|
||||
if frame_itr == 0:
|
||||
output_frames.append(frame0)
|
||||
if duplicate_first_last_frames:
|
||||
output_frames.append(frame0) # repeat the first frame
|
||||
output_frames.append(frame1)
|
||||
output_frames.append(new_frame)
|
||||
output_frames.append(frame2)
|
||||
if frame_itr == len(frames) - 4:
|
||||
output_frames.append(frame3)
|
||||
if duplicate_first_last_frames:
|
||||
output_frames.append(frame3) # repeat the last frame
|
||||
|
||||
# Try to avoid a memory overflow by clearing cuda cache regularly
|
||||
if number_of_frames_processed_since_last_cleared_cuda_cache >= clear_cache_after_n_frames:
|
||||
print("Comfy-VFI: Clearing cache...")
|
||||
soft_empty_cache()
|
||||
number_of_frames_processed_since_last_cleared_cuda_cache = 0
|
||||
print("Comfy-VFI: Done cache clearing")
|
||||
gc.collect()
|
||||
|
||||
dtype = torch.float32
|
||||
output_frames = [frame.cpu().to(dtype=dtype) for frame in output_frames] #Ensure all frames are in cpu
|
||||
out = torch.cat(output_frames, dim=0)
|
||||
# clear cache for courtesy
|
||||
print("Comfy-VFI: Final clearing cache...")
|
||||
soft_empty_cache()
|
||||
print("Comfy-VFI: Done cache clearing")
|
||||
return (postprocess_frames(out), )
|
||||
@@ -1,115 +0,0 @@
|
||||
import argparse
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import einops
|
||||
from torch.utils.data import DataLoader
|
||||
import pathlib
|
||||
from vfi_utils import load_file_from_github_release, preprocess_frames, postprocess_frames, InterpolationStateList
|
||||
import typing
|
||||
from comfy.model_management import get_torch_device
|
||||
|
||||
CKPT_CONFIGS = {
|
||||
"XVFInet_X4K1000FPS_exp1_latest.pt": {
|
||||
"module_scale_factor": 4,
|
||||
"S_trn": 3,
|
||||
"S_tst": 5
|
||||
},
|
||||
"XVFInet_Vimeo_exp1_latest.pt": {
|
||||
"module_scale_factor": 2,
|
||||
"S_trn": 1,
|
||||
"S_tst": 1
|
||||
}
|
||||
}
|
||||
|
||||
class XVFI_Inference(nn.Module):
|
||||
def __init__(self, model_path, model_config) -> None:
|
||||
super(XVFI_Inference, self).__init__()
|
||||
from .xvfi_arch import XVFInet, weights_init
|
||||
model_config = model_config
|
||||
args = argparse.Namespace(
|
||||
gpu=get_torch_device(),
|
||||
nf=64,
|
||||
**model_config,
|
||||
img_ch=3,
|
||||
)
|
||||
self.model = XVFInet(args).apply(weights_init).to(get_torch_device())
|
||||
self.model.load_state_dict(torch.load(model_path, map_location=get_torch_device())["state_dict_Model"])
|
||||
|
||||
def forward(self, I0, I1, timestep):
|
||||
#"Real" inference is called "test_custom" in the original repo
|
||||
#https://github.com/JihyongOh/XVFI/blob/main/utils.py#L434
|
||||
#https://github.com/JihyongOh/XVFI/blob/main/main.py#L336
|
||||
|
||||
x = torch.stack([I0, I1], dim=0)
|
||||
x = einops.rearrange(x, "t b c h w -> b c t h w")
|
||||
return self.model(x, timestep, is_training=False)
|
||||
|
||||
MODEL_TYPE = pathlib.Path(__file__).parent.name
|
||||
|
||||
class XVFI:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"ckpt_name": (list(CKPT_CONFIGS.keys()), ),
|
||||
"frames": ("IMAGE", ),
|
||||
"batch_size": ("INT", {"default": 1, "min": 1, "max": 100}),
|
||||
"multipler": ("INT", {"default": 2, "min": 2, "max": 1000}),
|
||||
},
|
||||
"optional": {
|
||||
"optional_interpolation_states": ("INTERPOLATION_STATES", ),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", )
|
||||
FUNCTION = "vfi"
|
||||
CATEGORY = "ComfyUI-Frame-Interpolation/VFI"
|
||||
|
||||
def vfi(
|
||||
self,
|
||||
ckpt_name: typing.AnyStr,
|
||||
frames: torch.Tensor,
|
||||
batch_size: typing.SupportsInt = 1,
|
||||
multipler: typing.SupportsInt = 2,
|
||||
optional_interpolation_states: InterpolationStateList = None
|
||||
):
|
||||
model_path = load_file_from_github_release(MODEL_TYPE, ckpt_name)
|
||||
ckpt_config = CKPT_CONFIGS[ckpt_name]
|
||||
global model
|
||||
model = XVFI_Inference(model_path, ckpt_config)
|
||||
|
||||
frames = preprocess_frames(frames)
|
||||
#https://github.com/JihyongOh/XVFI/blob/main/main.py#L314
|
||||
divide = 2 ** (ckpt_config["S_tst"]) * ckpt_config["module_scale_factor"] * 4
|
||||
B, C, H, W = frames.size()
|
||||
H_padding = (divide - H % divide) % divide
|
||||
W_padding = (divide - W % divide) % divide
|
||||
if H_padding != 0 or W_padding != 0:
|
||||
frames = F.pad(frames, (0, W_padding, 0, H_padding), "constant")
|
||||
|
||||
frame_dict = {
|
||||
str(i): frames[i].unsqueeze(0) for i in range(frames.shape[0])
|
||||
}
|
||||
|
||||
if optional_interpolation_states is None:
|
||||
interpolation_states = [True] * (frames.shape[0] - 1)
|
||||
else:
|
||||
interpolation_states = optional_interpolation_states
|
||||
|
||||
enabled_former_idxs = [i for i, state in enumerate(interpolation_states) if state]
|
||||
former_idxs_loader = DataLoader(enabled_former_idxs, batch_size=batch_size)
|
||||
|
||||
for former_idxs_batch in former_idxs_loader:
|
||||
for middle_i in range(1, multipler):
|
||||
_middle_frames = model(
|
||||
frames[former_idxs_batch],
|
||||
frames[former_idxs_batch + 1],
|
||||
timestep=torch.tensor([middle_i/multipler]).repeat(len(former_idxs_batch)).unsqueeze(1).to(get_torch_device())
|
||||
)
|
||||
for i, former_idx in enumerate(former_idxs_batch):
|
||||
frame_dict[f'{former_idx}.{middle_i}'] = _middle_frames[i].unsqueeze(0)
|
||||
|
||||
out_frames = torch.cat([frame_dict[key] for key in sorted(frame_dict.keys())], dim=0)[:, :, :H, :W]
|
||||
return (postprocess_frames(out_frames), )
|
||||
|
||||
@@ -1,506 +0,0 @@
|
||||
import functools, random
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from torch.autograd import Variable
|
||||
import numpy as np
|
||||
from torch.nn import init
|
||||
from comfy.model_management import get_torch_device
|
||||
|
||||
|
||||
|
||||
class XVFInet(nn.Module):
|
||||
|
||||
def __init__(self, args):
|
||||
super(XVFInet, self).__init__()
|
||||
self.args = args
|
||||
self.device = get_torch_device()
|
||||
self.nf = args.nf
|
||||
self.scale = args.module_scale_factor
|
||||
self.vfinet = VFInet(args)
|
||||
self.lrelu = nn.ReLU()
|
||||
self.in_channels = 3
|
||||
self.channel_converter = nn.Sequential(
|
||||
nn.Conv3d(self.in_channels, self.nf, [1, 3, 3], [1, 1, 1], [0, 1, 1]),
|
||||
nn.ReLU())
|
||||
|
||||
self.rec_ext_ds_module = [self.channel_converter]
|
||||
self.rec_ext_ds = nn.Conv3d(self.nf, self.nf, [1, 3, 3], [1, 2, 2], [0, 1, 1])
|
||||
for _ in range(int(np.log2(self.scale))):
|
||||
self.rec_ext_ds_module.append(self.rec_ext_ds)
|
||||
self.rec_ext_ds_module.append(nn.ReLU())
|
||||
self.rec_ext_ds_module.append(nn.Conv3d(self.nf, self.nf, [1, 3, 3], 1, [0, 1, 1]))
|
||||
self.rec_ext_ds_module.append(RResBlock2D_3D(args, T_reduce_flag=False))
|
||||
self.rec_ext_ds_module = nn.Sequential(*self.rec_ext_ds_module)
|
||||
|
||||
self.rec_ctx_ds = nn.Conv3d(self.nf, self.nf, [1, 3, 3], [1, 2, 2], [0, 1, 1])
|
||||
|
||||
print("The lowest scale depth for training (S_trn): ", self.args.S_trn)
|
||||
print("The lowest scale depth for test (S_tst): ", self.args.S_tst)
|
||||
|
||||
def forward(self, x, t_value, is_training=True):
|
||||
'''
|
||||
x shape : [B,C,T,H,W]
|
||||
t_value shape : [B,1] ###############
|
||||
'''
|
||||
B, C, T, H, W = x.size()
|
||||
B2, C2 = t_value.size()
|
||||
assert C2 == 1, "t_value shape is [B,]"
|
||||
assert T % 2 == 0, "T must be an even number"
|
||||
t_value = t_value.view(B, 1, 1, 1)
|
||||
|
||||
flow_l = None
|
||||
feat_x = self.rec_ext_ds_module(x)
|
||||
feat_x_list = [feat_x]
|
||||
self.lowest_depth_level = self.args.S_trn if is_training else self.args.S_tst
|
||||
for level in range(1, self.lowest_depth_level+1):
|
||||
feat_x = self.rec_ctx_ds(feat_x)
|
||||
feat_x_list.append(feat_x)
|
||||
|
||||
if is_training:
|
||||
out_l_list = []
|
||||
flow_refine_l_list = []
|
||||
out_l, flow_l, flow_refine_l = self.vfinet(x, feat_x_list[self.args.S_trn], flow_l, t_value, level=self.args.S_trn, is_training=True)
|
||||
out_l_list.append(out_l)
|
||||
flow_refine_l_list.append(flow_refine_l)
|
||||
for level in range(self.args.S_trn-1, 0, -1): ## self.args.S_trn, self.args.S_trn-1, ..., 1. level 0 is not included
|
||||
out_l, flow_l = self.vfinet(x, feat_x_list[level], flow_l, t_value, level=level, is_training=True)
|
||||
out_l_list.append(out_l)
|
||||
out_l, flow_l, flow_refine_l, occ_0_l0 = self.vfinet(x, feat_x_list[0], flow_l, t_value, level=0, is_training=True)
|
||||
out_l_list.append(out_l)
|
||||
flow_refine_l_list.append(flow_refine_l)
|
||||
return out_l_list[::-1], flow_refine_l_list[::-1], occ_0_l0, torch.mean(x, dim=2) # out_l_list should be reversed. [out_l0, out_l1, ...]
|
||||
|
||||
else: # Testing
|
||||
for level in range(self.args.S_tst, 0, -1): ## self.args.S_tst, self.args.S_tst-1, ..., 1. level 0 is not included
|
||||
flow_l = self.vfinet(x, feat_x_list[level], flow_l, t_value, level=level, is_training=False)
|
||||
out_l = self.vfinet(x, feat_x_list[0], flow_l, t_value, level=0, is_training=False)
|
||||
return out_l
|
||||
|
||||
|
||||
class VFInet(nn.Module):
|
||||
|
||||
def __init__(self, args):
|
||||
super(VFInet, self).__init__()
|
||||
self.args = args
|
||||
self.device = get_torch_device()
|
||||
self.nf = args.nf
|
||||
self.scale = args.module_scale_factor
|
||||
self.in_channels = 3
|
||||
|
||||
self.conv_flow_bottom = nn.Sequential(
|
||||
nn.Conv2d(2*self.nf, 2*self.nf, [4,4], 2, [1,1]),
|
||||
nn.ReLU(),
|
||||
nn.Conv2d(2*self.nf, 4*self.nf, [4,4], 2, [1,1]),
|
||||
nn.ReLU(),
|
||||
nn.UpsamplingNearest2d(scale_factor=2),
|
||||
nn.Conv2d(4 * self.nf, 2 * self.nf, [3, 3], 1, [1, 1]),
|
||||
nn.ReLU(),
|
||||
nn.UpsamplingNearest2d(scale_factor=2),
|
||||
nn.Conv2d(2 * self.nf, self.nf, [3, 3], 1, [1, 1]),
|
||||
nn.ReLU(),
|
||||
nn.Conv2d(self.nf, 6, [3,3], 1, [1,1]),
|
||||
)
|
||||
|
||||
self.conv_flow1 = nn.Conv2d(2*self.nf, self.nf, [3, 3], 1, [1, 1])
|
||||
|
||||
self.conv_flow2 = nn.Sequential(
|
||||
nn.Conv2d(2*self.nf + 4, 2 * self.nf, [4, 4], 2, [1, 1]),
|
||||
nn.ReLU(),
|
||||
nn.Conv2d(2 * self.nf, 4 * self.nf, [4, 4], 2, [1, 1]),
|
||||
nn.ReLU(),
|
||||
nn.UpsamplingNearest2d(scale_factor=2),
|
||||
nn.Conv2d(4 * self.nf, 2 * self.nf, [3, 3], 1, [1, 1]),
|
||||
nn.ReLU(),
|
||||
nn.UpsamplingNearest2d(scale_factor=2),
|
||||
nn.Conv2d(2 * self.nf, self.nf, [3, 3], 1, [1, 1]),
|
||||
nn.ReLU(),
|
||||
nn.Conv2d(self.nf, 6, [3, 3], 1, [1, 1]),
|
||||
)
|
||||
|
||||
self.conv_flow3 = nn.Sequential(
|
||||
nn.Conv2d(4 + self.nf * 4, self.nf, [1, 1], 1, [0, 0]),
|
||||
nn.ReLU(),
|
||||
nn.Conv2d(self.nf, 2 * self.nf, [4, 4], 2, [1, 1]),
|
||||
nn.ReLU(),
|
||||
nn.Conv2d(2 * self.nf, 4 * self.nf, [4, 4], 2, [1, 1]),
|
||||
nn.ReLU(),
|
||||
nn.UpsamplingNearest2d(scale_factor=2),
|
||||
nn.Conv2d(4 * self.nf, 2 * self.nf, [3, 3], 1, [1, 1]),
|
||||
nn.ReLU(),
|
||||
nn.UpsamplingNearest2d(scale_factor=2),
|
||||
nn.Conv2d(2 * self.nf, self.nf, [3, 3], 1, [1, 1]),
|
||||
nn.ReLU(),
|
||||
nn.Conv2d(self.nf, 4, [3, 3], 1, [1, 1]),
|
||||
)
|
||||
|
||||
self.refine_unet = RefineUNet(args)
|
||||
self.lrelu = nn.ReLU()
|
||||
|
||||
def forward(self, x, feat_x, flow_l_prev, t_value, level, is_training):
|
||||
'''
|
||||
x shape : [B,C,T,H,W]
|
||||
t_value shape : [B,1] ###############
|
||||
'''
|
||||
B, C, T, H, W = x.size()
|
||||
assert T % 2 == 0, "T must be an even number"
|
||||
|
||||
####################### For a single level
|
||||
l = 2 ** level
|
||||
x_l = x.permute(0,2,1,3,4)
|
||||
x_l = x_l.contiguous().view(B * T, C, H, W)
|
||||
|
||||
if level == 0:
|
||||
pass
|
||||
else:
|
||||
x_l = F.interpolate(x_l, scale_factor=(1.0 / l, 1.0 / l), mode='bicubic', align_corners=False)
|
||||
'''
|
||||
Down pixel-shuffle
|
||||
'''
|
||||
x_l = x_l.view(B, T, C, H//l, W//l)
|
||||
x_l = x_l.permute(0,2,1,3,4)
|
||||
|
||||
B, C, T, H, W = x_l.size()
|
||||
|
||||
## Feature extraction
|
||||
feat0_l = feat_x[:,:,0,:,:]
|
||||
feat1_l = feat_x[:,:,1,:,:]
|
||||
|
||||
## Flow estimation
|
||||
if flow_l_prev is None:
|
||||
flow_l_tmp = self.conv_flow_bottom(torch.cat((feat0_l, feat1_l), dim=1))
|
||||
flow_l = flow_l_tmp[:,:4,:,:]
|
||||
else:
|
||||
up_flow_l_prev = 2.0*F.interpolate(flow_l_prev.detach(), scale_factor=(2,2), mode='bilinear', align_corners=False)
|
||||
warped_feat1_l = self.bwarp(feat1_l, up_flow_l_prev[:,:2,:,:])
|
||||
warped_feat0_l = self.bwarp(feat0_l, up_flow_l_prev[:,2:,:,:])
|
||||
flow_l_tmp = self.conv_flow2(torch.cat([self.conv_flow1(torch.cat([feat0_l, warped_feat1_l],dim=1)), self.conv_flow1(torch.cat([feat1_l, warped_feat0_l],dim=1)), up_flow_l_prev],dim=1))
|
||||
flow_l = flow_l_tmp[:,:4,:,:] + up_flow_l_prev
|
||||
|
||||
if not is_training and level!=0:
|
||||
return flow_l
|
||||
|
||||
flow_01_l = flow_l[:,:2,:,:]
|
||||
flow_10_l = flow_l[:,2:,:,:]
|
||||
z_01_l = torch.sigmoid(flow_l_tmp[:,4:5,:,:])
|
||||
z_10_l = torch.sigmoid(flow_l_tmp[:,5:6,:,:])
|
||||
|
||||
## Complementary Flow Reversal (CFR)
|
||||
flow_forward, norm0_l = self.z_fwarp(flow_01_l, t_value * flow_01_l, z_01_l) ## Actually, F (t) -> (t+1). Translation only. Not normalized yet
|
||||
flow_backward, norm1_l = self.z_fwarp(flow_10_l, (1-t_value) * flow_10_l, z_10_l) ## Actually, F (1-t) -> (-t). Translation only. Not normalized yet
|
||||
|
||||
flow_t0_l = -(1-t_value) * ((t_value)*flow_forward) + (t_value) * ((t_value)*flow_backward) # The numerator of Eq.(1) in the paper.
|
||||
flow_t1_l = (1-t_value) * ((1-t_value)*flow_forward) - (t_value) * ((1-t_value)*flow_backward) # The numerator of Eq.(2) in the paper.
|
||||
|
||||
norm_l = (1-t_value)*norm0_l + t_value*norm1_l
|
||||
mask_ = (norm_l.detach() > 0).type(norm_l.type())
|
||||
flow_t0_l = (1-mask_) * flow_t0_l + mask_ * (flow_t0_l.clone() / (norm_l.clone() + (1-mask_))) # Divide the numerator with denominator in Eq.(1)
|
||||
flow_t1_l = (1-mask_) * flow_t1_l + mask_ * (flow_t1_l.clone() / (norm_l.clone() + (1-mask_))) # Divide the numerator with denominator in Eq.(2)
|
||||
|
||||
## Feature warping
|
||||
warped0_l = self.bwarp(feat0_l, flow_t0_l)
|
||||
warped1_l = self.bwarp(feat1_l, flow_t1_l)
|
||||
|
||||
## Flow refinement
|
||||
flow_refine_l = torch.cat([feat0_l, warped0_l, warped1_l, feat1_l, flow_t0_l, flow_t1_l], dim=1)
|
||||
flow_refine_l = self.conv_flow3(flow_refine_l) + torch.cat([flow_t0_l, flow_t1_l], dim=1)
|
||||
flow_t0_l = flow_refine_l[:, :2, :, :]
|
||||
flow_t1_l = flow_refine_l[:, 2:4, :, :]
|
||||
|
||||
warped0_l = self.bwarp(feat0_l, flow_t0_l)
|
||||
warped1_l = self.bwarp(feat1_l, flow_t1_l)
|
||||
|
||||
## Flow upscale
|
||||
flow_t0_l = self.scale * F.interpolate(flow_t0_l, scale_factor=(self.scale, self.scale), mode='bilinear',align_corners=False)
|
||||
flow_t1_l = self.scale * F.interpolate(flow_t1_l, scale_factor=(self.scale, self.scale), mode='bilinear',align_corners=False)
|
||||
|
||||
## Image warping and blending
|
||||
warped_img0_l = self.bwarp(x_l[:,:,0,:,:], flow_t0_l)
|
||||
warped_img1_l = self.bwarp(x_l[:,:,1,:,:], flow_t1_l)
|
||||
|
||||
refine_out = self.refine_unet(torch.cat([F.pixel_shuffle(torch.cat([feat0_l, feat1_l, warped0_l, warped1_l],dim=1), self.scale), x_l[:,:,0,:,:], x_l[:,:,1,:,:], warped_img0_l, warped_img1_l, flow_t0_l, flow_t1_l],dim=1))
|
||||
occ_0_l = torch.sigmoid(refine_out[:, 0:1, :, :])
|
||||
occ_1_l = 1-occ_0_l
|
||||
|
||||
out_l = (1-t_value)*occ_0_l*warped_img0_l + t_value*occ_1_l*warped_img1_l
|
||||
out_l = out_l / ( (1-t_value)*occ_0_l + t_value*occ_1_l ) + refine_out[:, 1:4, :, :]
|
||||
|
||||
if not is_training and level==0:
|
||||
return out_l
|
||||
|
||||
if is_training:
|
||||
if flow_l_prev is None:
|
||||
# if level == self.args.S_trn:
|
||||
return out_l, flow_l, flow_refine_l[:, 0:4, :, :]
|
||||
elif level != 0:
|
||||
return out_l, flow_l
|
||||
else: # level==0
|
||||
return out_l, flow_l, flow_refine_l[:, 0:4, :, :], occ_0_l
|
||||
|
||||
def bwarp(self, x, flo):
|
||||
'''
|
||||
x: [B, C, H, W] (im2)
|
||||
flo: [B, 2, H, W] flow
|
||||
'''
|
||||
B, C, H, W = x.size()
|
||||
# mesh grid
|
||||
xx = torch.arange(0, W).view(1, 1, 1, W).expand(B, 1, H, W)
|
||||
yy = torch.arange(0, H).view(1, 1, H, 1).expand(B, 1, H, W)
|
||||
grid = torch.cat((xx, yy), 1).float()
|
||||
|
||||
grid = grid.to(self.device)
|
||||
vgrid = torch.autograd.Variable(grid) + flo
|
||||
|
||||
# scale grid to [-1,1]
|
||||
vgrid[:, 0, :, :] = 2.0 * vgrid[:, 0, :, :].clone() / max(W - 1, 1) - 1.0
|
||||
vgrid[:, 1, :, :] = 2.0 * vgrid[:, 1, :, :].clone() / max(H - 1, 1) - 1.0
|
||||
|
||||
vgrid = vgrid.permute(0, 2, 3, 1) # [B,H,W,2]
|
||||
output = nn.functional.grid_sample(x, vgrid, align_corners=True)
|
||||
mask = torch.autograd.Variable(torch.ones(x.size())).to(self.device)
|
||||
mask = nn.functional.grid_sample(mask, vgrid, align_corners=True)
|
||||
|
||||
# mask[mask<0.9999] = 0
|
||||
# mask[mask>0] = 1
|
||||
mask = mask.masked_fill_(mask < 0.999, 0)
|
||||
mask = mask.masked_fill_(mask > 0, 1)
|
||||
|
||||
return output * mask
|
||||
|
||||
def fwarp(self, img, flo):
|
||||
|
||||
"""
|
||||
-img: image (N, C, H, W)
|
||||
-flo: optical flow (N, 2, H, W)
|
||||
elements of flo is in [0, H] and [0, W] for dx, dy
|
||||
https://github.com/lyh-18/EQVI/blob/EQVI-master/models/forward_warp_gaussian.py
|
||||
"""
|
||||
|
||||
# (x1, y1) (x1, y2)
|
||||
# +---------------+
|
||||
# | |
|
||||
# | o(x, y) |
|
||||
# | |
|
||||
# | |
|
||||
# | |
|
||||
# | |
|
||||
# +---------------+
|
||||
# (x2, y1) (x2, y2)
|
||||
|
||||
N, C, _, _ = img.size()
|
||||
|
||||
# translate start-point optical flow to end-point optical flow
|
||||
y = flo[:, 0:1:, :]
|
||||
x = flo[:, 1:2, :, :]
|
||||
|
||||
x = x.repeat(1, C, 1, 1)
|
||||
y = y.repeat(1, C, 1, 1)
|
||||
|
||||
# Four point of square (x1, y1), (x1, y2), (x2, y1), (y2, y2)
|
||||
x1 = torch.floor(x)
|
||||
x2 = x1 + 1
|
||||
y1 = torch.floor(y)
|
||||
y2 = y1 + 1
|
||||
|
||||
# firstly, get gaussian weights
|
||||
w11, w12, w21, w22 = self.get_gaussian_weights(x, y, x1, x2, y1, y2)
|
||||
|
||||
# secondly, sample each weighted corner
|
||||
img11, o11 = self.sample_one(img, x1, y1, w11)
|
||||
img12, o12 = self.sample_one(img, x1, y2, w12)
|
||||
img21, o21 = self.sample_one(img, x2, y1, w21)
|
||||
img22, o22 = self.sample_one(img, x2, y2, w22)
|
||||
|
||||
imgw = img11 + img12 + img21 + img22
|
||||
o = o11 + o12 + o21 + o22
|
||||
|
||||
return imgw, o
|
||||
|
||||
|
||||
def z_fwarp(self, img, flo, z):
|
||||
"""
|
||||
-img: image (N, C, H, W)
|
||||
-flo: optical flow (N, 2, H, W)
|
||||
elements of flo is in [0, H] and [0, W] for dx, dy
|
||||
modified from https://github.com/lyh-18/EQVI/blob/EQVI-master/models/forward_warp_gaussian.py
|
||||
"""
|
||||
|
||||
# (x1, y1) (x1, y2)
|
||||
# +---------------+
|
||||
# | |
|
||||
# | o(x, y) |
|
||||
# | |
|
||||
# | |
|
||||
# | |
|
||||
# | |
|
||||
# +---------------+
|
||||
# (x2, y1) (x2, y2)
|
||||
|
||||
N, C, _, _ = img.size()
|
||||
|
||||
# translate start-point optical flow to end-point optical flow
|
||||
y = flo[:, 0:1:, :]
|
||||
x = flo[:, 1:2, :, :]
|
||||
|
||||
x = x.repeat(1, C, 1, 1)
|
||||
y = y.repeat(1, C, 1, 1)
|
||||
|
||||
# Four point of square (x1, y1), (x1, y2), (x2, y1), (y2, y2)
|
||||
x1 = torch.floor(x)
|
||||
x2 = x1 + 1
|
||||
y1 = torch.floor(y)
|
||||
y2 = y1 + 1
|
||||
|
||||
# firstly, get gaussian weights
|
||||
w11, w12, w21, w22 = self.get_gaussian_weights(x, y, x1, x2, y1, y2, z+1e-5)
|
||||
|
||||
# secondly, sample each weighted corner
|
||||
img11, o11 = self.sample_one(img, x1, y1, w11)
|
||||
img12, o12 = self.sample_one(img, x1, y2, w12)
|
||||
img21, o21 = self.sample_one(img, x2, y1, w21)
|
||||
img22, o22 = self.sample_one(img, x2, y2, w22)
|
||||
|
||||
imgw = img11 + img12 + img21 + img22
|
||||
o = o11 + o12 + o21 + o22
|
||||
|
||||
return imgw, o
|
||||
|
||||
|
||||
def get_gaussian_weights(self, x, y, x1, x2, y1, y2, z=1.0):
|
||||
# z 0.0 ~ 1.0
|
||||
w11 = z * torch.exp(-((x - x1) ** 2 + (y - y1) ** 2))
|
||||
w12 = z * torch.exp(-((x - x1) ** 2 + (y - y2) ** 2))
|
||||
w21 = z * torch.exp(-((x - x2) ** 2 + (y - y1) ** 2))
|
||||
w22 = z * torch.exp(-((x - x2) ** 2 + (y - y2) ** 2))
|
||||
|
||||
return w11, w12, w21, w22
|
||||
|
||||
def sample_one(self, img, shiftx, shifty, weight):
|
||||
"""
|
||||
Input:
|
||||
-img (N, C, H, W)
|
||||
-shiftx, shifty (N, c, H, W)
|
||||
"""
|
||||
|
||||
N, C, H, W = img.size()
|
||||
|
||||
# flatten all (all restored as Tensors)
|
||||
flat_shiftx = shiftx.view(-1)
|
||||
flat_shifty = shifty.view(-1)
|
||||
flat_basex = torch.arange(0, H, requires_grad=False).view(-1, 1)[None, None].to(self.device).long().repeat(N, C,1,W).view(-1)
|
||||
flat_basey = torch.arange(0, W, requires_grad=False).view(1, -1)[None, None].to(self.device).long().repeat(N, C,H,1).view(-1)
|
||||
flat_weight = weight.view(-1)
|
||||
flat_img = img.contiguous().view(-1)
|
||||
|
||||
# The corresponding positions in I1
|
||||
idxn = torch.arange(0, N, requires_grad=False).view(N, 1, 1, 1).to(self.device).long().repeat(1, C, H, W).view(-1)
|
||||
idxc = torch.arange(0, C, requires_grad=False).view(1, C, 1, 1).to(self.device).long().repeat(N, 1, H, W).view(-1)
|
||||
idxx = flat_shiftx.long() + flat_basex
|
||||
idxy = flat_shifty.long() + flat_basey
|
||||
|
||||
# recording the inside part the shifted
|
||||
mask = idxx.ge(0) & idxx.lt(H) & idxy.ge(0) & idxy.lt(W)
|
||||
|
||||
# Mask off points out of boundaries
|
||||
ids = (idxn * C * H * W + idxc * H * W + idxx * W + idxy)
|
||||
ids_mask = torch.masked_select(ids, mask).clone().to(self.device)
|
||||
|
||||
# Note here! accmulate fla must be true for proper bp
|
||||
img_warp = torch.zeros([N * C * H * W, ]).to(self.device)
|
||||
img_warp.put_(ids_mask, torch.masked_select(flat_img * flat_weight, mask), accumulate=True)
|
||||
|
||||
one_warp = torch.zeros([N * C * H * W, ]).to(self.device)
|
||||
one_warp.put_(ids_mask, torch.masked_select(flat_weight, mask), accumulate=True)
|
||||
|
||||
return img_warp.view(N, C, H, W), one_warp.view(N, C, H, W)
|
||||
|
||||
class RefineUNet(nn.Module):
|
||||
def __init__(self, args):
|
||||
super(RefineUNet, self).__init__()
|
||||
self.args = args
|
||||
self.scale = args.module_scale_factor
|
||||
self.nf = args.nf
|
||||
self.conv1 = nn.Conv2d(self.nf, self.nf, [3,3], 1, [1,1])
|
||||
self.conv2 = nn.Conv2d(self.nf, self.nf, [3,3], 1, [1,1])
|
||||
self.lrelu = nn.ReLU()
|
||||
self.NN = nn.UpsamplingNearest2d(scale_factor=2)
|
||||
|
||||
self.enc1 = nn.Conv2d((4*self.nf)//self.scale//self.scale + 4*args.img_ch + 4, self.nf, [4, 4], 2, [1, 1])
|
||||
self.enc2 = nn.Conv2d(self.nf, 2*self.nf, [4, 4], 2, [1, 1])
|
||||
self.enc3 = nn.Conv2d(2*self.nf, 4*self.nf, [4, 4], 2, [1, 1])
|
||||
self.dec0 = nn.Conv2d(4*self.nf, 4*self.nf, [3, 3], 1, [1, 1])
|
||||
self.dec1 = nn.Conv2d(4*self.nf + 2*self.nf, 2*self.nf, [3, 3], 1, [1, 1]) ## input concatenated with enc2
|
||||
self.dec2 = nn.Conv2d(2*self.nf + self.nf, self.nf, [3, 3], 1, [1, 1]) ## input concatenated with enc1
|
||||
self.dec3 = nn.Conv2d(self.nf, 1+args.img_ch, [3, 3], 1, [1, 1]) ## input added with warped image
|
||||
|
||||
def forward(self, concat):
|
||||
enc1 = self.lrelu(self.enc1(concat))
|
||||
enc2 = self.lrelu(self.enc2(enc1))
|
||||
out = self.lrelu(self.enc3(enc2))
|
||||
|
||||
out = self.lrelu(self.dec0(out))
|
||||
out = self.NN(out)
|
||||
|
||||
out = torch.cat((out,enc2),dim=1)
|
||||
out = self.lrelu(self.dec1(out))
|
||||
|
||||
out = self.NN(out)
|
||||
out = torch.cat((out,enc1),dim=1)
|
||||
out = self.lrelu(self.dec2(out))
|
||||
|
||||
out = self.NN(out)
|
||||
out = self.dec3(out)
|
||||
return out
|
||||
|
||||
class ResBlock2D_3D(nn.Module):
|
||||
## Shape of input [B,C,T,H,W]
|
||||
## Shape of output [B,C,T,H,W]
|
||||
def __init__(self, args):
|
||||
super(ResBlock2D_3D, self).__init__()
|
||||
self.args = args
|
||||
self.nf = args.nf
|
||||
|
||||
self.conv3x3_1 = nn.Conv3d(self.nf, self.nf, [1,3,3], 1, [0,1,1])
|
||||
self.conv3x3_2 = nn.Conv3d(self.nf, self.nf, [1,3,3], 1, [0,1,1])
|
||||
self.lrelu = nn.ReLU()
|
||||
|
||||
def forward(self, x):
|
||||
'''
|
||||
x shape : [B,C,T,H,W]
|
||||
'''
|
||||
B, C, T, H, W = x.size()
|
||||
|
||||
out = self.conv3x3_2(self.lrelu(self.conv3x3_1(x)))
|
||||
|
||||
return x + out
|
||||
|
||||
class RResBlock2D_3D(nn.Module):
|
||||
|
||||
def __init__(self, args, T_reduce_flag=False):
|
||||
super(RResBlock2D_3D, self).__init__()
|
||||
self.args = args
|
||||
self.nf = args.nf
|
||||
self.T_reduce_flag = T_reduce_flag
|
||||
self.resblock1 = ResBlock2D_3D(self.args)
|
||||
self.resblock2 = ResBlock2D_3D(self.args)
|
||||
if T_reduce_flag:
|
||||
self.reduceT_conv = nn.Conv3d(self.nf, self.nf, [3,1,1], 1, [0,0,0])
|
||||
|
||||
def forward(self, x):
|
||||
'''
|
||||
x shape : [B,C,T,H,W]
|
||||
'''
|
||||
out = self.resblock1(x)
|
||||
out = self.resblock2(out)
|
||||
if self.T_reduce_flag:
|
||||
return self.reduceT_conv(out + x)
|
||||
else:
|
||||
return out + x
|
||||
|
||||
def weights_init(m):
|
||||
classname = m.__class__.__name__
|
||||
if (classname.find('Conv2d') != -1) or (classname.find('Conv3d') != -1):
|
||||
init.xavier_normal_(m.weight)
|
||||
# init.kaiming_normal_(m.weight, nonlinearity='relu')
|
||||
if hasattr(m, 'bias') and m.bias is not None:
|
||||
init.zeros_(m.bias)
|
||||
@@ -1,225 +0,0 @@
|
||||
import yaml
|
||||
import os
|
||||
from torch.hub import download_url_to_file, get_dir
|
||||
from urllib.parse import urlparse
|
||||
import torch
|
||||
import typing
|
||||
import traceback
|
||||
import einops
|
||||
import gc
|
||||
import torchvision.transforms.functional as transform
|
||||
from comfy.model_management import soft_empty_cache, get_torch_device
|
||||
import numpy as np
|
||||
|
||||
BASE_MODEL_DOWNLOAD_URLS = [
|
||||
"https://github.com/styler00dollar/VSGAN-tensorrt-docker/releases/download/models/",
|
||||
"https://github.com/Fannovel16/ComfyUI-Frame-Interpolation/releases/download/models/",
|
||||
"https://github.com/dajes/frame-interpolation-pytorch/releases/download/v1.0.0/"
|
||||
]
|
||||
|
||||
config_path = os.path.join(os.path.dirname(__file__), "./config.yaml")
|
||||
if os.path.exists(config_path):
|
||||
config = yaml.load(open(config_path, "r"), Loader=yaml.FullLoader)
|
||||
else:
|
||||
raise Exception("config.yaml file is neccessary, plz recreate the config file by downloading it from https://github.com/Fannovel16/ComfyUI-Frame-Interpolation")
|
||||
DEVICE = get_torch_device()
|
||||
|
||||
class InterpolationStateList():
|
||||
|
||||
def __init__(self, frame_indices: typing.List[int], is_skip_list: bool):
|
||||
self.frame_indices = frame_indices
|
||||
self.is_skip_list = is_skip_list
|
||||
|
||||
def is_frame_skipped(self, frame_index):
|
||||
is_frame_in_list = frame_index in self.frame_indices
|
||||
return self.is_skip_list and is_frame_in_list or not self.is_skip_list and not is_frame_in_list
|
||||
|
||||
|
||||
class MakeInterpolationStateList:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"frame_indices": ("STRING", {"multiline": True, "default": "1,2,3"}),
|
||||
"is_skip_list": ("BOOLEAN", {"default": True},),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INTERPOLATION_STATES",)
|
||||
FUNCTION = "create_options"
|
||||
CATEGORY = "ComfyUI-Frame-Interpolation/VFI"
|
||||
|
||||
def create_options(self, frame_indices: str, is_skip_list: bool):
|
||||
frame_indices_list = [int(item) for item in frame_indices.split(',')]
|
||||
|
||||
interpolation_state_list = InterpolationStateList(
|
||||
frame_indices=frame_indices_list,
|
||||
is_skip_list=is_skip_list,
|
||||
)
|
||||
return (interpolation_state_list,)
|
||||
|
||||
|
||||
def get_ckpt_container_path(model_type):
|
||||
return os.path.abspath(os.path.join(os.path.dirname(__file__), config["ckpts_path"], model_type))
|
||||
|
||||
def load_file_from_url(url, model_dir=None, progress=True, file_name=None):
|
||||
"""Load file form http url, will download models if necessary.
|
||||
|
||||
Ref:https://github.com/1adrianb/face-alignment/blob/master/face_alignment/utils.py
|
||||
|
||||
Args:
|
||||
url (str): URL to be downloaded.
|
||||
model_dir (str): The path to save the downloaded model. Should be a full path. If None, use pytorch hub_dir.
|
||||
Default: None.
|
||||
progress (bool): Whether to show the download progress. Default: True.
|
||||
file_name (str): The downloaded file name. If None, use the file name in the url. Default: None.
|
||||
|
||||
Returns:
|
||||
str: The path to the downloaded file.
|
||||
"""
|
||||
if model_dir is None: # use the pytorch hub_dir
|
||||
hub_dir = get_dir()
|
||||
model_dir = os.path.join(hub_dir, 'checkpoints')
|
||||
|
||||
os.makedirs(model_dir, exist_ok=True)
|
||||
|
||||
parts = urlparse(url)
|
||||
file_name = os.path.basename(parts.path)
|
||||
if file_name is not None:
|
||||
file_name = file_name
|
||||
cached_file = os.path.abspath(os.path.join(model_dir, file_name))
|
||||
if not os.path.exists(cached_file):
|
||||
print(f'Downloading: "{url}" to {cached_file}\n')
|
||||
download_url_to_file(url, cached_file, hash_prefix=None, progress=progress)
|
||||
return cached_file
|
||||
|
||||
def load_file_from_github_release(model_type, ckpt_name):
|
||||
error_strs = []
|
||||
for i, base_model_download_url in enumerate(BASE_MODEL_DOWNLOAD_URLS):
|
||||
try:
|
||||
return load_file_from_url(base_model_download_url + ckpt_name, get_ckpt_container_path(model_type))
|
||||
except Exception:
|
||||
traceback_str = traceback.format_exc()
|
||||
if i < len(BASE_MODEL_DOWNLOAD_URLS) - 1:
|
||||
print("Failed! Trying another endpoint.")
|
||||
error_strs.append(f"Error when downloading from: {base_model_download_url + ckpt_name}\n\n{traceback_str}")
|
||||
|
||||
error_str = '\n\n'.join(error_strs)
|
||||
raise Exception(f"Tried all GitHub base urls to download {ckpt_name} but no suceess. Below is the error log:\n\n{error_str}")
|
||||
|
||||
|
||||
def load_file_from_direct_url(model_type, url):
|
||||
return load_file_from_url(url, get_ckpt_container_path(model_type))
|
||||
|
||||
def preprocess_frames(frames):
|
||||
return einops.rearrange(frames, "n h w c -> n c h w")
|
||||
|
||||
def postprocess_frames(frames):
|
||||
return einops.rearrange(frames, "n c h w -> n h w c").cpu()
|
||||
|
||||
def assert_batch_size(frames, batch_size=2, vfi_name=None):
|
||||
subject_verb = "Most VFI models require" if vfi_name is None else f"{vfi_name} VFI model requires"
|
||||
assert len(frames) >= batch_size, f"{subject_verb} at least {batch_size} frames to work with, only found {frames.shape[0]}. Please check the frame input using PreviewImage."
|
||||
|
||||
def generic_frame_loop(
|
||||
frames,
|
||||
clear_cache_after_n_frames,
|
||||
multiplier: typing.SupportsInt,
|
||||
return_middle_frame_function,
|
||||
*return_middle_frame_function_args,
|
||||
interpolation_states: InterpolationStateList = None,
|
||||
use_timestep=True,
|
||||
dtype=torch.float16):
|
||||
|
||||
#https://github.com/hzwer/Practical-RIFE/blob/main/inference_video.py#L169
|
||||
def non_timestep_inference(frame0, frame1, n):
|
||||
middle = return_middle_frame_function(frame0, frame1, None, *return_middle_frame_function_args)
|
||||
if n == 1:
|
||||
return [middle]
|
||||
first_half = non_timestep_inference(frame0, middle, n=n//2)
|
||||
second_half = non_timestep_inference(middle, frame1, n=n//2)
|
||||
if n%2:
|
||||
return [*first_half, middle, *second_half]
|
||||
else:
|
||||
return [*first_half, *second_half]
|
||||
|
||||
assert_batch_size(frames) # Too lazy to include model name lol
|
||||
output_frames = torch.zeros(multiplier*frames.shape[0], *frames.shape[1:], dtype=dtype, device="cpu")
|
||||
out_len = 0
|
||||
|
||||
number_of_frames_processed_since_last_cleared_cuda_cache = 0
|
||||
|
||||
for frame_itr in range(len(frames) - 1): # Skip the final frame since there are no frames after it
|
||||
frame0 = frames[frame_itr:frame_itr+1]
|
||||
output_frames[out_len] = frame0 # Start with first frame
|
||||
out_len += 1
|
||||
# Ensure that input frames are in fp32 - the same dtype as model
|
||||
frame0 = frame0.to(dtype=torch.float32)
|
||||
frame1 = frames[frame_itr+1:frame_itr+2].to(dtype=torch.float32)
|
||||
|
||||
if interpolation_states is not None and interpolation_states.is_frame_skipped(frame_itr):
|
||||
continue
|
||||
|
||||
# Generate and append a batch of middle frames
|
||||
middle_frame_batches = []
|
||||
|
||||
if use_timestep:
|
||||
for middle_i in range(1, multiplier):
|
||||
timestep = middle_i/multiplier
|
||||
|
||||
middle_frame = return_middle_frame_function(
|
||||
frame0.to(DEVICE),
|
||||
frame1.to(DEVICE),
|
||||
timestep,
|
||||
*return_middle_frame_function_args
|
||||
).detach().cpu()
|
||||
middle_frame_batches.append(middle_frame.to(dtype=dtype))
|
||||
else:
|
||||
middle_frames = non_timestep_inference(frame0.to(DEVICE), frame1.to(DEVICE), multiplier - 1)
|
||||
middle_frame_batches.extend(torch.cat(middle_frames, dim=0).detach().cpu().to(dtype=dtype))
|
||||
|
||||
# Copy middle frames to output
|
||||
for middle_frame in middle_frame_batches:
|
||||
output_frames[out_len] = middle_frame
|
||||
out_len += 1
|
||||
|
||||
number_of_frames_processed_since_last_cleared_cuda_cache += 1
|
||||
# Try to avoid a memory overflow by clearing cuda cache regularly
|
||||
if number_of_frames_processed_since_last_cleared_cuda_cache >= clear_cache_after_n_frames:
|
||||
print("Comfy-VFI: Clearing cache...", end=' ')
|
||||
soft_empty_cache()
|
||||
number_of_frames_processed_since_last_cleared_cuda_cache = 0
|
||||
print("Done cache clearing")
|
||||
|
||||
gc.collect()
|
||||
|
||||
print(f"Comfy-VFI done! {len(output_frames)} frames generated at resolution: {output_frames[0].shape}")
|
||||
# Append final frame
|
||||
output_frames[out_len] = frames[-1:]
|
||||
out_len += 1
|
||||
# clear cache for courtesy
|
||||
print("Comfy-VFI: Final clearing cache...", end = ' ')
|
||||
soft_empty_cache()
|
||||
print("Done cache clearing")
|
||||
return output_frames[:out_len]
|
||||
|
||||
""" def generic_4frame_loop(
|
||||
frames,
|
||||
clear_cache_after_n_frames,
|
||||
multiplier: typing.SupportsInt,
|
||||
return_middle_frame_function,
|
||||
*return_middle_frame_function_args,
|
||||
interpolation_states: InterpolationStateList = None,
|
||||
use_timestep=False):
|
||||
|
||||
if use_timestep: raise NotImplementedError("Timestep 4 frame VFI model")
|
||||
def non_timestep_inference(frame_0, frame_1, frame_2, frame_3, n):
|
||||
middle = return_middle_frame_function(frame_0, frame_1, None, *return_middle_frame_function_args)
|
||||
if n == 1:
|
||||
return [middle]
|
||||
first_half = non_timestep_inference(frame_0, middle, n=n//2)
|
||||
second_half = non_timestep_inference(middle, frame_1, n=n//2)
|
||||
if n%2:
|
||||
return [*first_half, middle, *second_half]
|
||||
else:
|
||||
return [*first_half, *second_half] """
|
||||