From eb7eb501d74878b5a73bd337020121a82ed6b9ea Mon Sep 17 00:00:00 2001 From: Colin Date: Thu, 1 May 2025 12:25:14 -0400 Subject: [PATCH 1/2] Init --- .../core/configuration/configurationSchema.ts | 7 +- .../AllVsAllPAFAdapter/AllVsAllPAFAdapter.ts | 176 ++++++++++++++++++ .../src/AllVsAllPAFAdapter/configSchema.ts | 61 ++++++ .../src/AllVsAllPAFAdapter/index.ts | 21 +++ .../src/AllVsAllPAFAdapter/util.ts | 108 +++++++++++ plugins/comparative-adapters/src/index.ts | 2 + yarn.lock | 41 ++-- 7 files changed, 392 insertions(+), 24 deletions(-) create mode 100644 plugins/comparative-adapters/src/AllVsAllPAFAdapter/AllVsAllPAFAdapter.ts create mode 100644 plugins/comparative-adapters/src/AllVsAllPAFAdapter/configSchema.ts create mode 100644 plugins/comparative-adapters/src/AllVsAllPAFAdapter/index.ts create mode 100644 plugins/comparative-adapters/src/AllVsAllPAFAdapter/util.ts diff --git a/packages/core/configuration/configurationSchema.ts b/packages/core/configuration/configurationSchema.ts index 43131a826a..e9db4f9b95 100644 --- a/packages/core/configuration/configurationSchema.ts +++ b/packages/core/configuration/configurationSchema.ts @@ -204,7 +204,6 @@ function makeConfigurationSchemaModel< completeModel = completeModel.postProcessSnapshot(snap => { const newSnap: SnapshotOut = {} let matchesDefault = true - // let keyCount = 0 for (const [key, value] of Object.entries(snap)) { if (matchesDefault) { if (typeof defaultSnap[key] === 'object' && typeof value === 'object') { @@ -221,14 +220,10 @@ function makeConfigurationSchemaModel< !isEmptyObject(value) && !isEmptyArray(value) ) { - // keyCount += 1 newSnap[key] = value } } - if (matchesDefault) { - return {} - } - return newSnap + return matchesDefault ? {} : newSnap }) if (options.preProcessSnapshot) { diff --git a/plugins/comparative-adapters/src/AllVsAllPAFAdapter/AllVsAllPAFAdapter.ts b/plugins/comparative-adapters/src/AllVsAllPAFAdapter/AllVsAllPAFAdapter.ts new file mode 100644 index 0000000000..67462da0d2 --- /dev/null +++ b/plugins/comparative-adapters/src/AllVsAllPAFAdapter/AllVsAllPAFAdapter.ts @@ -0,0 +1,176 @@ +import { readConfObject } from '@jbrowse/core/configuration' +import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter' +import { fetchAndMaybeUnzip } from '@jbrowse/core/util' +import { openLocation } from '@jbrowse/core/util/io' +import { doesIntersect2 } from '@jbrowse/core/util/range' +import { ObservableCreate } from '@jbrowse/core/util/rxjs' +import { MismatchParser } from '@jbrowse/plugin-alignments' + +import SyntenyFeature from '../SyntenyFeature' +import { + flipCigar, + parseLineByLine, + parsePAFLine, + swapIndelCigar, +} from '../util' +import { getWeightedMeans } from './util' + +import type { PAFRecord } from './util' +import type { AnyConfigurationModel } from '@jbrowse/core/configuration' +import type { BaseOptions } from '@jbrowse/core/data_adapters/BaseAdapter' +import type { Feature } from '@jbrowse/core/util' +import type { Region } from '@jbrowse/core/util/types' + +const { parseCigar } = MismatchParser + +interface PAFOptions extends BaseOptions { + config?: AnyConfigurationModel +} + +export default class AllVsAllPAFAdapter extends BaseFeatureDataAdapter { + private setupP?: Promise + + public static capabilities = ['getFeatures', 'getRefNames'] + + async setup(opts?: BaseOptions) { + if (!this.setupP) { + this.setupP = this.setupPre(opts).catch((e: unknown) => { + this.setupP = undefined + throw e + }) + } + return this.setupP + } + + async setupPre(opts?: BaseOptions) { + return parseLineByLine( + await fetchAndMaybeUnzip( + openLocation(this.getConf('pafLocation'), this.pluginManager), + opts, + ), + parsePAFLine, + opts, + ) + } + + async hasDataForRefName() { + // determining this properly is basically a call to getFeatures so is not + // really that important, and has to be true or else getFeatures is never + // called (BaseAdapter filters it out) + return true + } + + getAssemblyNames() { + return this.getConf('assemblyNames') as string[] + } + + async getRefNames(opts: BaseOptions = {}) { + // @ts-expect-error + const r1 = opts.regions?.[0].assemblyName + const feats = await this.setup(opts) + + const idx = this.getAssemblyNames().indexOf(r1) + if (idx !== -1) { + const set = new Set() + for (const feat of feats) { + set.add(idx === 0 ? feat.qname : feat.tname) + } + return [...set] + } + console.warn('Unable to do ref renaming on adapter') + return [] + } + + getFeatures(query: Region, opts: PAFOptions = {}) { + return ObservableCreate(async observer => { + let pafRecords = await this.setup(opts) + const { config } = opts + + // note: this is not the adapter config, it is responding to a display + // setting passed in via the opts parameter + if (config && readConfObject(config, 'colorBy') === 'meanQueryIdentity') { + pafRecords = getWeightedMeans(pafRecords) + } + const assemblyNames = this.getAssemblyNames() + + // The index of the assembly name in the query list corresponds to the + // adapter in the subadapters list + const { start: qstart, end: qend, refName: qref, assemblyName } = query + const index = assemblyNames.indexOf(assemblyName) + + // if the getFeatures::query is on the query assembly, flip orientation + // of data + const flip = index === 0 + if (index === -1) { + console.warn(`${assemblyName} not found in this adapter`) + observer.complete() + } + + // eslint-disable-next-line unicorn/no-for-loop + for (let i = 0; i < pafRecords.length; i++) { + const r = pafRecords[i]! + let start = 0 + let end = 0 + let refName = '' + let mateName = '' + let mateStart = 0 + let mateEnd = 0 + + if (flip) { + start = r.qstart + end = r.qend + refName = r.qname + mateName = r.tname + mateStart = r.tstart + mateEnd = r.tend + } else { + start = r.tstart + end = r.tend + refName = r.tname + mateName = r.qname + mateStart = r.qstart + mateEnd = r.qend + } + const { extra, strand } = r + if (refName === qref && doesIntersect2(qstart, qend, start, end)) { + const { numMatches = 0, blockLen = 1, cg, ...rest } = extra + + let CIGAR = extra.cg + if (extra.cg) { + if (flip && strand === -1) { + CIGAR = flipCigar(parseCigar(extra.cg)).join('') + } else if (flip) { + CIGAR = swapIndelCigar(extra.cg) + } + } + + observer.next( + new SyntenyFeature({ + uniqueId: i + assemblyName, + assemblyName, + start, + end, + type: 'match', + refName, + strand, + ...rest, + CIGAR, + syntenyId: i, + identity: numMatches / blockLen, + numMatches, + blockLen, + mate: { + start: mateStart, + end: mateEnd, + refName: mateName, + assemblyName: assemblyNames[+flip], + }, + }), + ) + } + } + + observer.complete() + }) + } +} diff --git a/plugins/comparative-adapters/src/AllVsAllPAFAdapter/configSchema.ts b/plugins/comparative-adapters/src/AllVsAllPAFAdapter/configSchema.ts new file mode 100644 index 0000000000..3fce6b86d3 --- /dev/null +++ b/plugins/comparative-adapters/src/AllVsAllPAFAdapter/configSchema.ts @@ -0,0 +1,61 @@ +import { ConfigurationSchema } from '@jbrowse/core/configuration' + +/** + * #config AllVsAllPAFAdapter + */ +function x() {} // eslint-disable-line @typescript-eslint/no-unused-vars + +const AllVsAllPAFAdapter = ConfigurationSchema( + 'AllVsAllPAFAdapter', + { + /** + * #slot + */ + assemblyNames: { + type: 'stringArray', + defaultValue: [], + description: + 'Array of assembly names to use for this file. The query assembly name is the first value in the array, target assembly name is the second', + }, + /** + * #slot + * can be optionally gzipped + */ + pafLocation: { + type: 'fileLocation', + defaultValue: { + uri: '/path/to/file.paf', + locationType: 'UriLocation', + }, + }, + }, + { + explicitlyTyped: true, + + /** + * #preProcessSnapshot + * + * + * preprocessor to allow minimal config: + * ```json + * { + * "type": "AllVsAllPAFAdapter", + * "uri": "file.paf.gz" + * } + * ``` + */ + preProcessSnapshot: snap => { + return snap.uri + ? { + ...snap, + pafLocation: { + uri: snap.uri, + baseUri: snap.baseUri, + }, + } + : snap + }, + }, +) + +export default AllVsAllPAFAdapter diff --git a/plugins/comparative-adapters/src/AllVsAllPAFAdapter/index.ts b/plugins/comparative-adapters/src/AllVsAllPAFAdapter/index.ts new file mode 100644 index 0000000000..e5cc516728 --- /dev/null +++ b/plugins/comparative-adapters/src/AllVsAllPAFAdapter/index.ts @@ -0,0 +1,21 @@ +import AdapterType from '@jbrowse/core/pluggableElementTypes/AdapterType' + +import configSchema from './configSchema' + +import type PluginManager from '@jbrowse/core/PluginManager' + +export default function AllVsAllPAFAdapterF(pluginManager: PluginManager) { + pluginManager.addAdapterType( + () => + new AdapterType({ + name: 'AllVsAllPAFAdapter', + displayName: 'AllVsAllPAF adapter', + configSchema, + adapterMetadata: { + category: 'Synteny adapters', + }, + getAdapterClass: () => + import('./AllVsAllPAFAdapter').then(r => r.default), + }), + ) +} diff --git a/plugins/comparative-adapters/src/AllVsAllPAFAdapter/util.ts b/plugins/comparative-adapters/src/AllVsAllPAFAdapter/util.ts new file mode 100644 index 0000000000..d691794eb1 --- /dev/null +++ b/plugins/comparative-adapters/src/AllVsAllPAFAdapter/util.ts @@ -0,0 +1,108 @@ +import { zip } from '../util' + +export interface PAFRecord { + qname: string + qstart: number + qend: number + tname: string + tstart: number + tend: number + strand: number + extra: { + cg?: string + blockLen?: number + mappingQual?: number + numMatches?: number + meanScore?: number + } +} +// based on "weighted mean" method from https://github.com/tpoorten/dotPlotly +// License reproduced here +// +// MIT License + +// Copyright (c) 2017 Tom Poorten + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// Notes: in the weighted mean longer alignments factor in more heavily of all +// the fragments of a query vs the reference that it mapped to +// +// this uses a combined key query+'-'+ref to iteratively map all the alignments +// that match a particular ref from a particular query (so 1d array of what +// could be a 2d map) +// +// the result is a single number that says e.g. chr5 from human mapped to chr5 +// on mouse with 0.8 quality, and that0.8 is then attached to all the pieces of +// chr5 on human that mapped to chr5 on mouse. if chr5 on human also more +// weakly mapped to chr6 on mouse, then it would have another value e.g. 0.6. +// this can show strong and weak levels of synteny, especially in polyploidy +// situations + +export function getWeightedMeans(ret: PAFRecord[]) { + const scoreMap: Record = {} + for (const entry of ret) { + const query = entry.qname + const target = entry.tname + const key = `${query}-${target}` + if (!scoreMap[key]) { + scoreMap[key] = { quals: [], len: [] } + } + scoreMap[key].quals.push(entry.extra.mappingQual || 1) + scoreMap[key].len.push(entry.extra.blockLen || 1) + } + + const meanScoreMap = Object.fromEntries( + Object.entries(scoreMap).map(([key, val]) => { + const vals = zip(val.quals, val.len) + return [key, weightedMean(vals)] + }), + ) + for (const entry of ret) { + const query = entry.qname + const target = entry.tname + const key = `${query}-${target}` + entry.extra.meanScore = meanScoreMap[key] + } + + let min = 10000 + let max = 0 + for (const entry of ret) { + min = Math.min(entry.extra.meanScore || 0, min) + max = Math.max(entry.extra.meanScore || 0, max) + } + for (const entry of ret) { + const b = entry.extra.meanScore || 0 + entry.extra.meanScore = (b - min) / (max - min) + } + + return ret +} + +// https://gist.github.com/stekhn/a12ed417e91f90ecec14bcfa4c2ae16a +function weightedMean(tuples: [number, number][]) { + const [valueSum, weightSum] = tuples.reduce( + ([valueSum, weightSum], [value, weight]) => [ + valueSum + value * weight, + weightSum + weight, + ], + [0, 0], + ) + return valueSum / weightSum +} diff --git a/plugins/comparative-adapters/src/index.ts b/plugins/comparative-adapters/src/index.ts index 3f2bafe89b..ea0c08b0c6 100644 --- a/plugins/comparative-adapters/src/index.ts +++ b/plugins/comparative-adapters/src/index.ts @@ -1,5 +1,6 @@ import Plugin from '@jbrowse/core/Plugin' +import AllVsAllPAFAdapterF from './AllVsAllPAFAdapter' import BlastTabularAdapter from './BlastTabularAdapter' import ChainAdapterF from './ChainAdapter' import ComparativeAddTrackComponentF from './ComparativeAddTrackComponent' @@ -19,6 +20,7 @@ export default class ComparativeAdaptersPlugin extends Plugin { install(pluginManager: PluginManager) { PAFAdapterF(pluginManager) + AllVsAllPAFAdapterF(pluginManager) PairwiseIndexedPAFAdapterF(pluginManager) DeltaAdapterF(pluginManager) ChainAdapterF(pluginManager) diff --git a/yarn.lock b/yarn.lock index 7144111168..96e43cb64e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -148,9 +148,9 @@ tslib "^2.6.2" "@aws-sdk/client-s3@^3.787.0": - version "3.799.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.799.0.tgz#57c903e5f02f76829bfca1167e398314d1db8e38" - integrity sha512-v9S5UdMsFjnTmhKKkWcWOE2vJyFUlAHm9utr272x5Lr+1wJMp0ztPyL/9UiABmBqMKzc2mAPosQoGW9ZcdJw4Q== + version "3.800.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.800.0.tgz#4e7c7f64b42a6972a166fc52d019a98260c16ee6" + integrity sha512-SE33Y1kbeErd5h7KlmgWs1iJ0kKi+/t9XilI6NPIb5J5TmPKVUT5gf3ywa9ZSaq1x7LiAbICm0IPEz6k0WEBbQ== dependencies: "@aws-crypto/sha1-browser" "5.2.0" "@aws-crypto/sha256-browser" "5.2.0" @@ -168,7 +168,7 @@ "@aws-sdk/middleware-ssec" "3.775.0" "@aws-sdk/middleware-user-agent" "3.799.0" "@aws-sdk/region-config-resolver" "3.775.0" - "@aws-sdk/signature-v4-multi-region" "3.799.0" + "@aws-sdk/signature-v4-multi-region" "3.800.0" "@aws-sdk/types" "3.775.0" "@aws-sdk/util-endpoints" "3.787.0" "@aws-sdk/util-user-agent-browser" "3.775.0" @@ -550,10 +550,10 @@ "@smithy/util-middleware" "^4.0.2" tslib "^2.6.2" -"@aws-sdk/signature-v4-multi-region@3.799.0": - version "3.799.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.799.0.tgz#bfa379fa092ad2f20cd6c6245fe404b0fe9959a9" - integrity sha512-zCVugIzNfpBkjFYQFBeGEp6/qYtCH04pyE/X73SMOfBgYPsW+NhTo0JzrKmebGmKVYyxnuo7vT87bSJO2M8EfA== +"@aws-sdk/signature-v4-multi-region@3.800.0": + version "3.800.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.800.0.tgz#f8761f17d7714dfd076d85d1fee30e97aa4b7c4d" + integrity sha512-c71wZuiSUHNFCvcuqOv3jbqP+NquB2YKN4qX90OwYXEqUKn8F8fKJPpjjHjz1eK6qWKtECR4V/NTno2P70Yz/Q== dependencies: "@aws-sdk/middleware-sdk-s3" "3.799.0" "@aws-sdk/types" "3.775.0" @@ -7562,7 +7562,7 @@ decompress@^4.0.0: pify "^2.3.0" strip-dirs "^2.0.0" -dedent@1.5.3, dedent@^1.0.0: +dedent@1.5.3: version "1.5.3" resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.3.tgz#99aee19eb9bae55a67327717b6e848d0bf777e5a" integrity sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ== @@ -7572,6 +7572,11 @@ dedent@^0.7.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== +dedent@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.6.0.tgz#79d52d6389b1ffa67d2bcef59ba51847a9d503b2" + integrity sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA== + deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" @@ -8019,9 +8024,9 @@ electron-publish@25.1.7: mime "^2.5.2" electron-to-chromium@^1.5.73: - version "1.5.145" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.145.tgz#abd50700ac2c809e40a4694584f66711ee937fb6" - integrity sha512-pZ5EcTWRq/055MvSBgoFEyKf2i4apwfoqJbK/ak2jnFq8oHjZ+vzc3AhRcz37Xn+ZJfL58R666FLJx0YOK9yTw== + version "1.5.148" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.148.tgz#afed6a5771ec18d41e26541f71ba2ccc65f102ac" + integrity sha512-8uc1QXwwqayD4mblcsQYZqoi+cOc97A2XmKSBOIRbEAvbp6vrqmSYs4dHD2qVygUgn7Mi0qdKgPaJ9WC8cv63A== electron-updater@^6.1.1: version "6.6.2" @@ -12336,9 +12341,9 @@ nock@^13.5.4: propagate "^2.0.0" node-abi@^3.3.0, node-abi@^3.45.0: - version "3.74.0" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.74.0.tgz#5bfb4424264eaeb91432d2adb9da23c63a301ed0" - integrity sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w== + version "3.75.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.75.0.tgz#2f929a91a90a0d02b325c43731314802357ed764" + integrity sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg== dependencies: semver "^7.3.5" @@ -12813,9 +12818,9 @@ onetime@^7.0.0: mimic-function "^5.0.0" open@^10.0.3: - version "10.1.1" - resolved "https://registry.yarnpkg.com/open/-/open-10.1.1.tgz#5fd814699e47ae3e1a09962d39f4f4441cae6c22" - integrity sha512-zy1wx4+P3PfhXSEPJNtZmJXfhkkIaxU1VauWIrDZw1O7uJRDRJtKr9n3Ic4NgbA16KyOxOXO2ng9gYwCdXuSXA== + version "10.1.2" + resolved "https://registry.yarnpkg.com/open/-/open-10.1.2.tgz#d5df40984755c9a9c3c93df8156a12467e882925" + integrity sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw== dependencies: default-browser "^5.2.1" define-lazy-prop "^3.0.0" From c9b467b3652b5b400ee8467650f251ae8af1662b Mon Sep 17 00:00:00 2001 From: Colin Date: Thu, 1 May 2025 12:34:05 -0400 Subject: [PATCH 2/2] [skip ci] All vs All --- .../AllVsAllPAFAdapter/AllVsAllPAFAdapter.ts | 55 ++++++++++--------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/plugins/comparative-adapters/src/AllVsAllPAFAdapter/AllVsAllPAFAdapter.ts b/plugins/comparative-adapters/src/AllVsAllPAFAdapter/AllVsAllPAFAdapter.ts index 67462da0d2..8489ce372c 100644 --- a/plugins/comparative-adapters/src/AllVsAllPAFAdapter/AllVsAllPAFAdapter.ts +++ b/plugins/comparative-adapters/src/AllVsAllPAFAdapter/AllVsAllPAFAdapter.ts @@ -84,31 +84,32 @@ export default class AllVsAllPAFAdapter extends BaseFeatureDataAdapter { getFeatures(query: Region, opts: PAFOptions = {}) { return ObservableCreate(async observer => { let pafRecords = await this.setup(opts) - const { config } = opts // note: this is not the adapter config, it is responding to a display // setting passed in via the opts parameter - if (config && readConfObject(config, 'colorBy') === 'meanQueryIdentity') { + if ( + opts.config && + readConfObject(opts.config, 'colorBy') === 'meanQueryIdentity' + ) { pafRecords = getWeightedMeans(pafRecords) } const assemblyNames = this.getAssemblyNames() // The index of the assembly name in the query list corresponds to the // adapter in the subadapters list - const { start: qstart, end: qend, refName: qref, assemblyName } = query - const index = assemblyNames.indexOf(assemblyName) + const index = assemblyNames.indexOf(query.assemblyName) // if the getFeatures::query is on the query assembly, flip orientation // of data const flip = index === 0 if (index === -1) { - console.warn(`${assemblyName} not found in this adapter`) + console.warn(`${query.assemblyName} not found in this adapter`) observer.complete() } - // eslint-disable-next-line unicorn/no-for-loop - for (let i = 0; i < pafRecords.length; i++) { - const r = pafRecords[i]! + const len = pafRecords.length + for (let i = 0; i < len; i++) { + const currentPafRecord = pafRecords[i]! let start = 0 let end = 0 let refName = '' @@ -117,22 +118,26 @@ export default class AllVsAllPAFAdapter extends BaseFeatureDataAdapter { let mateEnd = 0 if (flip) { - start = r.qstart - end = r.qend - refName = r.qname - mateName = r.tname - mateStart = r.tstart - mateEnd = r.tend + start = currentPafRecord.qstart + end = currentPafRecord.qend + refName = currentPafRecord.qname + mateName = currentPafRecord.tname + mateStart = currentPafRecord.tstart + mateEnd = currentPafRecord.tend } else { - start = r.tstart - end = r.tend - refName = r.tname - mateName = r.qname - mateStart = r.qstart - mateEnd = r.qend + start = currentPafRecord.tstart + end = currentPafRecord.tend + refName = currentPafRecord.tname + mateName = currentPafRecord.qname + mateStart = currentPafRecord.qstart + mateEnd = currentPafRecord.qend } - const { extra, strand } = r - if (refName === qref && doesIntersect2(qstart, qend, start, end)) { + const { extra, strand } = currentPafRecord + const refName2 = refName.replace(`${query.assemblyName}#1#`, '') + if ( + refName2 === query.refName && + doesIntersect2(query.start, query.end, start, end) + ) { const { numMatches = 0, blockLen = 1, cg, ...rest } = extra let CIGAR = extra.cg @@ -146,12 +151,12 @@ export default class AllVsAllPAFAdapter extends BaseFeatureDataAdapter { observer.next( new SyntenyFeature({ - uniqueId: i + assemblyName, - assemblyName, + uniqueId: i + query.assemblyName, + assemblyName: query.assemblyName, start, end, type: 'match', - refName, + refName: refName2, strand, ...rest, CIGAR,