Skip to content

Commit b0ccbc6

Browse files
committed
added SimpleBeforeAfterRPT image view for T1w
1 parent 99b13c0 commit b0ccbc6

File tree

3 files changed

+277
-9
lines changed

3 files changed

+277
-9
lines changed

petdeface/qa.py

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,218 @@
1616
from functools import partial
1717
import seaborn as sns
1818
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}")
19231

20232

21233
def create_overlay_comparison(orig_path, defaced_path, subject_id, output_dir):
@@ -932,6 +1144,9 @@ def main():
9321144
print(f" - {s['id']}")
9331145
exit(1)
9341146

1147+
# create nireports svg's for comparison
1148+
generate_simple_before_and_after(subjects=subjects, output_dir=output_dir)
1149+
9351150
# Set number of jobs for parallel processing
9361151
n_jobs = args.n_jobs if args.n_jobs else mp.cpu_count()
9371152
print(f"Using {n_jobs} parallel processes")
@@ -1010,6 +1225,7 @@ def main():
10101225
10111226
<a href="side_by_side.html" class="link-button">Side by Side View</a>
10121227
<a href="animated.html" class="link-button">Animated GIF View</a>
1228+
<a href="SimpleBeforeAfterRPT.html" class="link-button">SVG Reports View</a>
10131229
10141230
<p style="margin-top: 30px; color: #999; font-size: 14px;">
10151231
Generated with {len(subjects)} subjects

poetry.lock

Lines changed: 51 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ petutils = "^0.0.1"
2121
niworkflows = "^1.11.0"
2222
niftifixer = {git = "https://github.com/openneuropet/nifti_fixer.git"}
2323
bids-validator-deno = "^2.0.5"
24+
nipreps = "^1.0"
25+
nireports = "^25.2.0"
26+
nibabel = "^5.3.2"
27+
nilearn = "^0.10.4"
28+
matplotlib = "^3.9.2"
29+
numpy = "^2.1.3"
30+
scipy = "^1.14.1"
31+
seaborn = "^0.13.2"
32+
pillow = "^11.0.0"
33+
imageio = "^2.36.0"
2434

2535

2636
[tool.poetry.group.dev.dependencies]

0 commit comments

Comments
 (0)