From 0222ad4a4008b3874b48d65e6e65c81a07f17740 Mon Sep 17 00:00:00 2001 From: Daniel McCloy Date: Wed, 17 Sep 2025 16:09:17 -0500 Subject: [PATCH 01/10] add array of keys to associations.coordsystem Co-authored-by: Ross Blair --- src/schema/associations.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/schema/associations.ts b/src/schema/associations.ts index 3c014415..0a44c679 100644 --- a/src/schema/associations.ts +++ b/src/schema/associations.ts @@ -3,6 +3,7 @@ import type { Schema as MetaSchema } from '@bids/schema/metaschema' import type { BIDSFile } from '../types/filetree.ts' import type { BIDSContext } from './context.ts' +import { loadJSON } from '../files/json.ts' import { loadTSV } from '../files/tsv.ts' import { parseBvalBvec } from '../files/dwi.ts' import { readSidecars, walkBack } from '../files/inheritance.ts' @@ -102,6 +103,16 @@ const associationLookup = { sidecar: await constructSidecar(file), } }, + coordsystem: async (file: BIDSFile, options: { maxRows: number }): Promise => { + const keys = Object.keys(await loadJSON(file, options.maxRows) + .catch((e) => { + return new Map() + })) + return { + path: file.path, + keys: keys, + } + }, } export async function buildAssociations( From 6f11708b5647ddce9c0fec9894ab164da0c6c24e Mon Sep 17 00:00:00 2001 From: Daniel McCloy Date: Wed, 17 Sep 2025 17:10:42 -0500 Subject: [PATCH 02/10] Apply suggestions from code review Co-authored-by: Chris Markiewicz --- src/schema/associations.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/schema/associations.ts b/src/schema/associations.ts index 0a44c679..ec6f99ca 100644 --- a/src/schema/associations.ts +++ b/src/schema/associations.ts @@ -103,10 +103,10 @@ const associationLookup = { sidecar: await constructSidecar(file), } }, - coordsystem: async (file: BIDSFile, options: { maxRows: number }): Promise => { + coordsystem: async (file: BIDSFile, options: { maxRows: number }): Promise<{path: string, keys: string[]}> => { const keys = Object.keys(await loadJSON(file, options.maxRows) .catch((e) => { - return new Map() + return [] })) return { path: file.path, From 9bfeda7ba816dec18ea6411c102d7bd4750f4fea Mon Sep 17 00:00:00 2001 From: Daniel McCloy Date: Wed, 17 Sep 2025 20:36:11 -0500 Subject: [PATCH 03/10] Update src/schema/associations.ts Co-authored-by: Chris Markiewicz --- src/schema/associations.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/schema/associations.ts b/src/schema/associations.ts index ec6f99ca..d7364305 100644 --- a/src/schema/associations.ts +++ b/src/schema/associations.ts @@ -104,10 +104,7 @@ const associationLookup = { } }, coordsystem: async (file: BIDSFile, options: { maxRows: number }): Promise<{path: string, keys: string[]}> => { - const keys = Object.keys(await loadJSON(file, options.maxRows) - .catch((e) => { - return [] - })) + const keys = Object.keys(await loadJSON(file).catch((e) => {})) return { path: file.path, keys: keys, From faf14769652b9da230e25f34500279615918b9e0 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Wed, 17 Sep 2025 21:39:22 -0400 Subject: [PATCH 04/10] Update src/schema/associations.ts --- src/schema/associations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/schema/associations.ts b/src/schema/associations.ts index d7364305..47c0d2b5 100644 --- a/src/schema/associations.ts +++ b/src/schema/associations.ts @@ -104,7 +104,7 @@ const associationLookup = { } }, coordsystem: async (file: BIDSFile, options: { maxRows: number }): Promise<{path: string, keys: string[]}> => { - const keys = Object.keys(await loadJSON(file).catch((e) => {})) + const keys = Object.keys(await loadJSON(file).catch((e) => {return {}})) return { path: file.path, keys: keys, From f2e69cc3c57361812c06cdda530be53040cd2901 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Fri, 19 Sep 2025 10:29:42 -0400 Subject: [PATCH 05/10] feat: Enable multi-file associations, load coordsystems --- src/schema/associations.ts | 70 ++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 26 deletions(-) diff --git a/src/schema/associations.ts b/src/schema/associations.ts index 47c0d2b5..59111309 100644 --- a/src/schema/associations.ts +++ b/src/schema/associations.ts @@ -9,12 +9,15 @@ import { parseBvalBvec } from '../files/dwi.ts' import { readSidecars, walkBack } from '../files/inheritance.ts' import { evalCheck } from './applyRules.ts' import { expressionFunctions } from './expressionLanguage.ts' +import { readEntities } from './entities.ts' import { readText } from '../files/access.ts' interface WithSidecar { sidecar: Record } +type LoadFunction = (file: BIDSFile, options: any) => Promise +type MultiLoadFunction = (files: BIDSFile[], options: any) => Promise function defaultAssociation(file: BIDSFile, _options: any): Promise<{ path: string }> { return Promise.resolve({ path: file.path }) @@ -34,7 +37,7 @@ async function constructSidecar(file: BIDSFile): Promise * * Many associations only consist of a path; this object is for more complex associations. */ -const associationLookup = { +const associationLookup: Record = { events: async (file: BIDSFile, options: { maxRows: number }): Promise => { const columns = await loadTSV(file, options.maxRows) .catch((e) => { @@ -103,11 +106,22 @@ const associationLookup = { sidecar: await constructSidecar(file), } }, - coordsystem: async (file: BIDSFile, options: { maxRows: number }): Promise<{path: string, keys: string[]}> => { - const keys = Object.keys(await loadJSON(file).catch((e) => {return {}})) +} +const multiAssociationLookup: Record = { + coordsystems: async ( + files: BIDSFile[], + options: any, + ): Promise<{ paths: string[]; spaces: string[]; parents: string[] }> => { + const jsons = await Promise.allSettled( + files.map((f) => loadJSON(f).catch(() => ({} as Record))), + ) + const parents = jsons.map((j) => + j.status === 'fulfilled' ? j.value?.ParentCoordinateSystem : undefined + ).filter((p) => p) as string[] return { - path: file.path, - keys: keys, + paths: files.map((f) => f.path), + spaces: files.map((f) => readEntities(f.name).entities?.space), + ParentCoordinateSystems: parents, } }, } @@ -142,33 +156,37 @@ export async function buildAssociations( rule.target.suffix, rule.target?.entities ?? [], ).next().value - if (Array.isArray(file)) { - file = file[0] - } - } catch (error) { - if ( - error && typeof error === 'object' && 'code' in error && - error.code === 'MULTIPLE_INHERITABLE_FILES' - ) { - // @ts-expect-error + } catch (error: any) { + if (error?.code === 'MULTIPLE_INHERITABLE_FILES') { context.dataset.issues.add(error) - break + continue } else { throw error } } - if (file) { - // @ts-expect-error - const load = associationLookup[key] ?? defaultAssociation - // @ts-expect-error - associations[key] = await load(file, { maxRows: context.dataset.options?.maxRows }).catch( - (error: any) => { - if (error.code) { - context.dataset.issues.add({ ...error, location: file.path }) - } - }, - ) + if (file && !(Array.isArray(file) && file.length === 0)) { + const options = { maxRows: context.dataset.options?.maxRows } + if (key in multiAssociationLookup) { + const load = multiAssociationLookup[key] + if (!Array.isArray(file)) { + file = [file] + } + associations[key as keyof Associations] = await load(file, options) + } else { + const load = associationLookup[key] ?? defaultAssociation + if (Array.isArray(file)) { + file = file[0] + } + const location = file.path + associations[key as keyof Associations] = await load(file, { maxRows: context.dataset.options?.maxRows }).catch( + (error: any) => { + if (error.code) { + context.dataset.issues.add({ ...error, location }) + } + }, + ) + } } } return Promise.resolve(associations) From 7a37e565639857e08a5465c30428448830579034 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Fri, 19 Sep 2025 14:08:56 -0400 Subject: [PATCH 06/10] fix: Type checking errors --- src/schema/associations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/schema/associations.ts b/src/schema/associations.ts index 59111309..d429f312 100644 --- a/src/schema/associations.ts +++ b/src/schema/associations.ts @@ -111,7 +111,7 @@ const multiAssociationLookup: Record = { coordsystems: async ( files: BIDSFile[], options: any, - ): Promise<{ paths: string[]; spaces: string[]; parents: string[] }> => { + ): Promise<{ paths: string[]; spaces: string[]; ParentCoordinateSystems: string[] }> => { const jsons = await Promise.allSettled( files.map((f) => loadJSON(f).catch(() => ({} as Record))), ) From c65d98d0667d34f266a1e4020af4fc79717ef531 Mon Sep 17 00:00:00 2001 From: Daniel McCloy Date: Fri, 19 Sep 2025 17:30:43 -0500 Subject: [PATCH 07/10] changelog --- ...9_172910_dan_add_keys_to_assoc_coordsys.md | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 changelog.d/20250919_172910_dan_add_keys_to_assoc_coordsys.md diff --git a/changelog.d/20250919_172910_dan_add_keys_to_assoc_coordsys.md b/changelog.d/20250919_172910_dan_add_keys_to_assoc_coordsys.md new file mode 100644 index 00000000..271e3ade --- /dev/null +++ b/changelog.d/20250919_172910_dan_add_keys_to_assoc_coordsys.md @@ -0,0 +1,48 @@ + + + +### Added + +- New `associations.coordsystems` to collate coordsystem files. + + + + + + + From 078393830872c096b84ad156e5e1e43aa1657ece Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Thu, 25 Sep 2025 15:35:49 -0400 Subject: [PATCH 08/10] Update changelog.d/20250919_172910_dan_add_keys_to_assoc_coordsys.md --- changelog.d/20250919_172910_dan_add_keys_to_assoc_coordsys.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog.d/20250919_172910_dan_add_keys_to_assoc_coordsys.md b/changelog.d/20250919_172910_dan_add_keys_to_assoc_coordsys.md index 271e3ade..3c9307fa 100644 --- a/changelog.d/20250919_172910_dan_add_keys_to_assoc_coordsys.md +++ b/changelog.d/20250919_172910_dan_add_keys_to_assoc_coordsys.md @@ -8,7 +8,8 @@ For top level release notes, leave all the headers commented out. ### Added -- New `associations.coordsystems` to collate coordsystem files. +- Implement `associations.coordsystems` to collate multiple `coordsystem.json` files, + as required by BEP 042 (EMG).