Fioceen commited on
Commit
6912dab
·
1 Parent(s): 2356eaf

ComfyUI node initial

Browse files
Files changed (3) hide show
  1. __init__.py +3 -0
  2. image_postprocess_gui.py +2 -2
  3. nodes.py +224 -0
__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
2
+
3
+ __all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
image_postprocess_gui.py CHANGED
@@ -1,7 +1,7 @@
1
  #!/usr/bin/env python3
2
- """
3
  Main GUI application for image_postprocess pipeline with camera-simulator controls.
4
- """
5
 
6
  import sys
7
  import os
 
1
  #!/usr/bin/env python3
2
+ """""
3
  Main GUI application for image_postprocess pipeline with camera-simulator controls.
4
+ """""
5
 
6
  import sys
7
  import os
nodes.py ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from PIL import Image
2
+ import numpy as np
3
+ import os
4
+ import tempfile
5
+ from types import SimpleNamespace
6
+ try:
7
+ from worker import Worker
8
+ except Exception as e:
9
+ Worker = None
10
+ IMPORT_ERROR = str(e)
11
+ else:
12
+ IMPORT_ERROR = None
13
+
14
+ class NovaNodes:
15
+ """
16
+ ComfyUI node: Full post-processing chain using Worker from GUI
17
+ All augmentations with tunable parameters.
18
+ """
19
+
20
+ @classmethod
21
+ def INPUT_TYPES(s):
22
+ return {
23
+ "required": {
24
+ "image": ("IMAGE",),
25
+
26
+ # EXIF
27
+ "apply_exif_o": ("BOOLEAN", {"default": True}),
28
+
29
+ # Noise
30
+ "noise_std_frac": ("FLOAT", {"default": 0.015, "min": 0.0, "max": 0.1, "step": 0.001}),
31
+ "hot_pixel_prob": ("FLOAT", {"default": 1e-6, "min": 0.0, "max": 1e-3, "step": 1e-7}),
32
+ "perturb_mag_frac": ("FLOAT", {"default": 0.008, "min": 0.0, "max": 0.05, "step": 0.001}),
33
+
34
+ # CLAHE
35
+ "clahe_clip": ("FLOAT", {"default": 2.0, "min": 0.5, "max": 10.0, "step": 0.1}),
36
+ "clahe_grid": ("INT", {"default": 8, "min": 2, "max": 32, "step": 1}),
37
+
38
+ # Fourier
39
+ "apply_fourier_o": ("BOOLEAN", {"default": True}),
40
+ "fourier_strength": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 1.0, "step": 0.01}),
41
+ "fourier_randomness": ("FLOAT", {"default": 0.05, "min": 0.0, "max": 0.5, "step": 0.01}),
42
+ "fourier_phase_perturb": ("FLOAT", {"default": 0.08, "min": 0.0, "max": 0.5, "step": 0.01}),
43
+ "fourier_alpha": ("FLOAT", {"default": 1.0, "min": 0.1, "max": 4.0, "step": 0.1}),
44
+ "fourier_radial_smooth": ("INT", {"default": 5, "min": 0, "max": 50, "step": 1}),
45
+ "fourier_mode": (["auto", "ref", "model"], {"default": "auto"}),
46
+
47
+ # Vignette
48
+ "apply_vignette_o": ("BOOLEAN", {"default": True}),
49
+ "vignette_strength": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
50
+
51
+ # Chromatic aberration
52
+ "apply_chromatic_aberration_o": ("BOOLEAN", {"default": True}),
53
+ "ca_shift": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 5.0, "step": 0.1}),
54
+
55
+ # Banding
56
+ "apply_banding_o": ("BOOLEAN", {"default": True}),
57
+ "banding_levels": ("INT", {"default": 64, "min": 2, "max": 256, "step": 1}),
58
+
59
+ # Motion blur
60
+ "apply_motion_blur_o": ("BOOLEAN", {"default": True}),
61
+ "motion_blur_ksize": ("INT", {"default": 7, "min": 3, "max": 31, "step": 2}),
62
+
63
+ # JPEG cycles
64
+ "apply_jpeg_cycles_o": ("BOOLEAN", {"default": True}),
65
+ "jpeg_cycles": ("INT", {"default": 2, "min": 1, "max": 10, "step": 1}),
66
+ "jpeg_quality": ("INT", {"default": 85, "min": 10, "max": 100, "step": 1}),
67
+
68
+ # Camera simulation
69
+ "sim_camera": ("BOOLEAN", {"default": False}),
70
+ "enable_bayer": ("BOOLEAN", {"default": True}),
71
+ "iso_scale": ("FLOAT", {"default": 1.0, "min": 0.1, "max": 16.0, "step": 0.1}),
72
+ "read_noise": ("FLOAT", {"default": 2.0, "min": 0.0, "max": 50.0, "step": 0.1}),
73
+ },
74
+ "optional": {
75
+ "ref_image": ("IMAGE",),
76
+ }
77
+ }
78
+
79
+ RETURN_TYPES = ("IMAGE", "STRING")
80
+ RETURN_NAMES = ("IMAGE", "EXIF")
81
+ FUNCTION = "process"
82
+ CATEGORY = "postprocessing"
83
+
84
+ def process(self, image, ref_image=None,
85
+ apply_exif_o=True,
86
+ noise_std_frac=0.015,
87
+ hot_pixel_prob=1e-6,
88
+ perturb_mag_frac=0.008,
89
+ clahe_clip=2.0,
90
+ clahe_grid=8,
91
+ apply_fourier_o=True,
92
+ fourier_strength=0.9,
93
+ fourier_randomness=0.05,
94
+ fourier_phase_perturb=0.08,
95
+ fourier_alpha=1.0,
96
+ fourier_radial_smooth=5,
97
+ fourier_mode="auto",
98
+ apply_vignette_o=True,
99
+ vignette_strength=0.5,
100
+ apply_chromatic_aberration_o=True,
101
+ ca_shift=1.0,
102
+ apply_banding_o=True,
103
+ banding_levels=64,
104
+ apply_motion_blur_o=True,
105
+ motion_blur_ksize=7,
106
+ apply_jpeg_cycles_o=True,
107
+ jpeg_cycles=2,
108
+ jpeg_quality=85,
109
+ sim_camera=False,
110
+ enable_bayer=True,
111
+ iso_scale=1.0,
112
+ read_noise=2.0):
113
+
114
+ if Worker is None:
115
+ raise ImportError(f"Could not import Worker module: {IMPORT_ERROR}")
116
+
117
+ # Ensure input image is a PIL Image
118
+ if not isinstance(image, Image.Image):
119
+ raise ValueError("Input image must be a PIL Image object")
120
+
121
+ # Save input image as temporary file
122
+ with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_input:
123
+ input_path = tmp_input.name
124
+ image.save(input_path)
125
+
126
+ # Prepare reference image if provided
127
+ ref_path = None
128
+ if ref_image is not None:
129
+ if not isinstance(ref_image, Image.Image):
130
+ raise ValueError("Reference image must be a PIL Image object")
131
+ with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_ref:
132
+ ref_path = tmp_ref.name
133
+ ref_image.save(ref_path)
134
+
135
+ # Create output temporary file path
136
+ with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp_output:
137
+ output_path = tmp_output.name
138
+
139
+ # Prepare parameters for Worker
140
+ args = SimpleNamespace(
141
+ noise_std=noise_std_frac,
142
+ hot_pixel_prob=hot_pixel_prob,
143
+ perturb=perturb_mag_frac,
144
+ clahe_clip=clahe_clip,
145
+ tile=clahe_grid,
146
+ fstrength=fourier_strength,
147
+ strength=fourier_strength,
148
+ randomness=fourier_randomness,
149
+ phase_perturb=fourier_phase_perturb,
150
+ alpha=fourier_alpha,
151
+ radial_smooth=fourier_radial_smooth,
152
+ fft_mode=fourier_mode,
153
+ vignette_strength=vignette_strength,
154
+ chroma_strength=ca_shift,
155
+ banding_strength=1.0 if apply_banding_o else 0.0,
156
+ motion_blur_kernel=motion_blur_ksize,
157
+ jpeg_cycles=jpeg_cycles,
158
+ jpeg_qmin=jpeg_quality,
159
+ jpeg_qmax=jpeg_quality,
160
+ sim_camera=sim_camera,
161
+ no_no_bayer=enable_bayer,
162
+ iso_scale=iso_scale,
163
+ read_noise=read_noise,
164
+ ref=ref_path,
165
+ fft_ref=ref_path,
166
+ seed=None, # Seed handling can be added if Worker supports it
167
+ cutoff=0.25 # Default value from GUI, adjustable if needed
168
+ )
169
+
170
+ # Run Worker
171
+ worker = Worker(input_path, output_path, args)
172
+ worker.run() # Assuming Worker has a synchronous run() method
173
+
174
+ # Load output image
175
+ output_img = Image.open(output_path)
176
+
177
+ # Handle EXIF
178
+ new_exif = ""
179
+ if apply_exif_o:
180
+ output_img, new_exif = self._add_fake_exif(output_img)
181
+
182
+ # Clean up temporary files
183
+ os.unlink(input_path)
184
+ if ref_path:
185
+ os.unlink(ref_path)
186
+ os.unlink(output_path)
187
+
188
+ return (output_img, new_exif)
189
+
190
+ def _add_fake_exif(self, img: Image.Image) -> tuple[Image.Image, str]:
191
+ """Insert random but realistic camera EXIF metadata."""
192
+ import random
193
+ import io
194
+ import piexif
195
+ exif_dict = {
196
+ "0th": {
197
+ piexif.ImageIFD.Make: random.choice(["Canon", "Nikon", "Sony", "Fujifilm", "Olympus", "Leica"]),
198
+ piexif.ImageIFD.Model: random.choice([
199
+ "EOS 5D Mark III", "D850", "Alpha 7R IV", "X-T4", "OM-D E-M1 Mark III", "Q2"
200
+ ]),
201
+ piexif.ImageIFD.Software: "Adobe Lightroom",
202
+ },
203
+ "Exif": {
204
+ piexif.ExifIFD.FNumber: (random.randint(10, 22), 10),
205
+ piexif.ExifIFD.ExposureTime: (1, random.randint(60, 4000)),
206
+ piexif.ExifIFD.ISOSpeedRatings: random.choice([100, 200, 400, 800, 1600, 3200]),
207
+ piexif.ExifIFD.FocalLength: (random.randint(24, 200), 1),
208
+ },
209
+ }
210
+ exif_bytes = piexif.dump(exif_dict)
211
+ output = io.BytesIO()
212
+ img.save(output, format="JPEG", exif=exif_bytes)
213
+ output.seek(0)
214
+ return (Image.open(output), str(exif_bytes))
215
+
216
+ # -------------
217
+ # Registration
218
+ # -------------
219
+ NODE_CLASS_MAPPINGS = {
220
+ "NovaNodes": NovaNodes,
221
+ }
222
+ NODE_DISPLAY_NAME_MAPPINGS = {
223
+ "NovaNodes": "Image Postprocess (FOOL AI)",
224
+ }