Skip to content

Commit 60e6b5a

Browse files
Merge 25.2 to develop
2 parents 25a7c15 + 6a8bca3 commit 60e6b5a

File tree

3 files changed

+110
-137
lines changed

3 files changed

+110
-137
lines changed

src/org/labkey/targetedms/parser/SkylineBinaryParser.java

Lines changed: 9 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,16 @@
1515
*/
1616
package org.labkey.targetedms.parser;
1717

18-
import com.google.protobuf.CodedInputStream;
1918
import org.apache.commons.io.IOUtils;
2019
import org.apache.logging.log4j.Logger;
2120
import org.jetbrains.annotations.NotNull;
2221
import org.labkey.api.exp.api.DataType;
23-
import org.labkey.api.pipeline.PipelineJobException;
2422
import org.labkey.targetedms.parser.proto.ChromatogramGroupDataOuterClass;
2523
import org.labkey.targetedms.parser.skyd.CacheFormat;
2624
import org.labkey.targetedms.parser.skyd.CacheFormatVersion;
2725
import org.labkey.targetedms.parser.skyd.CacheHeaderStruct;
2826
import org.labkey.targetedms.parser.skyd.CachedFileHeaderStruct;
2927
import org.labkey.targetedms.parser.skyd.ChromGroupHeaderInfo;
30-
import org.labkey.targetedms.parser.skyd.ChromPeak;
3128
import org.labkey.targetedms.parser.skyd.ChromTransition;
3229
import org.labkey.targetedms.parser.skyd.StructSerializer;
3330

@@ -39,7 +36,6 @@
3936
import java.nio.ByteBuffer;
4037
import java.nio.channels.Channels;
4138
import java.nio.channels.FileChannel;
42-
import java.nio.channels.SeekableByteChannel;
4339
import java.util.EnumSet;
4440
import java.util.List;
4541
import java.util.Objects;
@@ -64,7 +60,6 @@ public class SkylineBinaryParser
6460
private CacheHeaderStruct _cacheHeaderStruct;
6561

6662
private ChromGroupHeaderInfo[] _chromatograms;
67-
private float[] _allPeaksRt;
6863
private byte[] _seqBytes;
6964
private List<ChromatogramGroupId> _chromatogramGroupIds;
7065

@@ -122,7 +117,6 @@ public void parse() throws IOException
122117

123118
parseChromatogramGroupIds();
124119
parseFiles();
125-
parsePeaks();
126120
_log.debug("Starting to load chromatogram headers");
127121
parseChromatograms();
128122
_log.debug("Done loading chromatogram headers");
@@ -202,19 +196,6 @@ private String readStringOfByteLength(InputStream inputStream, int length) throw
202196
return new String(buffer, _cacheFormat.getCharset());
203197
}
204198

205-
private void parsePeaks() throws IOException
206-
{
207-
_channel.position(_cacheHeaderStruct.getLocationPeaks());
208-
ChromPeak[] chromPeaks = _cacheFormat.chromPeakSerializer()
209-
.readArray(Channels.newInputStream(_channel), _cacheHeaderStruct.getNumPeaks());
210-
_allPeaksRt = new float[chromPeaks.length];
211-
212-
for (int i = 0; i < chromPeaks.length; i++)
213-
{
214-
_allPeaksRt[i] = chromPeaks[i].getRetentionTime();
215-
}
216-
}
217-
218199
private void parseChromatogramGroupIds() throws IOException
219200
{
220201
if (_cacheFormat.getFormatVersion().compareTo(CacheFormatVersion.Eighteen) < 0)
@@ -253,27 +234,17 @@ private void parseChromatograms() throws IOException
253234
Channels.newInputStream(_channel), _cacheHeaderStruct.getNumChromatograms());
254235
}
255236

256-
public SeekableByteChannel getChannel()
257-
{
258-
return _channel;
259-
}
260-
261237
final int getCacheFileSize()
262238
{
263239
return _cacheFiles != null ? _cacheFiles.length : 0;
264240
}
265241

266-
public int matchTransitions(ChromGroupHeaderInfo header, List<? extends GeneralTransition> transitions, Double explicitRt, double tolerance, boolean multiMatch)
242+
/**
243+
* Returns the number of transitions from the Precursor in the Skyline document that have a matching
244+
* chromatogram in the ChromGroupHeaderInfo.
245+
*/
246+
public int countTransitionMatches(ChromGroupHeaderInfo header, List<? extends GeneralTransition> transitions, double tolerance)
267247
{
268-
int match = 0;
269-
270-
if (explicitRt != null)
271-
{
272-
// We have retention time info, use that in the match
273-
if (header.excludesTime(explicitRt))
274-
return match;
275-
}
276-
277248
var numChromTransitions = header.getNumTransitions();
278249
ChromTransition[] chromTransitions;
279250

@@ -285,44 +256,19 @@ public int matchTransitions(ChromGroupHeaderInfo header, List<? extends GeneralT
285256
{
286257
throw new RuntimeException(e);
287258
}
288-
259+
int matchCount = 0;
289260
for (GeneralTransition transition : transitions)
290261
{
291262
for (int i = 0; i < numChromTransitions; i++)
292263
{
293-
// Do we need to look through all of the transitions from the .skyd file?
294264
if (header.toSignedMz(transition.getMz()).compareTolerant(chromTransitions[i].getProduct(header), tolerance) == 0)
295265
{
296-
if (explicitRt == null)
297-
{
298-
match++;
299-
if (!multiMatch)
300-
{
301-
break; // only one match per transition
302-
}
303-
}
304-
else
305-
{
306-
match = multiMatch ? match + 1 : 1; // Examine all RT values even if we're not multimatch
307-
}
266+
matchCount++;
267+
break;
308268
}
309269
}
310270
}
311-
312-
return match;
313-
}
314-
315-
public byte[] readChromatogramBytes(ChromGroupHeaderInfo header) throws DataFormatException, IOException
316-
{
317-
// Get the compressed bytes
318-
ByteBuffer buffer = ByteBuffer.allocate(header.getCompressedSize());
319-
getChannel().position(header.getLocationPoints()).read(buffer);
320-
buffer.position(0);
321-
byte[] result = new byte[header.getCompressedSize()];
322-
buffer.get(result);
323-
// Make sure it uncompresses successfully so that we don't import bad content into the database
324-
uncompress(result, header.getUncompressedSize());
325-
return result;
271+
return matchCount;
326272
}
327273

328274
public static byte[] uncompressStoredBytes(byte[] bytes, Integer uncompressedSize, int numPoints, int numTransitions) throws DataFormatException

src/org/labkey/targetedms/parser/SkylineDocumentParser.java

Lines changed: 79 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -3179,125 +3179,133 @@ private List<ChromGroupHeaderInfo> tryLoadChromatogram(
31793179
GeneralPrecursor<?> precursor,
31803180
double tolerance)
31813181
{
3182-
// Add precursor matches to a list, if they match at least 1 transition
3183-
// in this group, and are potentially the maximal transition match.
3184-
3185-
// Using only the maximum works well for the case where there are 2
3186-
// precursors in the same document that match a single entry.
3187-
// TODO: But it messes up when there are 2 sets of transitions for
3188-
// the same precursor covering different numbers of transitions.
3189-
// Skyline never creates this case, but it has been reported
3190-
// int maxTranMatch = 1;
3191-
31923182
if (_binaryParser != null && _binaryParser.getChromatograms() != null)
31933183
{
31943184
// ChromatogramCache.TryLoadChromInfo() in Skyline code:
31953185
// Filter the list of chromatograms based on our precursor mZ
3196-
int i = findEntry(precursor.getSignedMz(), tolerance, _binaryParser.getChromatograms(), 0, _binaryParser.getChromatograms().length - 1);
3186+
ChromGroupHeaderInfo[] chromHeaders = _binaryParser.getChromatograms();
3187+
int i = findEntry(precursor.getSignedMz(), tolerance, chromHeaders, 0, chromHeaders.length - 1);
31973188
if (i == -1)
31983189
{
31993190
return Collections.emptyList();
32003191
}
32013192

32023193
Double explicitRT = molecule.getExplicitRetentionTime();
32033194

3195+
List<ChromGroupHeaderInfo> result = new ArrayList<>();
3196+
32043197
// Add entries to a list until they no longer match
3205-
List<ChromGroupHeaderInfo> listChromatograms = new ArrayList<>();
3206-
while (i < _binaryParser.getChromatograms().length &&
3207-
matchMz(precursor.getSignedMz(), _binaryParser.getChromatograms()[i].getPrecursor(), tolerance))
3198+
while (i < chromHeaders.length &&
3199+
matchMz(precursor.getSignedMz(), chromHeaders[i].getPrecursor(), tolerance))
32083200
{
3209-
ChromGroupHeaderInfo chrom = _binaryParser.getChromatograms()[i++];
3201+
ChromGroupHeaderInfo chrom = chromHeaders[i++];
3202+
// If explicit retention time info is available, use that to discard obvious mismatches
3203+
if (explicitRT != null && chrom.excludesTime(explicitRT))
3204+
{
3205+
continue;
3206+
}
3207+
32103208
// Sequence matching for extracted chromatogram data added in v1.5
32113209
ChromatogramGroupId chromTextId = _binaryParser.getTextId(chrom);
3212-
if (chromTextId != null)
3210+
if (chromTextId != null)
32133211
{
3212+
// If we match based on textId, consider it a chromatogram worth storing
32143213
if (!molecule.targetMatches(chromTextId.getTarget()))
3214+
{
32153215
continue;
3216+
}
32163217
try
32173218
{
32183219
SpectrumFilter spectrumFilter = SpectrumFilter.fromByteArray(precursor.getSpectrumFilter());
3219-
if (!Objects.equals(spectrumFilter, chromTextId.getSpectrumFilter()))
3220+
if (!Objects.equals(spectrumFilter, chromTextId.getSpectrumFilter()))
32203221
{
32213222
continue;
32223223
}
32233224
}
32243225
catch (InvalidProtocolBufferException e)
32253226
{
3226-
_log.warn("Error parsing spectrum filter {}", e);
3227-
return Collections.emptyList();
3227+
_log.warn("Error parsing spectrum filter", e);
3228+
continue;
32283229
}
32293230
}
3230-
3231-
// If explicit retention time info is available, use that to discard obvious mismatches
3232-
if (explicitRT == null || !chrom.excludesTime(explicitRT))
3233-
{
3234-
listChromatograms.add(chrom);
3235-
}
3231+
result.add(chrom);
32363232
}
3233+
return findChromatogramsWithMostTransitions(precursor.getMz(), transitions, result);
3234+
}
32373235

3238-
// MeasuredResults.TryLoadChromatogram in Skyline code:
3239-
// Since we are reading and returning chromatograms for all replicates we need to maintain
3240-
// the number of maximum transition matches for each replicate.
3241-
// MeasuredResults.TryLoadChromatogram in Skyline reads and returns chromatograms for a single replicate.
3242-
int[] maxTranMatches = new int[_binaryParser.getCacheFileSize()];
3236+
return Collections.emptyList();
3237+
}
32433238

3244-
ChromGroupHeaderInfo[] chromArray = new ChromGroupHeaderInfo[_binaryParser.getCacheFileSize()];
32453239

3246-
for (ChromGroupHeaderInfo chromInfo : listChromatograms)
3247-
{
3248-
// If the chromatogram set has an optimization function then the number
3249-
// of matching chromatograms per transition is a reflection of better
3250-
// matching. Otherwise, we only expect one match per transition.
3251-
// TODO - do we need this on the Java side?
3252-
boolean multiMatch = false;//chromatogram.OptimizationFunction != null;
3240+
/**
3241+
* Within each replicate, if there is more than one ChromGroupHeaderInfo, find the header infos with the most matching
3242+
* transitions, and, if there are multiple headers with the same number of matching transitions, then find the ones
3243+
* that have the closest match to the precursor m/z.
3244+
*/
3245+
private List<ChromGroupHeaderInfo> findChromatogramsWithMostTransitions(
3246+
double precursorMz, List<? extends GeneralTransition> transitions, List<ChromGroupHeaderInfo> headerInfos)
3247+
{
3248+
Map<String, List<ChromGroupHeaderInfo>> byFile = headerInfos.stream()
3249+
.collect(Collectors.groupingBy(headerInfo -> _binaryParser.getFilePath(headerInfo)));
32533250

3254-
int tranMatch = _binaryParser.matchTransitions(chromInfo, transitions, explicitRT, tolerance, multiMatch);
3251+
List<ChromGroupHeaderInfo> result = new ArrayList<>();
32553252

3256-
int fileIndex = chromInfo.getFileIndex();
3257-
int maxTranMatch = maxTranMatches[fileIndex];
3253+
for (SkylineReplicate skylineReplicate : _replicateList)
3254+
{
3255+
List<ChromGroupHeaderInfo> candidates = new ArrayList<>();
32583256

3259-
if (tranMatch >= maxTranMatch)
3257+
for (SampleFile sampleFile : skylineReplicate.getSampleFileList())
3258+
{
3259+
List<ChromGroupHeaderInfo> listInFile = byFile.get(sampleFile.getFilePath());
3260+
if (listInFile != null)
32603261
{
3261-
// If new maximum, clear anything collected at the previous maximum
3262-
if (tranMatch > maxTranMatch)
3263-
{
3264-
chromArray[fileIndex] = null;
3265-
}
3262+
candidates.addAll(listInFile);
3263+
}
3264+
}
32663265

3267-
maxTranMatches[fileIndex] = tranMatch;
3266+
if (candidates.size() <= 1)
3267+
{
3268+
result.addAll(candidates);
3269+
continue;
3270+
}
32683271

3269-
if(chromArray[fileIndex] != null)
3270-
{
3271-
// If more than one value was found, ensure that there
3272-
// is only one precursor match per file.
3273-
// Use the entry with the m/z closest to the target
3274-
ChromGroupHeaderInfo currentChromForFileIndex = chromArray[fileIndex];
3275-
// Use the entry with the m/z closest to the target
3276-
if (Math.abs(precursor.getMz() - chromInfo.getPrecursorMz()) <
3277-
Math.abs(precursor.getMz() - currentChromForFileIndex.getPrecursorMz()))
3278-
{
3279-
chromArray[fileIndex] = chromInfo;
3280-
}
3281-
}
3282-
else
3283-
{
3284-
chromArray[fileIndex] = chromInfo;
3285-
}
3272+
int[] transitionCounts = new int[candidates.size()];
3273+
int maxTransitionCount = 0;
3274+
for (int i = 0; i < candidates.size(); i++)
3275+
{
3276+
int transitionCount = _binaryParser.countTransitionMatches(candidates.get(i), transitions, _matchTolerance);
3277+
transitionCounts[i] = transitionCount;
3278+
maxTransitionCount = Math.max(transitionCount, maxTransitionCount);
3279+
}
3280+
List<ChromGroupHeaderInfo> candidatesWithMostTransitions = new ArrayList<ChromGroupHeaderInfo>();
3281+
for (int i = 0; i < candidates.size(); i++)
3282+
{
3283+
if (transitionCounts[i] == maxTransitionCount)
3284+
{
3285+
candidatesWithMostTransitions.add(candidates.get(i));
32863286
}
32873287
}
3288+
if (candidatesWithMostTransitions.size() <= 1)
3289+
{
3290+
result.addAll(candidatesWithMostTransitions);
3291+
continue;
3292+
}
32883293

3289-
List<ChromGroupHeaderInfo> finalList = new ArrayList<>();
3290-
for (ChromGroupHeaderInfo info : chromArray)
3294+
// If multiple chromGroups tied for the number of matching transitions, then break the tie using
3295+
// precursor m/z distance
3296+
double minMzDelta = candidatesWithMostTransitions.stream()
3297+
.mapToDouble(headerInfo -> Math.abs(precursorMz - headerInfo.getPrecursorMz()))
3298+
.min().getAsDouble();
3299+
for (ChromGroupHeaderInfo headerInfo : candidatesWithMostTransitions)
32913300
{
3292-
if (info != null)
3301+
if (Math.abs(precursorMz - headerInfo.getPrecursorMz()) == minMzDelta)
32933302
{
3294-
finalList.add(info);
3303+
result.add(headerInfo);
32953304
}
32963305
}
3297-
return finalList;
32983306
}
32993307

3300-
return Collections.emptyList();
3308+
return result;
33013309
}
33023310

33033311
public int getPeptideGroupCount()

webapp/TargetedMS/js/QCSummaryPanel.js

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,17 @@ Ext4.define('LABKEY.targetedms.QCSummary', {
299299
renderContainerSampleFileStats: function (params) {
300300
let container = params.container;
301301
let sampleFiles = params.sampleFiles;
302-
let metrics = sampleFiles[0].Metrics;
302+
let metrics = [];
303+
let seenMetrics = {};
304+
Ext4.iterate(sampleFiles, function (sampleFile) {
305+
Ext4.iterate(sampleFile.Metrics, function (metric) {
306+
if (!seenMetrics[metric.MetricId]) {
307+
metrics.push(metric);
308+
seenMetrics[metric.MetricId] = true;
309+
}
310+
});
311+
});
312+
303313
let showMetrics = LABKEY.ActionURL.getAction().toLowerCase() === 'qcSummaryHistory'.toLowerCase();
304314
let tableWidth = container.width - 100;
305315
let html = '';
@@ -340,8 +350,17 @@ Ext4.define('LABKEY.targetedms.QCSummary', {
340350

341351

342352
if (showMetrics) {
343-
Ext4.each(sampleFile.Metrics, function (metric) {
344-
html += '<td><div class="sample-file-item" style="text-align: right">' + Ext4.util.Format.htmlEncode(metric.Value) + '</div></td>';
353+
Ext4.each(metrics, function (metric) {
354+
let isMetricPresent = false;
355+
Ext4.each(sampleFile.Metrics, function (item) {
356+
if (metric.MetricId === item.MetricId) {
357+
isMetricPresent = true;
358+
html += '<td><div class="sample-file-item" style="text-align: right">' + Ext4.util.Format.htmlEncode(item.Value) + '</div></td>';
359+
}
360+
});
361+
if (!isMetricPresent) {
362+
html += '<td><div class="sample-file-item" style="text-align: right">N/A</div></td>';
363+
}
345364
});
346365
}
347366
if (sampleFile.IgnoreForAllMetric) {

0 commit comments

Comments
 (0)