Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feedback ontology loading #482

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
4 changes: 2 additions & 2 deletions packages/apollo-collaboration-server/.development.env
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ SESSION_SECRET=g9fGaRuw06T7hs960Tm7KYyfcFaYEIaG9jfFnVEQ4QyFXmq7
##############################################################################

# Google client id and secret.
GOOGLE_CLIENT_ID=1054515969695-3hpfg1gd0ld3sgj135kfgikolu86vv30.apps.googleusercontent.com
GOOGLE_CLIENT_ID=1000521104117-bhd8r4v11cc053g0b80ui00ss9s5fitv.apps.googleusercontent.com
# Alternatively, can be a path to a file with the client ID
# GOOGLE_CLIENT_ID_FILE=/run/secrets/google-client-id
GOOGLE_CLIENT_SECRET=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
GOOGLE_CLIENT_SECRET=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
# Alternatively, can be a path to a file with the client secret
# GOOGLE_CLIENT_SECRET_FILE=/run/secrets/google-client-secret

Expand Down
17 changes: 13 additions & 4 deletions packages/apollo-mst/src/AnnotationFeatureModel.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { intersection2 } from '@jbrowse/core/util'
import { getSession, intersection2 } from '@jbrowse/core/util'
import {
IAnyModelType,
IMSTMap,
Expand Down Expand Up @@ -127,7 +127,14 @@ export const AnnotationFeatureModel = types
return false
},
get transcriptParts(): TranscriptParts[] {
if (self.type !== 'mRNA') {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
const session = getSession(self) as any
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reuse eslint directives by moving them to top of file

// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use const session = getSession(self) as unknown as ApolloSessionModel instead of any ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const session = getSession(self) as unknown as ApolloSessionModel

I thought about that but I couldn't import ApolloSessionModel from jbrowse-plugin-apollo/src/session

const { apolloDataStore } = session
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
const { featureTypeOntology } = apolloDataStore.ontologyManager
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
if (!featureTypeOntology.isTypeOf(self.type, 'mRNA')) {
throw new Error(
'Only features of type "mRNA" or equivalent can calculate CDS locations',
)
Expand All @@ -137,7 +144,8 @@ export const AnnotationFeatureModel = types
throw new Error('no CDS or exons in mRNA')
}
const cdsChildren = [...children.values()].filter(
(child) => child.type === 'CDS',
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
(child) => featureTypeOntology.isTypeOf(child.type, 'CDS'),
)
if (cdsChildren.length === 0) {
throw new Error('no CDS in mRNA')
Expand All @@ -149,7 +157,8 @@ export const AnnotationFeatureModel = types
let hasIntersected = false
const exonLocations: TranscriptPartLocation[] = []
for (const [, child] of children) {
if (child.type === 'exon') {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
if (featureTypeOntology.isTypeOf(child.type, 'exon')) {
exonLocations.push({ min: child.min, max: child.max })
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/jbrowse-plugin-apollo/cypress.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ module.exports = defineConfig({
// Make viewport long and thin to avoid the scrollbar on the right interfere
// with the coordinates
viewportHeight: 2000,
viewportWidth: 1000,
viewportWidth: 1500,
retries: {
runMode: 2,
},
Expand Down
Binary file modified packages/jbrowse-plugin-apollo/cypress/data/go.json.gz
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ describe('Different ways of editing features', () => {
timeout: 60_000,
force: true,
})

cy.contains('li', /^start_codon$/, {
timeout: 60_000,
matchCase: false,
Expand Down
19 changes: 18 additions & 1 deletion packages/jbrowse-plugin-apollo/cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ async function loadOntology(
OntologyKey,
unknown[]
>
// @ts-expect-error could use more typing
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
ontologyData.meta[0].storeOptions.prefixes = new Map(
// @ts-expect-error could use more typing
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
Object.entries(ontologyData.meta[0].storeOptions.prefixes),
)
await openDB(name, version, {
async upgrade(database: IDBPDatabase): Promise<void> {
const meta = database.createObjectStore('meta')
Expand Down Expand Up @@ -76,7 +83,7 @@ Cypress.Commands.add('addOntologies', () => {
},
{
name: 'Sequence Ontology',
version: '3.1',
version: 'unversioned',
source: {
uri: 'http://localhost:9000/test_data/so-v3.1.json',
locationType: 'UriLocation',
Expand All @@ -94,6 +101,16 @@ Cypress.Commands.add('addOntologies', () => {
{ timeout: 120_000 },
)
})
cy.readFile('cypress/data/so.json.gz', null).then((soGZip: Buffer) => {
cy.wrap<Promise<void>>(
loadOntology(
soGZip,
'Apollo Ontology "Sequence Ontology" "unversioned"',
2,
),
{ timeout: 120_000 },
)
})
})

Cypress.Commands.add('addAssemblyFromGff', (assemblyName, fin) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,11 @@ export const TranscriptSequence = observer(function TranscriptSequence({
if (!refSeq) {
return null
}
if (feature.type !== 'mRNA') {
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
if (!featureTypeOntology) {
throw new Error('featureTypeOntology is undefined')
}
if (featureTypeOntology.isTypeOf(feature.type, 'mRNA')) {
return null
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { CanvasMouseEvent } from '../types'
import { Glyph } from './Glyph'
import { boxGlyph } from './BoxGlyph'
import { LinearApolloDisplayRendering } from '../stateModel/rendering'
import { OntologyRecord } from '../../OntologyManager'

let forwardFillLight: CanvasPattern | null = null
let backwardFillLight: CanvasPattern | null = null
Expand Down Expand Up @@ -80,6 +81,11 @@ function draw(
return
}
const { apolloSelectedFeature } = session
const { apolloDataStore } = session
const { featureTypeOntology } = apolloDataStore.ontologyManager
if (!featureTypeOntology) {
throw new Error('featureTypeOntology is undefined')
}

// Draw background for gene
const topLevelFeatureMinX =
Expand All @@ -93,7 +99,8 @@ function draw(
? topLevelFeatureMinX - topLevelFeatureWidthPx
: topLevelFeatureMinX
const topLevelFeatureTop = row * rowHeight
const topLevelFeatureHeight = getRowCount(feature) * rowHeight
const topLevelFeatureHeight =
getRowCount(feature, featureTypeOntology) * rowHeight

ctx.fillStyle = alpha(theme?.palette.background.paper ?? '#ffffff', 0.6)
ctx.fillRect(
Expand All @@ -106,7 +113,8 @@ function draw(
// Draw lines on different rows for each mRNA
let currentRow = 0
for (const [, mrna] of children) {
if (mrna.type !== 'mRNA') {
const isMrna = featureTypeOntology.isTypeOf(mrna.type, 'mRNA')
if (!isMrna) {
currentRow += 1
continue
}
Expand All @@ -115,7 +123,7 @@ function draw(
continue
}
for (const [, cds] of childrenOfmRNA) {
if (cds.type !== 'CDS') {
if (!featureTypeOntology.isTypeOf(cds.type, 'CDS')) {
continue
}
const minX =
Expand Down Expand Up @@ -144,7 +152,7 @@ function draw(
// Draw exon and CDS for each mRNA
currentRow = 0
for (const [, child] of children) {
if (child.type !== 'mRNA') {
if (!featureTypeOntology.isTypeOf(child.type, 'mRNA')) {
boxGlyph.draw(ctx, child, row, stateModel, displayedRegionIndex)
currentRow += 1
continue
Expand All @@ -155,7 +163,7 @@ function draw(
continue
}
for (const [, exon] of childrenOfmRNA) {
if (exon.type !== 'exon') {
if (!featureTypeOntology.isTypeOf(exon.type, 'exon')) {
continue
}
const minX =
Expand Down Expand Up @@ -296,7 +304,9 @@ function drawHover(
stateModel: LinearApolloDisplay,
ctx: CanvasRenderingContext2D,
) {
const { apolloHover, apolloRowHeight, lgv, theme } = stateModel
const { apolloHover, apolloRowHeight, lgv, session, theme } = stateModel
const { featureTypeOntology } = session.apolloDataStore.ontologyManager

if (!apolloHover) {
return
}
Expand All @@ -320,16 +330,26 @@ function drawHover(
const top = row * apolloRowHeight
const widthPx = length / bpPerPx
ctx.fillStyle = theme?.palette.action.selected ?? 'rgba(0,0,0,04)'
ctx.fillRect(startPx, top, widthPx, apolloRowHeight * getRowCount(feature))

if (!featureTypeOntology) {
throw new Error('featureTypeOntology is undefined')
}
ctx.fillRect(
startPx,
top,
widthPx,
apolloRowHeight * getRowCount(feature, featureTypeOntology),
)
}

function getFeatureFromLayout(
feature: AnnotationFeature,
bp: number,
row: number,
featureTypeOntology: OntologyRecord,
): AnnotationFeature | undefined {
const featureInThisRow: AnnotationFeature[] =
featuresForRow(feature)[row] || []
featuresForRow(feature, featureTypeOntology)[row] || []
for (const f of featureInThisRow) {
let featureObj
if (bp >= f.min && bp <= f.max && f.parent) {
Expand All @@ -339,9 +359,9 @@ function getFeatureFromLayout(
continue
}
if (
featureObj.type === 'CDS' &&
featureTypeOntology.isTypeOf(featureObj.type, 'CDS') &&
featureObj.parent &&
featureObj.parent.type === 'mRNA'
featureTypeOntology.isTypeOf(featureObj.parent.type, 'mRNA')
) {
const { cdsLocations } = featureObj.parent
for (const cdsLoc of cdsLocations) {
Expand All @@ -361,22 +381,28 @@ function getFeatureFromLayout(
return feature
}

function getRowCount(feature: AnnotationFeature, _bpPerPx?: number): number {
function getRowCount(
feature: AnnotationFeature,
featureTypeOntology: OntologyRecord,
_bpPerPx?: number,
): number {
const { children, type } = feature
if (!children) {
return 1
}
const isMrna = featureTypeOntology.isTypeOf(type, 'mRNA')
let rowCount = 0
if (type === 'mRNA') {
if (isMrna) {
for (const [, child] of children) {
if (child.type === 'CDS') {
const isCds = featureTypeOntology.isTypeOf(child.type, 'CDS')
if (isCds) {
rowCount += 1
}
}
return rowCount
}
for (const [, child] of children) {
rowCount += getRowCount(child)
rowCount += getRowCount(child, featureTypeOntology)
}
return rowCount
}
Expand All @@ -387,8 +413,12 @@ function getRowCount(feature: AnnotationFeature, _bpPerPx?: number): number {
* If the row contains an mRNA, the order is CDS -\> exon -\> mRNA -\> gene
* If the row does not contain an mRNA, the order is subfeature -\> gene
*/
function featuresForRow(feature: AnnotationFeature): AnnotationFeature[][] {
if (feature.type !== 'gene') {
function featuresForRow(
feature: AnnotationFeature,
featureTypeOntology: OntologyRecord,
): AnnotationFeature[][] {
const isGene = featureTypeOntology.isTypeOf(feature.type, 'gene')
if (!isGene) {
throw new Error('Top level feature for GeneGlyph must have type "gene"')
}
const { children } = feature
Expand All @@ -397,7 +427,7 @@ function featuresForRow(feature: AnnotationFeature): AnnotationFeature[][] {
}
const features: AnnotationFeature[][] = []
for (const [, child] of children) {
if (child.type !== 'mRNA') {
if (!featureTypeOntology.isTypeOf(child.type, 'mRNA')) {
features.push([child, feature])
continue
}
Expand All @@ -407,9 +437,9 @@ function featuresForRow(feature: AnnotationFeature): AnnotationFeature[][] {
const cdss: AnnotationFeature[] = []
const exons: AnnotationFeature[] = []
for (const [, grandchild] of child.children) {
if (grandchild.type === 'CDS') {
if (featureTypeOntology.isTypeOf(grandchild.type, 'CDS')) {
cdss.push(grandchild)
} else if (grandchild.type === 'exon') {
} else if (featureTypeOntology.isTypeOf(grandchild.type, 'exon')) {
exons.push(grandchild)
}
}
Expand All @@ -423,8 +453,9 @@ function featuresForRow(feature: AnnotationFeature): AnnotationFeature[][] {
function getRowForFeature(
feature: AnnotationFeature,
childFeature: AnnotationFeature,
featureTypeOntology: OntologyRecord,
) {
const rows = featuresForRow(feature)
const rows = featuresForRow(feature, featureTypeOntology)
for (const [idx, row] of rows.entries()) {
if (row.some((feature) => feature._id === childFeature._id)) {
return idx
Expand Down Expand Up @@ -496,7 +527,16 @@ function getDraggableFeatureInfo(
feature: AnnotationFeature,
stateModel: LinearApolloDisplay,
): { feature: AnnotationFeature; edge: 'min' | 'max' } | undefined {
if (feature.type === 'gene' || feature.type === 'mRNA') {
const { session } = stateModel
const { apolloDataStore } = session
const { featureTypeOntology } = apolloDataStore.ontologyManager
if (!featureTypeOntology) {
throw new Error('featureTypeOntology is undefined')
}
const isGene = featureTypeOntology.isTypeOf(feature.type, 'gene')
const isMrna = featureTypeOntology.isTypeOf(feature.type, 'mRNA')
const isCds = featureTypeOntology.isTypeOf(feature.type, 'CDS')
if (isGene || isMrna) {
return
}
const { bp, refName, regionNumber, x } = mousePosition
Expand All @@ -519,14 +559,19 @@ function getDraggableFeatureInfo(
if (Math.abs(maxPx - x) < 4) {
return { feature, edge: 'max' }
}
if (feature.type === 'CDS') {
if (isCds) {
const mRNA = feature.parent
if (!mRNA?.children) {
return
}
const exonChildren = [...mRNA.children.values()].filter(
(child) => child.type === 'exon',
)
const exonChildren: AnnotationFeature[] = []
for (const child of mRNA.children.values()) {
const childIsExon = featureTypeOntology.isTypeOf(child.type, 'exon')
if (childIsExon) {
exonChildren.push(child)
}
}

const overlappingExon = exonChildren.find((child) => {
const [start, end] = intersection2(bp, bp + 1, child.min, child.max)
return start !== undefined && end !== undefined
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@ import {
} from '../stateModel/mouseEvents'
import { LinearApolloDisplayRendering } from '../stateModel/rendering'
import { CanvasMouseEvent } from '../types'
import { OntologyRecord } from '../../OntologyManager'

export interface Glyph {
/** @returns number of layout rows used by this glyph with this feature and zoom level */
getRowCount(feature: AnnotationFeature, bpPerPx: number): number
getRowCount(
feature: AnnotationFeature,
featureTypeOntology: OntologyRecord,
bpPerPx: number,
): number
/** draw the feature's primary rendering on the canvas */
draw(
ctx: CanvasRenderingContext2D,
Expand All @@ -24,10 +29,12 @@ export interface Glyph {
feature: AnnotationFeature,
bp: number,
row: number,
featureTypeOntology: OntologyRecord,
): AnnotationFeature | undefined
getRowForFeature(
feature: AnnotationFeature,
childFeature: AnnotationFeature,
featureTypeOntology: OntologyRecord,
): number | undefined

drawHover(
Expand Down
Loading
Loading