Spaces:
Paused
Paused
Update app_fullbody_pretrained.py
Browse files- app_fullbody_pretrained.py +29 -48
app_fullbody_pretrained.py
CHANGED
|
@@ -663,15 +663,16 @@ async def generate_3d_local(file: UploadFile = File(...)):
|
|
| 663 |
# ============================================================
|
| 664 |
# ✅ MONAI + PyVista 3D Full-Body Visualization (Final HF Safe)
|
| 665 |
# ============================================================
|
|
|
|
| 666 |
from fastapi import UploadFile, File
|
| 667 |
from fastapi.responses import JSONResponse
|
| 668 |
-
import io, os, numpy as np, torch
|
| 669 |
from PIL import Image
|
| 670 |
from monai.networks.nets import UNet
|
| 671 |
import torch.nn.functional as F
|
| 672 |
-
import pyvista
|
| 673 |
-
from pyvista import
|
| 674 |
-
import
|
| 675 |
|
| 676 |
HF_SPACE_URL = os.getenv("HF_SPACE_URL", "https://abbhy123ghh-x-ray-analysis.hf.space").rstrip("/")
|
| 677 |
|
|
@@ -695,9 +696,7 @@ async def generate_3d_monai(file: UploadFile = File(...)):
|
|
| 695 |
volume = np.expand_dims(volume, axis=0) # [1, D, H, W]
|
| 696 |
|
| 697 |
# --- Step 3: MONAI lightweight 3D UNet (shallow for CPU) ---
|
| 698 |
-
x = torch.tensor(volume, dtype=torch.float32).unsqueeze(0) # [1,1,D,H,W]
|
| 699 |
-
|
| 700 |
-
# pad to multiples of 16 for shape-safe inference
|
| 701 |
pad_d = (16 - x.shape[2] % 16) % 16
|
| 702 |
pad_h = (16 - x.shape[3] % 16) % 16
|
| 703 |
pad_w = (16 - x.shape[4] % 16) % 16
|
|
@@ -719,54 +718,39 @@ async def generate_3d_monai(file: UploadFile = File(...)):
|
|
| 719 |
seg = (seg - seg.min()) / (seg.max() - seg.min() + 1e-8)
|
| 720 |
seg = np.clip(seg * 255, 0, 255).astype(np.uint8)
|
| 721 |
|
| 722 |
-
# --- Step 4: Create
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
except Exception:
|
| 726 |
-
grid = vtk.vtkImageData()
|
| 727 |
-
|
| 728 |
-
dims = np.array(seg.shape) + 1
|
| 729 |
-
try:
|
| 730 |
-
grid.dimensions = dims
|
| 731 |
-
except Exception:
|
| 732 |
-
grid.SetDimensions(int(dims[2]), int(dims[1]), int(dims[0]))
|
| 733 |
-
|
| 734 |
grid.spacing = (1, 1, 1)
|
| 735 |
grid.origin = (0, 0, 0)
|
|
|
|
| 736 |
|
| 737 |
-
|
| 738 |
-
|
| 739 |
-
except Exception:
|
| 740 |
-
# fallback for vtk.vtkImageData
|
| 741 |
-
arr_flat = seg.flatten(order="F")
|
| 742 |
-
for i, val in enumerate(arr_flat):
|
| 743 |
-
grid.GetPointData().GetScalars().SetTuple1(i, int(val))
|
| 744 |
-
|
| 745 |
-
pv_grid = pyvista.wrap(grid)
|
| 746 |
-
|
| 747 |
-
# --- Step 5: Render rotating 3D volume (headless safe mode) ---
|
| 748 |
-
try:
|
| 749 |
-
start_xvfb() # enable virtual framebuffer if available
|
| 750 |
-
except OSError:
|
| 751 |
-
print("⚠️ Xvfb not available — running headless fallback mode.")
|
| 752 |
-
|
| 753 |
-
os.environ["PYVISTA_OFF_SCREEN"] = "true"
|
| 754 |
-
|
| 755 |
-
plotter = Plotter(off_screen=True)
|
| 756 |
plotter.add_volume(
|
| 757 |
-
|
| 758 |
cmap="bone",
|
| 759 |
opacity="sigmoid_5",
|
| 760 |
shade=True,
|
|
|
|
|
|
|
|
|
|
| 761 |
)
|
| 762 |
plotter.set_background("black")
|
| 763 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 764 |
|
| 765 |
frames = []
|
| 766 |
for angle in range(0, 360, 5):
|
| 767 |
plotter.camera_position = [
|
| 768 |
-
(seg.shape[2]
|
| 769 |
-
(seg.shape[2]
|
| 770 |
(0, 0, 1),
|
| 771 |
]
|
| 772 |
plotter.camera.azimuth = angle
|
|
@@ -774,14 +758,10 @@ async def generate_3d_monai(file: UploadFile = File(...)):
|
|
| 774 |
frames.append(img_frame)
|
| 775 |
plotter.close()
|
| 776 |
|
| 777 |
-
# --- Step 6: Save rotating video ---
|
| 778 |
os.makedirs("static", exist_ok=True)
|
| 779 |
out_path = "static/xray_monai_3d_fullbody.mp4"
|
| 780 |
-
|
| 781 |
-
writer = imageio.get_writer(out_path, fps=12, codec="libx264", quality=8)
|
| 782 |
-
for f in frames:
|
| 783 |
-
writer.append_data(f)
|
| 784 |
-
writer.close()
|
| 785 |
|
| 786 |
return JSONResponse({
|
| 787 |
"ok": True,
|
|
@@ -793,6 +773,7 @@ async def generate_3d_monai(file: UploadFile = File(...)):
|
|
| 793 |
import traceback; traceback.print_exc()
|
| 794 |
return JSONResponse({"error": str(e)}, status_code=500)
|
| 795 |
|
|
|
|
| 796 |
|
| 797 |
|
| 798 |
|
|
|
|
| 663 |
# ============================================================
|
| 664 |
# ✅ MONAI + PyVista 3D Full-Body Visualization (Final HF Safe)
|
| 665 |
# ============================================================
|
| 666 |
+
|
| 667 |
from fastapi import UploadFile, File
|
| 668 |
from fastapi.responses import JSONResponse
|
| 669 |
+
import io, os, numpy as np, torch
|
| 670 |
from PIL import Image
|
| 671 |
from monai.networks.nets import UNet
|
| 672 |
import torch.nn.functional as F
|
| 673 |
+
import pyvista as pv
|
| 674 |
+
from pyvista import start_xvfb
|
| 675 |
+
import imageio.v3 as iio
|
| 676 |
|
| 677 |
HF_SPACE_URL = os.getenv("HF_SPACE_URL", "https://abbhy123ghh-x-ray-analysis.hf.space").rstrip("/")
|
| 678 |
|
|
|
|
| 696 |
volume = np.expand_dims(volume, axis=0) # [1, D, H, W]
|
| 697 |
|
| 698 |
# --- Step 3: MONAI lightweight 3D UNet (shallow for CPU) ---
|
| 699 |
+
x = torch.tensor(volume, dtype=torch.float32).unsqueeze(0) # [1, 1, D, H, W]
|
|
|
|
|
|
|
| 700 |
pad_d = (16 - x.shape[2] % 16) % 16
|
| 701 |
pad_h = (16 - x.shape[3] % 16) % 16
|
| 702 |
pad_w = (16 - x.shape[4] % 16) % 16
|
|
|
|
| 718 |
seg = (seg - seg.min()) / (seg.max() - seg.min() + 1e-8)
|
| 719 |
seg = np.clip(seg * 255, 0, 255).astype(np.uint8)
|
| 720 |
|
| 721 |
+
# --- Step 4: Create PyVista grid (Hugging Face compatible) ---
|
| 722 |
+
start_xvfb()
|
| 723 |
+
grid = pv.ImageData(dimensions=seg.shape)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 724 |
grid.spacing = (1, 1, 1)
|
| 725 |
grid.origin = (0, 0, 0)
|
| 726 |
+
grid.point_data["values"] = seg.flatten(order="F")
|
| 727 |
|
| 728 |
+
# --- Step 5: Enhanced 3D visualization with lighting ---
|
| 729 |
+
plotter = pv.Plotter(off_screen=True, window_size=[512, 512])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 730 |
plotter.add_volume(
|
| 731 |
+
grid,
|
| 732 |
cmap="bone",
|
| 733 |
opacity="sigmoid_5",
|
| 734 |
shade=True,
|
| 735 |
+
diffuse=1.0,
|
| 736 |
+
specular=0.3,
|
| 737 |
+
specular_power=15,
|
| 738 |
)
|
| 739 |
plotter.set_background("black")
|
| 740 |
+
|
| 741 |
+
# Add realistic light
|
| 742 |
+
light = pv.Light(
|
| 743 |
+
position=(seg.shape[2]*2, seg.shape[1]*2, seg.shape[0]),
|
| 744 |
+
color='white',
|
| 745 |
+
intensity=1.5,
|
| 746 |
+
)
|
| 747 |
+
plotter.add_light(light)
|
| 748 |
|
| 749 |
frames = []
|
| 750 |
for angle in range(0, 360, 5):
|
| 751 |
plotter.camera_position = [
|
| 752 |
+
(seg.shape[2]*2, 0, seg.shape[0]/2),
|
| 753 |
+
(seg.shape[2]/2, seg.shape[1]/2, seg.shape[0]/2),
|
| 754 |
(0, 0, 1),
|
| 755 |
]
|
| 756 |
plotter.camera.azimuth = angle
|
|
|
|
| 758 |
frames.append(img_frame)
|
| 759 |
plotter.close()
|
| 760 |
|
| 761 |
+
# --- Step 6: Save rotating MP4 video ---
|
| 762 |
os.makedirs("static", exist_ok=True)
|
| 763 |
out_path = "static/xray_monai_3d_fullbody.mp4"
|
| 764 |
+
iio.imwrite(out_path, frames, fps=12, codec="libx264")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 765 |
|
| 766 |
return JSONResponse({
|
| 767 |
"ok": True,
|
|
|
|
| 773 |
import traceback; traceback.print_exc()
|
| 774 |
return JSONResponse({"error": str(e)}, status_code=500)
|
| 775 |
|
| 776 |
+
|
| 777 |
|
| 778 |
|
| 779 |
|