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 11 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
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
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.
3 changes: 2 additions & 1 deletion packages/jbrowse-plugin-apollo/cypress/e2e/editFeature.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ describe('Different ways of editing features', () => {
cy.contains('td', '=CDS1').should('not.exist')
})

it('Suggest only valid SO terms from dropdown', () => {
it.only('Suggest only valid SO terms from dropdown', () => {
cy.addAssemblyFromGff('onegene.fasta.gff3', 'test_data/onegene.fasta.gff3')
cy.selectAssemblyToView('onegene.fasta.gff3')
cy.searchFeatures('gx1', 1)
Expand All @@ -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
Loading
Loading