File size: 5,565 Bytes
c712a4b
 
 
ac333a5
127d69e
ac333a5
 
 
 
c712a4b
96d9346
 
 
 
 
 
 
 
 
 
 
 
cafb0db
 
96d9346
127d69e
 
 
96d9346
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cafb0db
 
 
 
 
 
96d9346
 
 
 
b3e0730
 
96d9346
 
 
3ed2114
b3e0730
 
 
96d9346
 
 
 
 
 
 
2dc49d3
96d9346
5871972
96d9346
 
 
 
 
 
 
 
 
 
 
127d69e
 
 
 
 
44d5b58
127d69e
 
 
c712a4b
127d69e
 
c712a4b
127d69e
 
 
44d5b58
127d69e
 
44d5b58
127d69e
c712a4b
127d69e
 
 
c712a4b
127d69e
ac333a5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
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