Skip to content

Commit a506f62

Browse files
Clusterinfo (#121)
* add parent mass from cluster summary * add parent mass to test data * replace ms2_match with library_match * remove adduct, add parent mass and RT * change parent mz into bool param * add mz RT, remove adduct * specify index for library matches * parent mz to bool, ms2_matches to library_matches * fixing tests * add clarifying comment
1 parent af8795d commit a506f62

12 files changed

+81
-68
lines changed

q2_qemistree/_hierarchy.py

+8-7
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def merge_feature_data(fdata: pd.DataFrame) -> pd.DataFrame:
5959

6060
def make_hierarchy(csi_results: CSIDirFmt,
6161
feature_tables: biom.Table,
62-
ms2_matches: pd.DataFrame = None,
62+
library_matches: pd.DataFrame = None,
6363
qc_properties: bool = False,
6464
metric: str = 'euclidean') -> (TreeNode, biom.Table,
6565
pd.DataFrame):
@@ -75,7 +75,7 @@ def make_hierarchy(csi_results: CSIDirFmt,
7575
one or more CSI:FingerID output folder
7676
feature_table : biom.Table
7777
one or more feature tables with mass-spec feature intensity per sample
78-
ms2_matches: pd.DataFrame
78+
library_matches: pd.DataFrame
7979
one or more tables with MS/MS library match for mass-spec features
8080
qc_properties : bool, default False
8181
flag to filter molecular properties to keep only PUBCHEM fingerprints
@@ -104,20 +104,21 @@ def make_hierarchy(csi_results: CSIDirFmt,
104104
if len(feature_tables) != len(csi_results):
105105
raise ValueError("The feature tables and CSI results should have a "
106106
"one-to-one correspondance.")
107-
if ms2_matches and len(ms2_matches) != len(feature_tables):
107+
if library_matches and len(library_matches) != len(feature_tables):
108108
raise ValueError("The MS2 match tables should have a one-to-one "
109109
"correspondance with feature tables and CSI results.")
110110
for n, (feature_table, csi_result) in enumerate(zip(feature_tables,
111111
csi_results)):
112112
if feature_table.is_empty():
113113
raise ValueError("Cannot have empty feature table")
114-
if ms2_matches:
115-
ms2_match = ms2_matches[n]
116-
if 'Smiles' not in ms2_match.columns:
114+
if library_matches:
115+
library_match = library_matches[n]
116+
if 'Smiles' not in library_match.columns:
117117
raise ValueError("MS2 match tables must contain the "
118118
"column `Smiles`. Please check if you have "
119119
"the correct input file for this command.")
120-
collated_fps, smiles = process_csi_results(csi_result, ms2_match,
120+
collated_fps, smiles = process_csi_results(csi_result,
121+
library_match,
121122
qc_properties,
122123
metric=metric)
123124
else:

q2_qemistree/_match.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -66,16 +66,17 @@ def get_matched_tables(collated_fingerprints: pd.DataFrame,
6666
list_md5.append(md5)
6767
fps['label'] = list_md5
6868
filtered_table['label'] = list_md5
69-
feature_data = pd.DataFrame(columns=['label', '#featureID',
70-
'csi_smiles', 'ms2_smiles',
71-
'ms2_library_match', 'ms2_adduct'])
69+
feature_data = pd.DataFrame(columns=['label', '#featureID','csi_smiles',
70+
'ms2_smiles','ms2_library_match',
71+
'parent_mass', 'retention_time'])
7272
feature_data['label'] = list_md5
7373
feature_data['#featureID'] = allfps
7474
feature_data['csi_smiles'] = list(smiles.loc[allfps, 'csi_smiles'])
7575
feature_data['ms2_smiles'] = list(smiles.loc[allfps, 'ms2_smiles'])
7676
feature_data['ms2_library_match'] = list(
7777
smiles.loc[allfps, 'ms2_library_match'])
78-
feature_data['ms2_adduct'] = list(smiles.loc[allfps, 'ms2_adduct'])
78+
feature_data['parent_mass'] = list(smiles.loc[allfps, 'parent_mass'])
79+
feature_data['retention_time'] = list(smiles.loc[allfps, 'retention_time'])
7980
feature_data.set_index('label', inplace=True)
8081
relabel_fps = fps.groupby('label').first()
8182
matched_table = filtered_table.groupby('label').sum()

q2_qemistree/_plot.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,15 @@ def format_labels(feature_metadata, category, ms2_label, parent_mz):
8080
label = feature_metadata.loc[idx, category]
8181

8282
if parent_mz and label in missing_values:
83-
label = feature_metadata.loc[idx, parent_mz]
83+
label = feature_metadata.loc[idx, 'parent_mass']
8484

8585
labels.append('%s\t%s' % (idx, label))
8686
else:
8787
for idx in feature_metadata.index:
8888
label = feature_metadata.loc[idx, category]
8989

9090
if parent_mz and label in missing_values:
91-
label = feature_metadata.loc[idx, parent_mz]
91+
label = feature_metadata.loc[idx, 'parent_mass']
9292

9393
labels.append('%s\t%s' % (idx, label))
9494

@@ -125,8 +125,8 @@ def format_barplots(table: biom.Table):
125125

126126

127127
def plot(output_dir: str, tree: NewickFormat, feature_metadata: pd.DataFrame,
128-
category: str = 'class', ms2_label: bool = True,
129-
color_palette: str = 'Dark2', parent_mz: str = None,
128+
category: str = 'class', ms2_label: bool = False,
129+
color_palette: str = 'Dark2', parent_mz: bool = True,
130130
grouped_table: biom.Table = None) -> None:
131131
'''This function plots the phenetic tree in iTOL with clade colors,
132132
feature labels and relative abundance per sample group.
@@ -146,9 +146,9 @@ def plot(output_dir: str, tree: NewickFormat, feature_metadata: pd.DataFrame,
146146
The color palette to use for coloring tips
147147
ms2_label : bool, optional
148148
Whether to label the tips with the MS2 value
149-
parent_mz : str, optional
150-
If the feature is unclassified, label the tips using this
151-
column's value
149+
parent_mz : bool, optional
150+
If the feature is unclassified, label the tips using parent mass of
151+
compound
152152
153153
Raises
154154
------

q2_qemistree/_process_fingerprint.py

+14-11
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def collate_fingerprint(csi_result: CSIDirFmt, qc_properties: bool = False,
5757

5858

5959
def get_feature_smiles(csi_result: CSIDirFmt, collated_fps: pd.DataFrame,
60-
ms2_match: pd.DataFrame = None):
60+
library_match: pd.DataFrame = None):
6161
'''This function gets the SMILES of mass-spec features from
6262
CSI:FingerID and optionally, MS/MS library match results
6363
'''
@@ -70,26 +70,29 @@ def get_feature_smiles(csi_result: CSIDirFmt, collated_fps: pd.DataFrame,
7070
smiles['csi_smiles'] = csi_summary.loc[smiles.index, 'smiles'].str.strip()
7171
smiles['ms2_smiles'] = np.nan
7272
smiles['ms2_library_match'] = np.nan
73-
smiles['ms2_adduct'] = np.nan
74-
if ms2_match is not None:
75-
ms2_match.index = ms2_match.index.astype(str)
76-
ms2_ids = ms2_match.index.intersection(smiles.index)
77-
smiles['ms2_smiles'] = ms2_match.loc[ms2_ids, 'Smiles'].str.strip()
78-
smiles['ms2_library_match'] = ms2_match.loc[
79-
ms2_ids, 'Compound_Name']
80-
smiles['ms2_adduct'] = ms2_match.loc[ms2_ids, 'Adduct']
73+
smiles['parent_mass'] = np.nan
74+
smiles['retention_time'] = np.nan
75+
if library_match is not None:
76+
library_match.index = library_match.index.astype(str)
77+
ms2_ids = library_match.index.intersection(smiles.index)
78+
smiles['ms2_smiles'] = library_match.loc[ms2_ids, 'Smiles'].str.strip()
79+
smiles['ms2_library_match'] = library_match.loc[ms2_ids, 'LibraryID']
80+
smiles['parent_mass'] = library_match.loc[ms2_ids, 'parent mass']
81+
smiles['retention_time'] = library_match.loc[ms2_ids, 'RTConsensus']
8182
smiles = smiles.fillna('missing').apply(
8283
lambda x: x.replace({' ': 'missing', '': 'missing'}))
8384
return smiles
8485

8586

86-
def process_csi_results(csi_result: CSIDirFmt, ms2_match: pd.DataFrame = None,
87+
def process_csi_results(csi_result: CSIDirFmt,
88+
library_match: pd.DataFrame = None,
8789
qc_properties: bool = False,
8890
metric: str = 'euclidean') -> (pd.DataFrame,
8991
pd.DataFrame):
9092
'''This function parses CSI:FingerID result to generate tables
9193
of collated molecular fingerprints and SMILES for mass-spec features
9294
'''
9395
collated_fps = collate_fingerprint(csi_result, qc_properties, metric)
94-
feature_smiles = get_feature_smiles(csi_result, collated_fps, ms2_match)
96+
feature_smiles = get_feature_smiles(csi_result, collated_fps,
97+
library_match)
9598
return collated_fps, feature_smiles

q2_qemistree/_transformer.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ def _read_dataframe(fh):
88
# Using `dtype=object` and `set_index` to avoid type casting/inference
99
# of any columns or the index.
1010
df = pd.read_csv(fh, sep='\t', header=0, dtype='str')
11-
df.set_index(df.columns[0], drop=True, append=False, inplace=True)
12-
df.index.name = 'id'
1311
return df
1412

1513
# define a transformer from pd.DataFrame to -> TSVMolecules
@@ -25,6 +23,14 @@ def _1(data: pd.DataFrame) -> TSVMolecules:
2523
def _2(ff: TSVMolecules) -> pd.DataFrame:
2624
with ff.open() as fh:
2725
df = _read_dataframe(fh)
26+
# Using 'cluster index' as index explicity for library matches
27+
# since it may not be the first column
28+
if 'cluster index' in df.columns:
29+
df.set_index('cluster index', drop=True, append=False,
30+
inplace=True)
31+
else:
32+
df.set_index(df.columns[0], drop=True, append=False, inplace=True)
33+
df.index.name = 'id'
2834
return df
2935

3036
# define a transformer from TSVMolecules -> qiime2.Metadata

q2_qemistree/plugin_setup.py

+8-7
Original file line numberDiff line numberDiff line change
@@ -162,21 +162,22 @@
162162
description='Build a phylogeny based on molecular substructures',
163163
inputs={'csi_results': List[CSIFolder],
164164
'feature_tables': List[FeatureTable[Frequency]],
165-
'ms2_matches': List[FeatureData[Molecules]]},
165+
'library_matches': List[FeatureData[Molecules]]},
166166
parameters={'qc_properties': Bool,
167167
'metric': Str % Choices(['euclidean', 'jaccard'])},
168168
input_descriptions={'csi_results': 'one or more CSI:FingerID '
169169
'output folders',
170170
'feature_tables': 'one or more feature tables with '
171171
'mass-spec feature intensity '
172172
'per sample',
173-
'ms2_matches': 'one or more tables with MS/MS library '
174-
'match for mass-spec features'},
173+
'library_matches': 'one or more tables with MS/MS '
174+
'library match for mass-spec '
175+
'features'},
175176
parameter_descriptions={'qc_properties': 'filters molecular properties to '
176177
'retain PUBCHEM fingerprints',
177178
'metric': 'metric for hierarchical clustering of '
178-
'fingerprints. If the Jaccard metric is'
179-
' selected, molecular fingerprints are '
179+
'fingerprints. If the Jaccard metric is '
180+
'selected, molecular fingerprints are '
180181
'first binarized (probabilities above '
181182
'0.5 are True, and False otherwise).'},
182183
outputs=[('tree', Phylogeny[Rooted]),
@@ -245,7 +246,6 @@
245246
},
246247
parameters={
247248
'category': Str,
248-
'parent_mz': Str,
249249
'color_palette': Str % Choices(['Pastel1', 'Pastel2', 'Paired',
250250
'Accent', 'Dark2', 'Set1', 'Set2',
251251
'Set3', 'tab10', 'tab20', 'tab20b',
@@ -255,6 +255,7 @@
255255
'BuPu', 'GnBu', 'PuBu', 'YlGnBu',
256256
'PuBuGn', 'BuGn', 'YlGn']),
257257
'ms2_label': Bool,
258+
'parent_mz': Bool
258259
},
259260
input_descriptions={'grouped_table': 'Feature table of samples '
260261
'grouped by categories. We recommend '
@@ -274,7 +275,7 @@
274275
'tutorials/colors/colormaps.html',
275276
'ms2_label': 'Whether to label the tips with the MS2 value',
276277
'parent_mz': 'If the feature is unclassified, label the tips using '
277-
'this column\'s value'
278+
'this parent mass of the molecule'
278279
},
279280
citations=[citations['letunic2019itol']])
280281

+12-12
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
label #featureID csi_smiles ms2_smiles table_number important not_so_important structure_source ms2_library_match
2-
featurehash1 3 missing missing 1 a x CSIFingerID Spectral Match to Bleepbloop
3-
featurehash2 7 CCCCCCCCCCCCCCCC(=O)OCC(COP(=O)([O-])OCC[N+](C)(C)C)O 1 a x MS2 Caffeine
4-
featurehash3 2 missing missing 1 b x MS2 Fakeiine
5-
featurehash4 5 CC(=NC(=O)CC(=NC(=O)C)OOC(=O)C)O 1 c x MS2 missing
6-
featurehash5 6 CC(=NC(=O)CC(=NC(=O)C)OOC(=O)C)O 1 d x MS2 missing
7-
featurehash7 7 CC(=NC(=O)CC(=NC(=O)C)OOC(=O)C)O 1 e y missing Spectral Match to Glu-Val from NIST14
8-
featurehash8 8 CC(=NC(=O)CC(=NC(=O)C)OOC(=O)C)O 1 f y CSIFingerID Spectral Match to Glu-Val from NIST14
9-
featurehash10 10 CC(=NC(=O)CC(=NC(=O)C)OOC(=O)C)O 1 g y CSIFingerID missing
10-
featurehash22 22 CC(=NC(=O)CC(=NC(=O)C)OOC(=O)C)O 1 h y missing missing
11-
featurehash42 23 CC(=NC(=O)CC(=NC(=O)C)OOC(=O)C)O 1 i y CSIFingerID missing
12-
featurehash43 24 CC(=NC(=O)CC(=NC(=O)C)OOC(=O)C)O 1 j y missing missing
1+
label #featureID csi_smiles ms2_smiles table_number important not_so_important structure_source ms2_library_match parent_mass retention_time
2+
featurehash1 3 missing missing 1 a x CSIFingerID Spectral Match to Bleepbloop 193.2 4.1
3+
featurehash2 7 CCCCCCCCCCCCCCCC(=O)OCC(COP(=O)([O-])OCC[N+](C)(C)C)O 1 a x MS2 Caffeine 194.2 5.1
4+
featurehash3 2 missing missing 1 b x MS2 Fakeiine 195.2 6.1
5+
featurehash4 5 CC(=NC(=O)CC(=NC(=O)C)OOC(=O)C)O 1 c x MS2 missing 196.2 7.1
6+
featurehash5 6 CC(=NC(=O)CC(=NC(=O)C)OOC(=O)C)O 1 d x MS2 missing 197.2 8.1
7+
featurehash7 7 CC(=NC(=O)CC(=NC(=O)C)OOC(=O)C)O 1 e y missing Spectral Match to Glu-Val from NIST14 198.2 9.1
8+
featurehash8 8 CC(=NC(=O)CC(=NC(=O)C)OOC(=O)C)O 1 f y CSIFingerID Spectral Match to Glu-Val from NIST14 199.2 10.1
9+
featurehash10 10 CC(=NC(=O)CC(=NC(=O)C)OOC(=O)C)O 1 g y CSIFingerID missing 200.2 11.1
10+
featurehash22 22 CC(=NC(=O)CC(=NC(=O)C)OOC(=O)C)O 1 h y missing missing 201.2 12.1
11+
featurehash42 23 CC(=NC(=O)CC(=NC(=O)C)OOC(=O)C)O 1 i y CSIFingerID missing 202.2 13.1
12+
featurehash43 24 CC(=NC(=O)CC(=NC(=O)C)OOC(=O)C)O 1 j y missing missing 203.2 14.1
+4-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#featureID csi_smiles ms2_smiles ms2_library_match ms2_adduct
2-
3
3-
7 CC(=N)C1=CN(C)C=N1
4-
2
1+
#featureID csi_smiles ms2_smiles ms2_library_match parent_mass retention_time
2+
3 100.5 1.5
3+
7 CC(=N)C1=CN(C)C=N1 200.5 2.5
4+
2 300.5 3.5

q2_qemistree/tests/data/ms2_match.txt

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#Scan# Adduct CAS_Number Charge Compound_Name Compound_Source Data_Collector ExactMass FileScanUniqueID INCHI INCHI_AUX Instrument IonMode Ion_Source LibMZ LibraryName LibraryQualityString Library_Class MQScore MZErrorPPM MassDiff NumberHits Organism PI Precursor_MZ Pubmed_ID RT_Query SharedPeaks Smiles SpecCharge SpecMZ SpectrumFile SpectrumID TIC_Query UpdateWorkflowName tags
2-
3 M+H-H2O 5255174 1 Spectral Match to 3.beta.-Hydroxy-5-cholenoic acid from NIST14 Isolated Data deposited by fevargas 0 spectra/specs_ms.mgf1 N/A N/A HCD Positive ESI 357.278 lib-00032.mgf Bronze 3 0.863553 0.768752 0.000274658 1 GNPS-NIST14-MATCHES Data from Christopher A. Lowry 357.278 N/A 0 14 N/A 1 357.278 spectra/specs_ms.mgf CCMSLIB00003134721 173700 UPDATE-SINGLE-ANNOTATED-BRONZE
3-
4 149.1 628977 1 Spectral Match to Palmitic acid ethyl ester from NIST14 Isolated Data deposited by mjmeehan 0 spectra/specs_ms.mgf1000 N/A N/A IT/ion trap Positive ESI 149.096 lib-00032.mgf Bronze 3 0.662824 0 0 1 GNPS-NIST14-MATCHES Data from Gabriel Haddad 149.096 N/A 0 3 N/A 1 149.096 spectra/specs_ms.mgf CCMSLIB00003139076 3.50E+06 UPDATE-SINGLE-ANNOTATED-BRONZE
4-
2 M-H2O+H n/a 1 "NCGC00380522-01_C20H30O4_1-Phenanthrenecarboxylic acid, 1,2,3,4,4a,4b,5,6,7,9,10,10a-dodecahydro-7-hydroxy-1,4a-dimethyl-7-(1-methylethyl)-9-oxo-" isolated lfnothias 334.214 spectra/specs_ms.mgf1031 "InChI=1S/C20H30O4/c1-12(2)20(24)9-6-14-13(11-20)15(21)10-16-18(14,3)7-5-8-19(16,4)17(22)23/h11-12,14,16,24H,5-10H2,1-4H3,(H,22,23)" N/A Maxis II HD Q-TOF Bruker positive LC-ESI 317.211 lib-00002.mgf Gold 1 0.640657 0.673441 0.000213623 1 GNPS-NIH-NATURALPRODUCTSLIBRARY_ROUND2_POSITIVE Jadhav/Dorrestein 317.211 n/a 0 8 CC(C)C\1(O)CCC2\C(=C1)C(=O)CC3C2(C)CCCC3(C)C(O)=O 1 317.211 spectra/specs_ms.mgf CCMSLIB00000851506 664000 UPDATE-SINGLE-ANNOTATED-GOLD
1+
ATTRIBUTE_sample_type_group1 ATTRIBUTE_sample_type_group2 ATTRIBUTE_sample_type_group3 Annotated Adduct Features ID Best Ion Correlated Features Group ID G1 G2 G3 G4 G5 G6 GNPSGROUP:achene GNPSGROUP:algae GNPSGROUP:animal GNPSGROUP:complex GNPSGROUP:dairy GNPSGROUP:egg GNPSGROUP:fleshy fruit GNPSGROUP:fruit GNPSGROUP:fungi GNPSGROUP:game meat GNPSGROUP:gastropod GNPSGROUP:grain/grass GNPSGROUP:honey GNPSGROUP:insect GNPSGROUP:invertebrate GNPSGROUP:legume GNPSGROUP:meat GNPSGROUP:mineral GNPSGROUP:not collected GNPSGROUP:not provided GNPSGROUP:nut GNPSGROUP:plant GNPSGROUP:poultry GNPSGROUP:reptile GNPSGROUP:sap GNPSGROUP:seafood GNPSGROUP:seaweed GNPSGROUP:seed GNPSGROUP:soap GNPSGROUP:sugar GNPSGROUP:supplement GNPSGROUP:vegetable/herb GNPSGROUP:water GNPSLinkout_Cluster GNPSLinkout_Network INCHI LibraryID MQScore MS2 Verification Comment RTConsensus RTMean RTStdErr Smiles SpectrumID SumPeakIntensity UniqueFileSourcesCount cluster index componentindex neutral M mass number of spectra parent mass precursor charge precursor mass sum(precursor intensity)
2+
"fungi,algae,plant,animal" "fruit,fungi,algae,animal" "algae,fleshy fruit,seaweed,fungi,dairy" 0 0 0 0 0 0 0 350682.6827 371134.5664 0 1113403.699 0 108800.7569 108800.7569 44505.32383 0 0 0 0 0 0 0 0 0 0 0 0 108800.7569 0 0 0 0 350682.6827 0 0 0 0 0 0 "https://gnps.ucsd.edu/ProteoSAFe/result.jsp?task=096df6e409a14363ab59cba2fd530e80&view=view_all_clusters_withID&show=true#{""main.cluster index_lowerinput"":""1.0"",""main.cluster index_upperinput"":""1.0""}" https://gnps.ucsd.edu/ProteoSAFe/result.jsp?view=network_displayer&componentindex=6&task=096df6e409a14363ab59cba2fd530e80&show=true N/A Spectral Match to 3.beta.-Hydroxy-5-cholenoic acid from NIST14 N/A 6.1091 6.1091 0 N/A N/A 5504981.929 7 3 6 7 230.2476 1 230.2476 5504981.929
3+
"fungi,plant,animal" "fruit,fungi,animal" "fleshy fruit,fungi,honey,dairy" 0 0 0 0 0 0 0 0 605688.934 0 1739539.412 0 518945.9818 518945.9818 128470.8033 0 0 0 77527.38967 0 0 0 0 0 0 0 0 518945.9818 0 0 0 0 0 0 0 0 0 0 0 "https://gnps.ucsd.edu/ProteoSAFe/result.jsp?task=096df6e409a14363ab59cba2fd530e80&view=view_all_clusters_withID&show=true#{""main.cluster index_lowerinput"":""2.0"",""main.cluster index_upperinput"":""2.0""}" This Node is a Singleton N/A Spectral Match to Palmitic acid ethyl ester from NIST14 N/A 0.2616 0.2616 0 N/A N/A 10507126.65 15 4 -1 15 381.0796 1 381.0796 10507126.65
4+
animal animal "honey,dairy" 0 0 0 0 0 0 0 0 736410.3824 0 2192431.97 0 0 0 0 0 0 0 16799.17733 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 "https://gnps.ucsd.edu/ProteoSAFe/result.jsp?task=096df6e409a14363ab59cba2fd530e80&view=view_all_clusters_withID&show=true#{""main.cluster index_lowerinput"":""3.0"",""main.cluster index_upperinput"":""3.0""}" https://gnps.ucsd.edu/ProteoSAFe/result.jsp?view=network_displayer&componentindex=10&task=096df6e409a14363ab59cba2fd530e80&show=true N/A "NCGC00380522-01_C20H30O4_1-Phenanthrenecarboxylic acid, 1,2,3,4,4a,4b,5,6,7,9,10,10a-dodecahydro-7-hydroxy-1,4a-dimethyl-7-(1-methylethyl)-9-oxo-" 0.982019 0.3455 0.3455 0 CC(C)C\1(O)CCC2\C(=C1)C(=O)CC3C2(C)CCCC3(C)C(O)=O CCMSLIB00003139769 6627693.442 4 2 10 4 685.24 1 685.24 6627693.442

0 commit comments

Comments
 (0)