OpenMS
Loading...
Searching...
No Matches
ProteinGroupArrowExport_impl.h
Go to the documentation of this file.
1// Copyright (c) 2002-present, OpenMS Inc. -- EKU Tuebingen, ETH Zurich, and FU Berlin
2// SPDX-License-Identifier: BSD-3-Clause
3//
4// --------------------------------------------------------------------------
5// $Maintainer: Timo Sachsenberg $
6// $Authors: Timo Sachsenberg $
7// --------------------------------------------------------------------------
8
9#pragma once
10
11// INTERNAL HEADER -- deliberately not installed (not listed in the install rules for
12// OpenMS/FORMAT). It exists so ProteinGroupArrowExport and QPXCollectionExport share ONE
13// definition of what a QPX quantification unit is: the exporter decides what to write and the
14// preflight decides whether the collection may be written at all, and the two must not be able
15// to disagree about which designs are representable.
16
23
24#include <algorithm>
25#include <map>
26#include <set>
27#include <string>
28#include <tuple>
29#include <unordered_map>
30#include <vector>
31
32namespace OpenMS::Internal
33{
34
37{
38 unsigned label = 1;
40 std::string qpx_label;
41};
42
45{
47 std::vector<std::string> runs;
48 std::vector<QuantChannel> channels;
49};
50
58{
59public:
61
78 const std::set<std::pair<std::string, unsigned>>& known_cells)
79 {
80 std::set<std::string> known_runs;
81 for (const auto& [run, label] : known_cells) { (void)label; known_runs.insert(run); }
82
83 struct RunInfo
84 {
85 unsigned fraction_group = 0;
86 unsigned fraction = 0;
87 bool initialized = false;
88 std::map<unsigned, Size> label_sample;
89 };
90 std::map<std::string, RunInfo> run_info;
91 const Size n_samples = design.getNumberOfSamples();
92
93 for (const auto& entry : design.getMSFileSection())
94 {
95 const std::string run = ArrowIOHelpers::qpxRunFileName(entry.path);
96 if (run.empty()) { continue; }
97 if (!known_runs.count(run)) { dropped_runs_.insert(run); continue; }
98 if (entry.sample >= n_samples && invalid_sample_.empty())
99 {
100 invalid_sample_ = "run '" + run + "', label " + std::to_string(entry.label)
101 + " refers to sample " + std::to_string(entry.sample)
102 + ", but the sample section contains " + std::to_string(n_samples)
103 + " sample(s)";
104 }
105 if (!known_cells.count({run, entry.label}))
106 {
107 if (missing_cell_.empty())
108 {
109 missing_cell_ = "run '" + run + "' is in this map, but no column header describes its "
110 "label " + std::to_string(entry.label)
111 + ", which the experimental design declares";
112 }
113 continue;
114 }
115 auto& info = run_info[run];
116 if (!info.initialized)
117 {
118 info.fraction_group = entry.fraction_group;
119 info.fraction = entry.fraction;
120 info.initialized = true;
121 }
122 else if (info.fraction_group != entry.fraction_group && run_in_several_groups_.empty())
123 {
124 run_in_several_groups_ = "run '" + run + "' belongs to fraction groups "
125 + std::to_string(info.fraction_group) + " and "
126 + std::to_string(entry.fraction_group);
127 }
128
129 // (path, label) is unique in a well-formed design, so a repeat with a different sample
130 // means the design cannot say which sample this run's label belongs to.
131 auto [it, inserted] = info.label_sample.emplace(entry.label, entry.sample);
132 if (!inserted && it->second != entry.sample) { inconsistent_ = true; }
133 }
134
135 std::map<UInt, std::vector<std::string>> runs_by_fraction_group;
136 for (const auto& [run, info] : run_info)
137 {
138 runs_by_fraction_group[info.fraction_group].push_back(run);
139 }
140
141 for (auto& [fraction_group, runs] : runs_by_fraction_group)
142 {
143 QuantUnit unit;
144 unit.fraction_group = fraction_group;
145 unit.runs = std::move(runs);
146 std::sort(unit.runs.begin(), unit.runs.end(),
147 [&run_info](const std::string& a, const std::string& b)
148 {
149 const auto& ia = run_info.at(a);
150 const auto& ib = run_info.at(b);
151 return std::tie(ia.fraction, a) < std::tie(ib.fraction, b);
152 });
153
154 const std::string unit_name = "fraction group " + std::to_string(fraction_group);
155 std::map<unsigned, Size> label_sample;
156 for (const auto& run : unit.runs)
157 {
158 for (const auto& [label, sample] : run_info.at(run).label_sample)
159 {
160 auto [ls, ls_fresh] = label_sample.emplace(label, sample);
161 if (!ls_fresh && ls->second != sample && ambiguous_label_.empty())
162 {
163 ambiguous_label_ = "label " + std::to_string(label) + " of " + unit_name
164 + " maps to sample " + std::to_string(ls->second) + " and sample "
165 + std::to_string(sample);
166 }
167 }
168 }
169 for (const auto& run : unit.runs)
170 {
171 for (const auto& [label, sample] : label_sample)
172 {
173 if (run_info.at(run).label_sample.count(label)) { continue; }
174 if (!ragged_unit_.empty()) { continue; }
175 ragged_unit_ = "run '" + run + "' of " + unit_name
176 + " has no design row for label " + std::to_string(label)
177 + ", which resolves to sample " + std::to_string(sample)
178 + " in the other run(s)";
179 }
180 }
181 for (const auto& [label, sample] : label_sample) { unit.channels.push_back(QuantChannel{label, sample, ""}); }
182 for (const auto& run : unit.runs)
183 {
184 auto [it, inserted] = unit_of_run_.emplace(run, units_.size());
185 if (!inserted && run_in_several_groups_.empty())
186 {
187 run_in_several_groups_ = "run '" + run + "' belongs to more than one fraction group";
188 }
189 }
190 units_.push_back(std::move(unit));
191 }
192 }
193
194 bool empty() const { return units_.empty(); }
196 bool inconsistent() const { return inconsistent_; }
198 const std::string& ambiguousLabel() const { return ambiguous_label_; }
200 const std::string& raggedUnit() const { return ragged_unit_; }
202 const std::set<std::string>& droppedRuns() const { return dropped_runs_; }
204 const std::string& missingCell() const { return missing_cell_; }
206 const std::string& invalidSample() const { return invalid_sample_; }
208 const std::string& runInSeveralGroups() const { return run_in_several_groups_; }
209 const std::vector<QuantUnit>& units() const { return units_; }
210
212 size_t indexOf(const std::string& run) const
213 {
214 auto it = unit_of_run_.find(run);
215 return (it == unit_of_run_.end()) ? units_.size() : it->second;
216 }
217
223 template <typename LabelFor>
224 bool resolveLabels(LabelFor label_for, const std::string& context)
225 {
226 for (auto& unit : units_)
227 {
228 std::map<std::string, unsigned> label_to_channel;
229 for (auto& channel : unit.channels)
230 {
231 channel.qpx_label = label_for(unit.runs.front(), channel.label);
232 if (channel.qpx_label.empty()) { return false; } // already logged
233 for (size_t i = 1; i < unit.runs.size(); ++i)
234 {
235 const std::string other = label_for(unit.runs[i], channel.label);
236 if (other == channel.qpx_label) { continue; }
237 if (other.empty()) { return false; } // already logged
238 OPENMS_LOG_ERROR << context << ": runs '" << unit.runs.front() << "' and '"
239 << unit.runs[i] << "' belong to fraction group " << unit.fraction_group
240 << " but name label " << channel.label << " differently ('"
241 << channel.qpx_label << "' vs '" << other
242 << "'). A grouped row cannot choose one arbitrarily." << std::endl;
243 return false;
244 }
245 auto [label_it, label_inserted] = label_to_channel.emplace(channel.qpx_label, channel.label);
246 if (!label_inserted && label_it->second != channel.label)
247 {
248 OPENMS_LOG_ERROR << context << ": channels " << label_it->second << " and "
249 << channel.label << " of fraction group " << unit.fraction_group
250 << " both resolve to QPX label '" << channel.qpx_label
251 << "'. They would duplicate the protein-group primary key." << std::endl;
252 return false;
253 }
254 }
255 }
256 return true;
257 }
258
259private:
260 std::vector<QuantUnit> units_;
261 std::unordered_map<std::string, size_t> unit_of_run_;
262 std::set<std::string> dropped_runs_;
263 std::string missing_cell_;
264 std::string ambiguous_label_;
265 std::string ragged_unit_;
266 std::string invalid_sample_;
268 bool inconsistent_ = false;
269};
270
278{
279 for (const auto& array : group.getFloatDataArrays())
280 {
281 if (array.getName() == "abundances") { return &array; }
282 }
283 return nullptr;
284}
285
288{
289 bool present = false;
290 bool valid = true;
291 std::string error;
292 std::map<std::pair<UInt, UInt>, float> values;
293};
294
298{
299 constexpr const char* abundance_name = "fraction_group_level_abundance";
300 constexpr const char* fraction_group_name = "fraction_group_level_fraction_group";
301 constexpr const char* label_name = "fraction_group_level_label";
302
303 const ProteinIdentification::ProteinGroup::FloatDataArray* abundances = nullptr;
304 const ProteinIdentification::ProteinGroup::IntegerDataArray* fraction_groups = nullptr;
306 for (const auto& array : group.getFloatDataArrays())
307 {
308 if (array.getName() == abundance_name) { abundances = &array; }
309 }
310 for (const auto& array : group.getIntegerDataArrays())
311 {
312 if (array.getName() == fraction_group_name) { fraction_groups = &array; }
313 else if (array.getName() == label_name) { labels = &array; }
314 }
315
317 result.present = abundances != nullptr || fraction_groups != nullptr || labels != nullptr;
318 if (!result.present) { return result; }
319 if (abundances == nullptr || fraction_groups == nullptr || labels == nullptr)
320 {
321 result.valid = false;
322 result.error = "the fraction-group abundance, fraction-group key and label key arrays are not all present";
323 return result;
324 }
325 if (abundances->size() != fraction_groups->size() || abundances->size() != labels->size())
326 {
327 result.valid = false;
328 result.error = "the parallel fraction-group abundance arrays have different lengths";
329 return result;
330 }
331
332 for (Size i = 0; i < abundances->size(); ++i)
333 {
334 // Both keys are 1-based: MSFileSectionEntry defaults fraction_group to 1 and
335 // ExperimentalDesign::isValid_() requires the set to start at 1.
336 if ((*fraction_groups)[i] <= 0 || (*labels)[i] <= 0)
337 {
338 result.valid = false;
339 result.error = "a fraction-group or label key is not 1-based";
340 return result;
341 }
342 const auto key = std::make_pair(static_cast<UInt>((*fraction_groups)[i]),
343 static_cast<UInt>((*labels)[i]));
344 if (!result.values.emplace(key, (*abundances)[i]).second)
345 {
346 result.valid = false;
347 result.error = "the fraction-group abundance arrays contain a duplicate (fraction_group, label) key";
348 return result;
349 }
350 }
351 return result;
352}
353
366 const FractionGroupAbundanceData& quantities,
367 Size design_samples,
368 const QuantificationUnits& units,
369 const std::set<std::pair<UInt, UInt>>& expected_quantity_keys)
370{
371 if (!quantities.valid)
372 {
373 return "has invalid fraction-group abundance annotations: " + quantities.error;
374 }
375 if (sample_abundances != nullptr && sample_abundances->size() != design_samples)
376 {
377 return "carries " + std::to_string(sample_abundances->size())
378 + " sample abundances but the experimental design describes "
379 + std::to_string(design_samples)
380 + " samples, so the design does not belong to this quantification";
381 }
382
383 // A group carrying only the removed sample-level "abundances" array was quantified, by a version
384 // of OpenMS that wrote a grain this one can no longer interpret. Refuse it rather than treating
385 // it as identification-only: that would put the protein in the pg view with a null intensity,
386 // which is indistinguishable from "not quantified" and would hand back a complete-looking
387 // collection with no quantities in it, exit code 0. Re-quantify the map instead.
388 const bool quantified = sample_abundances != nullptr || quantities.present;
389 if (!quantified) { return {}; }
390 if (!quantities.present)
391 {
392 return "carries only the legacy sample-level abundances of an older OpenMS; the QPX "
393 "quantification unit is (fraction group, label), so this map has to be re-quantified";
394 }
395 if (units.empty())
396 {
397 return "carries fraction-group abundances, but the experimental design yields no matching "
398 "quantification unit";
399 }
400 if (quantities.values.size() != expected_quantity_keys.size())
401 {
402 return "carries " + std::to_string(quantities.values.size())
403 + " fraction-group/label quantities but the experimental design requires "
404 + std::to_string(expected_quantity_keys.size())
405 + ", so the annotations and design do not describe the same quantification";
406 }
407 for (const auto& key : expected_quantity_keys)
408 {
409 if (!quantities.values.count(key))
410 {
411 return "has no quantity for fraction group " + std::to_string(key.first)
412 + ", label " + std::to_string(key.second);
413 }
414 }
415 return {};
416}
417
418} // namespace OpenMS::Internal
#define OPENMS_LOG_ERROR
Macro for non-fatal errors (processing continues)
Definition LogStream.h:604
Float data array class.
Definition DataArrays.h:25
Integer data array class.
Definition DataArrays.h:75
Representation of an experimental design in OpenMS. Instances can be loaded with the ExperimentalDesi...
Definition ExperimentalDesign.h:449
unsigned getNumberOfSamples() const
Number of samples measured (= number of rows in the sample section)
const MSFileSection & getMSFileSection() const
Definition ProteinGroupArrowExport_impl.h:58
const std::set< std::string > & droppedRuns() const
Design files the ConsensusMap has no column header for, left out of every unit.
Definition ProteinGroupArrowExport_impl.h:202
std::vector< QuantUnit > units_
Definition ProteinGroupArrowExport_impl.h:260
size_t indexOf(const std::string &run) const
Index of the unit containing run, or units().size() when the design does not list it.
Definition ProteinGroupArrowExport_impl.h:212
const std::string & raggedUnit() const
Non-empty when a unit's runs do not all carry every label it publishes.
Definition ProteinGroupArrowExport_impl.h:200
const std::string & runInSeveralGroups() const
Non-empty when one run is assigned to multiple fraction groups.
Definition ProteinGroupArrowExport_impl.h:208
const std::string & missingCell() const
Non-empty when a run that IS in the map lacks a header for a label the design declares.
Definition ProteinGroupArrowExport_impl.h:204
std::string ambiguous_label_
Definition ProteinGroupArrowExport_impl.h:264
std::string invalid_sample_
Definition ProteinGroupArrowExport_impl.h:266
bool inconsistent_
Definition ProteinGroupArrowExport_impl.h:268
bool inconsistent() const
A design assigning one (run, label) to several samples; the rows would be wrong.
Definition ProteinGroupArrowExport_impl.h:196
bool empty() const
Definition ProteinGroupArrowExport_impl.h:194
std::set< std::string > dropped_runs_
Definition ProteinGroupArrowExport_impl.h:262
std::string missing_cell_
Definition ProteinGroupArrowExport_impl.h:263
const std::vector< QuantUnit > & units() const
Definition ProteinGroupArrowExport_impl.h:209
const std::string & ambiguousLabel() const
Non-empty when one label of a unit means several samples; the row cannot be written.
Definition ProteinGroupArrowExport_impl.h:198
std::unordered_map< std::string, size_t > unit_of_run_
Definition ProteinGroupArrowExport_impl.h:261
QuantificationUnits(const ExperimentalDesign &design, const std::set< std::pair< std::string, unsigned > > &known_cells)
Definition ProteinGroupArrowExport_impl.h:77
std::string ragged_unit_
Definition ProteinGroupArrowExport_impl.h:265
std::string run_in_several_groups_
Definition ProteinGroupArrowExport_impl.h:267
const std::string & invalidSample() const
Non-empty when a design row refers outside its sample section.
Definition ProteinGroupArrowExport_impl.h:206
bool resolveLabels(LabelFor label_for, const std::string &context)
Definition ProteinGroupArrowExport_impl.h:224
Bundles multiple (e.g. indistinguishable) proteins in a group.
Definition ProteinIdentification.h:73
const IntegerDataArrays & getIntegerDataArrays() const
Returns a const reference to the integer meta data arrays.
const FloatDataArrays & getFloatDataArrays() const
Returns a const reference to the float meta data arrays.
unsigned int UInt
Unsigned integer type.
Definition Types.h:64
size_t Size
Size type e.g. used as variable which can hold result of size()
Definition Types.h:97
std::string qpxRunFileName(const std::string &ms_run_path)
Reduce an MS run path to the QPX run_file_name form.
Namespace used to hide implementation details from users.
Definition BayesianProteinInferenceAlgorithm.h:26
std::string validateQuantificationAnnotations(const ProteinIdentification::ProteinGroup::FloatDataArray *sample_abundances, const FractionGroupAbundanceData &quantities, Size design_samples, const QuantificationUnits &units, const std::set< std::pair< UInt, UInt > > &expected_quantity_keys)
Validate one protein group's complete QPX 1.1 quantification annotation contract.
Definition ProteinGroupArrowExport_impl.h:364
UInt fraction_group
OpenMS fraction_group defining this unit.
Definition ProteinGroupArrowExport_impl.h:46
std::string qpx_label
QPX intensity label ("LFQ", "TMT126", ...)
Definition ProteinGroupArrowExport_impl.h:40
std::vector< QuantChannel > channels
one per label the unit's runs carry
Definition ProteinGroupArrowExport_impl.h:48
bool valid
Definition ProteinGroupArrowExport_impl.h:290
Size sample
design sample, retained to validate consistent run/sample resolution
Definition ProteinGroupArrowExport_impl.h:39
std::map< std::pair< UInt, UInt >, float > values
Definition ProteinGroupArrowExport_impl.h:292
std::string error
Definition ProteinGroupArrowExport_impl.h:291
const ProteinIdentification::ProteinGroup::FloatDataArray * sampleAbundances(const ProteinIdentification::ProteinGroup &group)
Definition ProteinGroupArrowExport_impl.h:276
FractionGroupAbundanceData fractionGroupAbundances(const ProteinIdentification::ProteinGroup &group)
Read PeptideAndProteinQuant's named unit-level arrays and validate their parallel-key contract.
Definition ProteinGroupArrowExport_impl.h:296
bool present
Definition ProteinGroupArrowExport_impl.h:289
unsigned label
design label / channel number, 1-based
Definition ProteinGroupArrowExport_impl.h:38
std::vector< std::string > runs
QPX run names, in fraction order.
Definition ProteinGroupArrowExport_impl.h:47
Parsed parallel arrays carrying protein quantities at (fraction_group, label) grain.
Definition ProteinGroupArrowExport_impl.h:288
One label of a quantification unit and the sample it resolves to in the design.
Definition ProteinGroupArrowExport_impl.h:37
One QPX quantification unit: exactly one OpenMS experimental-design fraction group.
Definition ProteinGroupArrowExport_impl.h:45