Add files using upload-large-folder tool
Browse files- README.md +19 -0
- __pycache__/pipeline_zoomldm.cpython-312.pyc +0 -0
- demo_data/0_ssl_feat.npy +3 -0
- demo_images/input.jpeg +0 -0
- demo_images/output.jpeg +0 -0
- pipeline_zoomldm.py +21 -2
- run_demo_inference.py +68 -0
README.md
CHANGED
|
@@ -9,6 +9,11 @@ tags:
|
|
| 9 |
- latent-diffusion
|
| 10 |
- custom-pipeline
|
| 11 |
- arxiv:2411.16969
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
---
|
| 13 |
|
| 14 |
# BiliSakura/ZoomLDM-brca
|
|
@@ -60,6 +65,20 @@ out = pipe(
|
|
| 60 |
images = out.images
|
| 61 |
```
|
| 62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
## Limitations
|
| 64 |
|
| 65 |
- Requires correctly precomputed BRCA conditioning features.
|
|
|
|
| 9 |
- latent-diffusion
|
| 10 |
- custom-pipeline
|
| 11 |
- arxiv:2411.16969
|
| 12 |
+
widget:
|
| 13 |
+
- src: demo_images/input.jpeg
|
| 14 |
+
prompt: BRCA sample conditioned on demo SSL feature (mag=0)
|
| 15 |
+
output:
|
| 16 |
+
url: demo_images/output.jpeg
|
| 17 |
---
|
| 18 |
|
| 19 |
# BiliSakura/ZoomLDM-brca
|
|
|
|
| 65 |
images = out.images
|
| 66 |
```
|
| 67 |
|
| 68 |
+
## Demo Generation (dataset-backed)
|
| 69 |
+
|
| 70 |
+
This repo includes `run_demo_inference.py`, which uses local repo assets only:
|
| 71 |
+
|
| 72 |
+
- image: `demo_images/input.jpeg`
|
| 73 |
+
- SSL feature: `demo_data/0_ssl_feat.npy`
|
| 74 |
+
- magnification label: `0`
|
| 75 |
+
|
| 76 |
+
Run:
|
| 77 |
+
|
| 78 |
+
```bash
|
| 79 |
+
python run_demo_inference.py
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
## Limitations
|
| 83 |
|
| 84 |
- Requires correctly precomputed BRCA conditioning features.
|
__pycache__/pipeline_zoomldm.cpython-312.pyc
CHANGED
|
Binary files a/__pycache__/pipeline_zoomldm.cpython-312.pyc and b/__pycache__/pipeline_zoomldm.cpython-312.pyc differ
|
|
|
demo_data/0_ssl_feat.npy
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a0ccbc75eb5c210505d2ded9e3e2745a975971c8a331bd76ae31fc4d5c08a44a
|
| 3 |
+
size 2176
|
demo_images/input.jpeg
ADDED
|
demo_images/output.jpeg
ADDED
|
pipeline_zoomldm.py
CHANGED
|
@@ -44,6 +44,10 @@ def _ensure_local_ldm_on_path():
|
|
| 44 |
|
| 45 |
|
| 46 |
_ensure_local_ldm_on_path()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
|
| 49 |
def _get_class(target: str):
|
|
@@ -299,6 +303,17 @@ class ZoomLDMPipeline(DiffusionPipeline):
|
|
| 299 |
path = Path(snapshot_download(pretrained_model_name_or_path))
|
| 300 |
|
| 301 |
path = path.resolve()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
|
| 303 |
def _is_diffusers_model_dir(candidate: Path) -> bool:
|
| 304 |
required = [
|
|
@@ -333,8 +348,6 @@ class ZoomLDMPipeline(DiffusionPipeline):
|
|
| 333 |
)
|
| 334 |
model_dir = candidate_dirs[0]
|
| 335 |
|
| 336 |
-
scheduler = DDIMScheduler.from_pretrained(model_dir / "scheduler")
|
| 337 |
-
|
| 338 |
_TARGETS = {
|
| 339 |
"unet": "ldm.modules.diffusionmodules.openaimodel.UNetModel",
|
| 340 |
"vae": "ldm.models.autoencoder.VQModelInterface",
|
|
@@ -381,6 +394,12 @@ class ZoomLDMPipeline(DiffusionPipeline):
|
|
| 381 |
component.eval()
|
| 382 |
return component
|
| 383 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 384 |
unet = load_custom_component("unet")
|
| 385 |
vae = load_custom_component("vae")
|
| 386 |
conditioning_encoder = load_custom_component("conditioning_encoder")
|
|
|
|
| 44 |
|
| 45 |
|
| 46 |
_ensure_local_ldm_on_path()
|
| 47 |
+
# Register module alias so diffusers component loading can resolve
|
| 48 |
+
# model_index entries like "pipeline_zoomldm" even when this file is loaded
|
| 49 |
+
# under a dynamic module name (e.g. diffusers_modules.local.*).
|
| 50 |
+
sys.modules["pipeline_zoomldm"] = sys.modules[__name__]
|
| 51 |
|
| 52 |
|
| 53 |
def _get_class(target: str):
|
|
|
|
| 303 |
path = Path(snapshot_download(pretrained_model_name_or_path))
|
| 304 |
|
| 305 |
path = path.resolve()
|
| 306 |
+
component_names = {"unet", "vae", "conditioning_encoder"}
|
| 307 |
+
# When diffusers loads components, it may call this class with a path like ".../unet".
|
| 308 |
+
requested_component = None
|
| 309 |
+
if path.name in component_names and (path / "config.json").exists():
|
| 310 |
+
requested_component = path.name
|
| 311 |
+
path = path.parent
|
| 312 |
+
|
| 313 |
+
# Also support explicit component requests via subfolder.
|
| 314 |
+
subfolder = kwargs.pop("subfolder", None)
|
| 315 |
+
if requested_component is None and subfolder in component_names:
|
| 316 |
+
requested_component = subfolder
|
| 317 |
|
| 318 |
def _is_diffusers_model_dir(candidate: Path) -> bool:
|
| 319 |
required = [
|
|
|
|
| 348 |
)
|
| 349 |
model_dir = candidate_dirs[0]
|
| 350 |
|
|
|
|
|
|
|
| 351 |
_TARGETS = {
|
| 352 |
"unet": "ldm.modules.diffusionmodules.openaimodel.UNetModel",
|
| 353 |
"vae": "ldm.models.autoencoder.VQModelInterface",
|
|
|
|
| 394 |
component.eval()
|
| 395 |
return component
|
| 396 |
|
| 397 |
+
# Diffusers component-loading path: return a single module.
|
| 398 |
+
if requested_component is not None:
|
| 399 |
+
return load_custom_component(requested_component)
|
| 400 |
+
|
| 401 |
+
scheduler = DDIMScheduler.from_pretrained(model_dir / "scheduler")
|
| 402 |
+
|
| 403 |
unet = load_custom_component("unet")
|
| 404 |
vae = load_custom_component("vae")
|
| 405 |
conditioning_encoder = load_custom_component("conditioning_encoder")
|
run_demo_inference.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Run ZoomLDM-BRCA demo inference using local demo assets."""
|
| 3 |
+
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
import numpy as np
|
| 7 |
+
import torch
|
| 8 |
+
import torch.nn.functional as F
|
| 9 |
+
from diffusers import DiffusionPipeline
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def preprocess_brca_ssl(npy_path: Path) -> torch.Tensor:
|
| 13 |
+
# Copied from dataset material:
|
| 14 |
+
# 1) cast to float32
|
| 15 |
+
# 2) normalize per-feature
|
| 16 |
+
# 3) reshape to (1024, h, h)
|
| 17 |
+
# 4) adaptive avg pool to max 8x8 if needed
|
| 18 |
+
feat = np.load(npy_path).astype(np.float32)
|
| 19 |
+
if feat.ndim == 1:
|
| 20 |
+
feat = feat[:, None]
|
| 21 |
+
mean = feat.mean(axis=0, keepdims=True)
|
| 22 |
+
std = feat.std(axis=0, keepdims=True)
|
| 23 |
+
feat = (feat - mean) / (std + 1e-8)
|
| 24 |
+
h = int(np.sqrt(feat.shape[1]))
|
| 25 |
+
feat = torch.tensor(feat.reshape((-1, h, h))).float() # (1024, h, h)
|
| 26 |
+
if h > 8:
|
| 27 |
+
feat = F.adaptive_avg_pool2d(feat, (8, 8))
|
| 28 |
+
return feat
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def main() -> None:
|
| 32 |
+
repo = Path(__file__).resolve().parent
|
| 33 |
+
demo_dir = repo / "demo_images"
|
| 34 |
+
demo_data = repo / "demo_data"
|
| 35 |
+
demo_dir.mkdir(exist_ok=True)
|
| 36 |
+
|
| 37 |
+
# Use repo-local demo assets only.
|
| 38 |
+
src_img = demo_dir / "input.jpeg"
|
| 39 |
+
src_feat = demo_data / "0_ssl_feat.npy"
|
| 40 |
+
if not src_img.exists():
|
| 41 |
+
raise FileNotFoundError(f"Missing demo input image: {src_img}")
|
| 42 |
+
if not src_feat.exists():
|
| 43 |
+
raise FileNotFoundError(f"Missing demo SSL feature: {src_feat}")
|
| 44 |
+
|
| 45 |
+
ssl_feat = preprocess_brca_ssl(src_feat).unsqueeze(0).to("cuda") # (1, 1024, h, h)
|
| 46 |
+
magnification = torch.tensor([0], device="cuda", dtype=torch.long)
|
| 47 |
+
|
| 48 |
+
pipe = DiffusionPipeline.from_pretrained(
|
| 49 |
+
str(repo),
|
| 50 |
+
custom_pipeline=str(repo / "pipeline_zoomldm.py"),
|
| 51 |
+
trust_remote_code=True,
|
| 52 |
+
local_files_only=True,
|
| 53 |
+
).to("cuda")
|
| 54 |
+
|
| 55 |
+
out = pipe(
|
| 56 |
+
ssl_features=ssl_feat,
|
| 57 |
+
magnification=magnification,
|
| 58 |
+
num_inference_steps=50,
|
| 59 |
+
guidance_scale=2.0,
|
| 60 |
+
generator=torch.Generator(device="cuda").manual_seed(42),
|
| 61 |
+
)
|
| 62 |
+
out.images[0].save(demo_dir / "output.jpeg")
|
| 63 |
+
print(f"Saved {demo_dir / 'input.jpeg'}")
|
| 64 |
+
print(f"Saved {demo_dir / 'output.jpeg'}")
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
if __name__ == "__main__":
|
| 68 |
+
main()
|