Newer
Older
SxplrAtlas, SxplrParcellation, SxplrTemplate, SxplrRegion, NgLayerSpec, NgPrecompMeshSpec, NgSegLayerSpec, VoiFeature, Point, TThreeMesh, LabelledMap, CorticalFeature, Feature, TabularFeature, GenericInfo, BoundingBox
} from "./sxplrTypes"
import { PathReturn } from "./typeV3"
class TranslateV3 {
#atlasMap: Map<string, PathReturn<"/atlases/{atlas_id}">> = new Map()
retrieveAtlas(atlas: SxplrAtlas): PathReturn<"/atlases/{atlas_id}"> {
return this.#atlasMap.get(atlas.id)
}
async translateAtlas(atlas:PathReturn<"/atlases/{atlas_id}">): Promise<SxplrAtlas> {
this.#atlasMap.set(atlas["@id"], atlas)
return {
id: atlas["@id"],
async translateDs(ds: PathReturn<"/parcellations/{parcellation_id}">['datasets'][number]): Promise<GenericInfo> {
return {
name: ds.name,
desc: ds.description,
link: ds.urls.map(v => ({
href: v.url,
text: 'Link'
}))
}
}
#parcellationMap: Map<string, PathReturn<"/parcellations/{parcellation_id}">> = new Map()
retrieveParcellation(parcellation: SxplrParcellation): PathReturn<"/parcellations/{parcellation_id}"> {
return this.#parcellationMap.get(parcellation.id)
}
async translateParcellation(parcellation:PathReturn<"/parcellations/{parcellation_id}">): Promise<SxplrParcellation> {
const ds = await Promise.all((parcellation.datasets || []).map(ds => this.translateDs(ds)))
const { name, ...rest } = ds[0] || {}
const { ['@id']: prevId } = parcellation.version?.prev || {}
return {
id: parcellation["@id"],
name: parcellation.name,
modality: parcellation.modality,
}
}
#templateMap: Map<string, PathReturn<"/spaces/{space_id}">> = new Map()
#sxplrTmplMap: Map<string, SxplrTemplate> = new Map()
retrieveTemplate(template:SxplrTemplate): PathReturn<"/spaces/{space_id}"> {
return this.#templateMap.get(template.id)
}
async translateTemplate(template:PathReturn<"/spaces/{space_id}">): Promise<SxplrTemplate> {
this.#sxplrTmplMap.set(tmpl.id, tmpl)
return tmpl
}
/**
* map of both name and id to region
*/
#regionMap: Map<string, PathReturn<"/regions/{region_id}">> = new Map()
retrieveRegion(region: SxplrRegion): PathReturn<"/regions/{region_id}"> {
return this.#regionMap.get(region.name)
}
async translateRegion(region: PathReturn<"/regions/{region_id}">): Promise<SxplrRegion> {
const { ['@id']: regionId } = region
this.#regionMap.set(regionId, region)
this.#regionMap.set(region.name, region)
return {
id: region["@id"],
name: region.name,
color: hexToRgb(region.hasAnnotation?.displayColor) as [number, number, number],
parentIds: region.hasParent.map( v => v["@id"] ),
type: "SxplrRegion",
centroid: region.hasAnnotation?.bestViewPoint
? await (async () => {
const bestViewPoint = region.hasAnnotation?.bestViewPoint
const fullSpace = this.#templateMap.get(bestViewPoint.coordinateSpace['@id'])
const space = await this.translateTemplate(fullSpace)
return {
loc: bestViewPoint.coordinates.map(v => v.value) as [number, number, number],
space
}
})()
: null
#hasNoFragment(input: Record<string, unknown>): input is Record<string, string> {
for (const key in input) {
if (typeof input[key] !== 'string') return false
}
return true
}
async #extractNgPrecompUnfrag(input: Record<string, unknown>) {
if (!this.#hasNoFragment(input)) {
throw new Error(`#extractNgPrecompUnfrag can only handle unfragmented volume`)
}
const returnObj: Record<string, {
info: Record<string, any>
}> = {}
for (const key in input) {
if (key !== 'neuroglancer/precomputed') {
continue
}
const url = input[key]
const [ transform, info ] = await Promise.all([
this.cFetch(`${url}/transform.json`).then(res => res.json()) as Promise<number[][]>,
this.cFetch(`${url}/info`).then(res => res.json()) as Promise<Record<string, any>>,
])
returnObj[key] = {
url: input[key],
transform: transform,
info: info,
}
}
return returnObj
}
async translateSpaceToVolumeImage(template: SxplrTemplate): Promise<NgLayerSpec[]> {
if (!template) return []
const space = this.retrieveTemplate(template)
if (!space) return []
const returnObj: NgLayerSpec[] = []
const validImages = space.defaultImage.filter(di => di.formats.includes("neuroglancer/precomputed"))
for (const defaultImage of validImages) {
const { providedVolumes } = defaultImage
const { "neuroglancer/precomputed": precomputedVol, ...rest } = await this.#extractNgPrecompUnfrag(providedVolumes)
if (!precomputedVol) {
console.error(`neuroglancer/precomputed data source has not been found!`)
continue
}
const { transform, info: _info, url } = precomputedVol
const { resolution, size } = _info.scales[0]
const info = {
voxel: size as [number, number, number],
real: [0, 1, 2].map(idx => resolution[idx] * size[idx]) as [number, number, number]
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
transform,
info,
})
}
return returnObj
}
async translateSpaceToSurfaceImage(template: SxplrTemplate): Promise<TThreeMesh[]> {
if (!template) return []
const space = this.retrieveTemplate(template)
if (!space) return []
const returnObj: TThreeMesh[] = []
const validImages = space.defaultImage.filter(di => di.formats.includes("gii-mesh"))
for (const defaultImage of validImages) {
const { providedVolumes, variant } = defaultImage
if (!variant) {
console.warn(`variant is not defined!`)
continue
}
const { ['gii-mesh']: giiMesh } = providedVolumes
if (!giiMesh) {
console.warn(`default image does not have gii-mesh in provided volumes`)
continue
}
if (typeof giiMesh === "string") {
console.warn(`three giiMesh is of type string, must be a dict!`)
continue
}
for (const lateriality in giiMesh) {
const url = giiMesh[lateriality]
returnObj.push({
id: `${template.name}-${variant}-${lateriality}`,
space: template.name,
variant,
laterality: /left/.test(lateriality)
? 'left'
: /right/.test(lateriality)
? 'right'
: null,
url,
})
}
}
return returnObj
}
async translateLabelledMapToThreeLabel(map:PathReturn<"/map">) {
const threeLabelMap: Record<string, { laterality: 'left' | 'right', url: string, region: LabelledMap[] }> = {}
const registerLayer = (url: string, laterality: 'left' | 'right', region: string, label: number) => {
if (!threeLabelMap[url]) {
threeLabelMap[url] = {
laterality,
threeLabelMap[url].region.push({
name: region,
label,
})
}
for (const regionname in map.indices) {
for (const { volume: volIdx, fragment, label } of map.indices[regionname]) {
const volume = map.volumes[volIdx || 0]
if (!volume.formats.includes("gii-label")) {
continue
}
const { ["gii-label"]: giiLabel } = volume.providedVolumes
if (!fragment || !["left hemisphere", "right hemisphere"].includes(fragment)) {
console.warn(`either fragment not defined, or fragment is not '{left|right} hemisphere'. Skipping!`)
continue
}
if (!giiLabel[fragment]) {
continue
}
let laterality: 'left' | 'right'
if (fragment.includes("left")) laterality = "left"
if (fragment.includes("right")) laterality = "right"
if (!laterality) {
console.warn(`cannot determine the laterality! skipping`)
continue
}
registerLayer(giiLabel[fragment], laterality, regionname, label)
}
}
return threeLabelMap
}
mapTPRToFrag = defaultdict(() => defaultdict(() => defaultdict(() => null as string)))
async translateLabelledMapToNgSegLayers(map:PathReturn<"/map">): Promise<Record<string,{layer:NgSegLayerSpec, region: LabelledMap[]}>> {
if (this.#wkmpLblMapToNgSegLayers.has(map)) {
return this.#wkmpLblMapToNgSegLayers.get(map)
}
const nglayerSpecMap: Record<string,{layer:NgSegLayerSpec, region: LabelledMap[]}> = {}
const registerLayer = async (url: string, label: number, region: LabelledMap) => {
let segLayerSpec: {layer:NgSegLayerSpec, region: LabelledMap[]}
if (url in nglayerSpecMap){
segLayerSpec = nglayerSpecMap[url]
} else {
const resp = await this.cFetch(`${url}/transform.json`)
const transform = await resp.json()
segLayerSpec = {
layer: {
labelIndicies: [],
source: `precomputed://${url}`,
transform,
},
region: []
}
nglayerSpecMap[url] = segLayerSpec
}
segLayerSpec.layer.labelIndicies.push(label)
segLayerSpec.region.push(region)
}
/**
* temporary fix
* see https://github.com/FZJ-INM1-BDA/siibra-python/issues/317
*/
if (mapId === "siibra-map-v0.0.1_bigbrain-cortical-labelled") {
if (regionname.includes("left") || regionname.includes("right")) {
continue
}
}
for (const index of map.indices[regionname]) {
const { volume:volumeIdx=0, fragment, label } = index
if (!label) {
console.error(`Attempmting to add labelledmap with label '${label}'`)
}
const error = `Attempting to access map volume with idx '${volumeIdx}'`
if (!map.volumes[volumeIdx]) {
console.error(`${error}, IndexError, Skipping`)
continue
}
const volume = map.volumes[volumeIdx]
if (!volume.providedVolumes["neuroglancer/precomputed"]) {
// volume does not provide neuroglancer/precomputed
// probably when fsaverage has been selected
continue
}
const precomputedVol = volume.providedVolumes["neuroglancer/precomputed"]
if (typeof precomputedVol === "string") {
await registerLayer(precomputedVol, label, { name: regionname, label })
continue
}
if (!precomputedVol[fragment]) {
console.error(`${error}, fragment provided is '${fragment}', but was not available in volume: ${Object.keys(precomputedVol)}`)
continue
}
await registerLayer(precomputedVol[fragment], label, { name: regionname, label })
}
}
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
#cFetchCache = new Map<string, string>()
/**
* Cached fetch
*
* Since translate v3 has no dependency on any angular components.
* We couldn't cache the response. This is a monkey patch to allow for caching of queries.
* @param url: string
* @returns { status: number, json: () => Promise<unknown> }
*/
async cFetch(url: string): Promise<{ status: number, json?: () => Promise<any> }> {
if (!this.#cFetchCache.has(url)) {
const resp = await fetch(url)
if (resp.status >= 400) {
return {
status: resp.status,
}
}
const text = await resp.text()
this.#cFetchCache.set(url, text)
}
const cachedText = this.#cFetchCache.get(url)
return {
status: 200,
json() {
return Promise.resolve(JSON.parse(cachedText))
}
}
}
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
async translateSpaceToAuxMesh(template: SxplrTemplate): Promise<NgPrecompMeshSpec[]>{
if (!template) return []
const space = this.retrieveTemplate(template)
if (!space) return []
const returnObj: NgPrecompMeshSpec[] = []
const validImages = space.defaultImage.filter(di => di.formats.includes("neuroglancer/precompmesh/surface"))
for (const defaultImage of validImages) {
const { providedVolumes } = defaultImage
const { ['neuroglancer/precompmesh/surface']: precompMeshVol } = providedVolumes
if (!precompMeshVol) {
console.error(`neuroglancer/precompmesh/surface data source has not been found!`)
continue
}
if (typeof precompMeshVol === "object") {
console.error(`template default image cannot have fragment`)
continue
}
const splitPrecompMeshVol = precompMeshVol.split(" ")
if (splitPrecompMeshVol.length !== 2) {
console.error(`Expecting exactly two fragments by splitting precompmeshvol, but got ${splitPrecompMeshVol.length}`)
continue
}
const resp = await this.cFetch(`${splitPrecompMeshVol[0]}/transform.json`)
if (resp.status >= 400) {
console.error(`cannot retrieve transform: ${resp.status}`)
continue
}
const transform: number[][] = await resp.json()
returnObj.push({
source: `precompmesh://${splitPrecompMeshVol[0]}`,
transform,
auxMeshes: [{
labelIndicies: [Number(splitPrecompMeshVol[1])],
async #translatePoint(point: components["schemas"]["CoordinatePointModel"]): Promise<Point> {
const getTmpl = (id: string) => {
return this.#sxplrTmplMap.get(id)
loc: point.coordinates.map(v => v.value) as [number, number, number],
get space() {
return getTmpl(point.coordinateSpace['@id'])
}
async translateFeature(feat: PathReturn<"/feature/{feature_id}">): Promise<TabularFeature<number|string|number[]>|Feature> {
if (this.#isTabular(feat)) {
return await this.translateTabularFeature(feat)
}
if (this.#isVoi(feat)) {
return await this.translateVoiFeature(feat)
}
return await this.translateBaseFeature(feat)
}
async translateBaseFeature(feat: PathReturn<"/feature/{feature_id}">): Promise<Feature>{
const { id, name, category, description, datasets } = feat
const dsDescs = datasets.map(ds => ds.description)
const urls = datasets.flatMap(ds => ds.urls).map(v => ({
href: v.url,
text: 'link to dataset'
}))
return {
id,
name,
category,
desc: dsDescs[0] || description,
link: urls,
}
}
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
#isVoi(feat: unknown): feat is PathReturn<"/feature/Image/{feature_id}"> {
return feat['@type'].includes("feature/volume_of_interest")
}
async translateVoiFeature(feat: PathReturn<"/feature/Image/{feature_id}">): Promise<VoiFeature> {
const [superObj, { loc: center }, { loc: maxpoint }, { loc: minpoint }, { "neuroglancer/precomputed": precomputedVol }] = await Promise.all([
this.translateBaseFeature(feat),
this.#translatePoint(feat.boundingbox.center),
this.#translatePoint(feat.boundingbox.maxpoint),
this.#translatePoint(feat.boundingbox.minpoint),
await this.#extractNgPrecompUnfrag(feat.volume.providedVolumes),
])
const { ['@id']: spaceId } = feat.boundingbox.space
const getSpace = (id: string) => this.#sxplrTmplMap.get(id)
const bbox: BoundingBox = {
center,
maxpoint,
minpoint,
get space() {
return getSpace(spaceId)
}
}
return {
...superObj,
bbox,
ngVolume: precomputedVol
}
}
#isTabular(feat: unknown): feat is PathReturn<"/feature/Tabular/{feature_id}"> {
return feat["@type"].includes("feature/tabular")
}
async translateTabularFeature(feat: unknown): Promise<TabularFeature<number | string| number[]>> {
if (!this.#isTabular(feat)) throw new Error(`Feature is not of tabular type`)
const superObj = await this.translateBaseFeature(feat)
const { data: _data } = feat
const { index, columns, data } = _data || {}
return {
...superObj,
columns,
index,
data
}
}
async translateCorticalProfile(feat: PathReturn<"/feature/CorticalProfile/{feature_id}">): Promise<CorticalFeature<number>> {
return {
id: feat.id,
name: feat.name,
desc: feat.description,
link: [
...feat.datasets
.map(ds => ds.urls)
.flatMap(v => v)
.map(url => ({
href: url.url,
text: url.url
})),
...feat.datasets
.map(ds => ({
href: ds.ebrains_page,
text: "ebrains resource"
}))
]
}
}