Download utils.py from Neemah/BrainScan-Auto-Report-Generator: direct link, hf CLI and curl.
- Browser
- Download file 5.57 kB
-
https://huggingface.co/spaces/Neemah/BrainScan-Auto-Report-Generator/resolve/main/utils.py
- Command line
-
hf download hf://spaces/Neemah/BrainScan-Auto-Report-Generator/utils.py
-
curl -L -o utils.py https://huggingface.co/spaces/Neemah/BrainScan-Auto-Report-Generator/resolve/main/utils.py
5.57 kB
| import pydicom | |
| import numpy as np | |
| from PIL import Image | |
| import time | |
| import uuid | |
| from datetime import datetime | |
| from reportlab.pdfgen import canvas | |
| from reportlab.lib.pagesizes import letter | |
| from reportlab.lib.units import inch | |
| import os | |
| import zipfile | |
| import subprocess | |
| import nibabel as nib | |
| def convert_dicom_zip_to_nifti(zip_file): | |
| """ | |
| Takes a uploaded ZIP file containing DICOM files. | |
| Runs dcm2niix to convert to NIfTI. | |
| Returns list of (sequence_name, PIL Image) tuples. | |
| """ | |
| SKIP_KEYWORDS = ["localizer", "scout", "loc"] | |
| # Step 1: Setup temp directories | |
| unique_id = uuid.uuid4().hex | |
| tmp_dir = f"/tmp/dicom_{unique_id}" | |
| nifti_dir = f"/tmp/nifti_{unique_id}" | |
| os.makedirs(tmp_dir, exist_ok=True) | |
| os.makedirs(nifti_dir, exist_ok=True) | |
| # Step 2: Unzip the uploaded file | |
| with zipfile.ZipFile(zip_file.name, 'r') as z: | |
| z.extractall(tmp_dir) | |
| # Step 3: Run dcm2niix on the extracted folder | |
| result = subprocess.run([ | |
| "dcm2niix", | |
| "-o", nifti_dir, # output directory | |
| "-z", "y", # compress output (.nii.gz) | |
| "-f", "%p_%s", # filename = protocol name + series number | |
| tmp_dir # input directory | |
| ], capture_output=True, text=True) | |
| print("dcm2niix output:", result.stdout) | |
| print("dcm2niix errors:", result.stderr) | |
| # Step 4: Read each NIfTI and extract middle slice | |
| sequence_images = [] | |
| nifti_files = [f for f in os.listdir(nifti_dir) if f.endswith(".nii.gz")] | |
| if not nifti_files: | |
| print("No NIfTI files generated — check DICOM folder structure") | |
| return [] | |
| for nifti_file in sorted(nifti_files): | |
| sequence_name = nifti_file.replace(".nii.gz", "") | |
| # Skip localizer images | |
| if any(skip in sequence_name.lower() for skip in SKIP_KEYWORDS): | |
| print(f"Skipping localizer: {sequence_name}") | |
| continue | |
| nifti_path = os.path.join(nifti_dir, nifti_file) | |
| try: | |
| # Load the 3D volume | |
| img = nib.as_closest_canonical(nifti_path) | |
| volume = img.get_fdata() | |
| # Extract middle axial slice | |
| mid = volume.shape[2] // 2 | |
| slice_2d = volume[:, :, mid] | |
| # Rotate to correct display orientation | |
| slice_2d = np.rot90(slice_2d) | |
| # Normalize to 0-255 | |
| s_min, s_max = slice_2d.min(), slice_2d.max() | |
| if s_max - s_min == 0: | |
| continue | |
| normalized = (slice_2d - s_min) / (s_max - s_min) * 255 | |
| image = Image.fromarray( | |
| normalized.astype(np.uint8) | |
| ).convert("RGB") | |
| sequence_images.append((sequence_name, image)) | |
| print(f"Loaded sequence: {sequence_name}") | |
| except Exception as e: | |
| print(f"Could not load {nifti_file}: {e}") | |
| continue | |
| return sequence_images | |
| # def load_dicoms(filepaths): | |
| # """ | |
| # Accepts a list of DICOM file objects. | |
| # Returns a list of PIL Images, one per sequence. | |
| # """ | |
| # images=[] | |
| # for file in filepaths: | |
| # dicom = pydicom.dcmread(file.name) | |
| # # Extract the pixel array | |
| # pixel_array = dicom.pixel_array.astype(float) | |
| # # Normalize to 0-255 range | |
| # pixel_min = pixel_array.min() | |
| # pixel_max = pixel_array.max() | |
| # if pixel_max - pixel_min == 0: | |
| # continue #to handle sequences not added | |
| # normalized = (pixel_array - pixel_min) / (pixel_max - pixel_min) * 255 | |
| # # Convert to uint8 RGB image | |
| # image = Image.fromarray(normalized.astype(np.uint8)).convert("RGB") | |
| # images.append(image) | |
| # return images | |
| def generate_pdf(report_text): | |
| """ | |
| Takes report text, returns path to a saved PDF file. | |
| """ | |
| if not report_text or not report_text.strip(): | |
| return None | |
| filename = f"brain_mri_report_{int(time.time())}.pdf" | |
| path = f"/tmp/{filename}" | |
| c = canvas.Canvas(path, pagesize=letter) | |
| width, height = letter | |
| left_margin = 0.75 * inch | |
| top_margin = height - 0.75 * inch | |
| line_height = 16 | |
| max_width = width - 2 * left_margin | |
| # Title | |
| c.setFont("Helvetica-Bold", 14) | |
| c.drawString(left_margin, top_margin, "Brain MRI Radiology Report") | |
| # Date | |
| c.setFont("Helvetica", 9) | |
| c.drawString(left_margin, top_margin - 16, f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}") | |
| # Divider line | |
| c.line(left_margin, top_margin - 24, width - left_margin, top_margin - 24) | |
| y = top_margin - 0.55 * inch | |
| # Write report body line by line | |
| c.setFont("Helvetica", 11) | |
| for paragraph in report_text.split("\n"): | |
| words = paragraph.split(" ") | |
| line = "" | |
| for word in words: | |
| test = (line + " " + word).strip() | |
| if c.stringWidth(test, "Helvetica", 11) <= max_width: | |
| line = test | |
| else: | |
| if y < 0.75 * inch: # new page if near bottom | |
| c.showPage() | |
| c.setFont("Helvetica", 11) | |
| y = height - 0.75 * inch | |
| c.drawString(left_margin, y, line) | |
| y -= line_height | |
| line = word | |
| # Draw remaining line | |
| if y < 0.75 * inch: | |
| c.showPage() | |
| c.setFont("Helvetica", 11) | |
| y = height - 0.75 * inch | |
| c.drawString(left_margin, y, line) | |
| y -= line_height | |
| c.save() | |
| return path |