Add image perspective editing and related tools
- Implemented the Image Perspective tool for editing image corners in Blender. - Created a new operator `EditImagePerspective` to handle the perspective transformation. - Added a preview feature for the perspective adjustment. - Introduced the `ImagePolylineTool` for editing image masks with polylines. - Developed backend support for Real-ESRGAN model inference, including ONNX integration. - Added functionality for image masking and background removal. - Created unit tests for the Real-ESRGAN ONNX model to ensure correct preprocessing and inference.
This commit is contained in:
@@ -79,7 +79,14 @@ class ServerTest(unittest.TestCase):
|
||||
snapshot = manager.snapshot()
|
||||
self.assertEqual(
|
||||
snapshot,
|
||||
{"loaded": {"ben2": "", "da3": "", "moge2": ""}},
|
||||
{
|
||||
"loaded": {
|
||||
"ben2": "",
|
||||
"da3": "",
|
||||
"moge2": "",
|
||||
"realesrgan": "",
|
||||
}
|
||||
},
|
||||
)
|
||||
self.assertIn("server.model_catalog", sys.modules)
|
||||
self.assertNotIn("model_catalog", sys.modules)
|
||||
@@ -94,6 +101,8 @@ class ServerTest(unittest.TestCase):
|
||||
cache.da3_key = None
|
||||
cache.moge_model = None
|
||||
cache.moge_key = None
|
||||
cache.realesrgan_model = None
|
||||
cache.realesrgan_key = None
|
||||
return cache
|
||||
|
||||
def create_server(self):
|
||||
@@ -184,6 +193,25 @@ class ServerTest(unittest.TestCase):
|
||||
self.assertIsNone(cache.da3_model)
|
||||
self.assertIsNotNone(cache.ben2_model)
|
||||
|
||||
def test_realesrgan_model_is_reused_for_matching_runtime_key(self):
|
||||
loads = []
|
||||
realesrgan = ModuleType("server.models.onnx_realesrgan")
|
||||
realesrgan.create_session = lambda model_dir, device: (
|
||||
loads.append((model_dir, device)) or object()
|
||||
)
|
||||
cache = self.create_cache()
|
||||
|
||||
with patch.dict(
|
||||
sys.modules,
|
||||
{"server.models.onnx_realesrgan": realesrgan},
|
||||
):
|
||||
first = cache.get_realesrgan("model", "cpu")
|
||||
second = cache.get_realesrgan("model", "cpu")
|
||||
|
||||
self.assertIs(first, second)
|
||||
self.assertEqual(loads, [("model", "cpu")])
|
||||
self.assertEqual(cache.snapshot()["realesrgan"], "model")
|
||||
|
||||
def test_dedicated_server_runs_only_one_job_at_a_time(self):
|
||||
server = self.server_module.JobServer("Test", storage_root=self.storage_root)
|
||||
first_started = threading.Event()
|
||||
@@ -220,7 +248,7 @@ class ServerTest(unittest.TestCase):
|
||||
"download-model",
|
||||
"download-required-models",
|
||||
"analyze-balloon",
|
||||
"extract-image-background",
|
||||
"refine-image-mask",
|
||||
"remove-background",
|
||||
"extract-frames",
|
||||
"generate-depth",
|
||||
@@ -228,6 +256,7 @@ class ServerTest(unittest.TestCase):
|
||||
"debug-da3",
|
||||
"debug-moge2",
|
||||
"debug-ben2",
|
||||
"debug-realesrgan-general-x4v3",
|
||||
},
|
||||
)
|
||||
|
||||
@@ -401,16 +430,16 @@ class ServerTest(unittest.TestCase):
|
||||
self.assertEqual(tuple(result[0, 0]), (20, 40, 60, 64))
|
||||
self.assertEqual(tuple(result[3, 3]), (0, 0, 0, 0))
|
||||
|
||||
def test_image_extract_canvas_clears_rgba_outside_the_selection(self):
|
||||
def test_image_mask_canvas_clears_rgba_outside_the_selection(self):
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
image_extract = sys.modules["server.jobs.image_extract"]
|
||||
image_mask = sys.modules["server.jobs.image_mask"]
|
||||
source_path = self.storage_root / "source.png"
|
||||
pixels = np.full((2, 2, 4), (20, 40, 60, 255), dtype=np.uint8)
|
||||
Image.fromarray(pixels, mode="RGBA").save(source_path)
|
||||
|
||||
canvas, _selection, bounds = image_extract.create_selection_canvas(
|
||||
canvas, _selection, bounds = image_mask.create_selection_canvas(
|
||||
source_path,
|
||||
((-1, -1), (2, -1), (-1, 2)),
|
||||
)
|
||||
@@ -421,25 +450,22 @@ class ServerTest(unittest.TestCase):
|
||||
self.assertEqual(tuple(result[2, 2]), (0, 0, 0, 0))
|
||||
self.assertEqual(tuple(result[1, 1]), (20, 40, 60, 255))
|
||||
|
||||
def test_image_extract_crops_to_alpha_with_pixel_padding(self):
|
||||
def test_image_mask_crops_tightly_to_alpha(self):
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
image_extract = sys.modules["server.jobs.image_extract"]
|
||||
image_mask = sys.modules["server.jobs.image_mask"]
|
||||
pixels = np.zeros((4, 5, 4), dtype=np.uint8)
|
||||
pixels[1:3, 2:4] = (20, 40, 60, 255)
|
||||
result, bounds = image_extract.crop_to_alpha(
|
||||
result, bounds = image_mask.crop_to_alpha(
|
||||
Image.fromarray(pixels, mode="RGBA"),
|
||||
(10, 20, 15, 24),
|
||||
padding=1,
|
||||
)
|
||||
result = np.asarray(result)
|
||||
|
||||
self.assertEqual(bounds, (11, 20, 15, 24))
|
||||
self.assertEqual(result.shape, (4, 4, 4))
|
||||
self.assertTrue(np.all(result[[0, -1], :, 3] == 0))
|
||||
self.assertTrue(np.all(result[:, [0, -1], 3] == 0))
|
||||
self.assertTrue(np.all(result[1:3, 1:3, 3] == 255))
|
||||
self.assertEqual(bounds, (12, 21, 14, 23))
|
||||
self.assertEqual(result.shape, (2, 2, 4))
|
||||
self.assertTrue(np.all(result[:, :, 3] == 255))
|
||||
|
||||
def test_balloon_job_sends_the_masked_crop_to_ben2(self):
|
||||
import numpy as np
|
||||
|
||||
Reference in New Issue
Block a user