Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Time alignment #762

Merged
merged 47 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
b68a94a
Provide better alignment visualization with mocked editing inputs
garrettmflynn Apr 5, 2024
d573658
Show options appropriately for SpikeGLX and Phy
garrettmflynn Apr 5, 2024
c8684b1
Provide add dummy recording button
garrettmflynn Apr 8, 2024
f02aa78
Update based on GUIDE meeting
garrettmflynn Apr 11, 2024
24504be
Update styling
garrettmflynn Apr 11, 2024
ed5c024
Merge branch 'main' into time-alignment-ui
garrettmflynn Apr 18, 2024
59edb85
Actually update time alignment
garrettmflynn Apr 22, 2024
1e687d8
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 22, 2024
0fa41e2
Merge branch 'main' into time-alignment-ui
CodyCBakerPhD Apr 23, 2024
fd0854c
Add MockRecordingInterface and print errors
garrettmflynn Apr 24, 2024
11618d9
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 24, 2024
2307a4f
Update labeling
garrettmflynn Apr 24, 2024
381c5a6
Merge branch 'time-alignment-ui' of https://github.com/NeurodataWitho…
garrettmflynn Apr 24, 2024
316b8c4
Only show compatible recording interfaces
garrettmflynn Apr 24, 2024
cf572f1
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 24, 2024
72947ca
Trigger an updated conversion
garrettmflynn Apr 24, 2024
6586929
Merge branch 'time-alignment-ui' of https://github.com/NeurodataWitho…
garrettmflynn Apr 24, 2024
2a410cf
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 24, 2024
ae195d8
Merge branch 'main' into time-alignment-ui
CodyCBakerPhD May 15, 2024
e787bd6
Merge branch 'main' into time-alignment-ui
CodyCBakerPhD May 15, 2024
34c09fe
Merge branch 'main' into time-alignment-ui
CodyCBakerPhD May 20, 2024
2bea2ba
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 20, 2024
c488ec7
Update manage_neuroconv.py
garrettmflynn May 20, 2024
0fde1a5
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 20, 2024
6d83739
Merge branch 'main' into time-alignment-ui
garrettmflynn May 20, 2024
26e0491
Update GuidedSourceData.js
garrettmflynn May 29, 2024
e6c7b2f
Merge branch 'main' into time-alignment-ui
garrettmflynn May 29, 2024
03b1d2c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 29, 2024
acce079
Move alignment page
garrettmflynn May 29, 2024
63a8d08
Merge branch 'time-alignment-ui' of https://github.com/NeurodataWitho…
garrettmflynn May 29, 2024
b9e5553
Update GuidedSourceData.js
garrettmflynn May 29, 2024
7c2cae1
Fix imports and extraneous logs
garrettmflynn May 29, 2024
c6dcf86
Merge branch 'main' into time-alignment-ui
CodyCBakerPhD May 30, 2024
b979b1f
Merge branch 'main' into time-alignment-ui
CodyCBakerPhD May 30, 2024
e6f6614
Fix compatible interface derivation
garrettmflynn May 30, 2024
475eb16
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 30, 2024
0c5f6f8
Simplify compatibility derivation and fix error handling during auto-…
garrettmflynn May 30, 2024
c67aa11
Merge branch 'time-alignment-ui' of https://github.com/NeurodataWitho…
garrettmflynn May 30, 2024
42f2339
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 30, 2024
589eb54
Update Dashboard.js
garrettmflynn May 30, 2024
62d6e9b
Merge branch 'time-alignment-ui' of https://github.com/NeurodataWitho…
garrettmflynn May 30, 2024
518c17a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 30, 2024
ad094dc
Merge branch 'main' into time-alignment-ui
CodyCBakerPhD May 30, 2024
f248d06
refactors for readability
CodyCBakerPhD May 30, 2024
5231454
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 30, 2024
08b171e
Merge branch 'main' into time-alignment-ui
CodyCBakerPhD May 30, 2024
fdee491
Merge branch 'main' into time-alignment-ui
CodyCBakerPhD May 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions src/electron/frontend/core/components/Dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,14 +274,15 @@ export class Dashboard extends LitElement {
}
})
.catch((e) => {
const previousId = previous?.info?.id;
if (previousId) {
page.onTransition(previousId); // Revert back to previous page
page.notify(
`<h4 style="margin: 0">Fallback to previous page after error occurred</h4><small>${e}</small>`,
"error"
);
} else reloadPageToHome();
const previousId = previous?.info?.id ?? -1;
this.main.onTransition(previousId); // Revert back to previous page
const hasHTML = /<[^>]*>/.test(e);
page.notify(
hasHTML
? e.message
: `<h4 style="margin: 0">Fallback to previous page after error occurred</h4><small>${e}</small>`,
"error"
);
});
}

Expand Down
31 changes: 31 additions & 0 deletions src/electron/frontend/core/components/JSONSchemaInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,16 @@ export class JSONSchemaInput extends LitElement {
padding: 0;
margin-bottom: 1em;
}

select {
background: url("data:image/svg+xml,<svg height='10px' width='10px' viewBox='0 0 16 16' fill='%23000000' xmlns='http://www.w3.org/2000/svg'><path d='M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z'/></svg>")
no-repeat;
background-position: calc(100% - 0.75rem) center !important;
-moz-appearance: none !important;
-webkit-appearance: none !important;
appearance: none !important;
padding-right: 2rem !important;
}
`;
}

Expand Down Expand Up @@ -1115,6 +1125,27 @@ export class JSONSchemaInput extends LitElement {

// Basic enumeration of properties on a select element
if (schema.enum && schema.enum.length) {
// Use generic selector
if (schema.strict && schema.search !== true) {
return html`
<select
class="guided--input schema-input"
@input=${(ev) => this.#updateData(fullPath, schema.enum[ev.target.value])}
@change=${() => validateOnChange && this.#triggerValidation(name, path)}
>
<option disabled selected value>${schema.placeholder ?? "Select an option"}</option>
${schema.enum
.sort()
.map(
(item, i) =>
html`<option value=${i} ?selected=${this.value === item}>
${schema.enumLabels?.[item] ?? item}
</option>`
)}
</select>
`;
}

const options = schema.enum.map((v) => {
return {
key: v,
Expand Down
3 changes: 2 additions & 1 deletion src/electron/frontend/core/components/pages/Page.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ export class Page extends LitElement {
const { subject, session, globalState = this.info.globalState } = info;
const file = `sub-${subject}/sub-${subject}_ses-${session}.nwb`;

const { conversion_output_folder, name, SourceData } = globalState.project;
const { conversion_output_folder, name, SourceData, alignment } = globalState.project;

const sessionResults = globalState.results[subject][session];

Expand All @@ -197,6 +197,7 @@ export class Page extends LitElement {
...conversionOptions, // Any additional conversion options override the defaults

interfaces: globalState.interfaces,
alignment,
},
swalOpts
).catch((error) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,8 +247,6 @@ export class GuidedMetadataPage extends ManagedPage {

const patternPropsToRetitle = ["Ophys.Fluorescence", "Ophys.DfOverF", "Ophys.SegmentationImages"];

console.log("schema", structuredClone(schema), structuredClone(results));

const ophys = schema.properties.Ophys;
if (ophys) {
drillSchemaProperties(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { JSONSchemaForm } from "../../../JSONSchemaForm.js";
import { InstanceManager } from "../../../InstanceManager.js";
import { ManagedPage } from "./ManagedPage.js";
import { onThrow } from "../../../../errors";
import { merge } from "../../utils";
import { merge, sanitize } from "../../utils";
import preprocessSourceDataSchema from "../../../../../../../schemas/source-data.schema";

import { TimeAlignment } from "./alignment/TimeAlignment.js";

import { createGlobalFormModal } from "../../../forms/GlobalFormModal";
import { header } from "../../../forms/utils";
import { Button } from "../../../Button.js";
Expand All @@ -18,6 +20,8 @@ import { getInfoFromId } from "./utils";
import { Modal } from "../../../Modal";
import Swal from "sweetalert2";

import { baseUrl } from "../../../../server/globals";

const propsToIgnore = {
"*": {
verbose: true,
Expand Down Expand Up @@ -80,7 +84,9 @@ export class GuidedSourceDataPage extends ManagedPage {
heightAuto: false,
backdrop: "rgba(0,0,0, 0.4)",
timerProgressBar: false,
didOpen: () => Swal.showLoading(),
didOpen: () => {
Swal.showLoading();
},
});
};

Expand All @@ -93,22 +99,39 @@ export class GuidedSourceDataPage extends ManagedPage {
const info = this.info.globalState.results[subject][session];

// NOTE: This clears all user-defined results
const result = await run(
`neuroconv/metadata`,
{
source_data: form.resolved, // Use resolved values, including global source data
const result = await fetch(`${baseUrl}/neuroconv/metadata`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
source_data: sanitize(structuredClone(form.resolved)), // Use resolved values, including global source data
interfaces: this.info.globalState.interfaces,
},
{ swal: false }
).catch((e) => {
Swal.close();
stillFireSwal = false;
this.notify(e.message, "error");
throw e;
});
}),
})
.then((res) => res.json())
.catch((error) => {
Swal.close();
stillFireSwal = false;
this.notify(`<b>Critical Error:</b> ${error.message}`, "error", 4000);
throw error;
});

Swal.close();

if (isStorybook) return;

if (result.message) {
const [type, ...splitText] = result.message.split(":");
const text = splitText.length
? splitText.join(":").replaceAll("<", "&lt").replaceAll(">", "&gt")
: result.traceback
? `<small><pre>${result.traceback.trim().split("\n").slice(-2)[0].trim()}</pre></small>`
: "";

const message = `<h4 style="margin: 0;">Request Failed</h4><small>${type}</small><p>${text}</p>`;
this.notify(message, "error");
throw result;
}

const { results: metadata, schema } = result;

// Merge arrays from generated pipeline data
Expand All @@ -128,8 +151,6 @@ export class GuidedSourceDataPage extends ManagedPage {
})
);

Swal.close();

await this.save(undefined, false); // Just save new raw values

return this.to(1);
Expand Down Expand Up @@ -205,9 +226,7 @@ export class GuidedSourceDataPage extends ManagedPage {
}

render() {
this.localState = {
results: structuredClone(this.info.globalState.results ?? {}),
};
this.localState = { results: structuredClone(this.info.globalState.results ?? {}) };

this.forms = this.mapSessions(this.createForm, this.localState.results);

Expand All @@ -223,114 +242,83 @@ export class GuidedSourceDataPage extends ManagedPage {
instances,
controls: [
{
name: "Check Alignment",
name: "View Temporal Alignment",
primary: true,
onClick: async (id) => {
const { globalState } = this.info;

const { subject, session } = getInfoFromId(id);

const souceCopy = structuredClone(globalState.results[subject][session].source_data);

const sessionInfo = {
interfaces: globalState.interfaces,
source_data: merge(globalState.project.SourceData, souceCopy),
};

const results = await run("neuroconv/alignment", sessionInfo, {
title: "Checking Alignment",
message: "Please wait...",
});
this.dismiss();

const header = document.createElement("div");
const h2 = document.createElement("h2");
Object.assign(h2.style, {
marginBottom: "10px",
});
h2.innerText = `Alignment Preview: ${subject}/${session}`;
const warning = document.createElement("small");
warning.innerHTML =
"<b>Warning:</b> This is just a preview. We do not currently have the features implemented to change the alignment of your interfaces.";
header.append(h2, warning);

const modal = new Modal({
header,
});

document.body.append(modal);

const content = document.createElement("div");
Object.assign(content.style, {
display: "flex",
flexDirection: "column",
gap: "20px",
padding: "20px",
});

modal.append(content);

const flatTimes = Object.values(results)
.map((interfaceTimestamps) => {
return [interfaceTimestamps[0], interfaceTimestamps.slice(-1)[0]];
})
.flat()
.filter((timestamp) => !isNaN(timestamp));
Object.assign(header.style, { paddingTop: "10px" });
const h2 = document.createElement("h3");
Object.assign(h2.style, { margin: "0px" });
const small = document.createElement("small");
small.innerText = `${subject}/${session}`;
h2.innerText = `Temporal Alignment`;

const minTime = Math.min(...flatTimes);
const maxTime = Math.max(...flatTimes);
header.append(h2, small);

const normalizeTime = (time) => (time - minTime) / (maxTime - minTime);
const normalizeTimePct = (time) => `${normalizeTime(time) * 100}%`;
const modal = new Modal({ header });

for (let name in results) {
const container = document.createElement("div");
const label = document.createElement("label");
label.innerText = name;
container.append(label);
let alignment;

const data = results[name];
modal.footer = new Button({
label: "Update",
primary: true,
onClick: async () => {
console.log("Submit to backend");

const barContainer = document.createElement("div");
Object.assign(barContainer.style, {
height: "10px",
width: "100%",
marginTop: "5px",
border: "1px solid lightgray",
position: "relative",
});
if (alignment) {
globalState.project.alignment = alignment.results;
this.unsavedUpdates = "conversions";
await this.save();
}

if (data.length) {
const firstTime = data[0];
const lastTime = data[data.length - 1];
const sourceCopy = structuredClone(globalState.results[subject][session].source_data);

label.innerText += ` (${firstTime.toFixed(2)} - ${lastTime.toFixed(2)} sec)`;
const alignmentInfo =
globalState.project.alignment ?? (globalState.project.alignment = {});

const firstTimePct = normalizeTimePct(firstTime);
const lastTimePct = normalizeTimePct(lastTime);
const sessionInfo = {
interfaces: globalState.interfaces,
source_data: merge(globalState.project.SourceData, sourceCopy),
alignment: alignmentInfo,
};

const width = `calc(${lastTimePct} - ${firstTimePct})`;

const bar = document.createElement("div");

Object.assign(bar.style, {
position: "absolute",
const data = await run("neuroconv/alignment", sessionInfo, {
title: "Checking Alignment",
message: "Please wait...",
});

left: firstTimePct,
width: width,
height: "100%",
background: "blue",
const { metadata } = data;
if (Object.keys(metadata).length === 0) {
this.notify(
`<h4 style="margin: 0">Time Alignment Failed</h4><small>Please ensure that all source data is specified.</small>`,
"error"
);
return false;
}

alignment = new TimeAlignment({
data,
interfaces: globalState.interfaces,
results: alignmentInfo,
});

barContainer.append(bar);
} else {
barContainer.style.background =
"repeating-linear-gradient(45deg, lightgray, lightgray 10px, white 10px, white 20px)";
}
modal.innerHTML = "";
modal.append(alignment);

container.append(barContainer);
return true;
},
});

const result = await modal.footer.onClick();
if (!result) return;

content.append(container);
}
document.body.append(modal);

modal.open = true;
},
Expand Down
Loading
Loading