feat(v5): add periodic noise and fft reconstruction

This commit is contained in:
2026-07-09 14:41:02 +03:30
parent ece63caa22
commit d9f2cd4d9d
3 changed files with 127 additions and 1 deletions

View File

@@ -3,6 +3,7 @@ from io import BytesIO
from pathlib import Path
from unittest.mock import patch
import numpy as np
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase, override_settings
from PIL import Image
@@ -69,12 +70,19 @@ class ApiTests(TestCase):
self.assertIn("median_filter", operation_ids)
self.assertIn("noise_filter", operation_ids)
self.assertIn("average_noisy_copies", operation_ids)
self.assertIn("periodic_noise", operation_ids)
self.assertIn("inverse_fft_reconstruction", operation_ids)
self.assertIn("rgb_to_gray", operation_ids)
self.assertEqual(operations["histeq"]["params"], {})
self.assertEqual(operations["noise_filter"]["label"], "Noise Filter")
self.assertEqual(operations["average_noisy_copies"]["label"], "Average N Noisy Copies")
self.assertEqual(operations["average_noisy_copies"]["params"]["N"]["default"], 100)
self.assertFalse(operations["average_noisy_copies"]["repeatable"])
self.assertEqual(operations["periodic_noise"]["label"], "Periodic Noise")
self.assertEqual(operations["periodic_noise"]["params"]["A"]["default"], 0.2)
self.assertEqual(operations["periodic_noise"]["params"]["T"]["default"], 100)
self.assertEqual(operations["inverse_fft_reconstruction"]["label"], "Inverse FFT Reconstruction")
self.assertFalse(operations["inverse_fft_reconstruction"]["repeatable"])
self.assertEqual(operations["box_filter"]["label"], "Average / Box Filter")
self.assertEqual(operations["gaussian_filter"]["label"], "Gaussian Filter")
self.assertFalse(operations["negative"]["repeatable"])
@@ -225,6 +233,76 @@ class ApiTests(TestCase):
self.assertEqual(result.ndim, 2)
self.assertEqual(int(result[0, 0]), 96)
def test_periodic_noise_preserves_grayscale_and_zero_amplitude(self):
upload = self.client.post("/api/images/", {"image": grayscale_png_upload(value=96)}, format="multipart")
s0_id = upload.data["states"][0]["state_id"]
response = self.client.post(
f"/api/states/{s0_id}/operations/",
{"operation": "periodic_noise", "params": {"A": 0, "T": 100}},
format="json",
)
self.assertEqual(response.status_code, 201)
self.assertEqual(response.data["channels"], 1)
self.assertEqual(response.data["color_mode"], "L")
result = load_image_array(ImageState.objects.get(id=response.data["state_id"]).image)
self.assertEqual(result.ndim, 2)
self.assertTrue(np.all(result == 96))
def test_periodic_noise_rgb_uses_shared_channel_mask(self):
upload = self.client.post("/api/images/", {"image": png_upload(color=(80, 80, 80), size=(8, 8))}, format="multipart")
s0_id = upload.data["states"][0]["state_id"]
response = self.client.post(
f"/api/states/{s0_id}/operations/",
{"operation": "periodic_noise", "params": {"A": 0.1, "T": 4}},
format="json",
)
self.assertEqual(response.status_code, 201)
result = load_image_array(ImageState.objects.get(id=response.data["state_id"]).image)
np.testing.assert_array_equal(result[:, :, 0], result[:, :, 1])
np.testing.assert_array_equal(result[:, :, 1], result[:, :, 2])
def test_periodic_noise_rejects_invalid_period(self):
upload = self.client.post("/api/images/", {"image": grayscale_png_upload()}, format="multipart")
s0_id = upload.data["states"][0]["state_id"]
response = self.client.post(
f"/api/states/{s0_id}/operations/",
{"operation": "periodic_noise", "params": {"A": 0.2, "T": 0}},
format="json",
)
self.assertEqual(response.status_code, 400)
def test_inverse_fft_reconstruction_matches_input_shape_and_values(self):
upload = self.client.post("/api/images/", {"image": png_upload(color=(32, 64, 128), size=(4, 4))}, format="multipart")
s0_id = upload.data["states"][0]["state_id"]
response = self.client.post(
f"/api/states/{s0_id}/operations/",
{"operation": "inverse_fft_reconstruction", "params": {}},
format="json",
)
self.assertEqual(response.status_code, 201)
self.assertEqual(response.data["channels"], 3)
result = load_image_array(ImageState.objects.get(id=response.data["state_id"]).image)
expected = load_image_array(ImageState.objects.get(id=s0_id).image)
np.testing.assert_allclose(result, expected, atol=1)
def test_inverse_fft_reconstruction_preserves_grayscale(self):
upload = self.client.post("/api/images/", {"image": grayscale_png_upload(value=96)}, format="multipart")
s0_id = upload.data["states"][0]["state_id"]
response = self.client.post(
f"/api/states/{s0_id}/operations/",
{"operation": "inverse_fft_reconstruction", "params": {}},
format="json",
)
self.assertEqual(response.status_code, 201)
self.assertEqual(response.data["channels"], 1)
result = load_image_array(ImageState.objects.get(id=response.data["state_id"]).image)
self.assertEqual(result.ndim, 2)
self.assertTrue(np.allclose(result, 96, atol=1))
def test_grayscale_operation_creates_state(self):
upload = self.client.post("/api/images/", {"image": png_upload()}, format="multipart")
s0_id = upload.data["states"][0]["state_id"]