Ptm stoich#2475
Conversation
3dd2d20 to
1075ee2
Compare
018ee87 to
af92657
Compare
… branch of mzlib. Code is untestested. Have seen some discrepancies with the quantity of quantified mods and their indexing.
…(uninitialized object). commit wip.
…iants can be written.
…upsOutput() was not consistent when run individually vs when run with all other tests. The ordering of the output was changing. Fixed to not look for the first data line but rather for the one with the right PG.
1543412 to
bd8a7d4
Compare
… it to gui options. default will be to still write those mods out.
trishorts
left a comment
There was a problem hiding this comment.
Automated code review - 4 finding(s) at severity >= Major, posted as inline comments.
nbollis
left a comment
There was a problem hiding this comment.
Review Summary
Approved findings from the dashboard were revalidated against the current PR head after a freshness sync. The head commit changed since initial analysis, but both approved findings still resolved to valid inline anchors on the updated patch.
Major (2)
MetaMorpheus/EngineLayer/ProteinParsimony/ProteinGroup.cs:99Alias collection getters now return detached copies instead of live mutable collections, creating a silent behavioral break for callers that mutate getter results.MetaMorpheus/EngineLayer/ProteinParsimony/ProteinGroup.cs:415ProteinGroup's custom coverage calculation no longer keeps the inheritedBioPolymerGroup.CoverageResultcontract in sync, so inherited callers can observe coverage that diverges from MetaMorpheus output.
Grouping: 2 approved findings -> 2 inline comments | 0 summary-only.
trishorts
left a comment
There was a problem hiding this comment.
Automated code review - 1 finding(s) at severity >= Major, posted as inline comments.
trishorts
left a comment
There was a problem hiding this comment.
Approving. I re-reviewed against the current head (ae97ad2) — not just the reply threads — and every Critical/Major finding from this round is resolved or acknowledged-and-deferred with a test:
- Variant NRE — reverted to master's
VariantCallFormatDataString.Description; the incomplete guard is gone. ✅ .OfType<SpectralMatch>()dropping PSMs — now a singleCast<SpectralMatch>().ToList()reused for Score/QValue/PEP, so all three are computed over the same population and an unexpected type throws loudly. ✅- ThreadSafeObservableCollection deadlock (Critical) + PropertyChanged marshalling — whole (out-of-scope) class reverted out of the PR. ✅
- Alias getters silently detached — now
IReadOnlywith the snapshot semantics documented. ✅ - Glyco
PopulateSampleGroupResultson null LFQ — glyco now always assignsIntensitiesByFile(zeros when no LFQ) and populates uniformly, matching the search path. ✅
Nice, responsive turnaround — the two genuinely risky/out-of-scope items (the deadlock and the variant patch) were cleanly reverted rather than papered over.
Follow-up notes (non-blocking — for the deferred BioPolymerGroup migration PR)
Two of the findings were reasonably deferred with tests. Recording the residual debt so it isn't lost in the reply threads:
-
Method-hiding is a trap for the very migration this defers to.
GetTabSeparatedHeader()andCalculateSequenceCoverage()are nownew-hidden on a type that is-aBioPolymerGroup. It's safe today only because every caller uses the concrete type — I confirmedWriteProteinGroupsToTsv(PostSearchAnalysisTask.cs:1554) holdsList<ProteinGroup>and calls.First().GetTabSeparatedHeader(), so the MM override wins. But the follow-up that moves logic ontoBioPolymerGroupmust resolve this together with theCoverageResultcontract finding (#3443992211) — the first base-typed caller would otherwise silently get empty columns / diverging coverage. -
Header/row column uniformity is enforced by producer discipline, not structure. The header is built from one representative group while each row re-derives its own columns from its own
SampleGroupResults/HasIntensityData. It stays aligned only because the search and glyco paths now both assignIntensitiesByFilefor every group (zeros for unmeasured), andTestMultipleProteinGroupsHeaderAndRowsHaveSameColumnCountlocks that case. It remains latent TSV-corruption risk if any future path populatesSampleGroupResultsnon-uniformly. The single-shared-schema fix (drive header + rows from one authoritative column shape) is the real resolution and should be the deferred PR — please track it as an issue, not just a reply. -
Trivial perf, optional: the
IntensitiesByFilegetter allocates a fresh dictionary on every call, and aroundProteinGroup.cs:650it's called twice to read a single value (null-check thenGetValueOrDefault). Cache in a local. Not worth holding anything for.
None of these block the merge.
| public IEnumerable<IBioPolymerWithSetMods> GetIdentifiedBioPolymersWithSetMods() => | ||
| BestMatchingBioPolymersWithSetMods.Select(h => h.SpecificBioPolymer); | ||
|
|
||
| public int CompareTo(ISpectralMatch? other) |
There was a problem hiding this comment.
Careful. We already have a carefully orchestrated compare method for spectral matches, we should unify them
| string.Join("|", proteinsOrderedByAccession.Select(p => p.Organism).Distinct())); | ||
|
|
||
| foreach (var psm in proteinGroup.AllPsmsBelowOnePercentFDR.Where(v => v.FullSequence != null)) | ||
| foreach (var psm in proteinGroup.AllPsmsBelowOnePercentFDR.OfType<SpectralMatch>() |
There was a problem hiding this comment.
There may be issues here with the spectral matches being of the type GlycoSpectralMatch. A cast or something may be more appropriate here, unsure, but please verify this.
Adds occupancy calculation to MM using peptide intensities from FlashLFQ. Backend calculation currently implemented in the
MzLib.MzLibUtil.PositionFrequencyAnalysisclass. In MM, the updating of the ProteinGroup.ModInfo field with intensity-based stoichiometry is done in thePostSearchAnalysis.QuantificationAnalysis()method, and theProteinGroup.ToString()takes the ModInfo data to produce the occupancy string.Description
This PR introduces PTM stoichiometry/occupancy quantification to MetaMorpheus, refactors
ProteinGroupto inherit from mzLib'sBioPolymerGroup, and adds several quality-of-life improvements and bug fixes.Key Changes
1. PTM Occupancy by Spectral Count and Intensity
Protein group TSV output now includes four new families of dynamic columns per sample group:
SpectralCount_<sample>— number of PSMs observing the proteinIntensity_<sample>— FlashLFQ intensity per sampleCountOccupancy_<sample>— modified spectral count / total spectral countIntensityOccupancy_<sample>— modified intensity / total intensityThese columns are generated via the new
PopulateSampleGroupResults()method onBioPolymerGroup. Columns only appear when quantification state is available upstream, preserving backward compatibility for workflows that return early from quantification.2. ProteinGroup → BioPolymerGroup Inheritance
ProteinGroupnow extendsOmics.BioPolymerGroup.BioPolymerGroupfrom mzLib, delegating quantification, sample grouping, and occupancy logic to the base class. Backward-compatible alias properties (Proteins,AllPeptides,ProteinGroupName,IntensitiesByFile, etc.) map to base class properties so existing consumers are unaffected.3. ScanMetadata Record
Scan metadata (scan number, retention time, precursor charge/mass/intensity, ion mobility, etc.) is now captured in a lightweight immutable
ScanMetadatarecord onMs2ScanWithSpecificMassandSpectralMatch. This allows heavyweight scan objects to be released from memory after scoring while preserving metadata access via pass-through properties.4. SpectralMatch → ISpectralMatch Interface
SpectralMatchnow implementsISpectralMatchfrom mzLib, with explicit interface members forOneBasedScanNumber,Intensities,GetIdentifiedBioPolymersWithSetMods(),CompareTo(ISpectralMatch?), andEquals(ISpectralMatch?).5. ThreadSafeObservableCollection Improvements
Updated to facilitate safe iteration through the collection without worrying about threading issues, with comprehensive test coverage.
6. Calling Code Adapted to New Property Semantics
IntensitiesByFilechanged from a simple auto-property (getter returned the backing reference) to an adapter property whose getter returns a new copy via.ToDictionary(). Calling code inPostSearchAnalysisTaskandSilacConversionswas updated from the old mutate-the-getter pattern (.Add()on the returned dictionary) to the build-locally-then-assign pattern.7. Bug Fixes
variant.Descriptioninstead ofvariant.VariantCallFormatDataString.Description) with null-safety guardpossibleVariantPsmsordering result being discarded (now assigned back)ExperimentalDesign.tsvin sharedTestData/directory affected other tests