Skip to content

Commit

Permalink
Add better status reporting for alignments tracks (#4818)
Browse files Browse the repository at this point in the history
  • Loading branch information
cmdcolin authored Feb 4, 2025
1 parent 2f6010f commit 59c1e24
Show file tree
Hide file tree
Showing 14 changed files with 261 additions and 190 deletions.
7 changes: 7 additions & 0 deletions packages/core/pluggableElementTypes/models/BaseTrackModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ export function createBaseTrackModel(
return getConf(self, 'textSearchAdapter')
},

/**
* #getter
*/
get adapterConfig() {
return getConf(self, 'adapter')
},

/**
* #getter
*/
Expand Down
56 changes: 28 additions & 28 deletions plugins/alignments/src/BamAdapter/BamAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,38 +80,31 @@ export default class BamAdapter extends BaseFeatureDataAdapter {
return bam.getHeaderText()
}

private async setupPre(opts?: BaseOptions) {
const { statusCallback = () => {} } = opts || {}
private async setupPre(_opts?: BaseOptions) {
const { bam } = await this.configure()
this.samHeader = await updateStatus(
'Downloading index',
statusCallback,
async () => {
const samHeader = await bam.getHeader()

// use the @SQ lines in the header to figure out the
// mapping between ref ref ID numbers and names
const idToName: string[] = []
const nameToId: Record<string, number> = {}
samHeader
?.filter(l => l.tag === 'SQ')
.forEach((sqLine, refId) => {
const SN = sqLine.data.find(item => item.tag === 'SN')
if (SN) {
// this is the ref name
const refName = SN.value
nameToId[refName] = refId
idToName[refId] = refName
}
})

return { idToName, nameToId }
},
)
const samHeader = await bam.getHeader()

// use the @SQ lines in the header to figure out the
// mapping between ref ref ID numbers and names
const idToName: string[] = []
const nameToId: Record<string, number> = {}
samHeader
?.filter(l => l.tag === 'SQ')
.forEach((sqLine, refId) => {
const SN = sqLine.data.find(item => item.tag === 'SN')
if (SN) {
// this is the ref name
const refName = SN.value
nameToId[refName] = refId
idToName[refId] = refName
}
})

this.samHeader = { idToName, nameToId }
return this.samHeader
}

async setup(opts?: BaseOptions) {
async setupPre2(opts?: BaseOptions) {
if (!this.setupP) {
this.setupP = this.setupPre(opts).catch((e: unknown) => {
this.setupP = undefined
Expand All @@ -121,6 +114,13 @@ export default class BamAdapter extends BaseFeatureDataAdapter {
return this.setupP
}

async setup(opts?: BaseOptions) {
const { statusCallback = () => {} } = opts || {}
return updateStatus('Downloading index', statusCallback, () =>
this.setupPre2(opts),
)
}

async getRefNames(opts?: BaseOptions) {
const { idToName } = await this.setup(opts)
return idToName
Expand Down
68 changes: 36 additions & 32 deletions plugins/alignments/src/CramAdapter/CramAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,42 +148,39 @@ export default class CramAdapter extends BaseFeatureDataAdapter {
return sequence
}

private async setupPre(opts?: BaseOptions) {
const { statusCallback = () => {} } = opts || {}
return updateStatus('Downloading index', statusCallback, async () => {
const conf = await this.configure()
const { cram } = conf
const samHeader = await cram.cram.getSamHeader()

// use the @SQ lines in the header to figure out the mapping between ref
// ID numbers and names
const idToName: string[] = []
const nameToId: Record<string, number> = {}
samHeader
.filter(l => l.tag === 'SQ')
.forEach((sqLine, refId) => {
const SN = sqLine.data.find(item => item.tag === 'SN')
if (SN) {
const refName = SN.value
nameToId[refName] = refId
idToName[refId] = refName
}
})
private async setupPre(_opts?: BaseOptions) {
const conf = await this.configure()
const { cram } = conf
const samHeader = await cram.cram.getSamHeader()

// use the @SQ lines in the header to figure out the mapping between ref
// ID numbers and names
const idToName: string[] = []
const nameToId: Record<string, number> = {}
samHeader
.filter(l => l.tag === 'SQ')
.forEach((sqLine, refId) => {
const SN = sqLine.data.find(item => item.tag === 'SN')
if (SN) {
const refName = SN.value
nameToId[refName] = refId
idToName[refId] = refName
}
})

const readGroups = samHeader
.filter(l => l.tag === 'RG')
.map(rgLine => rgLine.data.find(item => item.tag === 'ID')?.value)
const readGroups = samHeader
.filter(l => l.tag === 'RG')
.map(rgLine => rgLine.data.find(item => item.tag === 'ID')?.value)

const data = { idToName, nameToId, readGroups }
this.samHeader = data
return {
samHeader: data,
...conf,
}
})
const data = { idToName, nameToId, readGroups }
this.samHeader = data
return {
samHeader: data,
...conf,
}
}

private async setup(opts?: BaseOptions) {
private async setupPre2(opts?: BaseOptions) {
if (!this.setupP) {
this.setupP = this.setupPre(opts).catch((e: unknown) => {
this.setupP = undefined
Expand All @@ -193,6 +190,13 @@ export default class CramAdapter extends BaseFeatureDataAdapter {
return this.setupP
}

async setup(opts?: BaseOptions) {
const { statusCallback = () => {} } = opts || {}
return updateStatus('Downloading index', statusCallback, () =>
this.setupPre2(opts),
)
}

async getRefNames(opts?: BaseOptions) {
const { samHeader } = await this.setup(opts)
if (!samHeader.idToName) {
Expand Down
107 changes: 107 additions & 0 deletions plugins/alignments/src/LinearPileupDisplay/doAfterAttach.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { getContainingView, getSession } from '@jbrowse/core/util'
import { getRpcSessionId } from '@jbrowse/core/util/tracks'
import { isAlive } from 'mobx-state-tree'

import { getUniqueModifications } from '../shared/getUniqueModifications'
import { createAutorun } from '../util'

import type { ModificationType, SortedBy } from '../shared/types'
import type { AnyConfigurationModel } from '@jbrowse/core/configuration'
import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'

type LGV = LinearGenomeViewModel

export function doAfterAttach(model: {
autorunReady: boolean
sortedBy?: SortedBy
adapterConfig: AnyConfigurationModel
rendererType: { name: string }
sortReady: boolean
currSortBpPerPx: number
parentTrack: any
renderPropsPre: () => Record<string, unknown>
setCurrSortBpPerPx: (arg: number) => void
setError: (arg: unknown) => void
updateVisibleModifications: (arg: ModificationType[]) => void
setModificationsReady: (arg: boolean) => void
setSortReady: (arg: boolean) => void
setMessage: (arg: string) => void
}) {
createAutorun(
model,
async () => {
const view = getContainingView(model) as LGV
if (!model.autorunReady) {
return
}

model.setCurrSortBpPerPx(view.bpPerPx)
},
{ delay: 1000 },
)
createAutorun(
model,
async () => {
const { rpcManager } = getSession(model)
const view = getContainingView(model) as LGV
if (!model.autorunReady) {
return
}

const { sortedBy, adapterConfig, rendererType, sortReady } = model
const { bpPerPx } = view

if (sortedBy && (!sortReady || model.currSortBpPerPx === view.bpPerPx)) {
const { pos, refName, assemblyName } = sortedBy
// render just the sorted region first
// @ts-expect-error
await model.rendererType.renderInClient(rpcManager, {
assemblyName,
regions: [
{
start: pos,
end: pos + 1,
refName,
assemblyName,
},
],
adapterConfig,
rendererType: rendererType.name,
sessionId: getRpcSessionId(model),
layoutId: view.id,
timeout: 1_000_000,
statusCallback: (arg: string) => {
model.setMessage(arg)
},
...model.renderPropsPre(),
})
}
if (isAlive(model)) {
model.setCurrSortBpPerPx(bpPerPx)
model.setSortReady(true)
}
},
{ delay: 1000 },
)

createAutorun(
model,
async () => {
if (!model.autorunReady) {
return
}
const { adapterConfig } = model
const { staticBlocks } = getContainingView(model) as LGV
const vals = await getUniqueModifications({
model,
adapterConfig,
blocks: staticBlocks,
})
if (isAlive(model)) {
model.updateVisibleModifications(vals)
model.setModificationsReady(true)
}
},
{ delay: 1000 },
)
}
90 changes: 11 additions & 79 deletions plugins/alignments/src/LinearPileupDisplay/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,15 @@ import {
readConfObject,
} from '@jbrowse/core/configuration'
import { getContainingView, getEnv, getSession } from '@jbrowse/core/util'
import { getRpcSessionId } from '@jbrowse/core/util/tracks'
import ColorLensIcon from '@mui/icons-material/ColorLens'
import SwapVertIcon from '@mui/icons-material/SwapVert'
import VisibilityIcon from '@mui/icons-material/Visibility'
import WorkspacesIcon from '@mui/icons-material/Workspaces'
import { observable } from 'mobx'
import { isAlive, types } from 'mobx-state-tree'
import { types } from 'mobx-state-tree'

import { SharedLinearPileupDisplayMixin } from './SharedLinearPileupDisplayMixin'
import { getUniqueModifications } from '../shared/getUniqueModifications'
import {
createAutorun,
getColorForModification,
modificationData,
} from '../util'
import { getColorForModification, modificationData } from '../util'

import type {
ModificationType,
Expand Down Expand Up @@ -453,78 +447,16 @@ function stateModelFactory(configSchema: AnyConfigurationSchemaType) {
})
.actions(self => ({
afterAttach() {
createAutorun(
self,
async () => {
const view = getContainingView(self) as LGV
if (!self.autorunReady) {
return
}

self.setCurrSortBpPerPx(view.bpPerPx)
},
{ delay: 1000 },
)
createAutorun(
self,
async () => {
const { rpcManager } = getSession(self)
const view = getContainingView(self) as LGV
if (!self.autorunReady) {
return
}

const { sortedBy, adapterConfig, rendererType, sortReady } = self
const { bpPerPx } = view

if (
sortedBy &&
(!sortReady || self.currSortBpPerPx === view.bpPerPx)
) {
const { pos, refName, assemblyName } = sortedBy
// render just the sorted region first
// @ts-expect-error
await self.rendererType.renderInClient(rpcManager, {
assemblyName,
regions: [
{
start: pos,
end: pos + 1,
refName,
assemblyName,
},
],
adapterConfig,
rendererType: rendererType.name,
sessionId: getRpcSessionId(self),
layoutId: view.id,
timeout: 1_000_000,
...self.renderPropsPre(),
})
}
if (isAlive(self)) {
self.setCurrSortBpPerPx(bpPerPx)
self.setSortReady(true)
}
},
{ delay: 1000 },
)

createAutorun(self, async () => {
if (!self.autorunReady) {
return
}
const { staticBlocks } = getContainingView(self) as LGV
const vals = await getUniqueModifications({
self,
adapterConfig: getConf(self.parentTrack, 'adapter'),
blocks: staticBlocks,
})
if (isAlive(self)) {
self.updateVisibleModifications(vals)
self.setModificationsReady(true)
// eslint-disable-next-line @typescript-eslint/no-floating-promises
;(async () => {
try {
const { doAfterAttach } = await import('./doAfterAttach')
doAfterAttach(self)
} catch (e) {
getSession(self).notifyError(`${e}`, e)
console.error(e)
}
})
})()
},
}))
}
Expand Down
Loading

0 comments on commit 59c1e24

Please sign in to comment.