Skip to content

Commit 8bf96e5

Browse files
Merge pull request #406 from NeurodataWithoutBorders/CaimanSegmentationInterface
Add CaimanSegmentationInterface
2 parents 754f753 + 9d83abf commit 8bf96e5

File tree

8 files changed

+112
-48
lines changed

8 files changed

+112
-48
lines changed

guideGlobalMetadata.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"TiffImagingInterface",
2222
"MiniscopeImagingInterface",
2323
"SbxImagingInterface",
24+
"CaimanSegmentationInterface",
2425
"MCSRawRecordingInterface",
2526
"MEArecRecordingInterface"
2627
]

pyflask/manageNeuroconv/manage_neuroconv.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,9 @@ def coerce_schema_compliance_recursive(obj, schema):
8888
coerce_schema_compliance_recursive(value, prop_schema)
8989
elif isinstance(obj, list):
9090
for item in obj:
91-
coerce_schema_compliance_recursive(item, schema.get("items", {}))
91+
coerce_schema_compliance_recursive(
92+
item, schema.get("items", schema if "properties" else {})
93+
) # NEUROCONV PATCH
9294

9395
return obj
9496

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"required": [],
3+
"properties": {
4+
"CaimanSegmentationInterface": {
5+
"required": [
6+
"file_path"
7+
],
8+
"properties": {
9+
"file_path": {
10+
"format": "file",
11+
"type": "string"
12+
},
13+
"verbose": {
14+
"type": "boolean",
15+
"default": true
16+
}
17+
},
18+
"type": "object",
19+
"additionalProperties": false
20+
}
21+
},
22+
"type": "object",
23+
"additionalProperties": false,
24+
"$schema": "http://json-schema.org/draft-07/schema#",
25+
"$id": "source.schema.json",
26+
"title": "Source data schema",
27+
"description": "Schema for the source data, files and directories",
28+
"version": "0.1.0"
29+
}

src/renderer/src/stories/JSONSchemaForm.js

Lines changed: 60 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import { resolveProperties } from "./pages/guided-mode/data/utils";
1212
import { JSONSchemaInput } from "./JSONSchemaInput";
1313
import { InspectorListItem } from "./preview/inspector/InspectorList";
1414

15+
const selfRequiredSymbol = Symbol();
16+
1517
const componentCSS = `
1618
1719
* {
@@ -153,7 +155,7 @@ export class JSONSchemaForm extends LitElement {
153155
};
154156
}
155157

156-
#base = [];
158+
base = [];
157159
#nestedForms = {};
158160
tables = {};
159161
#nErrors = 0;
@@ -205,7 +207,7 @@ export class JSONSchemaForm extends LitElement {
205207

206208
if (props.onStatusChange) this.onStatusChange = props.onStatusChange;
207209

208-
if (props.base) this.#base = props.base;
210+
if (props.base) this.base = props.base;
209211
}
210212

211213
getTable = (path) => {
@@ -242,8 +244,8 @@ export class JSONSchemaForm extends LitElement {
242244
}
243245

244246
// Track resolved values for the form (data only)
245-
updateData(fullPath, value) {
246-
const path = [...fullPath];
247+
updateData(localPath, value) {
248+
const path = [...localPath];
247249
const name = path.pop();
248250

249251
const reducer = (acc, key) => (key in acc ? acc[key] : (acc[key] = {})); // NOTE: Create nested objects if required to set a new path
@@ -261,7 +263,7 @@ export class JSONSchemaForm extends LitElement {
261263
resolvedParent[name] = value;
262264
}
263265

264-
if (hasUpdate) this.onUpdate(fullPath, value); // Ensure the value has actually changed
266+
if (hasUpdate) this.onUpdate(localPath, value); // Ensure the value has actually changed
265267
}
266268

267269
#addMessage = (name, message, type) => {
@@ -271,9 +273,9 @@ export class JSONSchemaForm extends LitElement {
271273
container.appendChild(item);
272274
};
273275

274-
#clearMessages = (fullPath, type) => {
275-
if (Array.isArray(fullPath)) fullPath = fullPath.join("-"); // Convert array to string
276-
const container = this.shadowRoot.querySelector(`#${fullPath} .${type}`);
276+
#clearMessages = (localPath, type) => {
277+
if (Array.isArray(localPath)) localPath = localPath.join("-"); // Convert array to string
278+
const container = this.shadowRoot.querySelector(`#${localPath} .${type}`);
277279

278280
if (container) {
279281
const nChildren = container.children.length;
@@ -348,15 +350,15 @@ export class JSONSchemaForm extends LitElement {
348350
};
349351

350352
#get = (path, object = this.resolved) => {
351-
// path = path.slice(this.#base.length); // Correct for base path
353+
// path = path.slice(this.base.length); // Correct for base path
352354
return path.reduce((acc, curr) => (acc = acc[curr]), object);
353355
};
354356

355-
#checkRequiredAfterChange = async (fullPath) => {
356-
const path = [...fullPath];
357+
#checkRequiredAfterChange = async (localPath) => {
358+
const path = [...localPath];
357359
const name = path.pop();
358360
const element = this.shadowRoot
359-
.querySelector(`#${fullPath.join("-")}`)
361+
.querySelector(`#${localPath.join("-")}`)
360362
.querySelector("jsonschema-input")
361363
.getElement();
362364
const isValid = await this.triggerValidation(name, element, path, false);
@@ -367,13 +369,13 @@ export class JSONSchemaForm extends LitElement {
367369
if (typeof path === "string") path = path.split(".");
368370

369371
// NOTE: Still must correct for the base here
370-
if (this.#base.length) {
371-
const base = this.#base.slice(-1)[0];
372+
if (this.base.length) {
373+
const base = this.base.slice(-1)[0];
372374
const indexOf = path.indexOf(base);
373375
if (indexOf !== -1) path = path.slice(indexOf + 1);
374376
}
375377

376-
const resolved = path.reduce((acc, curr) => (acc = acc[curr]), schema);
378+
const resolved = this.#get(path, schema);
377379
if (resolved["$ref"]) return this.getSchema(resolved["$ref"].split("/").slice(1)); // NOTE: This assumes reference to the root of the schema
378380

379381
return resolved;
@@ -382,8 +384,8 @@ export class JSONSchemaForm extends LitElement {
382384
#renderInteractiveElement = (name, info, required, path = []) => {
383385
let isRequired = required[name];
384386

385-
const fullPath = [...path, name];
386-
const externalPath = [...this.#base, ...fullPath];
387+
const localPath = [...path, name];
388+
const externalPath = [...this.base, ...localPath];
387389

388390
const resolved = this.#get(path, this.resolved);
389391
const value = resolved[name];
@@ -392,11 +394,11 @@ export class JSONSchemaForm extends LitElement {
392394

393395
if (isConditional && !isRequired)
394396
isRequired = required[name] = async () => {
395-
const isRequiredAfterChange = await this.#checkRequiredAfterChange(fullPath);
397+
const isRequiredAfterChange = await this.#checkRequiredAfterChange(localPath);
396398
if (isRequiredAfterChange) {
397399
return true;
398400
} else {
399-
const linkResults = await this.#applyToLinkedProperties(this.#checkRequiredAfterChange, fullPath); // Check links
401+
const linkResults = await this.#applyToLinkedProperties(this.#checkRequiredAfterChange, localPath); // Check links
400402
if (linkResults.includes(true)) return true;
401403
// Handle updates when no longer required
402404
else return false;
@@ -405,7 +407,7 @@ export class JSONSchemaForm extends LitElement {
405407

406408
const interactiveInput = new JSONSchemaInput({
407409
info,
408-
path: fullPath,
410+
path: localPath,
409411
value,
410412
form: this,
411413
required: isRequired,
@@ -425,7 +427,7 @@ export class JSONSchemaForm extends LitElement {
425427

426428
return html`
427429
<div
428-
id=${fullPath.join("-")}
430+
id=${localPath.join("-")}
429431
class="form-section ${isRequired || isConditional ? "required" : ""} ${isConditional
430432
? "conditional"
431433
: ""}"
@@ -459,6 +461,10 @@ export class JSONSchemaForm extends LitElement {
459461

460462
for (let name in requirements) {
461463
let isRequired = requirements[name];
464+
465+
// // NOTE: Uncomment to block checking requirements inside optional properties
466+
// if (!requirements[name][selfRequiredSymbol] && !resolved[name]) continue; // Do not continue checking requirements if absent and not required
467+
462468
if (typeof isRequired === "function") isRequired = await isRequired.call(this.resolved);
463469
if (isRequired) {
464470
let path = parentPath ? `${parentPath}-${name}` : name;
@@ -511,7 +517,7 @@ export class JSONSchemaForm extends LitElement {
511517
if (this.ignore.includes(key)) return false;
512518
if (this.showLevelOverride >= path.length) return isRenderable(key, value);
513519
if (required[key]) return isRenderable(key, value);
514-
if (this.#getLink([...this.#base, ...path, key])) return isRenderable(key, value);
520+
if (this.#getLink([...this.base, ...path, key])) return isRenderable(key, value);
515521
if (!this.onlyRequired) return isRenderable(key, value);
516522
return false;
517523
})
@@ -549,7 +555,7 @@ export class JSONSchemaForm extends LitElement {
549555
#isLinkResolved = async (pathArr) => {
550556
return (
551557
await this.#applyToLinkedProperties((link) => {
552-
const isRequired = this.#isRequired(link.slice((this.#base ?? []).length));
558+
const isRequired = this.#isRequired(link.slice((this.base ?? []).length));
553559
if (typeof isRequired === "function") return !isRequired.call(this.resolved);
554560
else return !isRequired;
555561
}, pathArr)
@@ -558,8 +564,11 @@ export class JSONSchemaForm extends LitElement {
558564

559565
#isRequired = (path) => {
560566
if (typeof path === "string") path = path.split("-");
561-
// path = path.slice(this.#base.length); // Remove base path
562-
return path.reduce((obj, key) => obj && obj[key], this.#requirements);
567+
// path = path.slice(this.base.length); // Remove base path
568+
const res = path.reduce((obj, key) => obj && obj[key], this.#requirements);
569+
570+
if (typeof res === "object") res = res[selfRequiredSymbol];
571+
return res;
563572
};
564573

565574
#getLinkElement = (externalPath) => {
@@ -572,15 +581,17 @@ export class JSONSchemaForm extends LitElement {
572581
triggerValidation = async (name, element, path = [], checkLinks = true) => {
573582
const parent = this.#get(path, this.resolved);
574583

584+
const pathToValidate = [...(this.base ?? []), ...path];
585+
575586
const valid =
576587
!this.validateEmptyValues && !(name in parent)
577588
? true
578-
: await this.validateOnChange(name, parent, [...(this.#base ?? []), ...path]);
589+
: await this.validateOnChange(name, parent, pathToValidate);
579590

580-
const fullPath = [...path, name]; // Use basePath to augment the validation
581-
const externalPath = [...this.#base, name];
591+
const localPath = [...path, name]; // Use basePath to augment the validation
592+
const externalPath = [...this.base, name];
582593

583-
const isRequired = this.#isRequired(fullPath);
594+
const isRequired = this.#isRequired(localPath);
584595
let warnings = Array.isArray(valid)
585596
? valid.filter((info) => info.type === "warning" && (!isRequired || !info.missing))
586597
: [];
@@ -599,7 +610,7 @@ export class JSONSchemaForm extends LitElement {
599610

600611
// Clear old errors and warnings on linked properties
601612
this.#applyToLinkedProperties((path) => {
602-
const internalPath = path.slice((this.#base ?? []).length);
613+
const internalPath = path.slice((this.base ?? []).length);
603614
this.#clearMessages(internalPath, "errors");
604615
this.#clearMessages(internalPath, "warnings");
605616
}, externalPath);
@@ -613,9 +624,9 @@ export class JSONSchemaForm extends LitElement {
613624
}
614625

615626
// Clear old errors and warnings
616-
this.#clearMessages(fullPath, "errors");
617-
this.#clearMessages(fullPath, "warnings");
618-
this.#clearMessages(fullPath, "info");
627+
this.#clearMessages(localPath, "errors");
628+
this.#clearMessages(localPath, "warnings");
629+
this.#clearMessages(localPath, "info");
619630

620631
const isFunction = typeof valid === "function";
621632
const isValid =
@@ -632,8 +643,8 @@ export class JSONSchemaForm extends LitElement {
632643
this.checkStatus();
633644

634645
// Show aggregated errors and warnings (if any)
635-
warnings.forEach((info) => this.#addMessage(fullPath, info, "warnings"));
636-
info.forEach((info) => this.#addMessage(fullPath, info, "info"));
646+
warnings.forEach((info) => this.#addMessage(localPath, info, "warnings"));
647+
info.forEach((info) => this.#addMessage(localPath, info, "info"));
637648

638649
if (isValid && errors.length === 0) {
639650
element.classList.remove("invalid");
@@ -643,7 +654,7 @@ export class JSONSchemaForm extends LitElement {
643654

644655
await this.#applyToLinkedProperties((path, element) => {
645656
element.classList.remove("required", "conditional"); // Links manage their own error and validity states, but only one needs to be valid
646-
}, fullPath);
657+
}, localPath);
647658

648659
if (isFunction) valid(); // Run if returned value is a function
649660

@@ -661,7 +672,7 @@ export class JSONSchemaForm extends LitElement {
661672
[...path, name]
662673
);
663674

664-
errors.forEach((info) => this.#addMessage(fullPath, info, "errors"));
675+
errors.forEach((info) => this.#addMessage(localPath, info, "errors"));
665676
// element.title = errors.map((info) => info.message).join("\n"); // Set all errors to show on hover
666677

667678
return false;
@@ -679,7 +690,7 @@ export class JSONSchemaForm extends LitElement {
679690
if (renderable.length === 0) return html`<p>No properties to render</p>`;
680691

681692
let renderableWithLinks = renderable.reduce((acc, [name, info]) => {
682-
const externalPath = [...this.#base, ...path, name];
693+
const externalPath = [...this.base, ...path, name];
683694
const link = this.#getLink(externalPath); // Use the base path to find a link
684695

685696
if (link) {
@@ -740,7 +751,7 @@ export class JSONSchemaForm extends LitElement {
740751
// Render linked properties
741752
if (entry[isLink]) {
742753
const linkedProperties = info.properties.map((path) => {
743-
const pathCopy = [...path].slice((this.#base ?? []).length);
754+
const pathCopy = [...path].slice((this.base ?? []).length);
744755
const name = pathCopy.pop();
745756
return this.#renderInteractiveElement(name, schema.properties[name], required, pathCopy);
746757
});
@@ -756,7 +767,7 @@ export class JSONSchemaForm extends LitElement {
756767

757768
const hasMany = renderable.length > 1; // How many siblings?
758769

759-
const fullPath = [...path, name];
770+
const localPath = [...path, name];
760771

761772
if (this.mode === "accordion" && hasMany) {
762773
const headerName = header(name);
@@ -767,8 +778,10 @@ export class JSONSchemaForm extends LitElement {
767778
results: { ...results[name] },
768779
globals: this.globals?.[name],
769780

781+
mode: this.mode,
782+
770783
onUpdate: (internalPath, value) => {
771-
const path = [...fullPath, ...internalPath];
784+
const path = [...localPath, ...internalPath];
772785
this.updateData(path, value);
773786
},
774787

@@ -793,13 +806,13 @@ export class JSONSchemaForm extends LitElement {
793806
this.checkAllLoaded();
794807
},
795808
renderTable: (...args) => this.renderTable(...args),
796-
base: fullPath,
809+
base: [...this.base, ...localPath],
797810
});
798811

799812
const accordion = new Accordion({
800813
sections: {
801814
[headerName]: {
802-
subtitle: `${this.#getRenderable(info, required[name], fullPath, true).length} fields`,
815+
subtitle: `${this.#getRenderable(info, required[name], localPath, true).length} fields`,
803816
content: this.#nestedForms[name],
804817
},
805818
},
@@ -811,7 +824,7 @@ export class JSONSchemaForm extends LitElement {
811824
}
812825

813826
// Render properties in the sub-schema
814-
const rendered = this.#render(info, results?.[name], required[name], fullPath);
827+
const rendered = this.#render(info, results?.[name], required[name], localPath);
815828
return hasMany || path.length > 1
816829
? html`
817830
<div style="margin-top: 40px;">
@@ -834,7 +847,9 @@ export class JSONSchemaForm extends LitElement {
834847
Object.entries(schema.properties).forEach(([key, value]) => {
835848
if (value.properties) {
836849
let nextAccumulator = acc[key];
837-
if (!nextAccumulator || typeof nextAccumulator !== "object") nextAccumulator = acc[key] = {};
850+
const isNotObject = typeof nextAccumulator !== "object";
851+
if (!nextAccumulator || isNotObject)
852+
nextAccumulator = acc[key] = { [selfRequiredSymbol]: !!nextAccumulator };
838853
this.#registerRequirements(value, requirements[key], nextAccumulator);
839854
}
840855
});

src/renderer/src/stories/JSONSchemaInput.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ export class JSONSchemaInput extends LitElement {
200200
(this.onValidate
201201
? this.onValidate()
202202
: this.form
203-
? this.form.validateOnChange(key, parent, fullPath, v)
203+
? this.form.validateOnChange(key, parent, [...this.form.base, ...fullPath], v)
204204
: "")
205205
);
206206
},

0 commit comments

Comments
 (0)