feat: Add BEN2 background removal functionality and integrate into SnapForm
- Introduced BEN2 model for background removal in the panel and properties. - Updated runtime to handle background removal commands and model downloads. - Implemented background removal logic in worker, including image processing and saving results. - Enhanced tests to cover new background removal features and ensure proper integration. - Updated version to 0.9.28 to reflect new features and improvements.
This commit is contained in:
@@ -47,6 +47,52 @@ class WorkerTest(unittest.TestCase):
|
||||
source.index("import torch"),
|
||||
)
|
||||
|
||||
def test_optional_background_removal_runs_before_da3_model_load(self):
|
||||
source = inspect.getsource(self.worker.reconstruct)
|
||||
|
||||
self.assertLess(
|
||||
source.index("infer_foreground("),
|
||||
source.index("DepthAnything3.from_pretrained"),
|
||||
)
|
||||
args = self.worker.parser().parse_args(
|
||||
[
|
||||
"reconstruct",
|
||||
"--input",
|
||||
"input.png",
|
||||
"--model-dir",
|
||||
"da3",
|
||||
"--output-dir",
|
||||
"result",
|
||||
"--export-format",
|
||||
"mini_npz",
|
||||
"--status",
|
||||
"status.json",
|
||||
"--remove-background-model-dir",
|
||||
"ben2",
|
||||
"--process-res",
|
||||
"1024",
|
||||
"--process-res-method",
|
||||
"upper_bound_resize",
|
||||
"--conf-thresh-percentile",
|
||||
"40",
|
||||
"--num-max-points",
|
||||
"1000",
|
||||
"--show-cameras",
|
||||
"0",
|
||||
"--gs-views-interval",
|
||||
"1",
|
||||
"--video-frames",
|
||||
"16",
|
||||
"--device",
|
||||
"auto",
|
||||
"--use-ray-pose",
|
||||
"1",
|
||||
"--ref-view-strategy",
|
||||
"first",
|
||||
]
|
||||
)
|
||||
self.assertEqual(args.remove_background_model_dir, "ben2")
|
||||
|
||||
def test_prepares_a_single_image_file(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
image_path = Path(directory) / "input.png"
|
||||
@@ -69,6 +115,117 @@ class WorkerTest(unittest.TestCase):
|
||||
with self.assertRaisesRegex(RuntimeError, "Unsupported input type"):
|
||||
self.worker.prepare_input(input_path, 16, directory)
|
||||
|
||||
def test_download_passes_optional_file_patterns(self):
|
||||
calls = []
|
||||
fake_hugging_face = ModuleType("huggingface_hub")
|
||||
fake_hugging_face.snapshot_download = lambda **options: calls.append(
|
||||
options
|
||||
)
|
||||
args = SimpleNamespace(
|
||||
repository="PramaLLC/BEN2",
|
||||
model_dir="model",
|
||||
status="status.json",
|
||||
allow_pattern=["config.json", "model.safetensors"],
|
||||
)
|
||||
|
||||
with tempfile.TemporaryDirectory() as directory, patch.dict(
|
||||
sys.modules,
|
||||
{"huggingface_hub": fake_hugging_face},
|
||||
), patch.object(
|
||||
self.worker,
|
||||
"create_download_progress_class",
|
||||
return_value=object,
|
||||
):
|
||||
args.model_dir = str(Path(directory) / "model")
|
||||
args.status = str(Path(directory) / "status.json")
|
||||
self.worker.download_model(args)
|
||||
|
||||
self.assertEqual(
|
||||
calls[0]["allow_patterns"],
|
||||
["config.json", "model.safetensors"],
|
||||
)
|
||||
|
||||
def test_ben2_background_removal_writes_fast_transparent_png(self):
|
||||
inference_options = []
|
||||
save_options = []
|
||||
|
||||
class FakeImage:
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *_args):
|
||||
return None
|
||||
|
||||
def convert(self, mode):
|
||||
self.mode = mode
|
||||
return self
|
||||
|
||||
def save(self, path, **options):
|
||||
save_options.append(options)
|
||||
Path(path).touch()
|
||||
|
||||
class FakeModel:
|
||||
@classmethod
|
||||
def from_pretrained(cls, model_dir):
|
||||
self.assertEqual(model_dir, "model")
|
||||
return cls()
|
||||
|
||||
def to(model_self, device):
|
||||
self.assertEqual(device.type, "cpu")
|
||||
return model_self
|
||||
|
||||
def eval(self):
|
||||
return None
|
||||
|
||||
def inference(self, image, **options):
|
||||
inference_options.append((image, options))
|
||||
return FakeImage()
|
||||
|
||||
fake_torch = ModuleType("torch")
|
||||
fake_torch.cuda = SimpleNamespace(
|
||||
is_available=lambda: False,
|
||||
synchronize=lambda: None,
|
||||
)
|
||||
fake_torch.device = lambda device_type: SimpleNamespace(type=device_type)
|
||||
fake_torch.inference_mode = nullcontext
|
||||
fake_ben2 = ModuleType("ben2")
|
||||
fake_ben2.BEN_Base = FakeModel
|
||||
fake_pil = ModuleType("PIL")
|
||||
fake_pil.Image = SimpleNamespace(open=lambda _path: FakeImage())
|
||||
|
||||
with tempfile.TemporaryDirectory() as directory, patch.dict(
|
||||
sys.modules,
|
||||
{
|
||||
"torch": fake_torch,
|
||||
"ben2": fake_ben2,
|
||||
"PIL": fake_pil,
|
||||
},
|
||||
):
|
||||
root = Path(directory)
|
||||
input_path = root / "input.png"
|
||||
input_path.touch()
|
||||
args = SimpleNamespace(
|
||||
input=str(input_path),
|
||||
model_dir="model",
|
||||
output_dir=str(root / "result"),
|
||||
status=str(root / "status.json"),
|
||||
device="cpu",
|
||||
)
|
||||
self.worker.remove_background(args)
|
||||
status = json.loads(Path(args.status).read_text(encoding="utf-8"))
|
||||
|
||||
self.assertTrue((root / "result" / "foreground.png").is_file())
|
||||
|
||||
self.assertEqual(
|
||||
inference_options[0][1],
|
||||
{"refine_foreground": False},
|
||||
)
|
||||
self.assertEqual(
|
||||
save_options[0],
|
||||
{"format": "PNG", "compress_level": 1},
|
||||
)
|
||||
self.assertIn("Background removed in", status["message"])
|
||||
|
||||
def test_uses_official_da3_export_parameters_for_all_four_formats(self):
|
||||
inference_options = []
|
||||
export_calls = []
|
||||
|
||||
Reference in New Issue
Block a user