import { sidecarValueHasHed } from './utils'
import { Issue } from '../common/issues/issues'
import parseTSV from './tsvParser'
/**
* Base class for BIDS data.
* @deprecated Will be removed in v4.0.0.
*/
class BidsData {
/**
* A mapping from unparsed HED strings to ParsedHedString objects.
* @deprecated Will be removed in v4.0.0.
* @type {Map<string, ParsedHedString>}
*/
parsedStringMapping
/**
* A Mapping from definition names to their associated ParsedHedGroup objects.
* @deprecated Will be removed in v4.0.0.
* @type {Map<string, ParsedHedGroup>}
*/
definitions
/**
* A list of HED validation issues.
* This will be converted to BidsIssue objects later on.
* @deprecated Will be removed in v4.0.0.
* @type {Issue[]}
*/
hedIssues
constructor() {
this.parsedStringMapping = new Map()
this.definitions = new Map()
this.hedIssues = []
}
}
/**
* A BIDS file.
*/
class BidsFile extends BidsData {
/**
* The name of this file.
* @type {string}
*/
name
/**
* The file object representing this file data.
* This is used to generate BidsIssue objects.
* @type {object}
*/
file
constructor(name, file) {
super()
this.name = name
this.file = file
}
}
/**
* A BIDS JSON file.
*/
export class BidsJsonFile extends BidsFile {
/**
* This file's JSON data.
* @type {object}
*/
jsonData
constructor(name, jsonData, file) {
super(name, file)
this.jsonData = jsonData
}
}
/**
* A BIDS TSV file.
*/
export class BidsTsvFile extends BidsFile {
/**
* This file's parsed TSV data.
* @type {{headers: string[], rows: string[][]}}
*/
parsedTsv
/**
* HED strings in the "HED" column of the TSV data.
* @type {string[]}
*/
hedColumnHedStrings
/**
* The list of potential JSON sidecars.
* @type {string[]}
*/
potentialSidecars
/**
* The pseudo-sidecar object representing the merged sidecar data.
* @type {BidsSidecar}
*/
mergedSidecar
/**
* The extracted HED data for the merged pseudo-sidecar.
* @type {Map<string, string|Object<string, string>>}
*/
sidecarHedData
/**
* Constructor.
*
* @todo This interface is provisional and subject to modification in version 4.0.0.
*
* @param {string} name The name of the TSV file.
* @param {{headers: string[], rows: string[][]}|string} tsvData This file's TSV data.
* @param {object} file The file object representing this file.
* @param {string[]} potentialSidecars The list of potential JSON sidecars.
* @param {object} mergedDictionary The merged sidecar data.
*/
constructor(name, tsvData, file, potentialSidecars = [], mergedDictionary = {}) {
super(name, file)
if (typeof tsvData === 'string') {
tsvData = parseTSV(tsvData)
}
this.parsedTsv = tsvData
this.potentialSidecars = potentialSidecars
this.mergedSidecar = new BidsSidecar(name, mergedDictionary, null)
this.sidecarHedData = this.mergedSidecar.hedData
this._parseHedColumn()
}
_parseHedColumn() {
const hedColumnIndex = this.parsedTsv.headers.indexOf('HED')
if (hedColumnIndex === -1) {
this.hedColumnHedStrings = []
} else {
this.hedColumnHedStrings = this.parsedTsv.rows
.slice(1)
.map((rowCells) => rowCells[hedColumnIndex])
.map((hedCell) => (hedCell && hedCell !== 'n/a' ? hedCell : ''))
}
}
}
/**
* A BIDS events.tsv file.
*/
export class BidsEventFile extends BidsTsvFile {
/**
* Constructor.
*
* @todo This interface is subject to modification in version 4.0.0.
*
* @param {string} name The name of the event TSV file.
* @param {string[]} potentialSidecars The list of potential JSON sidecars.
* @param {object} mergedDictionary The merged sidecar data.
* @param {{headers: string[], rows: string[][]}|string} tsvData This file's TSV data.
* @param {object} file The file object representing this file.
*/
constructor(name, potentialSidecars, mergedDictionary, tsvData, file) {
super(name, tsvData, file, potentialSidecars, mergedDictionary)
}
}
/**
* A BIDS TSV file other than an events.tsv file.
*/
export class BidsTabularFile extends BidsTsvFile {
/**
* Constructor.
*
* @todo This interface is subject to modification in version 4.0.0.
*
* @param {string} name The name of the TSV file.
* @param {string[]} potentialSidecars The list of potential JSON sidecars.
* @param {object} mergedDictionary The merged sidecar data.
* @param {{headers: string[], rows: string[][]}|string} tsvData This file's TSV data.
* @param {object} file The file object representing this file.
*/
constructor(name, potentialSidecars, mergedDictionary, tsvData, file) {
super(name, tsvData, file, potentialSidecars, mergedDictionary)
}
}
export class BidsSidecar extends BidsJsonFile {
/**
* The extracted HED data for this sidecar.
* @type {Map<string, string|Object<string, string>>}
*/
hedData
/**
* The extracted HED value strings.
* @type {string[]}
*/
hedValueStrings
/**
* The extracted HED categorical strings.
* @type {string[]}
*/
hedCategoricalStrings
/**
* Constructor.
*
* @param {string} name The name of the sidecar file.
* @param {Object} sidecarData The raw JSON data.
* @param {Object} file The file object representing this file.
*/
constructor(name, sidecarData = {}, file) {
super(name, sidecarData, file)
this._filterHedStrings()
this._categorizeHedStrings()
}
_filterHedStrings() {
const sidecarHedTags = Object.entries(this.jsonData)
.map(([sidecarKey, sidecarValue]) => {
if (sidecarValueHasHed(sidecarValue)) {
return [sidecarKey, sidecarValue.HED]
} else {
return []
}
})
.filter((x) => x.length > 0)
this.hedData = new Map(sidecarHedTags)
}
_categorizeHedStrings() {
this.hedValueStrings = []
this.hedCategoricalStrings = []
for (const sidecarValue of this.hedData.values()) {
if (typeof sidecarValue === 'string') {
this.hedValueStrings.push(sidecarValue)
} else {
this.hedCategoricalStrings.push(...Object.values(sidecarValue))
}
}
}
/**
* The extracted HED strings.
* @returns {string[]}
*/
get hedStrings() {
return this.hedValueStrings.concat(this.hedCategoricalStrings)
}
/**
* An alias for {@link jsonData}.
* @returns {Object}
*/
get sidecarData() {
return this.jsonData
}
}
/**
* Fallback default dataset_description.json file.
* @deprecated Will be removed in v4.0.0.
* @type {BidsJsonFile}
*/
const fallbackDatasetDescription = new BidsJsonFile('./dataset_description.json', null)
export class BidsDataset extends BidsData {
/**
* The dataset's event file data.
* @type {BidsEventFile[]}
*/
eventData
/**
* The dataset's sidecar data.
* @type {BidsSidecar[]}
*/
sidecarData
/**
* The dataset's dataset_description.json file.
* @type {BidsJsonFile}
*/
datasetDescription
/**
* The dataset's root directory as an absolute path.
* @type {string|null}
*/
datasetRootDirectory
constructor(eventData, sidecarData, datasetDescription = fallbackDatasetDescription, datasetRootDirectory = null) {
super()
this.eventData = eventData
this.sidecarData = sidecarData
this.datasetDescription = datasetDescription
this.datasetRootDirectory = datasetRootDirectory
}
}
const bidsHedErrorCodes = new Set([104, 106, 107])
export class BidsIssue {
/**
* The BIDS issue code.
* @type {number}
*/
code
/**
* The file associated with this issue.
* @type {Object}
*/
file
/**
* The evidence for this issue.
* @type {string}
*/
evidence
constructor(issueCode, file, evidence) {
this.code = issueCode
this.file = file
this.evidence = evidence
}
/**
* Whether this issue is an error.
* @return {boolean}
*/
isError() {
return bidsHedErrorCodes.has(this.code)
}
static generateInternalErrorPromise(error) {
return Promise.resolve([new BidsIssue(107, null, error.message)])
}
}
export class BidsHedIssue extends BidsIssue {
/**
* The HED Issue object corresponding to this object.
* @type {Issue}
*/
hedIssue
constructor(hedIssue, file) {
super(hedIssue.level === 'warning' ? 105 : 104, file, hedIssue.message)
this.hedIssue = hedIssue
}
}