Skip to content

Commit

Permalink
Add CoreGetRegions
Browse files Browse the repository at this point in the history
  • Loading branch information
cmdcolin committed Mar 17, 2023
1 parent 7946f33 commit ee82b1d
Show file tree
Hide file tree
Showing 12 changed files with 327 additions and 200 deletions.
233 changes: 104 additions & 129 deletions packages/core/assemblyManager/assembly.ts

Large diffs are not rendered by default.

29 changes: 14 additions & 15 deletions packages/core/assemblyManager/assemblyConfigSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import { ConfigurationSchema } from '../configuration'
import PluginManager from '../PluginManager'

/**
* #config BaseAssembly
* This corresponds to the assemblies section of the config
* #config Assembly
* This corresponds to the assemblies section of the config, generally accessed
* through the assemblyManager
*/
function assemblyConfigSchema(pluginManager: PluginManager) {
export default function assemblyConfigSchema(pluginManager: PluginManager) {
return ConfigurationSchema(
'BaseAssembly',
{
Expand All @@ -21,8 +22,8 @@ function assemblyConfigSchema(pluginManager: PluginManager) {

/**
* #slot
* sequence refers to a reference sequence track that has an adapter containing,
* importantly, a sequence adapter such as IndexedFastaAdapter
* sequence refers to a reference sequence track that has an adapter
* containing, importantly, a sequence adapter such as IndexedFastaAdapter
*/
sequence: pluginManager.getTrackType('ReferenceSequenceTrack')
.configSchema,
Expand All @@ -42,9 +43,9 @@ function assemblyConfigSchema(pluginManager: PluginManager) {
{
/**
* #slot refNameAliases.adapter
* refNameAliases help resolve e.g. chr1 and 1 as the same entity
* the data for refNameAliases are fetched from an adapter, that is
* commonly a tsv like chromAliases.txt from UCSC or similar
* refNameAliases help resolve e.g. chr1 and 1 as the same entity the
* data for refNameAliases are fetched from an adapter, that is commonly a tsv
* like chromAliases.txt from UCSC or similar
*/
adapter: pluginManager.pluggableConfigSchemaType('adapter'),
},
Expand All @@ -64,8 +65,8 @@ function assemblyConfigSchema(pluginManager: PluginManager) {
{
/**
* #slot cytobands.adapter
* cytoband data is fetched from an adapter, and can be displayed by a
* view type as ideograms
* cytoband data is fetched from an adapter, and can be displayed by
* a view type as ideograms
*/
adapter: pluginManager.pluggableConfigSchemaType('adapter'),
},
Expand Down Expand Up @@ -94,13 +95,11 @@ function assemblyConfigSchema(pluginManager: PluginManager) {
{
/**
* #identifier name
* the name acts as a unique identifier in the config, so it cannot be duplicated.
* it usually a short human readable "id" like hg38, but you can also optionally
* customize the assembly "displayName" config slot
* the name acts as a unique identifier in the config, so it cannot be
* duplicated. it usually a short human readable "id" like hg38, but you can
* also optionally customize the assembly "displayName" config slot
*/
explicitIdentifier: 'name',
},
)
}

export default assemblyConfigSchema
27 changes: 14 additions & 13 deletions packages/core/assemblyManager/assemblyManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ import {
Instance,
IAnyType,
} from 'mobx-state-tree'
import { when } from '../util'
import { reaction } from 'mobx'
// locals
import { when } from '../util'
import { readConfObject, AnyConfigurationModel } from '../configuration'
import assemblyFactory, { Assembly } from './assembly'
import PluginManager from '../PluginManager'

function assemblyManagerFactory(conf: IAnyType, pm: PluginManager) {
export default function assemblyManagerFactory(
conf: IAnyType,
pm: PluginManager,
) {
type Conf = Instance<typeof conf> | string
return types
.model({
Expand Down Expand Up @@ -116,21 +120,20 @@ function assemblyManagerFactory(conf: IAnyType, pm: PluginManager) {
addDisposer(
self,
reaction(
// have to slice it to be properly reacted to
() => self.assemblyList,
assemblyConfigs => {
confs => {
self.assemblies.forEach(asm => {
if (!asm.configuration) {
this.removeAssembly(asm)
}
})
assemblyConfigs.forEach(assemblyConfig => {
const existingAssemblyIdx = self.assemblies.findIndex(
assembly =>
assembly.name === readConfObject(assemblyConfig, 'name'),
)
if (existingAssemblyIdx === -1) {
this.addAssembly(assemblyConfig)
confs.forEach(conf => {
if (
!self.assemblies.some(
a => a.name === readConfObject(conf, 'name'),
)
) {
this.addAssembly(conf)
}
})
},
Expand All @@ -154,5 +157,3 @@ function assemblyManagerFactory(conf: IAnyType, pm: PluginManager) {
},
}))
}

export default assemblyManagerFactory
65 changes: 65 additions & 0 deletions packages/core/assemblyManager/loadRefNameMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { BaseOptions, checkRefName, RefNameAliases } from './util'
import RpcManager from '../rpc/RpcManager'
import { when } from '../util'

export interface BasicRegion {
start: number
end: number
refName: string
assemblyName: string
}

export async function loadRefNameMap(
assembly: {
name: string
regions: BasicRegion[] | undefined
refNameAliases: RefNameAliases | undefined
getCanonicalRefName: (arg: string) => string
rpcManager: RpcManager
},
adapterConfig: unknown,
options: BaseOptions,
signal?: AbortSignal,
) {
const { sessionId } = options
await when(() => !!(assembly.regions && assembly.refNameAliases), {
signal,
name: 'when assembly ready',
})

const refNames = (await assembly.rpcManager.call(
sessionId,
'CoreGetRefNames',
{
adapterConfig,
signal,
...options,
},
{ timeout: 1000000 },
)) as string[]

const { refNameAliases } = assembly
if (!refNameAliases) {
throw new Error(`error loading assembly ${assembly.name}'s refNameAliases`)
}

const refNameMap = Object.fromEntries(
refNames.map(name => {
checkRefName(name)
return [assembly.getCanonicalRefName(name), name]
}),
)

// make the reversed map too
const reversed = Object.fromEntries(
Object.entries(refNameMap).map(([canonicalName, adapterName]) => [
adapterName,
canonicalName,
]),
)

return {
forwardMap: refNameMap,
reverseMap: reversed,
}
}
69 changes: 69 additions & 0 deletions packages/core/assemblyManager/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { AnyConfigurationModel } from '../configuration'
import jsonStableStringify from 'json-stable-stringify'
import { BaseRefNameAliasAdapter } from '../data_adapters/BaseAdapter'
import PluginManager from '../PluginManager'
import { BasicRegion } from './loadRefNameMap'

export type RefNameAliases = Record<string, string>

export interface BaseOptions {
signal?: AbortSignal
sessionId: string
statusCallback?: Function
}

export async function getRefNameAliases(
config: AnyConfigurationModel,
pm: PluginManager,
signal?: AbortSignal,
) {
const type = pm.getAdapterType(config.type)
const CLASS = await type.getAdapterClass()
const adapter = new CLASS(config, undefined, pm) as BaseRefNameAliasAdapter
return adapter.getRefNameAliases({ signal })
}

export async function getCytobands(
config: AnyConfigurationModel,
pm: PluginManager,
) {
const type = pm.getAdapterType(config.type)
const CLASS = await type.getAdapterClass()
const adapter = new CLASS(config, undefined, pm)

// @ts-expect-error
return adapter.getData()
}

export async function getAssemblyRegions(
assembly: any,
adapterConfig: AnyConfigurationModel,
signal?: AbortSignal,
): Promise<BasicRegion[]> {
const sessionId = 'loadRefNames'
return assembly.rpcManager.call(
sessionId,
'CoreGetRegions',
{
adapterConfig,
sessionId,
signal,
},
{ timeout: 1000000 },
)
}

const refNameRegex = new RegExp(
'[0-9A-Za-z!#$%&+./:;?@^_|~-][0-9A-Za-z!#$%&*+./:;=?@^_|~-]*',
)

// Valid refName pattern from https://samtools.github.io/hts-specs/SAMv1.pdf
export function checkRefName(refName: string) {
if (!refNameRegex.test(refName)) {
throw new Error(`Encountered invalid refName: "${refName}"`)
}
}

export function getAdapterId(adapterConf: unknown) {
return jsonStableStringify(adapterConf)
}
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,6 @@ export function createBaseTrackModel(
onClick: () => {
getSession(self).queueDialog(handleClose => [
SaveTrackDataDlg,
// @ts-ignore
{ model: self, handleClose },
])
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
TextField,
Typography,
} from '@mui/material'
import { IAnyStateTreeNode } from 'mobx-state-tree'
import { makeStyles } from 'tss-react/mui'
import { saveAs } from 'file-saver'
import { observer } from 'mobx-react'
Expand All @@ -22,7 +23,6 @@ import {
Region,
} from '@jbrowse/core/util'
import { getConf } from '@jbrowse/core/configuration'
import { BaseTrackModel } from '@jbrowse/core/pluggableElementTypes'

// icons
import GetAppIcon from '@mui/icons-material/GetApp'
Expand All @@ -41,7 +41,7 @@ const useStyles = makeStyles()({
})

async function fetchFeatures(
track: BaseTrackModel,
track: IAnyStateTreeNode,
regions: Region[],
signal?: AbortSignal,
) {
Expand All @@ -60,7 +60,7 @@ export default observer(function SaveTrackDataDlg({
model,
handleClose,
}: {
model: BaseTrackModel
model: IAnyStateTreeNode
handleClose: () => void
}) {
const { classes } = useStyles()
Expand All @@ -77,11 +77,9 @@ export default observer(function SaveTrackDataDlg({
// eslint-disable-next-line @typescript-eslint/no-floating-promises
;(async () => {
try {
const view = getContainingView(model)
const view = getContainingView(model) as { visibleRegions?: Region[] }
setError(undefined)
setFeatures(
await fetchFeatures(model, view.dynamicBlocks.contentBlocks),
)
setFeatures(await fetchFeatures(model, view.visibleRegions || []))
} catch (e) {
console.error(e)
setError(e)
Expand All @@ -92,20 +90,24 @@ export default observer(function SaveTrackDataDlg({
useEffect(() => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
;(async () => {
const view = getContainingView(model)
const session = getSession(model)
if (!features) {
return
}
const str = await (type === 'gff3'
? stringifyGFF3(features)
: stringifyGenbank({
features,
session,
assemblyName: view.dynamicBlocks.contentBlocks[0].assemblyName,
}))
try {
const view = getContainingView(model)
const session = getSession(model)
if (!features) {
return
}
const str = await (type === 'gff3'
? stringifyGFF3(features)
: stringifyGenbank({
features,
session,
assemblyName: view.dynamicBlocks.contentBlocks[0].assemblyName,
}))

setStr(str)
setStr(str)
} catch (e) {
setError(e)
}
})()
}, [type, features, model])

Expand All @@ -121,10 +123,7 @@ export default observer(function SaveTrackDataDlg({

<FormControl>
<FormLabel>File type</FormLabel>
<RadioGroup
value={type}
onChange={event => setType(event.target.value)}
>
<RadioGroup value={type} onChange={e => setType(e.target.value)}>
{Object.entries(options).map(([key, val]) => (
<FormControlLabel
key={key}
Expand Down Expand Up @@ -153,16 +152,10 @@ export default observer(function SaveTrackDataDlg({
<DialogActions>
<Button
onClick={() => {
saveAs(
new Blob([str || ''], {
type: 'text/plain;charset=utf-8',
}),
`jbrowse_track_data.${
options[type as keyof typeof options].extension
}`,
)
const ext = options[type as keyof typeof options].extension
const blob = new Blob([str], { type: 'text/plain;charset=utf-8' })
saveAs(blob, `jbrowse_track_data.${ext}`)
}}
disabled={!str || !!error}
startIcon={<GetAppIcon />}
>
Download
Expand Down
3 changes: 2 additions & 1 deletion packages/core/rpc/coreRpcMethods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ export { default as CoreGetFileInfo } from './methods/CoreGetFileInfo'
export { default as CoreGetFeatures } from './methods/CoreGetFeatures'
export { default as CoreRender } from './methods/CoreRender'
export { default as CoreFreeResources } from './methods/CoreFreeResources'
export { type RenderArgs } from './methods/util'
export { default as CoreEstimateRegionStats } from './methods/CoreEstimateRegionStats'
export { default as CoreGetRegions } from './methods/CoreGetRegions'
export { type RenderArgs } from './methods/util'
Loading

0 comments on commit ee82b1d

Please sign in to comment.