Skip to content

Commit fe2dbcd

Browse files
eurunuelaclaude
andauthored
fix: Fix inline build, BIDS server loading, and bump version to 2.1.6 (#121)
gulpfile.js: - Add rootpath: "build" to gulp-inline-source so absolute /static/ paths resolve to build/static/ correctly. Previously the default rootpath (process.cwd()) caused inlining to silently fail, leaving external <script>/<link> tags in the released index.html. When open_rica_report.py serves Rica at /rica/index.html those absolute paths 404 since /static/ doesn't exist at the server root. src/PopUps/IntroPopUp.js: - Fix server loading for BIDS-named tedana output: - report.txt: use endsWith instead of exact match - tedana log: support both tedana_20* prefix and _tedana_log.tsv suffix - CrossComponent_metrics.json: exclude PCA variants - Parallelize server file loading with Promise.all for faster load - Range request: only accept 206 Partial Content to avoid hanging Promise.all when the CRA proxy doesn't forward Range headers (proxy would return 200 + full NIfTI, blocking on arrayBuffer()) - Skip NIfTI buffer download in server mode; BrainViewer already prefers URL over buffer so the download was redundant and risky scripts/rica_server.py: - Use ThreadingHTTPServer to handle concurrent requests without blocking package.json: - Add proxy to localhost:8000 for npm start dev workflow - Bump version 2.1.5 -> 2.1.6 so open_rica_report.py detects update Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 20fd67f commit fe2dbcd

4 files changed

Lines changed: 49 additions & 26 deletions

File tree

gulpfile.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ function inlineAssets() {
1010
inlinesource({
1111
compress: false,
1212
ignore: ["png"],
13+
rootpath: "build",
1314
})
1415
)
1516
.pipe(dest("./build"));

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
{
22
"name": "rica",
3-
"version": "2.1.5",
3+
"version": "2.1.6",
44
"private": true,
5+
"proxy": "http://localhost:8000",
56
"engines": {
67
"node": ">=18.0.0"
78
},

scripts/rica_server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ def main():
167167

168168
# Start server
169169
try:
170-
with http.server.HTTPServer(("", args.port), RicaHandler) as httpd:
170+
with http.server.ThreadingHTTPServer(("", args.port), RicaHandler) as httpd:
171171
url = f"http://localhost:{args.port}"
172172
print(f"Rica server running at {url}")
173173
print(f"Serving files from: {cwd}")

src/PopUps/IntroPopUp.js

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -141,14 +141,14 @@ function IntroPopup({ onDataLoad, onLoadingStart, closePopup, isLoading, isDark
141141
(f) =>
142142
f.includes("comp_") ||
143143
f.includes(".svg") ||
144-
f === "report.txt" ||
144+
f.endsWith("report.txt") ||
145145
(f.includes("_metrics.tsv") && !f.toLowerCase().includes("pca")) ||
146-
(f.startsWith("tedana_20") && f.endsWith(".tsv")) ||
146+
(f.startsWith("tedana_20") || f.endsWith("_tedana_log.tsv")) ||
147147
(f.includes("_mixing.tsv") && !f.toLowerCase().includes("pca") && !f.toLowerCase().includes("orth")) ||
148148
(f.includes("_components.nii.gz") && f.toLowerCase().includes("ica") && !f.includes("stat-z") && !f.includes("echo-")) ||
149149
f === "betas_OC.nii.gz" ||
150150
f.includes("_mask.nii") ||
151-
f.includes("CrossComponent_metrics.json") ||
151+
(f.includes("CrossComponent_metrics.json") && !f.toLowerCase().includes("pca")) ||
152152
(f.includes("cross_component_metrics.json") && !f.toLowerCase().includes("pca")) ||
153153
f === "manual_classification.tsv" ||
154154
// QC NIfTI files
@@ -190,8 +190,9 @@ function IntroPopup({ onDataLoad, onLoadingStart, closePopup, isLoading, isDark
190190
// Repetition time from registry
191191
let repetitionTime = null;
192192

193-
// Process files via HTTP fetch
194-
for (const filepath of relevantFiles) {
193+
// Process files via HTTP fetch (parallel)
194+
try {
195+
const filePromises = relevantFiles.map(async (filepath) => {
195196
const filename = filepath.split("/").pop();
196197

197198
try {
@@ -221,7 +222,7 @@ function IntroPopup({ onDataLoad, onLoadingStart, closePopup, isLoading, isDark
221222
}
222223

223224
// Report info
224-
if (filename === "report.txt") {
225+
if (filename.endsWith("report.txt")) {
225226
const response = await fetch(`/${filepath}`);
226227
info = await response.text();
227228
setLoadingProgress((prev) => ({ ...prev, current: prev.current + 1 }));
@@ -243,7 +244,7 @@ function IntroPopup({ onDataLoad, onLoadingStart, closePopup, isLoading, isDark
243244
}
244245

245246
// Dataset path
246-
if (filename.startsWith("tedana_20") && filename.endsWith(".tsv")) {
247+
if (filename.startsWith("tedana_20") || filename.endsWith("_tedana_log.tsv")) {
247248
const response = await fetch(`/${filepath}`);
248249
const text = await response.text();
249250
const lines = text.split("\n");
@@ -271,12 +272,14 @@ function IntroPopup({ onDataLoad, onLoadingStart, closePopup, isLoading, isDark
271272
if ((filename.includes("_components.nii.gz") && filename.toLowerCase().includes("ica") && !filename.includes("stat-z") && !filename.includes("echo-")) || filename === "betas_OC.nii.gz") {
272273
// Extract TR from NIfTI header using a Range request (first 4KB is enough to
273274
// decompress the header from gzip), independent of loading the full file.
275+
// Only trust 206 Partial Content — if the proxy doesn't forward Range headers,
276+
// the server returns 200 with the full file, which would hang on arrayBuffer().
274277
if (!repetitionTime) {
275278
try {
276279
const headerResponse = await fetch(`/${filepath}`, {
277280
headers: { Range: "bytes=0-4095" },
278281
});
279-
if (headerResponse.ok || headerResponse.status === 206) {
282+
if (headerResponse.status === 206) {
280283
const headerBuffer = await headerResponse.arrayBuffer();
281284
const tr = await extractTRFromNifti(headerBuffer);
282285
if (tr) {
@@ -288,15 +291,9 @@ function IntroPopup({ onDataLoad, onLoadingStart, closePopup, isLoading, isDark
288291
// Range requests not supported; TR won't be extracted from header
289292
}
290293
}
291-
// Always set URL so BrainViewer can load even if buffer fails
294+
// Use URL — BrainViewer prefers URL over buffer anyway, and skipping the
295+
// full download avoids hanging Promise.all on large files through the proxy.
292296
niftiUrl = `/${filepath}`;
293-
// Also try loading the full buffer (fails gracefully for very large files)
294-
try {
295-
const response = await fetch(`/${filepath}`);
296-
niftiBuffer = await response.arrayBuffer();
297-
} catch {
298-
console.warn("[Rica] NIfTI too large for ArrayBuffer, Niivue will load from URL");
299-
}
300297
setLoadingProgress((prev) => ({ ...prev, current: prev.current + 1 }));
301298
}
302299

@@ -308,7 +305,7 @@ function IntroPopup({ onDataLoad, onLoadingStart, closePopup, isLoading, isDark
308305
}
309306

310307
// Cross-component metrics (for elbow thresholds)
311-
if (filename.includes("CrossComponent_metrics.json") ||
308+
if ((filename.includes("CrossComponent_metrics.json") && !filename.toLowerCase().includes("pca")) ||
312309
(filename.includes("cross_component_metrics.json") && !filename.toLowerCase().includes("pca"))) {
313310
const response = await fetch(`/${filepath}`);
314311
crossComponentMetrics = await response.json();
@@ -385,7 +382,8 @@ function IntroPopup({ onDataLoad, onLoadingStart, closePopup, isLoading, isDark
385382
} catch (error) {
386383
console.error(`Error fetching file ${filepath}:`, error);
387384
}
388-
}
385+
});
386+
await Promise.all(filePromises);
389387

390388
// Sort component figures by name
391389
compFigures.sort((a, b) => a.name.localeCompare(b.name));
@@ -419,6 +417,29 @@ function IntroPopup({ onDataLoad, onLoadingStart, closePopup, isLoading, isDark
419417
statusTableData,
420418
repetitionTime,
421419
});
420+
} catch (err) {
421+
console.error("[Rica] loadFromServer failed:", err);
422+
onDataLoad({
423+
componentFigures: compFigures,
424+
carpetFigures,
425+
diagnosticFigures,
426+
components: [components],
427+
info,
428+
originalData: [originalData],
429+
dirPath,
430+
mixingMatrix,
431+
niftiBuffer,
432+
niftiUrl,
433+
maskBuffer,
434+
crossComponentMetrics,
435+
qcNiftiBuffers,
436+
externalRegressorsFigure,
437+
hasManualClassifications: manualClassificationData && manualClassificationData.length > 0,
438+
decisionTreeData,
439+
statusTableData,
440+
repetitionTime,
441+
});
442+
}
422443
},
423444
[onDataLoad, onLoadingStart]
424445
);
@@ -457,15 +478,15 @@ function IntroPopup({ onDataLoad, onLoadingStart, closePopup, isLoading, isDark
457478
(f) =>
458479
f.name.includes("comp_") ||
459480
f.name.includes(".svg") ||
460-
f.name === "report.txt" ||
481+
f.name.endsWith("report.txt") ||
461482
(f.name.includes("_metrics.tsv") && !f.name.toLowerCase().includes("pca")) ||
462-
(f.name.startsWith("tedana_20") && f.name.endsWith(".tsv")) ||
483+
(f.name.startsWith("tedana_20") || f.name.endsWith("_tedana_log.tsv")) ||
463484
// New files for Niivue integration
464485
(f.name.includes("_mixing.tsv") && !f.name.toLowerCase().includes("pca") && !f.name.toLowerCase().includes("orth")) ||
465486
(f.name.includes("_components.nii.gz") && f.name.toLowerCase().includes("ica") && !f.name.includes("stat-z") && !f.name.includes("echo-")) ||
466487
f.name === "betas_OC.nii.gz" ||
467488
f.name.includes("_mask.nii") ||
468-
f.name.includes("CrossComponent_metrics.json") ||
489+
(f.name.includes("CrossComponent_metrics.json") && !f.name.toLowerCase().includes("pca")) ||
469490
(f.name.includes("cross_component_metrics.json") && !f.name.toLowerCase().includes("pca")) ||
470491
f.name === "manual_classification.tsv" ||
471492
// QC NIfTI files
@@ -534,7 +555,7 @@ function IntroPopup({ onDataLoad, onLoadingStart, closePopup, isLoading, isDark
534555
}
535556

536557
// Report info
537-
if (filename === "report.txt") {
558+
if (filename.endsWith("report.txt")) {
538559
info = await readFileAsText(file);
539560
setLoadingProgress((prev) => ({ ...prev, current: prev.current + 1 }));
540561
}
@@ -554,7 +575,7 @@ function IntroPopup({ onDataLoad, onLoadingStart, closePopup, isLoading, isDark
554575
}
555576

556577
// Dataset path
557-
if (filename.startsWith("tedana_20") && filename.endsWith(".tsv")) {
578+
if (filename.startsWith("tedana_20") || filename.endsWith("_tedana_log.tsv")) {
558579
const text = await readFileAsText(file);
559580
// Look for the line containing "Using output directory:"
560581
const lines = text.split("\n");
@@ -607,7 +628,7 @@ function IntroPopup({ onDataLoad, onLoadingStart, closePopup, isLoading, isDark
607628
}
608629

609630
// Cross-component metrics (for elbow thresholds)
610-
if (filename.includes("CrossComponent_metrics.json") ||
631+
if ((filename.includes("CrossComponent_metrics.json") && !filename.toLowerCase().includes("pca")) ||
611632
(filename.includes("cross_component_metrics.json") && !filename.toLowerCase().includes("pca"))) {
612633
const text = await readFileAsText(file);
613634
crossComponentMetrics = JSON.parse(text);

0 commit comments

Comments
 (0)