Skip to content

Commit

Permalink
Add parameters to set center, add entire image parameter to Defisheye
Browse files Browse the repository at this point in the history
  • Loading branch information
Kidev committed Jan 27, 2025
1 parent ffc2e65 commit 5fad86d
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 44 deletions.
20 changes: 11 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ Provides tools for applying and removing fisheye lens effects from images.
- Multiple distortion mappings: equidistant, equisolid, orthographic, stereographic
- Format options: fullframe and circular
- Adjustable FOV and PFOV parameters

### Tips
- The `orthographic` mapping keeps the entirety of the image data, so going back and forth works well, see `examples/BackAndForth.json`
- Adjustable center
- Option to always display the full image uncropped

## Installation

Expand Down Expand Up @@ -47,12 +46,15 @@ The nodes will appear in the node menu under the "image/processing" category:
### Parameters

- image: Input image
- mapping: Distortion mapping mode (equidistant, equisolid, orthographic, stereographic)
- format: Output format (fullframe, circular)
- fov: Field of view in degrees (0-360)
- pfov: Perspective field of view in degrees (0-360)
- mapping: Distortion mapping mode (`equidistant`, `equisolid`, `orthographic`, `stereographic`)
- format: Output format (`fullframe`, `circular`)
- fov: Field of view in degrees (`0.0`-`360.0`)
- pfov: Perspective field of view in degrees (`0.0`-`360.0`)
- entire_image: Always show the full image uncropped not matter the FOV/PFOV values (`True`-`False`)
- wcenter: Horizontal center of the effect (`0.0`-`1.0`)
- hcenter: Vertical center of the effect (`0.0`-`1.0`)

## Example Workflows
## Examples

Check the examples folder for sample [workflows](examples/BackAndForth.json) demonstrating various use cases.
Check the examples folder for a sample [workflow](examples/BackAndForth.json) and images demonstrating various use cases.
![Example](examples/BackAndForthFullframe.png)
1 change: 1 addition & 0 deletions base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import numpy as np
import torch


class FisheyeBase:
def setup_parameters(self, fov, pfov, mapping, format):
self.fov = fov
Expand Down
63 changes: 54 additions & 9 deletions defisheye.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,63 @@
import numpy as np
import cv2
import numpy as np
from numpy import arange, arctan, hypot, meshgrid, pi, sin, sqrt, tan

from .base import FisheyeBase
from numpy import arange, sqrt, arctan, sin, tan, meshgrid, pi, hypot


class DefisheyeNode(FisheyeBase):
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"mapping": (["equidistant", "equisolid", "orthographic", "stereographic"],),
"mapping": (
["equidistant", "equisolid", "orthographic", "stereographic"],
),
"format": (["fullframe", "circular"],),
"fov": ("FLOAT", {"default": 180.0, "min": 0.0, "max": 360.0, "step": 10.0}),
"pfov": ("FLOAT", {"default": 120.0, "min": 0.0, "max": 360.0, "step": 10.0}),
"fov": (
"FLOAT",
{"default": 180.0, "min": 0.0, "max": 360.0, "step": 10.0},
),
"pfov": (
"FLOAT",
{"default": 120.0, "min": 0.0, "max": 360.0, "step": 10.0},
),
"entire_image": ("BOOLEAN", {"default": False}),
"wcenter": (
"FLOAT",
{"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.1},
),
"hcenter": (
"FLOAT",
{"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.1},
),
},
}

RETURN_TYPES = ("IMAGE",)
FUNCTION = "remove_fisheye"
CATEGORY = "image/processing"

def calculate_zoom_factor(self, fov_in, fov_out, mapping):
fov_in_rad = fov_in * pi / 180.0
fov_out_rad = fov_out * pi / 180.0

if mapping == "equidistant":
max_in = fov_in_rad / 2.0
max_out = tan(fov_out_rad / 2.0)
elif mapping == "equisolid":
max_in = 2 * sin(fov_in_rad / 4.0)
max_out = tan(fov_out_rad / 2.0)
elif mapping == "orthographic":
max_in = sin(fov_in_rad / 2.0)
max_out = tan(fov_out_rad / 2.0)
elif mapping == "stereographic":
max_in = 2 * tan(fov_in_rad / 4.0)
max_out = tan(fov_out_rad / 2.0)

return max_out / max_in if max_in > 0 else 1.0

def map_defisheye(self, i, j, width, height, dim, xcenter, ycenter):
xd = i - xcenter
yd = j - ycenter
Expand All @@ -45,6 +83,10 @@ def map_defisheye(self, i, j, width, height, dim, xcenter, ycenter):
ifoc = dim / (2.0 * tan(self.fov * pi / 720))
rr = ifoc * tan(phiang / 2)

if self.entire_image:
zoom = self.calculate_zoom_factor(self.fov, self.pfov, self.mapping)
rr *= zoom

rdmask = rd != 0
xs = xd.astype(np.float32).copy()
ys = yd.astype(np.float32).copy()
Expand All @@ -57,19 +99,22 @@ def map_defisheye(self, i, j, width, height, dim, xcenter, ycenter):

return xs, ys

def remove_fisheye(self, image, mapping, format, fov, pfov):
def remove_fisheye(
self, image, mapping, format, fov, pfov, entire_image, wcenter, hcenter
):
self.setup_parameters(fov, pfov, mapping, format)
self.entire_image = entire_image

image_np = self.process_image_tensor(image)

height, width = image_np.shape[:2]
xcenter = width // 2
ycenter = height // 2
xcenter = width * wcenter
ycenter = height * hcenter

if format == "circular":
dim = min(width, height)
else: # fullframe
dim = sqrt(width ** 2 + height ** 2)
dim = sqrt(width**2 + height**2)

i = arange(width)
j = arange(height)
Expand Down
38 changes: 29 additions & 9 deletions fisheye.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,37 @@
import numpy as np
import cv2
from numpy import arange, sqrt, arctan, sin, tan, meshgrid, pi, hypot
import numpy as np
from numpy import arange, arctan, hypot, meshgrid, pi, sin, sqrt, tan

from .base import FisheyeBase


class FisheyeNode(FisheyeBase):
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"mapping": (["equidistant", "equisolid", "orthographic", "stereographic"],),
"mapping": (
["equidistant", "equisolid", "orthographic", "stereographic"],
),
"format": (["fullframe", "circular"],),
"fov": ("FLOAT", {"default": 180.0, "min": 0.0, "max": 360.0, "step": 10.0}),
"pfov": ("FLOAT", {"default": 120.0, "min": 0.0, "max": 360.0, "step": 10.0}),
"fov": (
"FLOAT",
{"default": 180.0, "min": 0.0, "max": 360.0, "step": 10.0},
),
"pfov": (
"FLOAT",
{"default": 120.0, "min": 0.0, "max": 360.0, "step": 10.0},
),
"entire_image": ("BOOLEAN", {"default": False}),
"wcenter": (
"FLOAT",
{"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.1},
),
"hcenter": (
"FLOAT",
{"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.1},
),
},
}

Expand Down Expand Up @@ -88,20 +106,22 @@ def map_fisheye(self, i, j, width, height, dim, xcenter, ycenter):

return xs, ys

def apply_fisheye(self, image, mapping, format, fov, pfov, entire_image):
def apply_fisheye(
self, image, mapping, format, fov, pfov, entire_image, wcenter, hcenter
):
self.setup_parameters(fov, pfov, mapping, format)
self.entire_image = entire_image

image_np = self.process_image_tensor(image)

height, width = image_np.shape[:2]
xcenter = width // 2
ycenter = height // 2
xcenter = width * wcenter
ycenter = height * hcenter

if format == "circular":
dim = min(width, height)
else: # fullframe
dim = sqrt(width ** 2 + height ** 2)
dim = sqrt(width**2 + height**2)

i = arange(width)
j = arange(height)
Expand Down
19 changes: 13 additions & 6 deletions install.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
import importlib.metadata
import os
import sys
import subprocess
import importlib.metadata
import sys


def install_requirements():
requirements_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "requirements.txt")
requirements_path = os.path.join(
os.path.dirname(os.path.realpath(__file__)), "requirements.txt"
)

with open(requirements_path) as f:
required = f.read().splitlines()

installed = {dist.metadata["Name"]: dist.version for dist in importlib.metadata.distributions()}
missing = [pkg for pkg in required if pkg.split('>=')[0] not in installed]
installed = {
dist.metadata["Name"]: dist.version
for dist in importlib.metadata.distributions()
}
missing = [pkg for pkg in required if pkg.split(">=")[0] not in installed]

if missing:
print(f"Installing missing packages: {', '.join(missing)}")
subprocess.check_call([sys.executable, '-m', 'pip', 'install', *missing])
subprocess.check_call([sys.executable, "-m", "pip", "install", *missing])
print("All requirements installed successfully!")
else:
print("All requirements already satisfied!")


if __name__ == "__main__":
install_requirements()
9 changes: 3 additions & 6 deletions nodes.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
from .fisheye import FisheyeNode
from .defisheye import DefisheyeNode
from .fisheye import FisheyeNode

NODE_CLASS_MAPPINGS = {
"Fisheye": FisheyeNode,
"Defisheye": DefisheyeNode
}
NODE_CLASS_MAPPINGS = {"Fisheye": FisheyeNode, "Defisheye": DefisheyeNode}

NODE_DISPLAY_NAME_MAPPINGS = {
"Fisheye": "Apply Fisheye Effect",
"Defisheye": "Remove Fisheye Effect"
"Defisheye": "Remove Fisheye Effect",
}
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[project]
name = "fisheye-effects"
description = "Provides tools for applying and removing fisheye lens effects from images."
version = "0.0.3"
version = "1.0.0"
license = {file = "LICENSE"}
dependencies = ["numpy>=1.22.0", "opencv-python>=4.8.0", "Pillow>=9.5.0"]
dependencies = ["numpy>=1.22.0", "opencv-python>=4.8.0", "Pillow>=9.5.0", "torch>=2.5.1"]

[project.urls]
Repository = "https://github.com/Kidev/ComfyUI-Fisheye-effects"
Expand Down
7 changes: 4 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
numpy==2.2.2
opencv-python==4.11.0.86
torch==2.5.1
numpy>=2.2.2
opencv-python>=4.11.0
torch>=2.5.1
Pillow>=9.5.0

0 comments on commit 5fad86d

Please sign in to comment.