comparison metfrag-vis.py @ 0:3dbe79671820 draft default tip

"planemo upload for repository https://github.com/computational-metabolomics/metfrag-galaxy commit b337c6296968848e3214f4b51df3d86776f84b6a"
author computational-metabolomics
date Tue, 14 Jul 2020 07:42:34 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:3dbe79671820
1 #!/usr/bin/env python
2
3 # Load modules
4 import argparse
5 import base64
6 import csv
7 import os
8 import re
9 import time
10 import urllib.parse
11
12 import matplotlib.pyplot as plt
13
14 import pubchempy
15
16 import requests
17
18 # Parse arguments
19 parser = argparse.ArgumentParser(
20 description='Visualise MetFrag results in html.')
21 parser.add_argument('-v', '--version', action='version',
22 version='MetFrag-vis Version 0.9',
23 help='show version')
24 parser.add_argument('-i', '--input', metavar='metfrag_results.tsv',
25 dest="input_tsv", required=True,
26 help='MetFrag results as input')
27 parser.add_argument('-o', '--output', metavar='metfrag_results.html',
28 dest="output_html", required=True,
29 help='Write MetFrag results into this output file')
30 parser.add_argument('-m', '--max-candidates', metavar='10',
31 dest="max_candidates", default=10, type=int,
32 required=False,
33 help='Maximum number of candidates per compound [1-1000]')
34 parser.add_argument('-s', '--synonyms', dest='synonyms', action='store_true',
35 required=False,
36 help='Fetch synonyms from PubChem [disabled by default]')
37 parser.add_argument('-c', '--classyfire', dest='classyfire',
38 action='store_true', required=False,
39 help='Fetch compound classes from ClassyFire'
40 ' [disabled by default]')
41
42 args = parser.parse_args()
43
44 # Input CSV with MetFrag results
45 input_tsv = args.input_tsv
46
47 # Output html of MetFrag results
48 output_html = args.output_html
49
50 # Max number of candidates per compound
51 max_candidates = args.max_candidates
52
53 # PubChem synonyms
54 pubchem_synonyms_enabled = args.synonyms
55
56 # ClassyFire classes
57 classyfire_classes_enabled = args.classyfire
58
59
60 # ---------- cdk_inchi_to_svg ----------
61 def cdk_inchi_to_svg(inchi):
62 if "cdk-inchi-to-svg" in os.environ:
63 JAVA_CMD = 'cdk-inchi-to-svg' + ' ' + str(
64 '\'' + inchi + '\'') + ' ' + 'cdk-inchi-to-svg-output.svg'
65 else:
66 JAVA_BINARY = '/usr/local/bin/java'
67 CDK_INCHI_TO_SVG_JAR = '/usr/local/bin/' \
68 'cdk-inchi-to-svg-0.0.1-' \
69 'SNAPSHOT-jar-with-dependencies.jar'
70 JAVA_CMD = str(
71 JAVA_BINARY + ' ' + '-jar' + ' ' + CDK_INCHI_TO_SVG_JAR + ' '
72 + str('\'' + inchi + '\'') + ' ' + 'cdk-inchi-to-svg-output.svg')
73
74 # Exec cdk-inchi-to-svg JAVA binary
75 exitcode = os.system(JAVA_CMD)
76
77 # Check whether binary has successfully been run
78 if (exitcode == 0):
79 with open("cdk-inchi-to-svg-output.svg", "r") as svg_file:
80 svg_string = []
81 for line in svg_file:
82 if not ('<?xml' in line) and not ('<!DOCTYPE' in line):
83 if (' fill=\'#FFFFFF\'' in line):
84 line = re.sub(' fill=\'#FFFFFF\'',
85 ' fill=\'#FFFFFF\' fill-opacity=\'0.0\'',
86 line)
87 svg_string.append(line)
88 svg_file.close()
89 os.remove("cdk-inchi-to-svg-output.svg")
90 return (str(''.join(svg_string)))
91 else:
92 return ('&nbsp;')
93
94
95 # ---------- pubchem_link ----------
96 def pubchem_link(compound_name):
97 return (str('https://pubchem.ncbi.nlm.nih.gov/#query=' + compound_name))
98
99
100 # ---------- kegg_link ----------
101 def kegg_link(compound_name):
102 return (str(
103 'https://www.genome.jp/dbget-bin/'
104 'www_bfind_sub?mode=bfind&max_hit=1000&dbkey=kegg&keywords=' +
105 compound_name))
106
107
108 # ---------- biocyc_link ----------
109 def biocyc_link(compound_name):
110 biocyc_url = urllib.parse.urlparse(
111 str(
112 'https://www.biocyc.org/'
113 'substring-search?type=NIL&object=' +
114 compound_name + '&quickSearch=Quick+Search'))
115 return (biocyc_url.geturl())
116
117
118 # ---------- hmdb_link ----------
119 def hmdb_link(compound_name):
120 hmdb_url = urllib.parse.urlparse(
121 str(
122 'https://hmdb.ca/unearth/q?utf8=\xe2&query=' +
123 compound_name + '&searcher=metabolites&button='))
124 return (hmdb_url.geturl())
125
126
127 # ---------- hmdb_link ----------
128 def chebi_link(inchi):
129 return (str(
130 'https://www.ebi.ac.uk/chebi/advancedSearchFT.do?searchString='
131 + inchi))
132
133
134 # ---------- PubChem Synonyms ----------
135 def fetch_pubchem_synonyms(inchi):
136 if not ('InChI=' in inchi):
137 return ('&nbsp;')
138
139 # Fetch CID from InChI
140 print('Retrieving PubChem CID from InChI...')
141 compound = pubchempy.get_compounds(identifier=inchi, namespace='inchi')
142 compound_cid = re.sub(r'\).*', '', re.sub(r'.*\(', '', str(compound)))
143 if len(compound_cid) <= 1:
144 print(str('Warning. No match for InChI \"' + str(inchi) + '\".'))
145 return ('&nbsp;')
146
147 # Retrieve compound
148 print('Retrieving PubChem compound information...')
149 compound = pubchempy.Compound.from_cid(compound_cid)
150 if ('synonyms' in dir(compound)):
151 return ('; '.join(compound.synonyms))
152 else:
153 print(str('Warning. No synonyms found for CID \"' + str(
154 compound_cid) + '\".'))
155 return ('&nbsp;')
156
157
158 # ---------- ClassyFire ----------
159 def fetch_classyfire_classes(inchi):
160 if not ('InChI=' in inchi):
161 return ('&nbsp;')
162
163 # Send POST request to ClassyFire
164 print('Sending request to ClassyFire...')
165 classyfire_url = 'http://classyfire.wishartlab.com/queries.json'
166 classyfire_post = str(
167 '{\"label\":\"metfrag\",\"query_input\":\"' + inchi +
168 '\",\"query_type\":\"STRUCTURE\"}')
169 classyfire_headers = {'Content-Type': 'application/json'}
170 classyfire_request = requests.post(classyfire_url, data=classyfire_post,
171 headers=classyfire_headers)
172
173 # Only continue when request has been successfully sent
174 if (classyfire_request.status_code != 201):
175 print('Error! Could not send request to ClassyFire. \"',
176 str(classyfire_request.status_code) + ': ' + str(
177 classyfire_request.reason), '\". Skipping entry.')
178 return ('&nbsp;')
179
180 # Get ClassyFire Query ID
181 classyfire_request.json()
182 classyfire_query_id = classyfire_request.json()['id']
183
184 # Query ClassyFire in max. 20 attempts
185 classyfire_request_loop = 0
186 while (classyfire_request_loop < 20):
187 print(str(
188 'Sending query ' + str(classyfire_query_id) + ' to ClassyFire...'))
189 time.sleep(10)
190 classyfire_query = requests.get(
191 str('http://classyfire.wishartlab.com/queries/' + str(
192 classyfire_query_id) + '.json'))
193
194 if (classyfire_query.status_code == 200) and (
195 classyfire_query.json()['classification_status'] == 'Done'):
196 classyfire_request_loop = 999
197 break
198 else:
199 classyfire_request_loop += 1
200
201 if classyfire_request_loop == 999:
202 # Direct parent
203 direct_parent_name = classyfire_query.json()[
204 'entities'][0]['direct_parent']['name']
205 direct_parent_url = classyfire_query.json()[
206 'entities'][0]['direct_parent']['url']
207 direct_parent = str(
208 '<a target="_blank" href="' + direct_parent_url + '">' +
209 direct_parent_name + '</a>')
210
211 # Alternative parents
212 alt_parents = []
213 for i in range(0, len(classyfire_query.json()['entities'][0][
214 'alternative_parents'])):
215 alt_parent_name = classyfire_query.json()[
216 'entities'][0]['alternative_parents'][i]['name']
217 alt_parent_url = classyfire_query.json()[
218 'entities'][0]['alternative_parents'][i]['url']
219 alt_parent = str(
220 '<a target="_blank" href="' + alt_parent_url + '">' +
221 alt_parent_name + '</a>')
222 alt_parents.append(alt_parent)
223
224 # Concat classes
225 classes = str('<b>' + direct_parent + '</b>, <br>' + str(
226 ', <br>'.join(alt_parents)))
227 else:
228 print('Warning. Timout sending query to ClassyFire. Skipping entry.')
229 classes = '&nbsp;'
230
231 return (classes)
232
233
234 # ---------- Plot Spectrum ----------
235 def plot_spectrum(spectrum, spectrum_explained, spectrum_explained_formulas):
236 # Plot
237 plt.figure(figsize=[5.5, 4.4])
238 plt.xlabel('m/z')
239 plt.ylabel('intensity')
240
241 # Plot spectrum
242 x = []
243 y = []
244 for i in spectrum.split(';'):
245 t = i.split('_')
246 x.append(t[0])
247 y.append(t[1])
248
249 for i in range(0, len(x)):
250 plt.plot([float(x[i]), float(x[i])], [0, float(y[i])], linewidth=1,
251 color='black')
252 plt.plot(float(x[i]), float(y[i]), 'o', color='black', markersize=4)
253
254 if not (spectrum_explained == 'NA') and not (
255 spectrum_explained_formulas == 'NA'):
256 # Plot explained peaks
257 ex = []
258 ey = []
259 for i in spectrum_explained.split(';'):
260 t = i.split('_')
261 ex.append(t[0])
262 ey.append(y[x.index(t[0])])
263
264 for i in range(0, len(ex)):
265 plt.plot([float(ex[i]), float(ex[i])], [0, float(ey[i])],
266 linewidth=3, color='#2b8126')
267 plt.plot(float(ex[i]), float(ey[i]), 'o', color='#2b8126',
268 markersize=8)
269
270 # Plot formulas on explained peaks
271 ex = []
272 ey = []
273 ez = []
274 for i in spectrum_explained_formulas.split(';'):
275 t = i.split(':')
276 ex.append(t[0])
277 ey.append(y[x.index(t[0])])
278 ez.append(t[1])
279
280 for i in range(0, len(ex)):
281 plt.text(float(ex[i]), float(ey[i]) + 1000, ez[i], color='#2b8126',
282 fontsize=8,
283 horizontalalignment='center', verticalalignment='bottom')
284
285 # Save SVG
286 plt.savefig("metfrag-vis-spectrum.svg", format="svg", transparent=True)
287 plt.close()
288
289 # Import SVG
290 with open("metfrag-vis-spectrum.svg", "r") as svg_file:
291 svg_string = []
292 for line in svg_file:
293 if not ('<?xml' in line) and not ('<!DOCTYPE' in line) and not (
294 ' "http://www.w3.org/Graphics' in line):
295 svg_string.append(line)
296 svg_file.close()
297 os.remove("metfrag-vis-spectrum.svg")
298 return (str(''.join(svg_string)))
299
300
301 # #################### MAIN ####################
302 if pubchem_synonyms_enabled:
303 print('Fetching of PubChem Synonyms enabled.')
304 if classyfire_classes_enabled:
305 print('Fetching of ClassyFire Classes enabled.')
306
307 # Open output html file
308 try:
309 metfrag_html = open(output_html, "w")
310 except Exception as e:
311 print("Error writing output file. {}".format(e))
312 exit(1)
313
314 # Write html header
315 metfrag_html.write('<!DOCTYPE html>\n')
316 metfrag_html.write('<html>\n')
317 metfrag_html.write('<head>\n')
318 metfrag_html.write('<title>' + 'msPurity MetFrag results' + '</title>\n')
319 metfrag_html.write('<style type="text/css">\n')
320 metfrag_html.write('svg { width: 200px; height: 100%; }\n')
321 metfrag_html.write(
322 'body { font-family: Lucida, Verdana, Arial, Helvetica, sans-serif; '
323 'font-size: 13px; text-align: left; '
324 'color: #000000; margin: 8px 8px 8px 8px; }\n')
325 metfrag_html.write(
326 'A { color: #2b8126; text-decoration: none; background: transparent; }\n')
327 metfrag_html.write(
328 'A:visited { '
329 'color: #19681a; text-decoration: none; background: transparent; '
330 '}\n')
331 metfrag_html.write(
332 'A:hover { '
333 'color: #8fc180; text-decoration: underline; background: transparent; '
334 '}\n')
335 metfrag_html.write(
336 'h1 { font-size: 32px; font-weight: bold; text-align: center; '
337 'padding: 0px 0px 4px 0px; margin: 26px 0px 0px 0px; }\n')
338 metfrag_html.write(
339 'h2 { font-size: 24px; font-weight: bold; text-align: left; '
340 'padding: 0px 0px 4px 0px; margin: 26px 0px 0px 0px; }\n')
341 metfrag_html.write(
342 'table { font-family: Lucida, Verdana, Arial, Helvetica, sans-serif; '
343 'font-size: 10px; text-align: left; '
344 'line-height: 10px; border: 1px solid #e3efdf; '
345 'background-color: #ecf5ea; margin-bottom: 8px; '
346 'min-width: 1600px; max-width: 2400px; }\n')
347 metfrag_html.write(
348 '#tablediv { width: 100%; min-width: 20px; max-width: 200px; }\n')
349 metfrag_html.write('.tdmax { min-width: 200px; max-width: 200px; }\n')
350 metfrag_html.write('.tdvar { min-width: 200px; max-width: 600px; }\n')
351 metfrag_html.write('tr:nth-child(even) { background-color: #f6faf5; }\n')
352 metfrag_html.write('</style>\n')
353 metfrag_html.write('</head>\n')
354 metfrag_html.write('<body>\n')
355
356 # Read input csv file
357 with open(input_tsv, "r") as metfrag_file:
358 metfrag_results = csv.DictReader(metfrag_file, delimiter='\t')
359 # Parse each line
360 line_count = 0
361 compound = ""
362 candidates = 0
363 for row in metfrag_results:
364
365 # Start new document
366 if (line_count == 0):
367 if os.path.join(os.path.dirname(os.path.abspath(__file__)),
368 'metfrag_logo.png'):
369 logo_pth = os.path.join(
370 os.path.dirname(os.path.abspath(__file__)),
371 'metfrag_logo.png')
372 else:
373 logo_pth = '/usr/local/share/metfrag/metfrag_logo.png'
374
375 with open(logo_pth, "rb") as png_file:
376 png_encoded = base64.b64encode(png_file.read())
377 metfrag_html.write(str(
378 '\n<h1><img style="vertical-align:bottom" '
379 'src="data:image/png;base64,' +
380 png_encoded.decode('utf-8') +
381 '" alt="metfrag-logo" width="150"></img><text '
382 'style="line-height:2.0">&nbsp;&nbsp;results</text></h1>\n'
383 ))
384 else:
385 # Parameter list at beginning of document
386 if (line_count == 1):
387 metfrag_html.write('\n<h2>Parameter list</h2>\n')
388 metfrag_html.write(str('MetFragDatabaseType=' +
389 re.sub(' .*', '',
390 re.sub('.*MetFragDatabaseType=',
391 '',
392 row[
393 "MetFragCLIString"]))
394 + '<br>\n')
395 )
396 metfrag_html.write(str('PrecursorIonMode=' +
397 re.sub(' .*', '',
398 re.sub('.*PrecursorIonMode=', '',
399 row[
400 "MetFragCLIString"]))
401 + '<br>\n')
402 )
403 metfrag_html.write(str('DatabaseSearchRelativeMassDeviation=' +
404 re.sub(' .*', '',
405 re.sub(
406 '.*DatabaseSearchRelative'
407 'MassDeviation=',
408 '',
409 row[
410 "MetFragCLIString"])) +
411 '<br>\n')
412 )
413 metfrag_html.write(
414 str('FragmentPeakMatchAbsoluteMassDeviation=' +
415 re.sub(' .*', '',
416 re.sub(
417 '.*FragmentPeakMatchAbsoluteMassDeviation=',
418 '',
419 row["MetFragCLIString"])) + '<br>\n')
420 )
421 metfrag_html.write(
422 str('FragmentPeakMatchRelativeMassDeviation=' +
423 re.sub(' .*', '',
424 re.sub(
425 '.*FragmentPeakMatchRelativeMassDeviation=',
426 '',
427 row["MetFragCLIString"])) + '<br>\n')
428 )
429 metfrag_html.write(str('FilterExcludedElements=' +
430 re.sub(' .*', '',
431 re.sub(
432 '.*FilterExcludedElements=',
433 '',
434 row[
435 "MetFragCLIString"])) +
436 '<br>\n')
437 )
438 metfrag_html.write(str('FilterIncludedElements=' +
439 re.sub(' .*', '',
440 re.sub(
441 '.*FilterIncludedElements=',
442 '',
443 row[
444 "MetFragCLIString"])) +
445 '<br>\n')
446 )
447 metfrag_html.write(str('MetFragScoreTypes=' +
448 re.sub(' .*', '',
449 re.sub('.*MetFragScoreTypes=',
450 '',
451 row[
452 "MetFragCLIString"]))
453 + '<br>\n')
454 )
455 # New compound in list
456 if (row["name"] != compound):
457 compound = row["name"]
458 candidates = 0
459 identifier = row["name"]
460 monoisotopic_mass = row["MonoisotopicMass"]
461 precursor_mz = row["precursor_mz"]
462
463 if "retention_time" in row:
464 precursor_rt = row["retention_time"]
465 try:
466 precursor_rt = round(float(precursor_rt), 4)
467 except ValueError:
468 continue
469 else:
470 precursor_rt = ''
471
472 if "precursor_type" in row:
473 precursor_type = row["precursor_type"]
474 elif "adduct" in row:
475 precursor_type = row["adduct"]
476 else:
477 precursor_type = ''
478
479 if line_count > 1:
480 metfrag_html.write(str('</table>\n'))
481
482 metfrag_html.write(str('\n' + '<h2>' + identifier + '</h2>\n'))
483 metfrag_html.write(str('<p><b>Precursor Type:</b> ' + str(
484 precursor_type) + '<br>'))
485 metfrag_html.write(str('<b>Precursor Mass:</b> ' + str(
486 round(float(precursor_mz), 4)) + '<br>'))
487 metfrag_html.write(
488 str('<b>Precursor Retention Time:</b> ' + str(
489 precursor_mz) + '<br></p>'))
490 metfrag_html.write(str('\n' + '<table>\n'))
491 metfrag_html.write(str(
492 '<tr style="vertical-align:bottom; '
493 'background-color:#e3efdf;">'
494 + '<td class="tdmax">' + '<b>Spectrum</b>' + '</td>'
495 + '<td class="tdmax">' + '<b>Structure</b>' + '</td>'
496 + '<td>' + '<b>Monoisotopic Mass</b>' + '</td>'
497 + '<td>' + '<b>Molecular Formula</b>' + '</td>'
498 + '<td>' + '<b>Compound Name</b>' + '</td>'
499 + '<td class="tdvar">' + '<b>PubChem Synonyms</b>'+'</td>'
500 + '<td>' + '<b>Compound Classes</b>' + '</td>'
501 + '<td>' + '<b>MetFrag Score</b>' + '</td>'
502 + '<td>' + '<b>MetFusion Score</b>' + '</td>'
503 + '<td>' + '<b>Fragmenter Score</b>' + '</td>'
504 + '<td>' + '<b>Suspectlist Score</b>' + '</td>'
505 + '<td>' + '<b>Explained Peaks</b>' + '</td>'
506 + '<td>' + '<b>MetFrag Web</b>' + '</td>'
507 + '<td>' + '<b>External Links</b>' + '</td>'
508 + '<td class="tdmax">' + '<b>InChI</b>' + '</td>'
509 + '</tr>\n'))
510
511 # Compound candidate
512 if (candidates < max_candidates):
513 # Column variables
514 inchi = row["InChI"]
515 smiles = row["SMILES"]
516 mol_formula = row["MolecularFormula"]
517 compound_name = row["IUPACName"]
518 frag_score = row["FragmenterScore"]
519 metfusion_score = row["OfflineMetFusionScore"]
520 score = row["Score"]
521 if "SuspectListScore" in row:
522 suspectlist_score = row["SuspectListScore"]
523 else:
524 suspectlist_score = 0
525 peaks_explained = row["NoExplPeaks"]
526 peaks_used = row["NumberPeaksUsed"]
527 spectrum_explained = row["ExplPeaks"]
528 spectrum_explained_formulas = row["FormulasOfExplPeaks"]
529 identifier = row["Identifier"]
530
531 # PubChem Synonyms
532 if pubchem_synonyms_enabled:
533 pubchem_synonyms = fetch_pubchem_synonyms(inchi)
534 else:
535 pubchem_synonyms = '&nbsp;'
536
537 # Compound Classes
538 if classyfire_classes_enabled:
539 compound_classes = fetch_classyfire_classes(inchi)
540 else:
541 compound_classes = '&nbsp;'
542
543 # Draw Spectrum
544 spectrum = re.sub(' .*', '', re.sub('.*PeakListString=', '',
545 row["MetFragCLIString"]))
546 spectrum_string = plot_spectrum(spectrum, spectrum_explained,
547 spectrum_explained_formulas)
548
549 # Draw SVG
550 svg_string = cdk_inchi_to_svg(str(inchi))
551
552 # External links
553 external_links = str(
554 '<a target="_blank" href="' + pubchem_link(
555 compound_name) + '">PubChem</a>' + ', ' +
556 '<a target="_blank" href="' + kegg_link(
557 compound_name) + '">KEGG</a>' + ', ' +
558 '<a target="_blank" href="' + hmdb_link(
559 compound_name) + '">HMDB</a>' + ', ' +
560 '<a target="_blank" href="' + biocyc_link(
561 compound_name) + '">BioCyc</a>' + ', ' +
562 '<a target="_blank" href="' + chebi_link(
563 inchi) + '">ChEBI</a>')
564
565 # MetFragWeb
566 FragmentPeakMatchAbsoluteMassDeviation = str(
567 '' +
568 re.sub(' .*', '',
569 re.sub(
570 '.*FragmentPeakMatchAbsoluteMassDeviation=',
571 'FragmentPeakMatchAbsoluteMassDeviation=',
572 row["MetFragCLIString"]))
573 )
574 FragmentPeakMatchRelativeMassDeviation = str(
575 '' +
576 re.sub(' .*', '',
577 re.sub(
578 '.*FragmentPeakMatchRelativeMassDeviation=',
579 'FragmentPeakMatchRelativeMassDeviation=',
580 row["MetFragCLIString"]))
581 )
582 DatabaseSearchRelativeMassDeviation = str(
583 '' +
584 re.sub(' .*', '',
585 re.sub(
586 '.*DatabaseSearchRelativeMassDeviation=',
587 'DatabaseSearchRelativeMassDeviation=',
588 row["MetFragCLIString"]))
589 )
590 IonizedPrecursorMass = str(
591 'IonizedPrecursorMass=' + str(row["precursor_mz"]))
592 NeutralPrecursorMass = str(
593 '' + re.sub(' .*', '',
594 re.sub(
595 '.*NeutralPrecursorMass=',
596 'NeutralPrecursorMass=',
597 row[
598 "MetFragCLIString"]))
599 )
600 NeutralPrecursorMolecularFormula = str(
601 'NeutralPrecursorMolecularFormula=' + str(
602 row["MolecularFormula"]))
603 PrecursorIonMode = str(
604 '' + re.sub(' .*', '', re.sub('.*PrecursorIonMode=',
605 'PrecursorIonMode=',
606 row["MetFragCLIString"])))
607 PeakList = str(
608 '' + re.sub(' .*', '',
609 re.sub('.*PeakListString=', 'PeakList=',
610 row["MetFragCLIString"])))
611 MetFragDatabaseType = str(
612 '' + re.sub(' .*', '',
613 re.sub(
614 '.*MetFragDatabaseType=',
615 'MetFragDatabaseType=',
616 row["MetFragCLIString"])))
617
618 metfrag_web = str(
619 'https://msbi.ipb-halle.de/MetFrag/landing.xhtml?' +
620 FragmentPeakMatchAbsoluteMassDeviation + '&' +
621 FragmentPeakMatchRelativeMassDeviation + '&' +
622 DatabaseSearchRelativeMassDeviation + '&' +
623 IonizedPrecursorMass + '&' +
624 NeutralPrecursorMass + '&' +
625 # NeutralPrecursorMolecularFormula + '&' +
626 PrecursorIonMode + '&' +
627 PeakList + '&' +
628 MetFragDatabaseType)
629
630 # Write html code
631 metfrag_html.write(str('<tr style="vertical-align:center">' +
632 '<td class="tdmax">' +
633 spectrum_string +
634 '</td>' +
635 '<td class="tdmax">' +
636 svg_string +
637 '</td>' +
638 '<td>' +
639 monoisotopic_mass +
640 '</td>' +
641 '<td>' +
642 mol_formula +
643 '</td>' +
644 '<td>' +
645 compound_name +
646 '</td>' +
647 '<td class="tdvar">' +
648 pubchem_synonyms +
649 '</td>' +
650 '<td>' +
651 compound_classes + '</td>' +
652 '<td>' +
653 str(round(float(score), 3)) +
654 '</td>' +
655 '<td>' +
656 str(round(float(metfusion_score), 3)) +
657 '</td>' +
658 '<td>' +
659 str(round(float(frag_score), 3)) +
660 '</td>' +
661 '<td>' +
662 str(
663 round(float(suspectlist_score), 3)
664 ) +
665 '</td>' +
666 '<td>' +
667 peaks_explained +
668 ' / ' +
669 peaks_used +
670 '</td>' +
671 '<td>' +
672 '<a target="_blank" href="' +
673 metfrag_web +
674 '">MetFragWeb</a>' +
675 '</td>' +
676 '<td>' +
677 external_links +
678 '</td>' +
679 '<td class="tdmax">' +
680 inchi +
681 '</td>' +
682 '</tr>\n'))
683
684 line_count += 1
685 candidates += 1
686
687 # Finish candidate list
688 metfrag_html.write(str('</table>\n'))
689
690 # Write html footer
691 metfrag_html.write('\n</body>\n')
692 metfrag_html.write('</html>\n')
693
694 # Close output html file
695 metfrag_html.close()