From 54791e9cb7da0172833c4e0ae42b1f200b0518c0 Mon Sep 17 00:00:00 2001 From: Garrett Michael Flynn Date: Tue, 12 Mar 2024 13:21:25 -0700 Subject: [PATCH] Fix Unknown Item Type Specification (#656) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Ben Dichter Co-authored-by: Cody Baker <51133164+CodyCBakerPhD@users.noreply.github.com> --- environments/environment-MAC.yml | 1 + .../BlackrockRecordingInterface.json | 4 +- .../generated/BlackrockSortingInterface.json | 6 ++- .../CellExplorerRecordingInterface.json | 35 ++++++++++++++++ .../json/generated/FicTracDataInterface.json | 7 ++++ .../OpenEphysRecordingInterface.json | 3 ++ .../json/generated/PhySortingInterface.json | 5 ++- .../generated/Spike2RecordingInterface.json | 4 +- .../json/generated/SpikeGLXConverterPipe.json | 5 ++- .../json/generated/SpikeGLXNIDQInterface.json | 6 ++- .../generated/SpikeGLXRecordingInterface.json | 5 ++- .../Suite2pSegmentationInterface.json | 20 ++++++--- .../json/generated/TdtRecordingInterface.json | 41 +++++++++++++++++++ schemas/source-data.schema.ts | 1 + src/renderer/src/stories/JSONSchemaInput.js | 38 +++++++++++++++-- .../pages/guided-mode/SourceData.stories.js | 35 ++++++++++++++++ 16 files changed, 194 insertions(+), 22 deletions(-) create mode 100644 schemas/json/generated/CellExplorerRecordingInterface.json create mode 100644 schemas/json/generated/TdtRecordingInterface.json diff --git a/environments/environment-MAC.yml b/environments/environment-MAC.yml index 73c55d9f5..af203d6e5 100644 --- a/environments/environment-MAC.yml +++ b/environments/environment-MAC.yml @@ -11,6 +11,7 @@ dependencies: - jsonschema = 4.18.0 # installs jsonschema-specifications - pip - pip: + - scipy<1.12.0 # Fix needed for scipy._lib._testutils - chardet == 5.1.0 - configparser == 6.0.0 - flask == 2.3.2 diff --git a/schemas/json/generated/BlackrockRecordingInterface.json b/schemas/json/generated/BlackrockRecordingInterface.json index 25190ebf2..415e95cc3 100644 --- a/schemas/json/generated/BlackrockRecordingInterface.json +++ b/schemas/json/generated/BlackrockRecordingInterface.json @@ -8,8 +8,8 @@ "properties": { "file_path": { "format": "file", - "type": "string", - "description": "Path to Blackrock file." + "description": "Path to Blackrock file.", + "type": "string" }, "nsx_override": { "format": "file", diff --git a/schemas/json/generated/BlackrockSortingInterface.json b/schemas/json/generated/BlackrockSortingInterface.json index 02661e794..da459b824 100644 --- a/schemas/json/generated/BlackrockSortingInterface.json +++ b/schemas/json/generated/BlackrockSortingInterface.json @@ -8,13 +8,15 @@ "properties": { "file_path": { "format": "file", - "type": "string", - "description": "Path to Blackrock file." + "description": "Path to Blackrock file.", + "type": "string" }, "sampling_frequency": { + "description": "The sampling frequency for the sorting extractor. When the signal data is available (.ncs) those files will be", "type": "number" }, "verbose": { + "description": "Enables verbosity", "type": "boolean", "default": true } diff --git a/schemas/json/generated/CellExplorerRecordingInterface.json b/schemas/json/generated/CellExplorerRecordingInterface.json new file mode 100644 index 000000000..1e4e94428 --- /dev/null +++ b/schemas/json/generated/CellExplorerRecordingInterface.json @@ -0,0 +1,35 @@ +{ + "required": [], + "properties": { + "CellExplorerRecordingInterface": { + "required": [ + "folder_path" + ], + "properties": { + "folder_path": { + "format": "directory", + "description": "The folder where the session data is located. It should contain a\n`{folder.name}.session.mat` file and the binary files `{folder.name}.dat`\nor `{folder.name}.lfp` for the LFP interface.", + "type": "string" + }, + "verbose": { + "description": "Whether to output verbose text.", + "type": "boolean", + "default": true + }, + "es_key": { + "type": "string", + "default": "ElectricalSeries" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "source.schema.json", + "title": "Source data schema", + "description": "Schema for the source data, files and directories", + "version": "0.1.0" +} diff --git a/schemas/json/generated/FicTracDataInterface.json b/schemas/json/generated/FicTracDataInterface.json index 47804a267..a04818bf7 100644 --- a/schemas/json/generated/FicTracDataInterface.json +++ b/schemas/json/generated/FicTracDataInterface.json @@ -10,6 +10,13 @@ "format": "file", "type": "string" }, + "radius": { + "type": "number" + }, + "configuration_file_path": { + "format": "file", + "type": "string" + }, "verbose": { "type": "boolean", "default": true diff --git a/schemas/json/generated/OpenEphysRecordingInterface.json b/schemas/json/generated/OpenEphysRecordingInterface.json index f6193ea0c..df1202bc2 100644 --- a/schemas/json/generated/OpenEphysRecordingInterface.json +++ b/schemas/json/generated/OpenEphysRecordingInterface.json @@ -13,6 +13,9 @@ "stream_name": { "type": "string" }, + "block_index": { + "type": "number" + }, "verbose": { "type": "boolean", "default": true diff --git a/schemas/json/generated/PhySortingInterface.json b/schemas/json/generated/PhySortingInterface.json index edcc2183d..a1c9e140b 100644 --- a/schemas/json/generated/PhySortingInterface.json +++ b/schemas/json/generated/PhySortingInterface.json @@ -11,7 +11,10 @@ "type": "string" }, "exclude_cluster_groups": { - "type": "array" + "type": "array", + "items": { + "type": "string" + } }, "verbose": { "type": "boolean", diff --git a/schemas/json/generated/Spike2RecordingInterface.json b/schemas/json/generated/Spike2RecordingInterface.json index 81c785ad8..e9bf8f6b1 100644 --- a/schemas/json/generated/Spike2RecordingInterface.json +++ b/schemas/json/generated/Spike2RecordingInterface.json @@ -8,8 +8,8 @@ "properties": { "file_path": { "format": "file", - "type": "string", - "description": "Path to CED data file." + "description": "Path to CED data file.", + "type": "string" }, "verbose": { "type": "boolean", diff --git a/schemas/json/generated/SpikeGLXConverterPipe.json b/schemas/json/generated/SpikeGLXConverterPipe.json index 06814d990..4b2fb1eae 100644 --- a/schemas/json/generated/SpikeGLXConverterPipe.json +++ b/schemas/json/generated/SpikeGLXConverterPipe.json @@ -8,10 +8,11 @@ "properties": { "folder_path": { "format": "directory", - "type": "string", - "description": "Path to the folder containing SpikeGLX streams." + "description": "Path to the folder containing SpikeGLX streams.", + "type": "string" }, "verbose": { + "description": "Whether to output verbose text.", "type": "boolean", "default": false } diff --git a/schemas/json/generated/SpikeGLXNIDQInterface.json b/schemas/json/generated/SpikeGLXNIDQInterface.json index 14ad82eb8..0903b1580 100644 --- a/schemas/json/generated/SpikeGLXNIDQInterface.json +++ b/schemas/json/generated/SpikeGLXNIDQInterface.json @@ -8,14 +8,16 @@ "properties": { "file_path": { "format": "file", - "type": "string", - "description": "Path to SpikeGLX .nidq file." + "description": "Path to SpikeGLX .nidq file.", + "type": "string" }, "verbose": { + "description": "Whether to output verbose text.", "type": "boolean", "default": true }, "load_sync_channel": { + "description": "Whether to load the last channel in the stream, which is typically used for synchronization.\nIf True, then the probe is not loaded.", "type": "boolean", "default": false }, diff --git a/schemas/json/generated/SpikeGLXRecordingInterface.json b/schemas/json/generated/SpikeGLXRecordingInterface.json index 4b2034df8..fe04f74ee 100644 --- a/schemas/json/generated/SpikeGLXRecordingInterface.json +++ b/schemas/json/generated/SpikeGLXRecordingInterface.json @@ -8,10 +8,11 @@ "properties": { "file_path": { "format": "file", - "type": "string", - "description": "Path to SpikeGLX ap.bin or lf.bin file." + "description": "Path to SpikeGLX ap.bin or lf.bin file.", + "type": "string" }, "verbose": { + "description": "Whether to output verbose text.", "type": "boolean", "default": true }, diff --git a/schemas/json/generated/Suite2pSegmentationInterface.json b/schemas/json/generated/Suite2pSegmentationInterface.json index e5256c2f5..f0e101c81 100644 --- a/schemas/json/generated/Suite2pSegmentationInterface.json +++ b/schemas/json/generated/Suite2pSegmentationInterface.json @@ -10,17 +10,25 @@ "format": "directory", "type": "string" }, - "combined": { - "type": "boolean", - "default": false + "channel_name": { + "type": "string" }, - "plane_no": { - "type": "number", - "default": 0 + "plane_name": { + "type": "string" + }, + "plane_segmentation_name": { + "type": "string" }, "verbose": { "type": "boolean", "default": true + }, + "combined": { + "type": "boolean", + "default": false + }, + "plane_no": { + "type": "number" } }, "type": "object", diff --git a/schemas/json/generated/TdtRecordingInterface.json b/schemas/json/generated/TdtRecordingInterface.json new file mode 100644 index 000000000..d48e0f968 --- /dev/null +++ b/schemas/json/generated/TdtRecordingInterface.json @@ -0,0 +1,41 @@ +{ + "required": [], + "properties": { + "TdtRecordingInterface": { + "required": [ + "folder_path", + "gain" + ], + "properties": { + "folder_path": { + "format": "directory", + "type": "string" + }, + "gain": { + "type": "number" + }, + "stream_id": { + "type": "string", + "default": "0" + }, + "verbose": { + "type": "boolean", + "default": true + }, + "es_key": { + "type": "string", + "default": "ElectricalSeries" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "type": "object", + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "source.schema.json", + "title": "Source data schema", + "description": "Schema for the source data, files and directories", + "version": "0.1.0" +} diff --git a/schemas/source-data.schema.ts b/schemas/source-data.schema.ts index b47f5b51f..d30cc9194 100644 --- a/schemas/source-data.schema.ts +++ b/schemas/source-data.schema.ts @@ -8,6 +8,7 @@ export default function preprocessSourceDataSchema (schema) { if (key === 'VideoInterface' || key === 'AudioInterface') { if (schema.properties.file_paths) { Object.assign(schema.properties.file_paths, { + items: { type: 'string' }, description: 'Only one file supported at this time. Multiple file support coming soon.', maxItems: 1, }) diff --git a/src/renderer/src/stories/JSONSchemaInput.js b/src/renderer/src/stories/JSONSchemaInput.js index b9a884e34..7d850aa8f 100644 --- a/src/renderer/src/stories/JSONSchemaInput.js +++ b/src/renderer/src/stories/JSONSchemaInput.js @@ -14,6 +14,9 @@ import { JSONSchemaForm, getIgnore } from "./JSONSchemaForm"; import { Search } from "./Search"; import tippy from "tippy.js"; import { merge } from "./pages/utils"; +import { InspectorListItem } from "./preview/inspector/InspectorList"; + +const isDevelopment = !!import.meta.env; const dateTimeRegex = /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})/; @@ -502,6 +505,27 @@ export class JSONSchemaInput extends LitElement { required = false; validateOnChange = true; + // Print the default value of the schema if not caught + onUncaughtSchema = (schema) => { + // In development, show uncaught schemas + if (!isDevelopment) { + if (this.form) { + const inputContainer = this.form.shadowRoot.querySelector(`#${this.path.slice(-1)[0]}`); + inputContainer.style.display = "none"; + } + } + + if (schema.default) return `
${JSON.stringify(schema.default, null, 2)}
`; + + const error = new InspectorListItem({ + message: + "

Internal GUIDE Error

Cannot render this property because of a misformatted schema.", + }); + error.style.width = "100%"; + + return error; + }; + constructor(props) { super(); Object.assign(this, props); @@ -870,7 +894,16 @@ export class JSONSchemaInput extends LitElement { if (isArray) { const hasItemsRef = "items" in schema && "$ref" in schema.items; if (!("items" in schema)) schema.items = {}; - if (!("type" in schema.items) && !hasItemsRef) schema.items.type = this.#getType(this.value?.[0]); + if (!("type" in schema.items) && !hasItemsRef) { + // Guess the type of the first item + if (this.value) { + const itemToCheck = this.value[0]; + schema.items.type = itemToCheck ? this.#getType(itemToCheck) : "string"; + } + + // If no value, handle uncaught schema + else return this.onUncaughtSchema(schema); + } } const itemSchema = this.form?.getSchema ? this.form.getSchema("items", schema) : schema["items"]; @@ -1134,8 +1167,7 @@ export class JSONSchemaInput extends LitElement { } } - // Print out the immutable default value - return html`
${schema.default ? JSON.stringify(schema.default, null, 2) : "No default value"}
`; + return this.onUncaughtSchema(schema); } } diff --git a/src/renderer/src/stories/pages/guided-mode/SourceData.stories.js b/src/renderer/src/stories/pages/guided-mode/SourceData.stories.js index 076277682..348d1fc09 100644 --- a/src/renderer/src/stories/pages/guided-mode/SourceData.stories.js +++ b/src/renderer/src/stories/pages/guided-mode/SourceData.stories.js @@ -12,6 +12,7 @@ import BlackrockRecordingInterfaceSchema from "../../../../../../schemas/json/ge import BlackrockSortingInterfaceSchema from "../../../../../../schemas/json/generated/BlackrockSortingInterface.json"; import CellExplorerSortingInterfaceSchema from "../../../../../../schemas/json/generated/CellExplorerSortingInterface.json"; import KiloSortSortingInterfaceSchema from "../../../../../../schemas/json/generated/KiloSortSortingInterface.json"; +import TdtRecordingInterfaceSchema from "../../../../../../schemas/json/generated/TdtRecordingInterface.json"; import Spike2RecordingInterfaceSchema from "../../../../../../schemas/json/generated/Spike2RecordingInterface.json"; import BrukerTiffSinglePlaneImagingInterfaceSchema from "../../../../../../schemas/json/generated/BrukerTiffSinglePlaneImagingInterface.json"; import ExtractSegmentationInterfaceSchema from "../../../../../../schemas/json/generated/ExtractSegmentationInterface.json"; @@ -28,7 +29,9 @@ import MEArecRecordingInterfaceSchema from "../../../../../../schemas/json/gener import PlexonRecordingInterfaceSchema from "../../../../../../schemas/json/generated/PlexonRecordingInterface.json"; import PlexonSortingInterfaceSchema from "../../../../../../schemas/json/generated/PlexonSortingInterface.json"; import AxonaRecordingInterfaceSchema from "../../../../../../schemas/json/generated/AxonaRecordingInterface.json"; +import VideoInterfaceSchema from "../../../../../../schemas/json/generated/VideoInterface.json"; import NeuralynxRecordingInterfaceSchema from "../../../../../../schemas/json/generated/NeuralynxRecordingInterface.json"; +import Suite2pSegmentationInterfaceSchema from "../../../../../../schemas/json/generated/Suite2pSegmentationInterface.json"; import AlphaOmegaRecordingInterfaceSchema from "../../../../../../schemas/json/generated/AlphaOmegaRecordingInterface.json"; import DeepLabCutInterfaceSchema from "../../../../../../schemas/json/generated/DeepLabCutInterface.json"; import SLEAPInterfaceSchema from "../../../../../../schemas/json/generated/SLEAPInterface.json"; @@ -40,6 +43,7 @@ import SpikeGLXConverterPipeSchema from "../../../../../../schemas/json/generate import BrukerTiffSinglePlaneConverterSchema from "../../../../../../schemas/json/generated/BrukerTiffSinglePlaneConverter.json"; import BrukerTiffMultiPlaneConverterSchema from "../../../../../../schemas/json/generated/BrukerTiffMultiPlaneConverter.json"; import MiniscopeConverterSchema from "../../../../../../schemas/json/generated/MiniscopeConverter.json"; +import CellExplorerRecordingInterfaceSchema from "../../../../../../schemas/json/generated/CellExplorerRecordingInterface.json"; export default { title: "Pages/Guided Mode/Source Data", @@ -77,6 +81,8 @@ globalStateCopy.schema.source_data.properties.CellExplorerSortingInterface = CellExplorerSortingInterfaceSchema.properties.CellExplorerSortingInterface; globalStateCopy.schema.source_data.properties.KiloSortSortingInterface = KiloSortSortingInterfaceSchema.properties.KiloSortSortingInterface; +globalStateCopy.schema.source_data.properties.TdtRecordingInterface = + TdtRecordingInterfaceSchema.properties.TdtRecordingInterface; globalStateCopy.schema.source_data.properties.Spike2RecordingInterface = Spike2RecordingInterfaceSchema.properties.Spike2RecordingInterface; globalStateCopy.schema.source_data.properties.BrukerTiffSinglePlaneImagingInterface = @@ -109,8 +115,11 @@ globalStateCopy.schema.source_data.properties.PlexonSortingInterface = PlexonSortingInterfaceSchema.properties.PlexonSortingInterface; globalStateCopy.schema.source_data.properties.AxonaRecordingInterface = AxonaRecordingInterfaceSchema.properties.AxonaRecordingInterface; +globalStateCopy.schema.source_data.properties.VideoInterface = VideoInterfaceSchema.properties.VideoInterface; globalStateCopy.schema.source_data.properties.NeuralynxRecordingInterface = NeuralynxRecordingInterfaceSchema.properties.NeuralynxRecordingInterface; +globalStateCopy.schema.source_data.properties.Suite2pSegmentationInterface = + Suite2pSegmentationInterfaceSchema.properties.Suite2pSegmentationInterface; globalStateCopy.schema.source_data.properties.AlphaOmegaRecordingInterface = AlphaOmegaRecordingInterfaceSchema.properties.AlphaOmegaRecordingInterface; globalStateCopy.schema.source_data.properties.DeepLabCutInterface = @@ -131,6 +140,8 @@ globalStateCopy.schema.source_data.properties.BrukerTiffMultiPlaneConverter = BrukerTiffMultiPlaneConverterSchema.properties.BrukerTiffMultiPlaneConverter; globalStateCopy.schema.source_data.properties.MiniscopeConverter = MiniscopeConverterSchema.properties.MiniscopeConverter; +globalStateCopy.schema.source_data.properties.CellExplorerRecordingInterface = + CellExplorerRecordingInterfaceSchema.properties.CellExplorerRecordingInterface; const results = globalStateCopy.results; for (let sub in results) { @@ -219,6 +230,12 @@ KiloSortSortingInterfaceGlobalCopy.interfaces.interface = KiloSortSortingInterfa KiloSortSortingInterfaceGlobalCopy.schema.source_data = KiloSortSortingInterfaceSchema; KiloSortSortingInterface.args = { activePage, globalState: KiloSortSortingInterfaceGlobalCopy }; +export const TdtRecordingInterface = PageTemplate.bind({}); +const TdtRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +TdtRecordingInterfaceGlobalCopy.interfaces.interface = TdtRecordingInterface; +TdtRecordingInterfaceGlobalCopy.schema.source_data = TdtRecordingInterfaceSchema; +TdtRecordingInterface.args = { activePage, globalState: TdtRecordingInterfaceGlobalCopy }; + export const Spike2RecordingInterface = PageTemplate.bind({}); const Spike2RecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); Spike2RecordingInterfaceGlobalCopy.interfaces.interface = Spike2RecordingInterface; @@ -318,12 +335,24 @@ AxonaRecordingInterfaceGlobalCopy.interfaces.interface = AxonaRecordingInterface AxonaRecordingInterfaceGlobalCopy.schema.source_data = AxonaRecordingInterfaceSchema; AxonaRecordingInterface.args = { activePage, globalState: AxonaRecordingInterfaceGlobalCopy }; +export const VideoInterface = PageTemplate.bind({}); +const VideoInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +VideoInterfaceGlobalCopy.interfaces.interface = VideoInterface; +VideoInterfaceGlobalCopy.schema.source_data = VideoInterfaceSchema; +VideoInterface.args = { activePage, globalState: VideoInterfaceGlobalCopy }; + export const NeuralynxRecordingInterface = PageTemplate.bind({}); const NeuralynxRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); NeuralynxRecordingInterfaceGlobalCopy.interfaces.interface = NeuralynxRecordingInterface; NeuralynxRecordingInterfaceGlobalCopy.schema.source_data = NeuralynxRecordingInterfaceSchema; NeuralynxRecordingInterface.args = { activePage, globalState: NeuralynxRecordingInterfaceGlobalCopy }; +export const Suite2pSegmentationInterface = PageTemplate.bind({}); +const Suite2pSegmentationInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +Suite2pSegmentationInterfaceGlobalCopy.interfaces.interface = Suite2pSegmentationInterface; +Suite2pSegmentationInterfaceGlobalCopy.schema.source_data = Suite2pSegmentationInterfaceSchema; +Suite2pSegmentationInterface.args = { activePage, globalState: Suite2pSegmentationInterfaceGlobalCopy }; + export const AlphaOmegaRecordingInterface = PageTemplate.bind({}); const AlphaOmegaRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); AlphaOmegaRecordingInterfaceGlobalCopy.interfaces.interface = AlphaOmegaRecordingInterface; @@ -389,3 +418,9 @@ const MiniscopeConverterGlobalCopy = JSON.parse(JSON.stringify(globalState)); MiniscopeConverterGlobalCopy.interfaces.interface = MiniscopeConverter; MiniscopeConverterGlobalCopy.schema.source_data = MiniscopeConverterSchema; MiniscopeConverter.args = { activePage, globalState: MiniscopeConverterGlobalCopy }; + +export const CellExplorerRecordingInterface = PageTemplate.bind({}); +const CellExplorerRecordingInterfaceGlobalCopy = JSON.parse(JSON.stringify(globalState)); +CellExplorerRecordingInterfaceGlobalCopy.interfaces.interface = CellExplorerRecordingInterface; +CellExplorerRecordingInterfaceGlobalCopy.schema.source_data = CellExplorerRecordingInterfaceSchema; +CellExplorerRecordingInterface.args = { activePage, globalState: CellExplorerRecordingInterfaceGlobalCopy };