fix(v5): default fft spectrum to log magnitude
This commit is contained in:
@@ -86,7 +86,7 @@ The app is organized as a small MATLAB-like image workspace. Each operation crea
|
|||||||
|
|
||||||
### Chapter 4: Frequency Domain
|
### Chapter 4: Frequency Domain
|
||||||
|
|
||||||
- **FFT/DFT spectrum view** shows magnitude, log magnitude, or phase of the image in the frequency domain. Formula: `F(u,v) = DFT{f(x,y)}`. Apply it to a periodic-noisy state with `log_magnitude` to see the noise peaks.
|
- **FFT/DFT spectrum view** shows the log magnitude of the image in the frequency domain. Formula: `log(1 + |fftshift(fft2(f))|)`. Apply it to a periodic-noisy state to see the noise peaks.
|
||||||
- **Inverse FFT reconstruction** reconstructs from a previously saved FFT state. Formula: `f = real(ifft2(ifftshift(F)))`.
|
- **Inverse FFT reconstruction** reconstructs from a previously saved FFT state. Formula: `f = real(ifft2(ifftshift(F)))`.
|
||||||
|
|
||||||
### Chapter 6: RGB Color Processing
|
### Chapter 6: RGB Color Processing
|
||||||
|
|||||||
@@ -354,19 +354,14 @@ def rgb_channel(image, params):
|
|||||||
|
|
||||||
|
|
||||||
def fft_spectrum(image, params):
|
def fft_spectrum(image, params):
|
||||||
"""Display the DFT magnitude, log magnitude, or phase spectrum of an image.
|
"""Display the DFT log-magnitude spectrum of an image.
|
||||||
|
|
||||||
Use it to understand whether image information is concentrated in low or high frequencies.
|
Use it to understand whether image information is concentrated in low or high frequencies.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
gray = to_gray(image).astype(np.float32)
|
gray = to_gray(image).astype(np.float32)
|
||||||
spectrum = np.fft.fftshift(np.fft.fft2(gray))
|
spectrum = np.fft.fftshift(np.fft.fft2(gray))
|
||||||
mode = params.get("mode", "log_magnitude")
|
magnitude = np.log1p(np.abs(spectrum))
|
||||||
if mode == "phase":
|
|
||||||
return gray_to_rgb(normalize_to_uint8(np.angle(spectrum)))
|
|
||||||
magnitude = np.abs(spectrum)
|
|
||||||
if mode == "log_magnitude":
|
|
||||||
magnitude = np.log1p(magnitude)
|
|
||||||
return gray_to_rgb(normalize_to_uint8(magnitude))
|
return gray_to_rgb(normalize_to_uint8(magnitude))
|
||||||
|
|
||||||
|
|
||||||
@@ -436,7 +431,7 @@ OPERATIONS = [
|
|||||||
kernel_pair_preview("Sobel", [[-1, -2, -1], [0, 0, 0], [1, 2, 1]], [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]),
|
kernel_pair_preview("Sobel", [[-1, -2, -1], [0, 0, 0], [1, 2, 1]], [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]),
|
||||||
], formula="Gradient image = abs(imfilter(f,Gx)) + abs(imfilter(f,Gy))."),
|
], formula="Gradient image = abs(imfilter(f,Gx)) + abs(imfilter(f,Gy))."),
|
||||||
operation("high_boost", "High-Boost / Edge Emphasis", CH3, "High-Boost Filtering", high_boost_slide, {"A": float_param(1.5, 1, 6, 0.1, description="Boost factor A, where A >= 1."), "K": odd_param(3, 35, description="Odd averaging mask size used for the blurred image.")}, formula="f_hb = A f - blurred(f)."),
|
operation("high_boost", "High-Boost / Edge Emphasis", CH3, "High-Boost Filtering", high_boost_slide, {"A": float_param(1.5, 1, 6, 0.1, description="Boost factor A, where A >= 1."), "K": odd_param(3, 35, description="Odd averaging mask size used for the blurred image.")}, formula="f_hb = A f - blurred(f)."),
|
||||||
operation("fft_spectrum", "FFT/DFT Spectrum View", CH4, "DFT and FFT", fft_spectrum, {"mode": select_param("log_magnitude", ["magnitude", "log_magnitude", "phase"], description="Choose magnitude, log magnitude, or phase display.")}, formula="F(u,v) = DFT{f(x,y)}", repeatable=False),
|
operation("fft_spectrum", "FFT/DFT Spectrum View", CH4, "DFT and FFT", fft_spectrum, formula="Display log(1 + |fftshift(fft2(f))|).", repeatable=False),
|
||||||
operation("inverse_fft_reconstruction", "Inverse FFT Reconstruction", CH4, "DFT and FFT", inverse_fft_reconstruction, formula="f = real(ifft2(ifftshift(F))). Apply this to an FFT/DFT Spectrum View state.", repeatable=False),
|
operation("inverse_fft_reconstruction", "Inverse FFT Reconstruction", CH4, "DFT and FFT", inverse_fft_reconstruction, formula="f = real(ifft2(ifftshift(F))). Apply this to an FFT/DFT Spectrum View state.", repeatable=False),
|
||||||
operation("rgb_to_gray", "Convert to Grayscale", CH6, "Color Conversion", rgb_to_gray_matlab, {"red_weight": float_param(0.299, 0, 1, 0.001, description="R coefficient in gray = aR + bG + cB."), "green_weight": float_param(0.587, 0, 1, 0.001, description="G coefficient in gray = aR + bG + cB."), "blue_weight": float_param(0.114, 0, 1, 0.001, description="B coefficient in gray = aR + bG + cB.")}, formula="gray = 0.299R + 0.587G + 0.114B by default.", repeatable=False),
|
operation("rgb_to_gray", "Convert to Grayscale", CH6, "Color Conversion", rgb_to_gray_matlab, {"red_weight": float_param(0.299, 0, 1, 0.001, description="R coefficient in gray = aR + bG + cB."), "green_weight": float_param(0.587, 0, 1, 0.001, description="G coefficient in gray = aR + bG + cB."), "blue_weight": float_param(0.114, 0, 1, 0.001, description="B coefficient in gray = aR + bG + cB.")}, formula="gray = 0.299R + 0.587G + 0.114B by default.", repeatable=False),
|
||||||
operation("rgb_channel", "RGB Channel View", CH6, "RGB color model", rgb_channel, {"channel": select_param("r", ["r", "g", "b"], description="Select the RGB channel to view.")}, formula="Show one RGB channel as grayscale.", repeatable=False),
|
operation("rgb_channel", "RGB Channel View", CH6, "RGB color model", rgb_channel, {"channel": select_param("r", ["r", "g", "b"], description="Select the RGB channel to view.")}, formula="Show one RGB channel as grayscale.", repeatable=False),
|
||||||
|
|||||||
@@ -223,15 +223,12 @@ def image_state_fft_spectrum_create(*, state, params):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
source = load_image_array(state.image)
|
source = load_image_array(state.image)
|
||||||
mode = params.get("mode", "log_magnitude")
|
|
||||||
if mode not in {"magnitude", "log_magnitude", "phase"}:
|
|
||||||
raise ProcessingError("FFT mode must be magnitude, log_magnitude, or phase.")
|
|
||||||
spectrum = image_fft(source)
|
spectrum = image_fft(source)
|
||||||
preview = fft_preview_image(spectrum, mode)
|
preview = fft_preview_image(spectrum)
|
||||||
fft_data_path = save_fft_array(spectrum, "state-fft-data")
|
fft_data_path = save_fft_array(spectrum, "state-fft-data")
|
||||||
operation_params = {
|
operation_params = {
|
||||||
**params,
|
**(params or {}),
|
||||||
"mode": mode,
|
"mode": "log_magnitude",
|
||||||
"fft_data_path": fft_data_path,
|
"fft_data_path": fft_data_path,
|
||||||
"source_state_id": str(state.id),
|
"source_state_id": str(state.id),
|
||||||
"source_color_mode": "RGB" if source.ndim == 3 else "L",
|
"source_color_mode": "RGB" if source.ndim == 3 else "L",
|
||||||
@@ -276,15 +273,10 @@ def image_fft(image):
|
|||||||
return np.stack(channels, axis=2)
|
return np.stack(channels, axis=2)
|
||||||
|
|
||||||
|
|
||||||
def fft_preview_image(spectrum, mode):
|
def fft_preview_image(spectrum):
|
||||||
"""Convert complex FFT data to a display-only uint8 preview image."""
|
"""Convert complex FFT data to a display-only log-magnitude uint8 preview image."""
|
||||||
|
|
||||||
if mode == "phase":
|
preview = np.log1p(np.abs(spectrum))
|
||||||
preview = np.angle(spectrum)
|
|
||||||
else:
|
|
||||||
preview = np.abs(spectrum)
|
|
||||||
if mode == "log_magnitude":
|
|
||||||
preview = np.log1p(preview)
|
|
||||||
return normalize_to_uint8(preview)
|
return normalize_to_uint8(preview)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ class ApiTests(TestCase):
|
|||||||
self.assertEqual(operations["periodic_noise"]["params"]["T"]["default"], 100)
|
self.assertEqual(operations["periodic_noise"]["params"]["T"]["default"], 100)
|
||||||
self.assertEqual(operations["inverse_fft_reconstruction"]["label"], "Inverse FFT Reconstruction")
|
self.assertEqual(operations["inverse_fft_reconstruction"]["label"], "Inverse FFT Reconstruction")
|
||||||
self.assertFalse(operations["inverse_fft_reconstruction"]["repeatable"])
|
self.assertFalse(operations["inverse_fft_reconstruction"]["repeatable"])
|
||||||
|
self.assertEqual(operations["fft_spectrum"]["params"], {})
|
||||||
self.assertEqual(operations["box_filter"]["label"], "Average / Box Filter")
|
self.assertEqual(operations["box_filter"]["label"], "Average / Box Filter")
|
||||||
self.assertEqual(operations["gaussian_filter"]["label"], "Gaussian Filter")
|
self.assertEqual(operations["gaussian_filter"]["label"], "Gaussian Filter")
|
||||||
self.assertFalse(operations["negative"]["repeatable"])
|
self.assertFalse(operations["negative"]["repeatable"])
|
||||||
@@ -312,7 +313,7 @@ class ApiTests(TestCase):
|
|||||||
s0_id = upload.data["states"][0]["state_id"]
|
s0_id = upload.data["states"][0]["state_id"]
|
||||||
spectrum = self.client.post(
|
spectrum = self.client.post(
|
||||||
f"/api/states/{s0_id}/operations/",
|
f"/api/states/{s0_id}/operations/",
|
||||||
{"operation": "fft_spectrum", "params": {"mode": "log_magnitude"}},
|
{"operation": "fft_spectrum", "params": {}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -327,7 +328,7 @@ class ApiTests(TestCase):
|
|||||||
s0_id = upload.data["states"][0]["state_id"]
|
s0_id = upload.data["states"][0]["state_id"]
|
||||||
spectrum = self.client.post(
|
spectrum = self.client.post(
|
||||||
f"/api/states/{s0_id}/operations/",
|
f"/api/states/{s0_id}/operations/",
|
||||||
{"operation": "fft_spectrum", "params": {"mode": "log_magnitude"}},
|
{"operation": "fft_spectrum", "params": {}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
@@ -347,7 +348,7 @@ class ApiTests(TestCase):
|
|||||||
s0_id = upload.data["states"][0]["state_id"]
|
s0_id = upload.data["states"][0]["state_id"]
|
||||||
spectrum = self.client.post(
|
spectrum = self.client.post(
|
||||||
f"/api/states/{s0_id}/operations/",
|
f"/api/states/{s0_id}/operations/",
|
||||||
{"operation": "fft_spectrum", "params": {"mode": "log_magnitude"}},
|
{"operation": "fft_spectrum", "params": {}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
|
|||||||
Reference in New Issue
Block a user