|
16 | 16 | from functools import partial |
17 | 17 | import seaborn as sns |
18 | 18 | from PIL import Image, ImageDraw |
| 19 | +from nipype import Workflow, Node |
| 20 | +from nireports.interfaces.reporting.base import SimpleBeforeAfterRPT |
| 21 | +from tempfile import TemporaryDirectory |
| 22 | +from pathlib import Path |
| 23 | + |
| 24 | + |
| 25 | +def generate_simple_before_and_after(subjects: dict, output_dir): |
| 26 | + if not output_dir: |
| 27 | + output_dir = TemporaryDirectory() |
| 28 | + wf = Workflow( |
| 29 | + name="simple_before_after_report", base_dir=Path(output_dir) / "images/" |
| 30 | + ) |
| 31 | + |
| 32 | + # Create a list to store all nodes |
| 33 | + nodes = [] |
| 34 | + |
| 35 | + for s in subjects: |
| 36 | + # only run this on the T1w images for now |
| 37 | + if "T1w" in s["orig_path"]: |
| 38 | + o_path = Path(s["orig_path"]) |
| 39 | + # Create a valid node name by replacing invalid characters |
| 40 | + valid_name = f"before_after_{s['id'].replace('-', '_').replace('_', '')}" |
| 41 | + node = Node( |
| 42 | + SimpleBeforeAfterRPT( |
| 43 | + before=s["orig_path"], |
| 44 | + after=s["defaced_path"], |
| 45 | + before_label="Original", |
| 46 | + after_label="Defaced", |
| 47 | + out_report=f"{s['id']}_simple_before_after.svg", |
| 48 | + ), |
| 49 | + name=valid_name, |
| 50 | + ) |
| 51 | + nodes.append(node) |
| 52 | + |
| 53 | + # Add all nodes to the workflow |
| 54 | + wf.add_nodes(nodes) |
| 55 | + wf.run(plugin="MultiProc", plugin_args={"n_procs": mp.cpu_count()}) |
| 56 | + |
| 57 | + # Collect SVG files and move them to images folder |
| 58 | + collect_svg_reports(wf, output_dir) |
| 59 | + |
| 60 | + |
| 61 | +def collect_svg_reports(wf, output_dir): |
| 62 | + """Collect SVG reports from workflow and move them to images folder.""" |
| 63 | + import glob |
| 64 | + |
| 65 | + # Find all SVG files in the workflow directory |
| 66 | + workflow_dir = wf.base_dir |
| 67 | + svg_files = glob.glob(os.path.join(workflow_dir, "**", "*.svg"), recursive=True) |
| 68 | + |
| 69 | + print(f"Found {len(svg_files)} SVG reports") |
| 70 | + |
| 71 | + # Move each SVG to the images folder |
| 72 | + for svg_file in svg_files: |
| 73 | + filename = os.path.basename(svg_file) |
| 74 | + dest_path = os.path.join(output_dir, "images", filename) |
| 75 | + shutil.move(svg_file, dest_path) |
| 76 | + print(f" Moved: {filename}") |
| 77 | + |
| 78 | + # Create HTML page for SVG reports |
| 79 | + create_svg_index_html(svg_files, output_dir) |
| 80 | + |
| 81 | + |
| 82 | +def create_svg_index_html(svg_files, output_dir): |
| 83 | + """Create HTML index page for SVG reports.""" |
| 84 | + |
| 85 | + svg_entries = "" |
| 86 | + for svg_file in svg_files: |
| 87 | + filename = os.path.basename(svg_file) |
| 88 | + subject_id = filename.replace("_simple_before_after.svg", "") |
| 89 | + |
| 90 | + svg_entries += f""" |
| 91 | + <div class="svg-report"> |
| 92 | + <h3>{subject_id}</h3> |
| 93 | + <object data="images/{filename}" type="image/svg+xml" style="width: 100%; height: 600px;"> |
| 94 | + <p>Your browser does not support SVG. <a href="images/{filename}">Download SVG</a></p> |
| 95 | + </object> |
| 96 | + </div> |
| 97 | + """ |
| 98 | + |
| 99 | + html_content = f""" |
| 100 | + <!DOCTYPE html> |
| 101 | + <html> |
| 102 | + <head> |
| 103 | + <meta charset="utf-8"> |
| 104 | + <title>PET Deface SVG Reports</title> |
| 105 | + <style> |
| 106 | + body {{ |
| 107 | + font-family: Arial, sans-serif; |
| 108 | + margin: 20px; |
| 109 | + background: #f5f5f5; |
| 110 | + }} |
| 111 | + .header {{ |
| 112 | + text-align: center; |
| 113 | + margin-bottom: 30px; |
| 114 | + color: #333; |
| 115 | + }} |
| 116 | + .svg-report {{ |
| 117 | + background: white; |
| 118 | + margin-bottom: 40px; |
| 119 | + padding: 20px; |
| 120 | + border-radius: 10px; |
| 121 | + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| 122 | + }} |
| 123 | + .svg-report h3 {{ |
| 124 | + color: #2c3e50; |
| 125 | + margin-top: 0; |
| 126 | + margin-bottom: 15px; |
| 127 | + text-align: center; |
| 128 | + }} |
| 129 | + .navigation {{ |
| 130 | + position: fixed; |
| 131 | + top: 20px; |
| 132 | + right: 20px; |
| 133 | + background: white; |
| 134 | + padding: 15px; |
| 135 | + border-radius: 10px; |
| 136 | + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); |
| 137 | + z-index: 1000; |
| 138 | + }} |
| 139 | + .nav-button {{ |
| 140 | + display: block; |
| 141 | + margin: 5px 0; |
| 142 | + padding: 8px 12px; |
| 143 | + background: #3498db; |
| 144 | + color: white; |
| 145 | + border: none; |
| 146 | + border-radius: 5px; |
| 147 | + cursor: pointer; |
| 148 | + font-size: 12px; |
| 149 | + text-decoration: none; |
| 150 | + }} |
| 151 | + .nav-button:hover {{ |
| 152 | + background: #2980b9; |
| 153 | + }} |
| 154 | + </style> |
| 155 | + <script> |
| 156 | + function scrollToReport(direction) {{ |
| 157 | + const reports = document.querySelectorAll('.svg-report'); |
| 158 | + const currentScroll = window.pageYOffset; |
| 159 | + const windowHeight = window.innerHeight; |
| 160 | + let targetReport = null; |
| 161 | + |
| 162 | + if (direction === 'next') {{ |
| 163 | + for (let report of reports) {{ |
| 164 | + if (report.offsetTop > currentScroll + windowHeight * 0.3) {{ |
| 165 | + targetReport = report; |
| 166 | + break; |
| 167 | + }} |
| 168 | + }} |
| 169 | + if (!targetReport && reports.length > 0) {{ |
| 170 | + targetReport = reports[0]; |
| 171 | + }} |
| 172 | + }} else if (direction === 'prev') {{ |
| 173 | + for (let i = reports.length - 1; i >= 0; i--) {{ |
| 174 | + if (reports[i].offsetTop < currentScroll - windowHeight * 0.3) {{ |
| 175 | + targetReport = reports[i]; |
| 176 | + break; |
| 177 | + }} |
| 178 | + }} |
| 179 | + if (!targetReport && reports.length > 0) {{ |
| 180 | + targetReport = reports[reports.length - 1]; |
| 181 | + }} |
| 182 | + }} |
| 183 | + |
| 184 | + if (targetReport) {{ |
| 185 | + targetReport.scrollIntoView({{ behavior: 'smooth', block: 'center' }}); |
| 186 | + }} |
| 187 | + }} |
| 188 | + |
| 189 | + // Keyboard navigation |
| 190 | + document.addEventListener('keydown', function(e) {{ |
| 191 | + if (e.key === 'ArrowDown' || e.key === ' ') {{ |
| 192 | + e.preventDefault(); |
| 193 | + scrollToReport('next'); |
| 194 | + }} else if (e.key === 'ArrowUp') {{ |
| 195 | + e.preventDefault(); |
| 196 | + scrollToReport('prev'); |
| 197 | + }} |
| 198 | + }}); |
| 199 | + </script> |
| 200 | + </head> |
| 201 | + <body> |
| 202 | + <div class="navigation"> |
| 203 | + <button class="nav-button" onclick="scrollToReport('prev')">↑ Previous</button> |
| 204 | + <button class="nav-button" onclick="scrollToReport('next')">↓ Next</button> |
| 205 | + <div style="margin-top: 10px; font-size: 10px; color: #666;"> |
| 206 | + Use arrow keys or spacebar |
| 207 | + </div> |
| 208 | + </div> |
| 209 | + |
| 210 | + <div class="header"> |
| 211 | + <h1>PET Deface SVG Reports</h1> |
| 212 | + <p>Before/After comparison reports using nireports</p> |
| 213 | + </div> |
| 214 | + |
| 215 | + <div class="reports-container"> |
| 216 | + {svg_entries} |
| 217 | + </div> |
| 218 | + |
| 219 | + <div style="text-align: center; margin-top: 30px;"> |
| 220 | + <a href="index.html">← Back to Index</a> |
| 221 | + </div> |
| 222 | + </body> |
| 223 | + </html> |
| 224 | + """ |
| 225 | + |
| 226 | + svg_index_file = os.path.join(output_dir, "SimpleBeforeAfterRPT.html") |
| 227 | + with open(svg_index_file, "w") as f: |
| 228 | + f.write(html_content) |
| 229 | + |
| 230 | + print(f"Created SVG reports index: {svg_index_file}") |
19 | 231 |
|
20 | 232 |
|
21 | 233 | def create_overlay_comparison(orig_path, defaced_path, subject_id, output_dir): |
@@ -932,6 +1144,9 @@ def main(): |
932 | 1144 | print(f" - {s['id']}") |
933 | 1145 | exit(1) |
934 | 1146 |
|
| 1147 | + # create nireports svg's for comparison |
| 1148 | + generate_simple_before_and_after(subjects=subjects, output_dir=output_dir) |
| 1149 | + |
935 | 1150 | # Set number of jobs for parallel processing |
936 | 1151 | n_jobs = args.n_jobs if args.n_jobs else mp.cpu_count() |
937 | 1152 | print(f"Using {n_jobs} parallel processes") |
@@ -1010,6 +1225,7 @@ def main(): |
1010 | 1225 | |
1011 | 1226 | <a href="side_by_side.html" class="link-button">Side by Side View</a> |
1012 | 1227 | <a href="animated.html" class="link-button">Animated GIF View</a> |
| 1228 | + <a href="SimpleBeforeAfterRPT.html" class="link-button">SVG Reports View</a> |
1013 | 1229 | |
1014 | 1230 | <p style="margin-top: 30px; color: #999; font-size: 14px;"> |
1015 | 1231 | Generated with {len(subjects)} subjects |
|
0 commit comments