Files
2024-09-30 09:22:34 +08:00

430 lines
16 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import torch
#import torch.nn.functional as F
import torchvision.transforms.v2 as T
import numpy as np
import comfy.utils
import comfy.model_management as mm
import gc
from tqdm import tqdm
import cv2
import numpy as np
from scipy.interpolate import RBFInterpolator
import folder_paths
import os
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
log = logging.getLogger(__name__)
script_directory = os.path.dirname(os.path.abspath(__file__))
def tensor_to_image(image):
return np.array(T.ToPILImage()(image.permute(2, 0, 1)).convert('RGB'))
def image_to_tensor(image):
return T.ToTensor()(image).permute(1, 2, 0)
#return T.ToTensor()(Image.fromarray(image)).permute(1, 2, 0)
class FFTNode:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"image": ("IMAGE", ),
}
}
RETURN_TYPES = ("IMAGE","FFTData")
RETURN_NAMES = ("image","FFTData")
FUNCTION = "toFFT"
CATEGORY = "FFT"
def toFFT(self, image):
FFTImageList=[]
FFT_Channel_Data=[]
channelCount=3
imageCount=image.shape[0]
# print("image.shape:",image.shape)
# ## torch.Size([1, 64, 64, 3]) 表示来了1张图,64*64,3通道 loadImage节点过来就是这个
# print("type of image:",type(image)) ## type of image: <class 'torch.Tensor'>
# pixel = image[0][0][0]
# print("pixel:",pixel) ## pixel: tensor([0., 0., 0.])
# tValue=pixel[0]
# print("Type of tValue:",type(tValue)) ## type of tensors_out: <class 'torch.Tensor'>
for i in range(image.shape[0]):
sourceImg = image[i]
cv2Image = (sourceImg.contiguous() * 255).byte()
#image1=image[0]
#print(image1[0][0])
## tensor([0., 0., 0.])
## 是0-1之间的浮点数
dim = sourceImg.dim()
if dim == 3:
#print(cv2Image.shape)
##torch.Size([64, 64, 3]) 变成0-255的byte, 颜色值, 3通道
R_channel = cv2Image[:, :,0]
G_channel = cv2Image[:, :,1]
B_channel = cv2Image[:, :,2]
fshiftData=[]
#傅里叶变换
R_fft = np.fft.fft2(R_channel)
R_fshift = np.fft.fftshift(R_fft)
fshiftData.append(R_fshift)
G_fft = np.fft.fft2(G_channel)
G_fshift = np.fft.fftshift(G_fft)
fshiftData.append(G_fshift)
B_fft = np.fft.fft2(B_channel)
B_fshift = np.fft.fftshift(B_fft)
fshiftData.append(B_fshift)
R_img = np.log(np.abs(R_fshift))
B_img = np.log(np.abs(B_fshift))
G_img = np.log(np.abs(G_fshift))
R_img = R_img / np.max(R_img)
B_img = B_img / np.max(B_img)
G_img = G_img / np.max(G_img)
fftImg = np.dstack((R_img, G_img, B_img))
fftImg = fftImg.astype(np.float32)
#image = image.astype(np.uint8)
FFT_Channel_Data.append(fshiftData)
# # # one_channel = cv2Image[:, :,2]
#print(red_channel.shape)
## torch.Size([64, 64]) 拿到其中一个颜色通道
# # # f = np.fft.fft2(one_channel)
# # # #dft = cv2.dft(np.float32(one_channel), flags = cv2.DFT_COMPLEX_OUTPUT)
# # # FFT_Data.append(f)
# # # #print(f.shape)
# # # ## (64, 64) 现在不是torch, tensor了,是个二维数组
# # # #默认结果中心点位置是在左上角,
# # # #调用fftshift()函数转移到中间位置
# # # fshift = np.fft.fftshift(f)
# # # #fft结果是复数, 其绝对值结果是振幅
# # # fimg = np.log(np.abs(fshift))
# # # #print(fimg.shape)
# # # ## (64, 64)
# # # ## 取复数的模,然后取对数。仅用于显示,原始数据还在f里
# # # ## 此时是取对数后的float结果,范围不一定是0-255,也不一定是0-1,可能很大也可能很小
# # # max_v = np.max(fimg)
# # # fimg = fimg / max_v
# # # print(max_v)
# # # img_3_channels = np.repeat(fimg[:, :, np.newaxis], 3, axis=2)
#print(img_3_channels.shape)
## (64, 64, 3) 复制到其余两个通道。让它变成灰度图
#print(img_3_channels[0][0])
FFTImageList.append(fftImg)
else:
channelCount=1
fshiftData=[]
#傅里叶变换
R_fft = np.fft.fft2(cv2Image)
R_fshift = np.fft.fftshift(R_fft)
fshiftData.append(R_fshift)
fftImg = np.log(np.abs(R_fshift))
fftImg = fftImg / np.max(fftImg)
fftImg = fftImg.astype(np.float32)
FFTImageList.append(fftImg)
FFT_Channel_Data.append(fshiftData)
tensors_out = (
torch.stack([torch.from_numpy(np_array) for np_array in FFTImageList])
)
FFT_Data={'channelCount':channelCount,'FFT_Channel_Data':FFT_Channel_Data,'imageCount':imageCount}
# print("in FFTNode, before output")
# print("type of tensors_out:",type(tensors_out))
# print("tensors_out shape:",tensors_out.shape)
# print("type of tensors_out[0]:",type(tensors_out[0]))
# print("tensors_out shape[0]:",tensors_out[0].shape)
# pixel = tensors_out[0][0][0]
# print("pixel:",pixel) ## pixel: tensor([0., 0., 0.])
# tValue=pixel[0]
# print("Type of tValue:",type(tValue)) ## Type of tValue: <class 'torch.Tensor'>
return (tensors_out,FFT_Data)
def ApplyMaskAndInvert(l_fshift,l_mask):
# print("in fucntion, mask shape:",l_mask.shape)
# print("in fucntion, fshift shape:",l_fshift.shape)
f = l_fshift * l_mask
ishift = np.fft.ifftshift(f)
iimg = np.fft.ifft2(ishift)
iimg = np.abs(iimg)
return f,iimg
class InvertFFTNode:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"ff": ("FFTData", ),
"filterRadius": ("INT", {
"default": 100,
})
}
}
RETURN_TYPES = ("IMAGE","IMAGE","IMAGE",)
RETURN_NAMES = ("image","IMAGE","IMAGE",)
FUNCTION = "fromFFT"
CATEGORY = "FFT"
def DoOneChannel(self,fshift,filterRadius):
FFTImageList=[]
#f=ff[0][0]
#fshift = np.fft.fftshift(f)
#傅里叶逆变换
#ishift = np.fft.ifftshift(fshift)
iimg = np.fft.ifft2(fshift)
iimg = np.abs(iimg)
FFTImageList.append(iimg)
#设置高通滤波器
rows, cols = fshift.shape
crow,ccol = int(rows/2), int(cols/2)
mask = np.ones((rows, cols))
mask[crow-filterRadius:crow+filterRadius, ccol-filterRadius:ccol+filterRadius] = 0
#fshift[crow-30:crow+30, ccol-30:ccol+30] = 0
mask2 = np.zeros((rows, cols))
mask2[crow-filterRadius:crow+filterRadius, ccol-filterRadius:ccol+filterRadius] = 1
#傅里叶逆变换
fshiftmask,hiPassImg = ApplyMaskAndInvert(fshift,mask)
FFTImageList.append(mask*255)
# # ishift = np.fft.ifftshift(fshift)
# # iimg = np.fft.ifft2(ishift)
# # iimg = np.abs(iimg)
# print("np.max(hiPass):",np.max(hiPassImg))
# print("np.min(hiPass):",np.min(hiPassImg))
FFTImageList.append(hiPassImg)
fimg = np.log(np.abs(fshiftmask))
#print(fimg.shape)
## (64, 64)
## 取复数的模,然后取对数。仅用于显示,原始数据还在f里
## 此时是取对数后的float结果,范围不一定是0-255,也不一定是0-1,可能很大也可能很小
max_v = np.max(fimg)
fimg = fimg / max_v * 254
# print("max_v of fimg:",max_v)
# print("max_v of after fimg:",np.max(fimg))
FFTImageList.append(fimg)
fshiftmask,lowPassImg = ApplyMaskAndInvert(fshift,mask2)
FFTImageList.append(mask2*255)
# print("np.max(lowPassImg):",np.max(lowPassImg))
# print("np.min(lowPassImg):",np.min(lowPassImg))
fimg = np.log(np.abs(fshiftmask))
#print(fimg.shape)
## (64, 64)
## 取复数的模,然后取对数。仅用于显示,原始数据还在f里
## 此时是取对数后的float结果,范围不一定是0-255,也不一定是0-1,可能很大也可能很小
max_v = np.max(fimg)
fimg = fimg / max_v * 254
# print("max_v of fimg:",max_v)
# print("max_v of after fimg:",np.max(fimg))
#img_3_channels = np.repeat(fimg[:, :, np.newaxis], 3, axis=2)
FFTImageList.append(fimg)
FFTImageList.append(lowPassImg)
tensors_out = (
torch.stack([torch.from_numpy(np_array) for np_array in FFTImageList])/255
)
return tensors_out
def fromFFT(self, ff,filterRadius):
f0=ff[0][0]
#f1=ff[0][1]
#f2=ff[0][2]
out1=self.DoOneChannel(f0,filterRadius)
#print("C1")
#out2=self.DoOneChannel(f1,filterRadius)
#print("C2")
#out3=self.DoOneChannel(f2,filterRadius)
return (out1,out1,out1,)
class InvertFFTWithMask:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"ff": ("FFTData", ),
"mask": ("MASK",)
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image",)
FUNCTION = "fromFFT"
CATEGORY = "FFT"
def DoOneChannel(self,fshift,mask):
fshiftmask,hiPassImg = ApplyMaskAndInvert(fshift,mask)
return hiPassImg/255
def fromFFT(self, ff,mask):
mask0=mask[0]
maskNP=np.array(mask0)
channelCount = ff['channelCount']
imageCount = ff['imageCount']
### ff['FFT_Channel_Data'] 是数组,每个元素对应一张图片。每个成员也是数组。
### ff['FFT_Channel_Data'][0] 是一个数组,对应一张图片。成员数量是3或者1,对应3个通道或者1个通道
res=[]
for i in range(imageCount):
if channelCount==3:
f0 = ff['FFT_Channel_Data'][i][0]
#print("type of f0:",type(f0))
out0=self.DoOneChannel(f0,maskNP)
#print("type of out0:",type(out0))
#print("out0 shape:",out0.shape)
f1 = ff['FFT_Channel_Data'][i][1]
out1=self.DoOneChannel(f1,maskNP)
#print("type of out1:",type(out1))
#print("out1 shape:",out1.shape)
f2 = ff['FFT_Channel_Data'][i][2]
out2=self.DoOneChannel(f2,maskNP)
#print("type of out2:",type(out2))
#print("out2 shape:",out2.shape)
doneImg = np.dstack((out0, out1, out2))
doneImg = doneImg.astype(np.float32)
#print("doneImg shape:",doneImg.shape)
res.append(doneImg)
else:
f0 = ff['FFT_Channel_Data'][i]
doneImg=self.DoOneChannel(f0,maskNP)
doneImg = doneImg.astype(np.float32)
#doneImg = out0
res.append(doneImg)
#print("C1")
#out2=self.DoOneChannel(f1,filterRadius)
#print("C2")
#out3=self.DoOneChannel(f2,filterRadius)
tensors_out = (
torch.stack([torch.from_numpy(np_array) for np_array in res])
)
# print("in InvertFFTWitMask, before output")
# print("type of tensors_out:",type(tensors_out))
# print("tensors_out shape:",tensors_out.shape)
# print("type of tensors_out[0]:",type(tensors_out[0]))
# print("tensors_out shape[0]:",tensors_out[0].shape)
# pixel = tensors_out[0][0][0]
# print("pixel shape:",pixel.shape)
# print("pixel:",pixel)
# tvalue=pixel[0]
# print("tvalue:",tvalue)
# print("tvalue type:",type(tvalue))
return (tensors_out,)
class FindFFTSpot:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
#"FFTImage": ("IMAGE", ),
"ff": ("FFTData", ),
"hiPassFactor": ("INT", {"default": 10, "min": 1, "max": 1024}),
"centerRange": ("INT", {"default": 30, "min": 1, "max": 1024}),
"crossWidth": ("INT", {"default": 30, "min": 0, "max": 100}),
"threshold": ("INT", {"default": 100, "min": 0, "max": 254}),
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image",)
FUNCTION = "FindSpot"
CATEGORY = "FFT"
def FindSpot(self, ff,hiPassFactor,centerRange,crossWidth,threshold):
FFTImageList=[]
#imageCount=FFTImage.shape[0]
channelCount = ff['channelCount']
imageCount = ff['imageCount']
for i in range(imageCount):
if(channelCount==1):
ffData = ff['FFT_Channel_Data'][i]
else:
ffData = ff['FFT_Channel_Data'][i][0]
rows, cols = ffData.shape
crow,ccol = int(rows/2), int(cols/2) #中心位置
mask = np.ones((rows,cols),np.uint8)
mask[crow-hiPassFactor:crow+hiPassFactor, ccol-hiPassFactor:ccol+hiPassFactor] = 0
_,R_channel = ApplyMaskAndInvert(ffData,mask)
# sourceImg=FFTImage[i]
# cv2Image = (sourceImg.contiguous() * 255).byte()
# dim = channelCount
# if dim == 3:
# R_channel = cv2Image[:, :,0]
# else:
# R_channel = cv2Image
# #print("R_channel shape:",R_channel.shape)
# R_channel = np.asanyarray(R_channel)
# #print("R_channel AS NP shape:",R_channel.shape)
min_val, max_val = np.min(R_channel), np.max(R_channel)
out1 = (R_channel - min_val)*(255.0/(max_val - min_val))
rows, cols = R_channel.shape
crow,ccol = int(rows/2), int(cols/2) #中心位置
out1[crow-centerRange:crow+centerRange, ccol-centerRange:ccol+centerRange] = 0
out1[:, ccol-crossWidth:ccol+crossWidth] = 0
out1[crow-crossWidth:crow+crossWidth, :] = 0
if(threshold>0):
out1[out1<threshold]=0
out1[out1>threshold]=255
fftImg = np.dstack((out1, out1, out1))
fftImg = fftImg.astype(np.float32)
FFTImageList.append(fftImg)
tensors_out = (
torch.stack([torch.from_numpy(np_array) for np_array in FFTImageList])/255
)
# print("find spot")
# print("type of tensors_out:",type(tensors_out))
# print("tensors_out shape:",tensors_out.shape)
# print("type of tensors_out[0]:",type(tensors_out[0]))
# print("tensors_out shape[0]:",tensors_out[0].shape)
# pixel = tensors_out[0][0][0]
# print("pixel:",pixel) ## pixel: tensor([0., 0., 0.])
# tValue=pixel[0]
# print("Type of tValue:",type(tValue)) ## Type of tValue: <class 'float'>
return (tensors_out,)
# A dictionary that contains all nodes you want to export with their names
# NOTE: names should be globally unique
NODE_CLASS_MAPPINGS = {
"InvertFFTNode":InvertFFTNode,
"FFTNode":FFTNode,
"InvertFFTWithMask":InvertFFTWithMask,
"FindFFTSpot":FindFFTSpot,
}
# A dictionary that contains the friendly/humanly readable titles for the nodes
NODE_DISPLAY_NAME_MAPPINGS = {
"InvertFFTNode":"InvertFFTNode",
"FFTNode":"FFTNode",
"InvertFFTWithMask":"InvertFFTWithMask",
"FindFFTSpot":"FindFFTSpot",
}