Files from your host process
Open a track on bytes your host already holds — a notebook kernel, an R session — with no web server and no CORS.
localFiles is name -> bytes, and a track then refers to that name as if it
were a URL. It exists for hosts whose data lives in a process rather than at a
URL — a Jupyter kernel, an R session, a desktop app. On an ordinary web page
you would serve the file; the reason to reach for this is that there is no
server to serve it from, and no CORS or public bucket to arrange.
The bytes become a Blob, which JBrowse reads by byte range through
Blob.slice(). So an indexed file stays indexed — a bgzipped+tabixed table, a
BAM with its .bai, a bigWig — and only the region on screen is ever touched.
That is what makes this the answer for a result too big to inline as features,
which the notebooks below measure.
Register an index under its conventional sibling name (peaks.bed.gz +
peaks.bed.gz.tbi). The adapter asks for it by that name, derived from the data
file’s, so nothing on your side has to know which adapter wanted which file.
To add files to a view that is already up, rather than rebuilding it, use
createLinearGenomeView’s addLocalFiles.
In a notebook
jbrowse-anywidget wraps this as
add_local_file(path), which reads the file and its sibling index out of the
kernel:
- Large results — every NCBI RefSeq exon in the human genome, as tabix rather than as JSON.
- Large signal — the same comparison for quantitative data, via bigWig.
View source — 103 lines
import { useEffect, useState } from 'react'
import {
JBrowseLinearGenomeView,
useCreateViewState,
} from '@jbrowse/react-linear-genome-view2'
import type { LocalFileInput } from '@jbrowse/react-linear-genome-view2'
const assembly = {
name: 'volvox',
uri: 'https://jbrowse.org/genomes/volvox/volvox.2bit',
}
// The track refers to the file by the name it was registered under, exactly as
// it would a URL — so the `.bam` extension still picks the adapter, and the
// adapter still derives its `.bai` sibling by name. Nothing here knows the file
// is local.
const tracks = [
{
type: 'AlignmentsTrack',
trackId: 'local_bam',
name: 'volvox-sorted.bam (in memory)',
assemblyNames: ['volvox'],
adapter: { type: 'BamAdapter', uri: 'volvox-sorted.bam' },
},
]
// `localFiles` is read once, when the engine is built, like every other option.
// So a new set of files is a new engine, and this component is remounted on a
// `key` below to get one. The engine it leaves behind is useCreateViewState's
// to destroy — React owns it, and unmounting takes the RPC worker threads and
// the autoruns with it.
function LocalFileView({ localFiles }: { localFiles: LocalFileInput }) {
const state = useCreateViewState({
assembly,
tracks,
localFiles,
init: { loc: 'ctgA:1..20000', tracks: ['local_bam'] },
})
return <JBrowseLinearGenomeView viewState={state} />
}
export default function WithLocalFiles() {
const [files, setFiles] = useState<LocalFileInput>()
const [error, setError] = useState<unknown>()
// Standing in for whatever puts bytes in your host's memory. In a Jupyter
// kernel that is `path.read_bytes()` and anywidget's binary channel; in an R
// session, `readBin`. A web page has no such thing, so this demo fetches —
// which is the one part of it you would not write.
useEffect(() => {
const mount = { unmounted: false }
void (async () => {
try {
const [bam, bai] = await Promise.all(
['volvox-sorted.bam', 'volvox-sorted.bam.bai'].map(async name => {
const res = await fetch(
`https://jbrowse.org/code/jb2/main/test_data/volvox/${name}`,
)
if (!res.ok) {
throw new Error(`HTTP ${res.status} fetching ${name}`)
}
return new Uint8Array(await res.arrayBuffer())
}),
)
if (!mount.unmounted) {
// the whole API: a name, and the bytes behind it. Registering the
// index under its conventional sibling name is what keeps the file
// indexed — without it the adapter has no index to seek with
setFiles({
'volvox-sorted.bam': bam!,
'volvox-sorted.bam.bai': bai!,
})
}
} catch (e) {
console.error(e)
setError(e)
}
})()
return () => {
mount.unmounted = true
}
}, [])
const bytes = files?.['volvox-sorted.bam'] as Uint8Array | undefined
return (
<div>
<div style={{ padding: 8, fontSize: 13, background: '#8881' }}>
{error
? `could not read the file: ${error}`
: bytes
? `${bytes.length.toLocaleString()} bytes of BAM held in this page's memory — the pileup below is read out of it by byte range, so panning touches only the bytes for the region on screen`
: 'reading the file into memory…'}
</div>
{files ? (
// keyed on the names, so a different set of files rebuilds the engine
<LocalFileView key={Object.keys(files).join(',')} localFiles={files} />
) : null}
</div>
)
}