medimaging commited on
Commit
e257a8a
·
verified ·
1 Parent(s): bd9a996

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +94 -85
app.py CHANGED
@@ -5,11 +5,9 @@ from torchvision import transforms
5
  from PIL import Image
6
  import numpy as np
7
  import nibabel as nib
8
- from functools import lru_cache
9
-
10
 
11
  # -----------------------------
12
- # (1) Minimal SIREN definition
13
  # -----------------------------
14
  class SirenLayer(torch.nn.Module):
15
  def __init__(self, dim_in, dim_out, w0=30.0):
@@ -38,67 +36,57 @@ class Siren(torch.nn.Module):
38
  def forward(self, x):
39
  for layer in self.net:
40
  x = layer(x)
41
- return torch.sigmoid(self.final(x)) # output ∈ [0,1]
42
 
43
 
44
- # ----------------------------------
45
- # (2) Cached model loader
46
- # ----------------------------------
47
- @lru_cache(maxsize=1)
48
  def load_model():
49
- repo_id = "medimaging/physics-informed-sirenMRI-model"
50
- filename = "sirenMRI_full_model.pt"
51
-
52
- model_path = hf_hub_download(repo_id=repo_id, filename=filename)
53
  obj = torch.load(model_path, map_location="cpu")
54
 
55
- # Try to interpret the object
56
  if isinstance(obj, torch.nn.Module):
57
  model = obj
58
  elif isinstance(obj, dict):
59
- if "all_slice_models" in obj:
 
 
 
60
  first_slice = list(obj["all_slice_models"].values())[0]
61
- model = Siren(dim_in=2, dim_hidden=256, dim_out=1, num_layers=3)
62
  model.load_state_dict(first_slice)
63
- elif "model_state_dict" in obj:
64
- model = Siren(dim_in=2, dim_hidden=256, dim_out=1, num_layers=3)
65
- model.load_state_dict(obj["model_state_dict"])
66
  else:
67
- model = Siren(dim_in=2, dim_hidden=256, dim_out=1, num_layers=3)
68
  model.load_state_dict(obj)
69
  else:
70
- raise ValueError("Unrecognized model format in the .pt file.")
71
 
72
  model.eval()
73
  return model
74
 
75
 
76
- # ----------------------------------
77
- # (3) Pre/post-processing
78
- # ----------------------------------
79
- preprocess = transforms.Compose([
80
- transforms.Resize((256, 256)),
81
- transforms.ToTensor(),
82
- ])
83
- postprocess = transforms.ToPILImage()
84
 
 
 
 
 
 
85
 
86
- # ----------------------------------
87
- # (4) Utility functions
88
- # ----------------------------------
89
  def psnr(img1, img2):
90
  mse = torch.mean((img1 - img2) ** 2)
91
- if mse == 0:
92
- return float("inf")
93
- return 20 * torch.log10(1.0 / torch.sqrt(mse))
94
-
95
 
96
- def estimate_compression_ratio(image, model_params):
97
- img_bytes = image.size[0] * image.size[1] * 3
98
  model_bytes = model_params * 4
99
  return round(img_bytes / model_bytes, 2)
100
 
101
-
102
  def extract_middle_slice(nii_path):
103
  nii = nib.load(nii_path)
104
  data = nii.get_fdata()
@@ -109,23 +97,15 @@ def extract_middle_slice(nii_path):
109
  return Image.fromarray(slice_img)
110
 
111
 
112
- # ----------------------------------
113
- # (5) Core pipeline
114
- # ----------------------------------
115
  def compress_and_reconstruct(file):
116
- try:
117
- model = load_model()
118
- except Exception as e:
119
- return None, f"❌ Model failed to load: {e}"
120
-
121
  filename = getattr(file, "name", "")
122
- try:
123
- if filename.endswith((".nii", ".nii.gz")):
124
- image = extract_middle_slice(filename)
125
- else:
126
- image = Image.open(file).convert("RGB")
127
- except Exception:
128
- return None, "❌ Failed to read image or NIfTI file."
129
 
130
  original = preprocess(image).unsqueeze(0)
131
 
@@ -142,37 +122,66 @@ def compress_and_reconstruct(file):
142
  combined.paste(orig_img, (0, 0))
143
  combined.paste(recon_img, (orig_img.width, 0))
144
 
145
- return combined, f"✅ PSNR: {score:.2f} dB | Compression Ratio: {ratio}:1"
146
-
147
-
148
- # ----------------------------------
149
- # (6) Gradio UI
150
- # ----------------------------------
151
- title = "🧠 Physics-Informed SIREN MRI Compression"
152
- description = """
153
- Upload an **MRI file (.nii / .nii.gz)** or **image (PNG/JPG)**
154
- to visualize compression → reconstruction using the **Physics-Informed SIREN** model.
155
-
156
- Shows:
157
- - Side-by-side comparison *(Original → Reconstructed)*
158
- - PSNR & Compression Ratio metrics
159
-
160
- **Framework:** PyTorch + Gradio
161
- **Author:** medimaging
162
- """
163
-
164
- interface = gr.Interface(
165
- fn=compress_and_reconstruct,
166
- inputs=gr.File(label="Upload MRI or Image File (.nii, .nii.gz, .png, .jpg)"),
167
- outputs=[
168
- gr.Image(label="Original (Left) → Reconstructed (Right)"),
169
- gr.Textbox(label="Quality Metrics", interactive=False)
170
- ],
171
- title=title,
172
- description=description,
173
- allow_flagging="never",
174
- examples=None,
175
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
 
177
  if __name__ == "__main__":
178
- interface.launch()
 
5
  from PIL import Image
6
  import numpy as np
7
  import nibabel as nib
 
 
8
 
9
  # -----------------------------
10
+ # 1️⃣ Minimal SIREN definition
11
  # -----------------------------
12
  class SirenLayer(torch.nn.Module):
13
  def __init__(self, dim_in, dim_out, w0=30.0):
 
36
  def forward(self, x):
37
  for layer in self.net:
38
  x = layer(x)
39
+ return torch.sigmoid(self.final(x))
40
 
41
 
42
+ # -----------------------------
43
+ # 2️⃣ Load trained model
44
+ # -----------------------------
 
45
  def load_model():
46
+ model_path = hf_hub_download(
47
+ repo_id="medimaging/physics-informed-sirenMRI-model",
48
+ filename="sirenMRI_full_model.pt"
49
+ )
50
  obj = torch.load(model_path, map_location="cpu")
51
 
 
52
  if isinstance(obj, torch.nn.Module):
53
  model = obj
54
  elif isinstance(obj, dict):
55
+ if "model_state_dict" in obj:
56
+ model = Siren()
57
+ model.load_state_dict(obj["model_state_dict"])
58
+ elif "all_slice_models" in obj:
59
  first_slice = list(obj["all_slice_models"].values())[0]
60
+ model = Siren()
61
  model.load_state_dict(first_slice)
 
 
 
62
  else:
63
+ model = Siren()
64
  model.load_state_dict(obj)
65
  else:
66
+ raise ValueError("❌ Unknown model format")
67
 
68
  model.eval()
69
  return model
70
 
71
 
72
+ model = load_model()
73
+
 
 
 
 
 
 
74
 
75
+ # -----------------------------
76
+ # 3️⃣ Helper functions
77
+ # -----------------------------
78
+ preprocess = transforms.Compose([transforms.Resize((256, 256)), transforms.ToTensor()])
79
+ postprocess = transforms.ToPILImage()
80
 
 
 
 
81
  def psnr(img1, img2):
82
  mse = torch.mean((img1 - img2) ** 2)
83
+ return float("inf") if mse == 0 else 20 * torch.log10(1.0 / torch.sqrt(mse))
 
 
 
84
 
85
+ def estimate_compression_ratio(original_img, model_params):
86
+ img_bytes = original_img.size[0] * original_img.size[1] * 3
87
  model_bytes = model_params * 4
88
  return round(img_bytes / model_bytes, 2)
89
 
 
90
  def extract_middle_slice(nii_path):
91
  nii = nib.load(nii_path)
92
  data = nii.get_fdata()
 
97
  return Image.fromarray(slice_img)
98
 
99
 
100
+ # -----------------------------
101
+ # 4️⃣ Main function
102
+ # -----------------------------
103
  def compress_and_reconstruct(file):
 
 
 
 
 
104
  filename = getattr(file, "name", "")
105
+ if filename.endswith(".nii") or filename.endswith(".nii.gz"):
106
+ image = extract_middle_slice(filename)
107
+ else:
108
+ image = Image.open(file).convert("RGB")
 
 
 
109
 
110
  original = preprocess(image).unsqueeze(0)
111
 
 
122
  combined.paste(orig_img, (0, 0))
123
  combined.paste(recon_img, (orig_img.width, 0))
124
 
125
+ metrics = f"📊 PSNR: {score:.2f} dB | 💾 Compression Ratio: {ratio}:1"
126
+ return combined, metrics
127
+
128
+
129
+ # -----------------------------
130
+ # 5️⃣ Custom Gradio UI
131
+ # -----------------------------
132
+ with gr.Blocks(css="""
133
+ @keyframes gradient {
134
+ 0% {background-position: 0% 50%;}
135
+ 50% {background-position: 100% 50%;}
136
+ 100% {background-position: 0% 50%;}
137
+ }
138
+ #title {
139
+ font-size: 32px;
140
+ font-weight: 800;
141
+ text-align: center;
142
+ background: linear-gradient(-45deg, #2563eb, #7c3aed, #06b6d4, #3b82f6);
143
+ background-size: 400% 400%;
144
+ -webkit-background-clip: text;
145
+ -webkit-text-fill-color: transparent;
146
+ animation: gradient 6s ease infinite;
147
+ margin-top: 20px;
148
+ }
149
+ #description {
150
+ text-align: center;
151
+ font-size: 18px;
152
+ color: #475569;
153
+ max-width: 720px;
154
+ margin: 0 auto 25px auto;
155
+ }
156
+ #footer {
157
+ text-align: center;
158
+ font-size: 14px;
159
+ color: #94a3b8;
160
+ margin-top: 30px;
161
+ }
162
+ """) as demo:
163
+
164
+ gr.HTML("<div id='title'>🧠 Compress Medical Images to ease documentation and processing</div>")
165
+ gr.HTML("""
166
+ <div id='description'>
167
+ Upload an <b>MRI file (.nii / .nii.gz)</b> or <b>image (.png / .jpg)</b> to visualize
168
+ <b>compression → reconstruction</b> using a Physics-Informed SIREN model.<br>
169
+ <small>View PSNR and compression ratio below.</small>
170
+ </div>
171
+ """)
172
+
173
+ with gr.Row():
174
+ with gr.Column(scale=1, min_width=300):
175
+ file_input = gr.File(label="📁 Upload MRI or Image File")
176
+ submit_btn = gr.Button("🚀 Run Compression", variant="primary")
177
+ with gr.Column(scale=2):
178
+ output_image = gr.Image(label="Original (Left) → Reconstructed (Right)")
179
+ output_text = gr.Textbox(label="Metrics", interactive=False)
180
+
181
+ submit_btn.click(compress_and_reconstruct, inputs=file_input, outputs=[output_image, output_text])
182
+
183
+ gr.HTML("<div id='footer'>Made with ❤️ using PyTorch + Gradio</div>")
184
+
185
 
186
  if __name__ == "__main__":
187
+ demo.launch()