1 #!/bin/env python
2 #
3 # File: RDKitDrawMoleculesAndDataTable.py
4 # Author: Manish Sud <msud@san.rr.com>
5 #
6 # Copyright (C) 2026 Manish Sud. All rights reserved.
7 #
8 # The functionality available in this script is implemented using RDKit, an
9 # open source toolkit for cheminformatics developed by Greg Landrum.
10 #
11 # This file is part of MayaChemTools.
12 #
13 # MayaChemTools is free software; you can redistribute it and/or modify it under
14 # the terms of the GNU Lesser General Public License as published by the Free
15 # Software Foundation; either version 3 of the License, or (at your option) any
16 # later version.
17 #
18 # MayaChemTools is distributed in the hope that it will be useful, but without
19 # any warranty; without even the implied warranty of merchantability of fitness
20 # for a particular purpose. See the GNU Lesser General Public License for more
21 # details.
22 #
23 # You should have received a copy of the GNU Lesser General Public License
24 # along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or
25 # write to the Free Software Foundation Inc., 59 Temple Place, Suite 330,
26 # Boston, MA, 02111-1307, USA.
27 #
28
29 from __future__ import print_function
30
31 import os
32 import sys
33 import time
34 import re
35 import random
36
37 # RDKit imports...
38 try:
39 from rdkit import rdBase
40 from rdkit import Chem
41 from rdkit.Chem import AllChem
42 except ImportError as ErrMsg:
43 sys.stderr.write("\nFailed to import RDKit module/package: %s\n" % ErrMsg)
44 sys.stderr.write("Check/update your RDKit environment and try again.\n\n")
45 sys.exit(1)
46
47 # MayaChemTools imports...
48 sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), "..", "lib", "Python"))
49 try:
50 from docopt import docopt
51 import MiscUtil
52 import RDKitUtil
53 except ImportError as ErrMsg:
54 sys.stderr.write("\nFailed to import MayaChemTools module/package: %s\n" % ErrMsg)
55 sys.stderr.write("Check/update your MayaChemTools environment and try again.\n\n")
56 sys.exit(1)
57
58 ScriptName = os.path.basename(sys.argv[0])
59 Options = {}
60 OptionsInfo = {}
61
62
63 def main():
64 """Start execution of the script."""
65
66 MiscUtil.PrintInfo(
67 "\n%s (RDKit v%s; MayaChemTools v%s; %s): Starting...\n"
68 % (ScriptName, rdBase.rdkitVersion, MiscUtil.GetMayaChemToolsVersion(), time.asctime())
69 )
70
71 (WallClockTime, ProcessorTime) = MiscUtil.GetWallClockAndProcessorTime()
72
73 # Retrieve command line arguments and options...
74 RetrieveOptions()
75
76 # Process and validate command line arguments and options...
77 ProcessOptions()
78
79 # Perform actions required by the script...
80 GenerateMoleculesAndDataTable()
81
82 MiscUtil.PrintInfo("\n%s: Done...\n" % ScriptName)
83 MiscUtil.PrintInfo("Total time: %s" % MiscUtil.GetFormattedElapsedTime(WallClockTime, ProcessorTime))
84
85
86 def GenerateMoleculesAndDataTable():
87 """Generate a HTML table containing molecules and alphanumerical data."""
88
89 # Retrieve data...
90 ValidMols = RetrieveMoleculesAndData()
91
92 # Setup data type map...
93 DataMap = IdentifyStructureAndNumericalData(ValidMols)
94
95 # Validate data labels used to specify highlighting data...
96 ValidateSpecifiedDataLabels(DataMap)
97
98 # Validate show molecule name option...
99 ValidateShowMolNameOption(DataMap)
100
101 # Compute 2D coordinates before alignment...
102 if OptionsInfo["Compute2DCoords"]:
103 MiscUtil.PrintInfo("\nComputing 2D coordinates for primary structure data...")
104 for Mol in ValidMols:
105 AllChem.Compute2DCoords(Mol)
106
107 # Perform alignment to a common template for primary molecular structure data...
108 PerformAlignment(ValidMols)
109
110 # Write out a HTML file...
111 WriteHTMLTableFile(ValidMols, DataMap)
112
113
114 def WriteHTMLTableFile(ValidMols, DataMap):
115 """Write out a HTML table file."""
116
117 Outfile = OptionsInfo["Outfile"]
118
119 Writer = open(Outfile, "w")
120 if Writer is None:
121 MiscUtil.PrintError("Failed to setup a writer for output fie %s " % Outfile)
122
123 MiscUtil.PrintInfo("\nGenerating file %s..." % Outfile)
124
125 WriteHTMLPageHeader(Writer, DataMap)
126 WriteHTMLPageTitle(Writer)
127
128 WriteHTMLTableHeader(Writer)
129 WriteHTMLTableRows(Writer, ValidMols, DataMap)
130 WriteHTMLTableEnd(Writer)
131
132 WriteHTMLPageFooter(Writer)
133 WriteHTMLPageEnd(Writer)
134
135 if Writer is not None:
136 Writer.close()
137
138
139 def WriteHTMLTableRows(Writer, ValidMols, DataMap):
140 """Write out HTML table rows."""
141
142 WriteTableHeaderRow(Writer, ValidMols, DataMap)
143 WriteTableDataRows(Writer, ValidMols, DataMap)
144 WriteTableFooterRow(Writer, ValidMols, DataMap)
145
146
147 def WriteTableDataRows(Writer, ValidMols, DataMap):
148 """Write out table data row."""
149
150 Writer.write(""" <tbody>\n""")
151
152 MolCount = 0
153 for Mol in ValidMols:
154 MolCount += 1
155 Writer.write(""" <tr>\n""")
156
157 if OptionsInfo["CounterCol"]:
158 Writer.write(""" <td></td>\n""")
159
160 SetupPrimaryStructureTableData(Writer, Mol)
161
162 if OptionsInfo["ShowMolName"]:
163 MolName = RDKitUtil.GetMolName(Mol, MolCount)
164 WrappedMolName = MiscUtil.WrapText(MolName, "<br/>", OptionsInfo["WrapTextWidth"])
165 Writer.write(""" <td>%s</td>\n""" % WrappedMolName)
166
167 # Set up rest of the data..
168 AvailableDataLabelsMap = Mol.GetPropsAsDict(includePrivate=False, includeComputed=False)
169 for DataLabel in DataMap["DataLabels"]:
170 if DataLabel not in AvailableDataLabelsMap:
171 Writer.write(""" <td></td>\n""")
172 continue
173
174 # Check for empty value...
175 DataValue = "%s" % AvailableDataLabelsMap[DataLabel]
176 DataValue = DataValue.strip()
177 if MiscUtil.IsEmpty(DataValue):
178 Writer.write(""" <td></td>\n""")
179 continue
180
181 if DataMap["StructureDataMap"][DataLabel]:
182 SetupNonPrimaryStructureTableData(Writer, DataLabel, DataValue, DataMap)
183 else:
184 SetupAlphanumericTableData(Writer, DataLabel, DataValue, DataMap)
185
186 Writer.write(""" </tr>\n""")
187
188 Writer.write(""" </tbody>\n""")
189
190
191 def SetupPrimaryStructureTableData(Writer, Mol):
192 """Set up an inline SVG image for primary structure data for a table cell."""
193
194 HightlightAtomList = SetupAtomListToHighlight(Mol, "Structure")
195 SVGImageTag = SetupMolInLineSVGImageTag(Mol, HightlightAtomList)
196
197 Writer.write(""" <td bgcolor="white"><%s></td>\n""" % SVGImageTag)
198
199
200 def SetupNonPrimaryStructureTableData(Writer, DataLabel, DataValue, DataMap):
201 """Set up an inline SVG image for non primary structure data cell."""
202
203 WrappedDataValue = DataValue
204 if OptionsInfo["WrapText"]:
205 WrappedDataValue = MiscUtil.WrapText(DataValue, "<br/>", OptionsInfo["WrapTextWidth"])
206
207 if DataMap["SMILESDataMap"][DataLabel]:
208 Mol = Chem.MolFromSmiles(DataValue, sanitize=False)
209 Mol.UpdatePropertyCache(strict=False)
210 else:
211 MiscUtil.PrintWarning(
212 "\nIgnoring uknown structure data column type with column label %s: %s\n" % (DataLabel, DataValue)
213 )
214 Writer.write(""" <td>%s</td>\n""" % WrappedDataValue)
215 return
216
217 if Mol is None:
218 MiscUtil.PrintWarning("\nSMILES parsing failed for data label %s: %s\n" % (DataLabel, DataValue))
219 Writer.write(""" <td>%s</td>\n""" % WrappedDataValue)
220 return
221 elif not Mol.GetNumHeavyAtoms():
222 Writer.write(""" <td>%s</td>\n""" % WrappedDataValue)
223 return
224 elif AllChem.Compute2DCoords(Mol) < 0:
225 Writer.write(""" <td>%s</td>\n""" % WrappedDataValue)
226 return
227
228 HightlightAtomList = SetupAtomListToHighlight(Mol, DataLabel)
229 SVGImageTag = SetupMolInLineSVGImageTag(Mol, HightlightAtomList)
230
231 Writer.write(""" <td bgcolor="white"><%s></td>\n""" % SVGImageTag)
232
233
234 def SetupAlphanumericTableData(Writer, DataLabel, DataValue, DataMap):
235 """Set up alphanumeric data."""
236
237 BackgroundColor, BackgroundColorType = GetAlphanumeircValueHighlightBackgroundColor(DataLabel, DataValue, DataMap)
238 SetupAlphanumericTableDataValue(Writer, DataValue, BackgroundColor, BackgroundColorType)
239
240
241 def WriteTableHeaderRow(Writer, ValidMols, DataMap):
242 """Write out table header row."""
243
244 TableHeaderStyle = OptionsInfo["TableHeaderStyle"]
245 if TableHeaderStyle is None:
246 Writer.write(""" <thead>\n""")
247 Writer.write(""" <tr>\n""")
248 elif re.match("^(thead|table)", TableHeaderStyle):
249 Writer.write(""" <thead class="%s">\n""" % TableHeaderStyle)
250 Writer.write(""" <tr>\n""")
251 else:
252 Writer.write(""" <thead>\n""")
253 Writer.write(""" <tr bgcolor="%s"\n""" % TableHeaderStyle)
254
255 if OptionsInfo["CounterCol"]:
256 Writer.write(""" <th></th>\n""")
257 Writer.write(""" <th>Structure</th>\n""")
258 if OptionsInfo["ShowMolName"]:
259 Writer.write(""" <th>%s</th>\n""" % OptionsInfo["ShowMolNameDataLabel"])
260
261 # Write out rest of the column headers...
262 for DataLabel in DataMap["DataLabels"]:
263 Writer.write(""" <th>%s</th>\n""" % DataLabel)
264
265 Writer.write(""" </tr>\n""")
266 Writer.write(""" </thead>\n""")
267
268
269 def WriteTableFooterRow(Writer, ValidMols, DataMap):
270 """Write out table footer row."""
271
272 if not OptionsInfo["TableFooter"]:
273 return
274
275 Writer.write(""" <tfoot>\n""")
276 Writer.write(""" <tr>\n""")
277
278 if OptionsInfo["CounterCol"]:
279 Writer.write(""" <td></td>\n""")
280 Writer.write(""" <td>Structure</td>\n""")
281 if OptionsInfo["ShowMolName"]:
282 Writer.write(""" <td>%s</td>\n""" % OptionsInfo["ShowMolNameDataLabel"])
283
284 # Write out rest of the column headers...
285 for DataLabel in DataMap["DataLabels"]:
286 Writer.write(""" <td>%s</td>\n""" % DataLabel)
287
288 Writer.write(""" </tr>\n""")
289 Writer.write(""" </tfoot>\n""")
290
291
292 def WriteHTMLPageHeader(Writer, DataMap):
293 """Write out HTML page header."""
294
295 # Collect column indices containing counter and structure data to disable
296 # sorting and searching. In addition, set up a list to exclude counter and
297 # primary structure columns from column visibility pulldown along with
298 # any other columns...
299 #
300 if OptionsInfo["CounterCol"]:
301 StrColIndicesList = ["0", "1"]
302 ColVisibilityExcludeColIndicesList = ["0", "1"]
303 ColIndexOffset = 2
304 FreezeLeftColumns = "2"
305 else:
306 StrColIndicesList = ["0"]
307 ColVisibilityExcludeColIndicesList = ["0"]
308 ColIndexOffset = 1
309 FreezeLeftColumns = "1"
310
311 if OptionsInfo["ShowMolName"]:
312 ColIndexOffset += 1
313
314 MaxColVisColCount = OptionsInfo["ColVisibilityCtrlMax"]
315 MaxDataColVisColCount = MaxColVisColCount - len(ColVisibilityExcludeColIndicesList)
316 MaxDataColVisColCount = MaxColVisColCount
317
318 DataColVisibilityExclude = False
319 ColCount = len(DataMap["DataLabels"])
320 if OptionsInfo["ColVisibility"]:
321 if ColCount > MaxDataColVisColCount:
322 DataColVisibilityExclude = True
323 MiscUtil.PrintWarning(
324 "The number of data columns, %d, is more than %d. Only first %d data columns will be available in column visibility pulldown."
325 % (ColCount, MaxColVisColCount, MaxColVisColCount)
326 )
327
328 DisplayButtons = False
329 if OptionsInfo["ColVisibility"]:
330 if ColCount > 0 or OptionsInfo["ShowMolName"]:
331 DisplayButtons = True
332
333 FreezeCols = False
334 if OptionsInfo["FreezeCols"] and OptionsInfo["ScrollX"]:
335 FreezeCols = True
336
337 for Index, DataLabel in enumerate(DataMap["DataLabels"]):
338 if DataMap["StructureDataMap"][DataLabel]:
339 StrColIndex = Index + ColIndexOffset
340 StrColIndicesList.append("%s" % StrColIndex)
341
342 if OptionsInfo["ColVisibility"]:
343 if Index >= MaxDataColVisColCount:
344 ColIndex = Index + ColIndexOffset
345 ColVisibilityExcludeColIndicesList.append("%s" % ColIndex)
346
347 StrColIndices = MiscUtil.JoinWords(StrColIndicesList, ", ")
348 ColVisibilityExcludeColIndices = MiscUtil.JoinWords(ColVisibilityExcludeColIndicesList, ", ")
349
350 Paging = "true" if OptionsInfo["Paging"] else "false"
351 PageLength = "%d" % OptionsInfo["PageLength"]
352 PagingType = '"%s"' % OptionsInfo["PagingType"]
353
354 ScrollX = "true" if OptionsInfo["ScrollX"] else "false"
355
356 ScrollY = ""
357 if OptionsInfo["ScrollY"]:
358 if re.search("vh$", OptionsInfo["ScrollYSize"]):
359 ScrollY = '"%s"' % OptionsInfo["ScrollYSize"]
360 else:
361 ScrollY = "%s" % OptionsInfo["ScrollYSize"]
362
363 RegexSearch = "true" if OptionsInfo["RegexSearch"] else "false"
364
365 # Start HTML header...
366 Title = "Molecules and data table" if OptionsInfo["Header"] is None else OptionsInfo["Header"]
367
368 Writer.write(
369 """\
370 <!doctype html>
371 <html lang="en">
372 <head>
373 <title>%s</title>
374 <meta charset="utf-8">
375 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
376 <link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
377 <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.16/css/dataTables.bootstrap4.min.css">
378
379 """
380 % (Title)
381 )
382
383 if FreezeCols:
384 Writer.write("""\
385 <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/fixedcolumns/3.2.4/css/fixedColumns.bootstrap4.min.css">
386 """)
387
388 if OptionsInfo["KeysNavigation"]:
389 Writer.write("""\
390 <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/keytable/2.3.2/css/keyTable.bootstrap4.min.css">
391 """)
392
393 Writer.write("""\
394
395 <script type="text/javascript" language="javascript" src="https://code.jquery.com/jquery-1.12.4.js"></script>
396 <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js"></script>
397 <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/1.10.16/js/dataTables.bootstrap4.min.js"></script>
398
399 """)
400
401 if DisplayButtons:
402 Writer.write("""\
403 <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/buttons/1.5.1/js/dataTables.buttons.min.js"></script>
404 <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/buttons/1.5.1/js/buttons.bootstrap4.min.js"></script>
405 <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/buttons/1.5.1/js/buttons.colVis.min.js"></script>
406
407 """)
408
409 if FreezeCols:
410 Writer.write("""\
411 <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/fixedcolumns/3.2.4/js/dataTables.fixedColumns.min.js"></script>
412 """)
413
414 if OptionsInfo["KeysNavigation"]:
415 Writer.write("""\
416 <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/keytable/2.3.2/js/dataTables.keyTable.min.js"></script>
417 """)
418
419 # Intialize table using Bootstrap, DataTables and JQuery frameworks...
420 Writer.write(
421 """\
422
423 <script type="text/javascript" class="init">
424
425 $(document).ready(function() {
426 var MolsAndDataTable = $('#MolsAndDataTable').DataTable( {
427 "columnDefs": [
428 {
429 "orderable": false,
430 "searchable": false,
431 "targets": [%s]
432 },
433 """
434 % (StrColIndices)
435 )
436
437 if OptionsInfo["ColVisibility"]:
438 Writer.write(
439 """\
440 {
441 "className": "noColVisCtrl",
442 "targets": [%s]
443 }
444 """
445 % (ColVisibilityExcludeColIndices)
446 )
447
448 Writer.write("""\
449 ],
450 """)
451
452 # Setup column visibility control pulldown by excluding counter column
453 # and primary structure column from the list...
454 #
455 if OptionsInfo["ColVisibility"]:
456 # Set up dom for button display...
457 if OptionsInfo["Paging"]:
458 Writer.write("""\
459 "dom": "<'row'<'col'l><'col'B><'col'f>>" +
460 "<'row'<'col-sm-12'tr>>" +
461 "<'row'<'col-sm-5'i><'col-sm-7'p>>",
462 """)
463 else:
464 Writer.write("""\
465 "dom": "<'row'<'col-sm-6'B><'col-sm-6'f>>" +
466 "<'row'<'col-sm-12'tr>>" +
467 "<'row'<'col-sm-5'i><'col-sm-7'p>>",
468 """)
469 # Set up buttons...
470 Writer.write("""\
471 "buttons": [
472 {
473 "extend": "colvis",
474 "text": "Column visibility",
475 "className": "btn btn-outline-light text-dark",
476 "columns": ":not(.noColVisCtrl)",
477 """)
478 if not DataColVisibilityExclude:
479 Writer.write("""\
480 "prefixButtons": [ "colvisRestore" ],
481 """)
482
483 Writer.write("""\
484 "columnText": function ( dt, colIndex, colLabel ) {
485 return (colIndex + 1) + ": " + colLabel;
486 },
487 }
488 ],
489 """)
490
491 # Write out rest of the variables for DataTables...
492 if FreezeCols:
493 Writer.write(
494 """\
495 "fixedColumns": {
496 "leftColumns": %s
497 },
498 """
499 % (FreezeLeftColumns)
500 )
501
502 if OptionsInfo["KeysNavigation"]:
503 Writer.write("""\
504 "keys": true,
505 """)
506
507 Writer.write(
508 """\
509 "pageLength": %s,
510 "lengthMenu": [ [10, 15, 25, 50, 100, 500, 1000, -1], [10, 15, 25, 50, 100, 500, 1000, "All"] ],
511 "paging": %s,
512 "pagingType": %s,
513 "scrollX": %s,
514 "scrollY": %s,
515 "scrollCollapse": true,
516 "order": [],
517 "search" : {"regex" : %s},
518 } );
519 """
520 % (PageLength, Paging, PagingType, ScrollX, ScrollY, RegexSearch)
521 )
522
523 if OptionsInfo["CounterCol"]:
524 Writer.write("""\
525 MolsAndDataTable.on( 'order.dt search.dt', function () {
526 MolsAndDataTable.column(0, {search:'applied', order:'applied'}).nodes().each( function (cell, rowIndex) {
527 cell.innerHTML = rowIndex + 1;
528 } );
529 } ).draw();
530 """)
531
532 # End of Javacscript code...
533 Writer.write("""\
534 } );
535
536 </script>
537 """)
538
539 # Finish up HTML header...
540 Writer.write("""\
541
542 </head>
543 <body>
544 <div class="container-fluid">
545 <br/>
546 """)
547
548
549 def WriteHTMLPageEnd(Writer):
550 """Write out HTML page end."""
551
552 Writer.write("""\
553 </div>
554 </body>
555 </html>
556 """)
557
558
559 def WriteHTMLPageTitle(Writer):
560 """Write out HTML page title."""
561
562 if OptionsInfo["Header"] is None:
563 return
564
565 Writer.write(
566 """ <%s class="text-center">%s</%s>\n"""
567 % (OptionsInfo["HeaderStyle"], OptionsInfo["Header"], OptionsInfo["HeaderStyle"])
568 )
569
570
571 def WriteHTMLPageFooter(Writer):
572 """Write out HTML page footer."""
573
574 if OptionsInfo["Footer"] is None:
575 return
576
577 Writer.write(""" <br/>\n <p class="%s">%s</p>\n""" % (OptionsInfo["FooterClass"], OptionsInfo["Footer"]))
578
579
580 def WriteHTMLTableHeader(Writer):
581 """Write out HTML table header."""
582
583 if OptionsInfo["TableStyle"] is None:
584 Writer.write("""\n <table id="MolsAndDataTable" cellspacing="0" width="100%">\n""")
585 else:
586 Writer.write(
587 """ <table id="MolsAndDataTable" class="%s" cellspacing="0" width="100%s">\n"""
588 % (OptionsInfo["TableStyle"], "%")
589 )
590
591
592 def WriteHTMLTableEnd(Writer):
593 """Write out HTML table end."""
594
595 Writer.write(""" </table>\n\n""")
596
597
598 def RetrieveMoleculesAndData():
599 """Retrieve molecules and data from input file."""
600
601 MiscUtil.PrintInfo("\nReading file %s..." % OptionsInfo["Infile"])
602
603 if MiscUtil.CheckFileExt(OptionsInfo["Infile"], "smi csv tsv txt"):
604 # Check for the presence of SMILES column name in title line...
605 Infile = open(OptionsInfo["Infile"], "r")
606 if Infile is None:
607 MiscUtil.PrintError("Couldn't open file %s..." % OptionsInfo["Infile"])
608 Line = Infile.readline()
609 Infile.close()
610
611 if not re.search("SMILES", Line, re.I):
612 MiscUtil.PrintError(
613 "The input file, %s, must contain a title line containing a column name with SMILES in its name."
614 % OptionsInfo["Infile"]
615 )
616
617 if MiscUtil.CheckFileExt(OptionsInfo["Infile"], "sdf sd smi"):
618 ValidMols, MolCount, ValidMolCount = RDKitUtil.ReadAndValidateMolecules(
619 OptionsInfo["Infile"], **OptionsInfo["InfileParams"]
620 )
621 else:
622 ValidMols, MolCount, ValidMolCount = RetrieveMoleculesFromTextFile(OptionsInfo["Infile"])
623
624 MiscUtil.PrintInfo("Total number of molecules: %d" % MolCount)
625 MiscUtil.PrintInfo("Number of valid molecules: %d" % ValidMolCount)
626 MiscUtil.PrintInfo("Number of ignored molecules: %d" % (MolCount - ValidMolCount))
627
628 return ValidMols
629
630
631 def RetrieveMoleculesFromTextFile(Infile):
632 """Retrieve molecules from a CSV/TSV text file."""
633
634 # Read and parse text lines...
635 Delimiter = "," if MiscUtil.CheckFileExt(Infile, "csv") else "\t"
636 QuoteChar = '"'
637 IgnoreHeaderLine = False
638 TextLinesWords = MiscUtil.GetTextLinesWords(Infile, Delimiter, QuoteChar, IgnoreHeaderLine)
639
640 # Process column names...
641 ColNames = TextLinesWords[0]
642 ColCount = len(ColNames)
643
644 MolColIndex = None
645 MolDataColIndices = []
646
647 FirstSMILES = True
648 for ColIndex in range(0, ColCount):
649 if re.search("SMILES", ColNames[ColIndex], re.I) and FirstSMILES:
650 MolColIndex = ColIndex
651 FirstSMILES = False
652 continue
653
654 MolDataColIndices.append(ColIndex)
655
656 if MolColIndex is None:
657 MiscUtil.PrintError(
658 "The input file, %s, must contain a title line containing a column name with SMILES in its name." % Infile
659 )
660
661 ValidMols = []
662 MolCount = 0
663
664 Sanitize = OptionsInfo["InfileParams"]["Sanitize"]
665
666 # Process data lines...
667 for LineIndex in range(1, len(TextLinesWords)):
668 MolCount += 1
669 LineWords = TextLinesWords[LineIndex]
670 if len(LineWords) != ColCount:
671 MiscUtil.PrintWarning(
672 "Ignoring text line number %d: Number of columns, %d, must match number of columns, %d, in title line.\nLine: %s"
673 % (MolCount, len(LineWords), ColCount, Delimiter.join(LineWords))
674 )
675 continue
676
677 # Process molecule column...
678 MolSMILES = LineWords[MolColIndex]
679 Mol = Chem.MolFromSmiles(MolSMILES, sanitize=Sanitize)
680 if Mol is None:
681 MiscUtil.PrintWarning(
682 "Ignoring text line number %d: SMILES parsing failed\nLine: %s" % (MolCount, Delimiter.join(LineWords))
683 )
684 continue
685
686 # Process molecule data columns...
687 for ColIndex in MolDataColIndices:
688 Name = ColNames[ColIndex]
689 Value = LineWords[ColIndex]
690 Mol.SetProp(Name, Value)
691
692 ValidMols.append(Mol)
693
694 ValidMolCount = len(ValidMols)
695
696 return (ValidMols, MolCount, ValidMolCount)
697
698
699 def IdentifyStructureAndNumericalData(ValidMols):
700 """Identify structure and alphanumerical data."""
701
702 DataMap = {}
703 DataMap["DataLabels"] = []
704 DataMap["DataLabelsMap"] = {}
705 DataMap["CanonicalDataLabelsMap"] = {}
706
707 DataMap["StructureDataMap"] = {}
708 DataMap["SMILESDataMap"] = {}
709
710 # Retrieve all possible data labels...
711 if MiscUtil.CheckFileExt(OptionsInfo["Infile"], "smi csv tsv txt"):
712 # First molecule contains all possible data fields...
713 Mol = ValidMols[0]
714 ProcessMolDataLabels(ValidMols[0], DataMap)
715 else:
716 # Go over all molecules to identify unique data labels...
717 MiscUtil.PrintInfo("\nRetrieving unique data labels for data in file %s..." % OptionsInfo["Infile"])
718 for Mol in ValidMols:
719 ProcessMolDataLabels(Mol, DataMap)
720
721 return DataMap
722
723
724 def ProcessMolDataLabels(Mol, DataMap):
725 """Process data label to identify and track its type."""
726
727 for DataLabel in Mol.GetPropNames(includePrivate=False, includeComputed=False):
728 if DataLabel in DataMap["DataLabelsMap"]:
729 continue
730
731 # Track labels...
732 DataMap["DataLabels"].append(DataLabel)
733 DataMap["DataLabelsMap"][DataLabel] = DataLabel
734 DataMap["CanonicalDataLabelsMap"][DataLabel.lower()] = DataLabel
735
736 DataMap["StructureDataMap"][DataLabel] = False
737 DataMap["SMILESDataMap"][DataLabel] = False
738
739 if re.search("SMILES", DataLabel, re.I):
740 DataMap["StructureDataMap"][DataLabel] = True
741 DataMap["SMILESDataMap"][DataLabel] = True
742
743
744 def ValidateShowMolNameOption(DataMap):
745 """Validate show molecule name option."""
746
747 if not OptionsInfo["ShowMolName"]:
748 return
749
750 if not MiscUtil.CheckFileExt(OptionsInfo["Infile"], "sdf sd smi"):
751 OptionsInfo["ShowMolName"] = False
752 return
753
754 CanonicalDataLabel = OptionsInfo["ShowMolNameDataLabel"].lower()
755 if CanonicalDataLabel in DataMap["CanonicalDataLabelsMap"]:
756 OptionsInfo["ShowMolName"] = False
757 if not OptionsInfo["ShowMolNameAuto"]:
758 MiscUtil.PrintWarning(
759 'Ignoring "--showMolName" option: Data label "Name" corresponding to molecule name is already present in input file.'
760 )
761
762
763 def ValidateSpecifiedDataLabels(DataMap):
764 """Validate data labels used to specify highlighting data."""
765
766 ValidateSpecifiedDataLabelsForHighlightSMARTS(DataMap)
767
768 ValidateSpecifiedDataLabelsForHighlightValues(DataMap)
769 ValidateSpecifiedDataLabelsForHighlightRanges(DataMap)
770 ValidateSpecifiedDataLabelsForHighlightClasses(DataMap)
771
772
773 def ValidateSpecifiedDataLabelsForHighlightSMARTS(DataMap):
774 """Validate data labels used to specify highlighting SMARTS option."""
775
776 if OptionsInfo["HighlightSMARTSAllMode"]:
777 return
778
779 for DataLabel in OptionsInfo["HighlightSMARTSDataLabels"]:
780 if re.match("^Structure$", DataLabel, re.I):
781 continue
782
783 CanonicalDataLabel = DataLabel.lower()
784 if CanonicalDataLabel not in DataMap["CanonicalDataLabelsMap"]:
785 MiscUtil.PrintError(
786 'The data label specified, %s, using option "--highlightSMARTS" doesn\'t exist in input file.'
787 % DataLabel
788 )
789
790 Label = DataMap["CanonicalDataLabelsMap"][CanonicalDataLabel]
791 if not DataMap["StructureDataMap"][Label]:
792 MiscUtil.PrintError(
793 'The data label specified, %s, using option "--highlightSMARTS" doesn\'t correspond to structure data: Valid structure data labels: SMILES in data label.'
794 % DataLabel
795 )
796
797
798 def ValidateSpecifiedDataLabelsForHighlightValues(DataMap):
799 """Validate data labels used to specify highlighting values option."""
800
801 ValidateDataLabels("--highlightValues", DataMap, OptionsInfo["HighlightValuesLabels"])
802
803
804 def ValidateSpecifiedDataLabelsForHighlightRanges(DataMap):
805 """Validate data labels used to specify highlighting ranges option."""
806
807 ValidateDataLabels("--highlightRanges", DataMap, OptionsInfo["HighlightRangesLabels"])
808
809
810 def ValidateSpecifiedDataLabelsForHighlightClasses(DataMap):
811 """Validate data labels used to specify highlighting classes option."""
812
813 if OptionsInfo["HighlightClassesRules"] is None:
814 return
815
816 ValidDataLabelsList = []
817 NotValidDataLabelsList = []
818 for Label in OptionsInfo["HighlightClassesLabels"]:
819 ValidCanonicalLabel = None
820
821 for LabelSynonym in OptionsInfo["HighlightClassesSynonymsMap"][Label]:
822 CanonicalLabel = LabelSynonym.lower()
823
824 # Is this label already in use...
825 if CanonicalLabel in OptionsInfo["HighlightValuesCanonicalLabelsMap"]:
826 MiscUtil.PrintInfo("")
827 MiscUtil.PrintWarning(
828 'The data label, %s, for class, %s , in option "--highlightValuesClasses" has already been used in "--highlightValues" option. It\'ll be ignored during highlighting.'
829 % (LabelSynonym, OptionsInfo["HighlightClasses"])
830 )
831 continue
832
833 if CanonicalLabel in OptionsInfo["HighlightRangesCanonicalLabelsMap"]:
834 MiscUtil.PrintInfo("")
835 MiscUtil.PrintWarning(
836 'The data label, %s, for class, %s , in option "--highlightValuesClasses" has already been used in "--highlightValuesRanges" option. It\'ll be ignored during highlighting.'
837 % (LabelSynonym, OptionsInfo["HighlightClasses"])
838 )
839 continue
840
841 # Is this label present in input file...
842 if CanonicalLabel in DataMap["CanonicalDataLabelsMap"]:
843 ValidCanonicalLabel = CanonicalLabel
844 break
845
846 if ValidCanonicalLabel is None:
847 MiscUtil.PrintWarning(
848 'The data label or its synonyms - %s - for class, %s , in option "--highlightValuesClasses" either don\'t exist in input file or have already been used for highlighting in option "--highlightValuesClasses" or "--highlightValuesRanges". It\'ll be ignored during highlighting.'
849 % (
850 MiscUtil.JoinWords(OptionsInfo["HighlightClassesSynonymsMap"][Label], ", "),
851 OptionsInfo["HighlightClasses"],
852 )
853 )
854 NotValidDataLabelsList.append(Label)
855 continue
856
857 # Track label...
858 OptionsInfo["HighlightClassesCanonicalLabelsMap"][ValidCanonicalLabel] = Label
859 ValidDataLabelsList.append(DataMap["CanonicalDataLabelsMap"][ValidCanonicalLabel])
860
861 ValidDataLabelsCount = len(ValidDataLabelsList)
862 DataLabelsCount = len(OptionsInfo["HighlightClassesLabels"])
863
864 if ValidDataLabelsCount == 0:
865 MiscUtil.PrintInfo("")
866 MiscUtil.PrintWarning(
867 'The data labels and their synonyms for class, %s , in option "--highlightValuesClasses" either don\'t exists in input file or have already been used for highlighting in option "--highlightValuesClasses" or "--highlightValuesRanges". No class highlighting will be performed. Missing data labels: %s'
868 % (OptionsInfo["HighlightClasses"], MiscUtil.JoinWords(OptionsInfo["HighlightClassesLabels"], ", "))
869 )
870 elif ValidDataLabelsCount < DataLabelsCount:
871 MiscUtil.PrintInfo("")
872 MiscUtil.PrintWarning(
873 'The class, %s, based highlighting specified using "--highlightValuesClasses" option will be performed using only, %d, out of, %d, data labels: %s\nThe rest of the data label(s) - %s - either don\'t exist in the input file or have aready been used for highlighting in option "--highlightValuesClasses" or "--highlightValuesRanges".'
874 % (
875 OptionsInfo["HighlightClasses"],
876 ValidDataLabelsCount,
877 DataLabelsCount,
878 MiscUtil.JoinWords(ValidDataLabelsList, ", "),
879 MiscUtil.JoinWords(NotValidDataLabelsList, ", "),
880 )
881 )
882
883
884 def ValidateDataLabels(OptionName, DataMap, DataLabels):
885 """Validate data labels."""
886
887 for DataLabel in DataLabels:
888 if re.match("^Structure$", DataLabel, re.I):
889 MiscUtil.PrintError(
890 'The data label specified, %s, using option "-%s" must not correspond to structure data. Structure label is not allowed.'
891 % (DataLabel, OptionName)
892 )
893
894 CanonicalDataLabel = DataLabel.lower()
895 if CanonicalDataLabel not in DataMap["CanonicalDataLabelsMap"]:
896 MiscUtil.PrintError(
897 'The data label specified, %s, using option "%s" doesn\'t exist in input file.'
898 % (DataLabel, OptionName)
899 )
900
901 Label = DataMap["CanonicalDataLabelsMap"][CanonicalDataLabel]
902 if DataMap["StructureDataMap"][Label]:
903 MiscUtil.PrintError(
904 'The data label specified, %s, using option "%s" must not correspond to structure data: Valid structure data labels contain "SMILES" in their name..'
905 % (DataLabel, OptionName)
906 )
907
908
909 def SetupMolInLineSVGImageTag(Mol, HightlightAtomList):
910 """Setup a inline SVG image tag for molecule."""
911
912 SVGText = RDKitUtil.GetInlineSVGForMolecule(
913 Mol,
914 OptionsInfo["MolImageWidth"],
915 OptionsInfo["MolImageHeight"],
916 AtomListToHighlight=HightlightAtomList,
917 Base64Encoded=OptionsInfo["MolImageEncoded"],
918 )
919
920 if OptionsInfo["MolImageEncoded"]:
921 SVGInlineImageTag = 'img src="data:image/svg+xml;base64,\n%s"' % SVGText
922 else:
923 SVGInlineImageTag = 'img src="data:image/svg+xml;charset=UTF-8,\n%s"' % SVGText
924
925 return SVGInlineImageTag
926
927
928 def SetupAtomListToHighlight(Mol, DataLabel):
929 """Set up atom list to highlight using specified SMARTS patterns."""
930
931 HighlightAtomList = None
932 if OptionsInfo["HighlightSMARTS"] is None:
933 return HighlightAtomList
934
935 if OptionsInfo["HighlightSMARTSAllMode"]:
936 PatternMol = OptionsInfo["HighlightSMARTSPatternMol"]
937 else:
938 CanonicalDataLabel = DataLabel.lower()
939 if CanonicalDataLabel not in OptionsInfo["HighlightSMARTSCanonicalDataLabelsMap"]:
940 return HighlightAtomList
941
942 Label = OptionsInfo["HighlightSMARTSCanonicalDataLabelsMap"][CanonicalDataLabel]
943 PatternMol = OptionsInfo["HighlightSMARTSPatternMolsMap"][Label]
944
945 # Get matched atom lists and flatten it...
946 MatchedAtomsLists = Mol.GetSubstructMatches(PatternMol)
947 MatchedAtoms = [Atom for AtomsList in MatchedAtomsLists for Atom in AtomsList]
948
949 if len(MatchedAtoms):
950 HighlightAtomList = MatchedAtoms
951
952 return HighlightAtomList
953
954
955 def GetAlphanumeircValueHighlightBackgroundColor(DataLabel, DataValue, DataMap):
956 """Get background highlight color for a value."""
957
958 BackgroundColor = None
959 BackgroundColorType = None
960
961 CanonicalDataLabel = DataLabel.lower()
962 if CanonicalDataLabel in OptionsInfo["HighlightValuesCanonicalLabelsMap"]:
963 return GetBackgroundColorUsingHighlightValuesMode(DataLabel, DataValue, DataMap)
964 elif CanonicalDataLabel in OptionsInfo["HighlightRangesCanonicalLabelsMap"]:
965 return GetBackgroundColorUsingHighlightRangesMode(DataLabel, DataValue, DataMap)
966 elif CanonicalDataLabel in OptionsInfo["HighlightClassesCanonicalLabelsMap"]:
967 return GetBackgroundColorUsingHighlightClassesMode(DataLabel, DataValue, DataMap)
968 elif OptionsInfo["HighlightClassesRandom"]:
969 return GetBackgroundColorUsingRandomMode(DataLabel, DataValue, DataMap)
970
971 return (BackgroundColor, BackgroundColorType)
972
973
974 def GetBackgroundColorUsingHighlightValuesMode(DataLabel, DataValue, DataMap):
975 """Get background highlight color for a value."""
976
977 BackgroundColor = None
978 BackgroundColorType = None
979
980 CanonicalDataLabel = DataLabel.lower()
981 if CanonicalDataLabel not in OptionsInfo["HighlightValuesCanonicalLabelsMap"]:
982 return (BackgroundColor, BackgroundColorType)
983
984 Label = OptionsInfo["HighlightValuesCanonicalLabelsMap"][CanonicalDataLabel]
985 DataType = OptionsInfo["HighlightValuesTypesMap"][Label]
986 Criterion = OptionsInfo["HighlightValuesCriteriaMap"][Label]
987 CriterionValue = OptionsInfo["HighlightValuesCriteriaValuesMap"][Label]
988
989 return GetBackgroundColorForHighlightingValue(DataLabel, DataValue, DataType, Criterion, CriterionValue)
990
991
992 def GetBackgroundColorUsingHighlightClassesMode(DataLabel, DataValue, DataMap):
993 """Get background highlight color for a value."""
994
995 BackgroundColor = None
996 BackgroundColorType = None
997
998 CanonicalDataLabel = DataLabel.lower()
999 if CanonicalDataLabel not in OptionsInfo["HighlightClassesCanonicalLabelsMap"]:
1000 return (BackgroundColor, BackgroundColorType)
1001
1002 Label = OptionsInfo["HighlightClassesCanonicalLabelsMap"][CanonicalDataLabel]
1003 DataType = OptionsInfo["HighlightClassesTypesMap"][Label]
1004 Criterion = OptionsInfo["HighlightClassesCriteriaMap"][Label]
1005 CriterionValue = OptionsInfo["HighlightClassesCriteriaValuesMap"][Label]
1006
1007 return GetBackgroundColorForHighlightingValue(DataLabel, DataValue, DataType, Criterion, CriterionValue)
1008
1009
1010 def GetBackgroundColorForHighlightingValue(DataLabel, DataValue, DataType, Criterion, CriterionValue):
1011 """Get background color for highlighting a value."""
1012
1013 ValueOkay = False
1014
1015 BackgroundColor = OptionsInfo["HighlightColorsList"][0] if ValueOkay else OptionsInfo["HighlightColorsList"][1]
1016 BackgroundColorType = OptionsInfo["HighlightColorsType"]
1017
1018 if re.match("^numeric$", DataType, re.I):
1019 if not MiscUtil.IsNumber(DataValue):
1020 MiscUtil.PrintWarning(
1021 "Ignoring data value, %s, for data label, %s, during numeric highlighting: It must be a number"
1022 % (DataValue, DataLabel)
1023 )
1024 return (BackgroundColor, BackgroundColorType)
1025
1026 DataValue = float(DataValue)
1027 if re.match("^gt$", Criterion, re.I):
1028 ValueOkay = True if DataValue > CriterionValue else False
1029 elif re.match("^lt$", Criterion, re.I):
1030 ValueOkay = True if DataValue < CriterionValue else False
1031 elif re.match("^ge$", Criterion, re.I):
1032 ValueOkay = True if DataValue >= CriterionValue else False
1033 elif re.match("^le$", Criterion, re.I):
1034 ValueOkay = True if DataValue <= CriterionValue else False
1035 elif re.match("^eq$", Criterion, re.I):
1036 ValueOkay = True if DataValue == CriterionValue else False
1037 elif re.match("^ne$", Criterion, re.I):
1038 ValueOkay = True if DataValue != CriterionValue else False
1039 else:
1040 return (BackgroundColor, BackgroundColorType)
1041 elif re.match("^text$", DataType, re.I):
1042 DataValue = "%s" % DataValue
1043 if re.match("^gt$", Criterion, re.I):
1044 ValueOkay = True if DataValue > CriterionValue else False
1045 elif re.match("^lt$", Criterion, re.I):
1046 ValueOkay = True if DataValue < CriterionValue else False
1047 elif re.match("^ge$", Criterion, re.I):
1048 ValueOkay = True if DataValue >= CriterionValue else False
1049 elif re.match("^le$", Criterion, re.I):
1050 ValueOkay = True if DataValue <= CriterionValue else False
1051 elif re.match("^eq$", Criterion, re.I):
1052 ValueOkay = True if DataValue == CriterionValue else False
1053 elif re.match("^ne$", Criterion, re.I):
1054 ValueOkay = True if DataValue != CriterionValue else False
1055 else:
1056 return (BackgroundColor, BackgroundColorType)
1057 elif re.match("^regex$", DataType, re.I):
1058 DataValue = "%s" % DataValue
1059 if re.match("^eq$", Criterion, re.I):
1060 ValueOkay = True if re.search("%s" % CriterionValue, DataValue, re.I) else False
1061 elif re.match("^ne$", Criterion, re.I):
1062 ValueOkay = False if re.search("%s" % CriterionValue, DataValue, re.I) else True
1063 else:
1064 return (BackgroundColor, BackgroundColorType)
1065
1066 BackgroundColor = OptionsInfo["HighlightColorsList"][0] if ValueOkay else OptionsInfo["HighlightColorsList"][1]
1067 BackgroundColorType = OptionsInfo["HighlightColorsType"]
1068
1069 return (BackgroundColor, BackgroundColorType)
1070
1071
1072 def GetBackgroundColorUsingHighlightRangesMode(DataLabel, DataValue, DataMap):
1073 """Get background highlight color for value range."""
1074
1075 BackgroundColor = None
1076 BackgroundColorType = None
1077
1078 CanonicalDataLabel = DataLabel.lower()
1079 if CanonicalDataLabel not in OptionsInfo["HighlightRangesCanonicalLabelsMap"]:
1080 return (BackgroundColor, BackgroundColorType)
1081
1082 Label = OptionsInfo["HighlightRangesCanonicalLabelsMap"][CanonicalDataLabel]
1083 DataType = OptionsInfo["HighlightRangesTypesMap"][Label]
1084 CriterionLower = OptionsInfo["HighlightRangesCriteriaLowerMap"][Label]
1085 CriterionLowerValue = OptionsInfo["HighlightRangesCriteriaLowerValuesMap"][Label]
1086 CriterionUpper = OptionsInfo["HighlightRangesCriteriaUpperMap"][Label]
1087 CriterionUpperValue = OptionsInfo["HighlightRangesCriteriaUpperValuesMap"][Label]
1088
1089 if re.match("^numeric$", DataType, re.I):
1090 if not MiscUtil.IsNumber(DataValue):
1091 MiscUtil.PrintWarning(
1092 "Ignoring data value, %s, for data label, %s, during numeric highlighting: It must be a number"
1093 % (DataValue, DataLabel)
1094 )
1095 return (BackgroundColor, BackgroundColorType)
1096
1097 DataValue = float(DataValue)
1098 ColorIndex = 1
1099
1100 if DataValue < CriterionLowerValue and re.match("^lt$", CriterionLower, re.I):
1101 ColorIndex = 0
1102 elif DataValue <= CriterionLowerValue and re.match("^le$", CriterionLower, re.I):
1103 ColorIndex = 0
1104 elif DataValue > CriterionUpperValue and re.match("^gt$", CriterionUpper, re.I):
1105 ColorIndex = 2
1106 elif DataValue >= CriterionUpperValue and re.match("^ge$", CriterionUpper, re.I):
1107 ColorIndex = 2
1108 elif re.match("^text$", DataType, re.I):
1109 DataValue = "%s" % DataValue
1110 ColorIndex = 1
1111
1112 if DataValue < CriterionLowerValue and re.match("^lt$", CriterionLower, re.I):
1113 ColorIndex = 0
1114 elif DataValue <= CriterionLowerValue and re.match("^le$", CriterionLower, re.I):
1115 ColorIndex = 0
1116 elif DataValue > CriterionUpperValue and re.match("^gt$", CriterionUpper, re.I):
1117 ColorIndex = 2
1118 elif DataValue >= CriterionUpperValue and re.match("^ge$", CriterionUpper, re.I):
1119 ColorIndex = 2
1120 else:
1121 return (BackgroundColor, BackgroundColorType)
1122
1123 BackgroundColor = OptionsInfo["HighlightColorsRangesList"][ColorIndex]
1124 BackgroundColorType = OptionsInfo["HighlightColorsRangesType"]
1125
1126 return (BackgroundColor, BackgroundColorType)
1127
1128
1129 def GetBackgroundColorUsingRandomMode(DataLabel, DataValue, DataMap):
1130 """Get a random background highlight color for a value."""
1131
1132 BackgroundColor = random.choice(OptionsInfo["HighlightColorsRandomList"])
1133 BackgroundColorType = OptionsInfo["HighlightColorsRandomType"]
1134
1135 return (BackgroundColor, BackgroundColorType)
1136
1137
1138 def SetupAlphanumericTableDataValue(Writer, DataValue, BackgroundColor, BackgroundColorType):
1139 """Set up alphanumeric data value for a table cell."""
1140
1141 WrappedDataValue = "%s" % DataValue
1142
1143 # Look for new lines...
1144 if re.search("(\r\n|\r|\n)", WrappedDataValue):
1145 WrappedDataValue = re.sub("(\r\n|\r|\n)", "<br/>", DataValue)
1146
1147 # Wrap text...
1148 if OptionsInfo["WrapText"] and len(WrappedDataValue) > OptionsInfo["WrapTextWidth"]:
1149 WrappedDataLines = []
1150 for DataLine in WrappedDataValue.split("<br/>"):
1151 WrappedDataLine = MiscUtil.WrapText(DataLine, "<br/>", OptionsInfo["WrapTextWidth"])
1152 WrappedDataLines.append(WrappedDataLine)
1153
1154 WrappedDataValue = "<br/>".join(WrappedDataLines)
1155
1156 # Highlight value...
1157 if BackgroundColor is not None:
1158 ColorTypeTag = GetBackgroundColorTypeTagForTableValue(BackgroundColor, BackgroundColorType)
1159 Writer.write(""" <td %s = "%s">%s</td>\n""" % (ColorTypeTag, BackgroundColor, WrappedDataValue))
1160 else:
1161 Writer.write(""" <td>%s</td>\n""" % WrappedDataValue)
1162
1163
1164 def GetBackgroundColorTypeTagForTableValue(Color, ColorType):
1165 """Setup color type tage for setting background of a table value."""
1166
1167 ColorTypeTag = "class" if re.match("^colorclass", ColorType, re.I) else "bgcolor"
1168
1169 return ColorTypeTag
1170
1171
1172 def PerformAlignment(ValidMols):
1173 """Perform alignment to a common template specified by a SMARTS pattern."""
1174
1175 if OptionsInfo["AlignmentSMARTSPattern"] is None:
1176 return
1177
1178 MiscUtil.PrintInfo("\nPerforming alignment for primary structure data...")
1179
1180 PatternMol = Chem.MolFromSmarts(OptionsInfo["AlignmentSMARTSPattern"])
1181 AllChem.Compute2DCoords(PatternMol)
1182
1183 MatchedValidMols = [ValidMol for ValidMol in ValidMols if ValidMol.HasSubstructMatch(PatternMol)]
1184 for ValidMol in MatchedValidMols:
1185 AllChem.GenerateDepictionMatching2DStructure(ValidMol, PatternMol)
1186
1187
1188 def ProcessHighlightSMARTSOption():
1189 """Process highlight SMARTS option."""
1190
1191 OptionsInfo["HighlightSMARTS"] = None
1192 OptionsInfo["HighlightSMARTSAllMode"] = False
1193 OptionsInfo["HighlightSMARTSPatternMol"] = None
1194
1195 OptionsInfo["HighlightSMARTSDataLabels"] = []
1196 OptionsInfo["HighlightSMARTSDataLabelsMap"] = {}
1197
1198 OptionsInfo["HighlightSMARTSCanonicalDataLabelsMap"] = {}
1199 OptionsInfo["HighlightSMARTSPatternsMap"] = {}
1200 OptionsInfo["HighlightSMARTSPatternMolsMap"] = {}
1201
1202 OptionsInfo["HighlightSMARTSDelim"] = Options["--highlightSMARTSDelim"]
1203
1204 if re.match("^None$", Options["--highlightSMARTS"], re.I):
1205 # Nothing to proecess...
1206 return
1207
1208 HighlightSMARTS = Options["--highlightSMARTS"].strip()
1209 if not HighlightSMARTS:
1210 MiscUtil.PrintError('No valid values specified using "--highlightSMARTS" option.')
1211
1212 OptionsInfo["HighlightSMARTS"] = HighlightSMARTS
1213 HighlightSMARTSWords = HighlightSMARTS.split(OptionsInfo["HighlightSMARTSDelim"])
1214
1215 if len(HighlightSMARTSWords) == 1:
1216 PatternMol = Chem.MolFromSmarts(HighlightSMARTS)
1217 if PatternMol is None:
1218 MiscUtil.PrintError(
1219 'The value specified, %s, using option "--highlightSMARTS" is not a valid SMARTS: Failed to create pattern molecule'
1220 % Options["--highlightSMARTS"]
1221 )
1222 OptionsInfo["HighlightSMARTSAllMode"] = True
1223 OptionsInfo["HighlightSMARTSPatternMol"] = PatternMol
1224 return
1225
1226 if len(HighlightSMARTSWords) % 2:
1227 MiscUtil.PrintError(
1228 'The number of comma delimited paramater names and values, %d, specified using "--highlightSMARTS" option must be an even number.'
1229 % (len(HighlightSMARTSWords))
1230 )
1231
1232 for Index in range(0, len(HighlightSMARTSWords), 2):
1233 DataLabel = HighlightSMARTSWords[Index].strip()
1234 SMARTSPattern = HighlightSMARTSWords[Index + 1].strip()
1235
1236 PatternMol = Chem.MolFromSmarts(SMARTSPattern)
1237 if PatternMol is None:
1238 MiscUtil.PrintError(
1239 'The value specified, %s, using option "--highlightSMARTS" is not a valid SMARTS: Failed to create pattern molecule'
1240 % Options["--highlightSMARTS"]
1241 )
1242
1243 if DataLabel in OptionsInfo["HighlightSMARTSDataLabelsMap"]:
1244 MiscUtil.PrintError(
1245 'The datalabel, %s, specified in pair, "%s, %s", using option "--highlightSMARTS" is not a valid: Multiple occurences of data label'
1246 % (DataLabel, DataLabel, SMARTSPattern)
1247 )
1248
1249 OptionsInfo["HighlightSMARTSDataLabels"].append(DataLabel)
1250 OptionsInfo["HighlightSMARTSDataLabelsMap"][DataLabel] = DataLabel
1251 OptionsInfo["HighlightSMARTSCanonicalDataLabelsMap"][DataLabel.lower()] = DataLabel
1252 OptionsInfo["HighlightSMARTSPatternsMap"][DataLabel] = SMARTSPattern
1253 OptionsInfo["HighlightSMARTSPatternMolsMap"][DataLabel] = PatternMol
1254
1255
1256 def ProcessHighlightDataOptions():
1257 """Process highlight values and colors option."""
1258
1259 ProcessHighlightValuesOption()
1260 ProcessHighlightValuesRangesOption()
1261 ProcessHighlightValuesClassesOption()
1262
1263 ProcessHighlightColorsOption()
1264 ProcessHighlightColorsRangesOption()
1265 ProcessHighlightColorsRandomOption()
1266
1267
1268 def ProcessHighlightValuesOption():
1269 """Process highlight values option."""
1270
1271 OptionsInfo["HighlightValues"] = None
1272 OptionsInfo["HighlightValuesLabels"] = []
1273
1274 OptionsInfo["HighlightValuesLabelsMap"] = {}
1275 OptionsInfo["HighlightValuesCanonicalLabelsMap"] = {}
1276
1277 OptionsInfo["HighlightValuesTypesMap"] = {}
1278 OptionsInfo["HighlightValuesCriteriaMap"] = {}
1279 OptionsInfo["HighlightValuesCriteriaValuesMap"] = {}
1280
1281 HighlightValues = Options["--highlightValues"].strip()
1282 if re.match("^None$", HighlightValues, re.I):
1283 return
1284
1285 OptionsInfo["HighlightValues"] = HighlightValues
1286 HighlightValuesWords = HighlightValues.split(",")
1287
1288 if len(HighlightValuesWords) % 4:
1289 MiscUtil.PrintError(
1290 'The number of comma delimited paramater names and values, %d, specified using "--highlightValues" option must be a multiple of 4.'
1291 % (len(HighlightValuesWords))
1292 )
1293
1294 for Index in range(0, len(HighlightValuesWords), 4):
1295 DataLabel = HighlightValuesWords[Index].strip()
1296 DataType = HighlightValuesWords[Index + 1].strip()
1297 DataCriterion = HighlightValuesWords[Index + 2].strip()
1298 DataValue = HighlightValuesWords[Index + 3].strip()
1299
1300 if not re.match("^(numeric|text|regex)$", DataType, re.I):
1301 MiscUtil.PrintError(
1302 'The data type, %s, specified in quratet "%s,%s,%s,%s", using "--highlightValues" option is not valid. Supported values: numeric, regex or text.'
1303 % (DataType, DataLabel, DataType, DataCriterion, DataValue)
1304 )
1305
1306 if re.match("^regex$", DataType, re.I):
1307 if not re.match("^(eq|ne)$", DataCriterion, re.I):
1308 MiscUtil.PrintError(
1309 'The data criterion, %s, specified in quratet "%s,%s,%s,%s", using "--highlightValues" option is not valid. Supported values: eq or ne'
1310 % (DataType, DataLabel, DataType, DataCriterion, DataValue)
1311 )
1312 else:
1313 if not re.match("^(gt|lt|ge|le|eq|ne)$", DataCriterion, re.I):
1314 MiscUtil.PrintError(
1315 'The data criterion, %s, specified in quratet "%s,%s,%s,%s", using "--highlightValues" option is not valid. Supported values: gt, lt, ge, le, eq, or ne.'
1316 % (DataType, DataLabel, DataType, DataCriterion, DataValue)
1317 )
1318
1319 # Check criterion value...
1320 if re.match("^numeric$", DataType, re.I):
1321 if not MiscUtil.IsNumber(DataValue):
1322 MiscUtil.PrintError(
1323 'The data value, %s, specified in quratet "%s,%s,%s,%s", using "--highlightValues" option is not valid. It must be a number for data type, %s'
1324 % (DataType, DataLabel, DataType, DataCriterion, DataValue, DataType)
1325 )
1326 DataValue = float(DataValue)
1327
1328 # Track values...
1329 if DataLabel in OptionsInfo["HighlightValuesLabelsMap"]:
1330 MiscUtil.PrintError(
1331 'The data label, %s, specified in quratet "%s,%s,%s,%s", using "--highlightValues" option is not valid: Multiple occurences of data label'
1332 % (DataLabel, DataLabel, DataType, DataCriterion, DataValue)
1333 )
1334
1335 OptionsInfo["HighlightValuesLabels"].append(DataLabel)
1336 OptionsInfo["HighlightValuesLabelsMap"][DataLabel] = DataLabel
1337 OptionsInfo["HighlightValuesCanonicalLabelsMap"][DataLabel.lower()] = DataLabel
1338
1339 OptionsInfo["HighlightValuesTypesMap"][DataLabel] = DataType
1340 OptionsInfo["HighlightValuesCriteriaMap"][DataLabel] = DataCriterion
1341 OptionsInfo["HighlightValuesCriteriaValuesMap"][DataLabel] = DataValue
1342
1343
1344 def ProcessHighlightValuesRangesOption():
1345 """Process highlight values ranges option."""
1346
1347 OptionsInfo["HighlightRanges"] = None
1348 OptionsInfo["HighlightRangesLabels"] = []
1349
1350 OptionsInfo["HighlightRangesLabelsMap"] = {}
1351 OptionsInfo["HighlightRangesCanonicalLabelsMap"] = {}
1352
1353 OptionsInfo["HighlightRangesTypesMap"] = {}
1354 OptionsInfo["HighlightRangesCriteriaLowerMap"] = {}
1355 OptionsInfo["HighlightRangesCriteriaLowerValuesMap"] = {}
1356 OptionsInfo["HighlightRangesCriteriaUpperMap"] = {}
1357 OptionsInfo["HighlightRangesCriteriaUpperValuesMap"] = {}
1358
1359 HighlightRanges = Options["--highlightValuesRanges"].strip()
1360 if re.match("^None$", HighlightRanges, re.I):
1361 return
1362
1363 OptionsInfo["HighlightRanges"] = HighlightRanges
1364 HighlightRangesWords = HighlightRanges.split(",")
1365
1366 if len(HighlightRangesWords) % 6:
1367 MiscUtil.PrintError(
1368 'The number of comma delimited paramater names and values, %d, specified in sextet "%s" using "--highlightValuesRanges" option must be a multiple of 6.'
1369 % (len(HighlightRangesWords), HighlightRanges)
1370 )
1371
1372 for Index in range(0, len(HighlightRangesWords), 6):
1373 DataLabel = HighlightRangesWords[Index].strip()
1374 DataType = HighlightRangesWords[Index + 1].strip()
1375 LowerBoundDataCriterion = HighlightRangesWords[Index + 2].strip()
1376 LowerBoundDataValue = HighlightRangesWords[Index + 3].strip()
1377 UpperBoundDataCriterion = HighlightRangesWords[Index + 4].strip()
1378 UpperBoundDataValue = HighlightRangesWords[Index + 5].strip()
1379
1380 SpecifiedSextet = "%s,%s,%s,%s,%s,%s" % (
1381 DataLabel,
1382 DataType,
1383 LowerBoundDataCriterion,
1384 LowerBoundDataValue,
1385 UpperBoundDataCriterion,
1386 UpperBoundDataValue,
1387 )
1388
1389 CanonicalDataLabel = DataLabel.lower()
1390 if CanonicalDataLabel in OptionsInfo["HighlightValuesCanonicalLabelsMap"]:
1391 MiscUtil.PrintError(
1392 'The data label specified, %s, using option "--highlightRanges" has already been used in "--highlightValues" option'
1393 % DataLabel
1394 )
1395
1396 if not re.match("^(numeric|text)$", DataType, re.I):
1397 MiscUtil.PrintError(
1398 'The data type, %s, specified in sextet "%s" using "--highlightValuesRanges" option is not valid. Supported values: numeric text.'
1399 % (DataType, SpecifiedSextet)
1400 )
1401
1402 if not re.match("^(lt|le)$", LowerBoundDataCriterion, re.I):
1403 MiscUtil.PrintError(
1404 'The lower bound criterion, %s, specified in sextet "%s" using "--highlightValuesRanges" option is not valid. Supported values: lt or le.'
1405 % (LowerBoundDataCriterion, SpecifiedSextet)
1406 )
1407
1408 if not re.match("^(gt|ge)$", UpperBoundDataCriterion, re.I):
1409 MiscUtil.PrintError(
1410 'The upper bound criterion, %s, specified in sextet "%s" using "--highlightValuesRanges" option is not valid. Supported values: gt or ge.'
1411 % (UpperBoundDataCriterion, SpecifiedSextet)
1412 )
1413
1414 if re.match("^numeric$", DataType, re.I):
1415 if not MiscUtil.IsNumber(LowerBoundDataValue):
1416 MiscUtil.PrintError(
1417 'The lower bound data value, %s, specified in sextet "%s", using "--highlightValuesRanges" option is not valid. It must be a number for "%s" data type.'
1418 % (LowerBoundDataValue, SpecifiedSextet, DataType)
1419 )
1420
1421 if not MiscUtil.IsNumber(UpperBoundDataValue):
1422 MiscUtil.PrintError(
1423 'The upper bound data value, %s, specified in sextet "%s", using "--highlightValuesRanges" option is not valid. It must be a number for "%s"data type.'
1424 % (UpperBoundDataValue, SpecifiedSextet, DataType)
1425 )
1426
1427 if float(LowerBoundDataValue) >= float(UpperBoundDataValue):
1428 MiscUtil.PrintError(
1429 'The lower bound data value, %s, must be less than upper bound value, %s, specified in sextet "%s" using "--highlightValuesRanges" option.'
1430 % (LowerBoundDataValue, UpperBoundDataValue, SpecifiedSextet)
1431 )
1432
1433 LowerBoundDataValue = float(LowerBoundDataValue)
1434 UpperBoundDataValue = float(UpperBoundDataValue)
1435 else:
1436 if LowerBoundDataValue >= UpperBoundDataValue:
1437 MiscUtil.PrintError(
1438 'The lower bound data value, %s, must be less than upper bound value, %s, specified in sextet "%s", using "--highlightValuesRanges" option is not valid. It must be a number for data type, %s'
1439 % (LowerBoundDataValue, UpperBoundDataValue, SpecifiedSextet, DataType)
1440 )
1441
1442 # Track values...
1443 if DataLabel in OptionsInfo["HighlightRangesLabelsMap"]:
1444 MiscUtil.PrintError(
1445 'The data label, %s, specified in sextet "%s", using "--highlightValuesRanges" option is not valid. Multiple occurences of data label'
1446 % (DataLabel, SpecifiedSextet)
1447 )
1448
1449 OptionsInfo["HighlightRangesLabels"].append(DataLabel)
1450 OptionsInfo["HighlightRangesLabelsMap"][DataLabel] = DataLabel
1451 OptionsInfo["HighlightRangesCanonicalLabelsMap"][CanonicalDataLabel] = DataLabel
1452
1453 OptionsInfo["HighlightRangesTypesMap"][DataLabel] = DataType
1454
1455 OptionsInfo["HighlightRangesCriteriaLowerMap"][DataLabel] = LowerBoundDataCriterion
1456 OptionsInfo["HighlightRangesCriteriaLowerValuesMap"][DataLabel] = LowerBoundDataValue
1457 OptionsInfo["HighlightRangesCriteriaUpperMap"][DataLabel] = UpperBoundDataCriterion
1458 OptionsInfo["HighlightRangesCriteriaUpperValuesMap"][DataLabel] = UpperBoundDataValue
1459
1460
1461 def ProcessHighlightValuesClassesOption():
1462 """Process highlight values classes option."""
1463
1464 OptionsInfo["HighlightClasses"] = None
1465 OptionsInfo["HighlightClassesRules"] = None
1466 OptionsInfo["HighlightClassesSynonymsMap"] = None
1467 OptionsInfo["HighlightClassesRandom"] = False
1468
1469 OptionsInfo["HighlightClassesLabels"] = []
1470 OptionsInfo["HighlightClassesLabelsMap"] = {}
1471 OptionsInfo["HighlightClassesCanonicalLabelsMap"] = {}
1472
1473 OptionsInfo["HighlightClassesTypesMap"] = {}
1474 OptionsInfo["HighlightClassesCriteriaMap"] = {}
1475 OptionsInfo["HighlightClassesCriteriaValuesMap"] = {}
1476
1477 HighlightClasses = Options["--highlightValuesClasses"].strip()
1478 if re.match("^None$", HighlightClasses, re.I):
1479 return
1480
1481 OptionsInfo["HighlightClasses"] = HighlightClasses
1482
1483 if re.match("^RuleOf5$", HighlightClasses, re.I):
1484 HighlightClassessRules = "MolecularWeight,numeric,le,500,HydrogenBondDonors,numeric,le,5,HydrogenBondAcceptors,numeric,le,10,LogP,numeric,le,5"
1485 elif re.match("^RuleOf3$", HighlightClasses, re.I):
1486 HighlightClassessRules = "MolecularWeight,numeric,le,300,HydrogenBondDonors,numeric,le,3,HydrogenBondAcceptors,numeric,le,3,LogP,numeric,le,3,RotatableBonds,numeric,le,3,TPSA,numeric,le,60"
1487 elif re.match("^DrugLike$", HighlightClasses, re.I):
1488 HighlightClassessRules = "MolecularWeight,numeric,le,500,HydrogenBondDonors,numeric,le,5,HydrogenBondAcceptors,numeric,le,10,LogP,numeric,le,5,RotatableBonds,numeric,le,10,TPSA,numeric,le,140"
1489 elif re.match("^Random$", HighlightClasses, re.I):
1490 if OptionsInfo["HighlightValues"] is not None:
1491 MiscUtil.PrintError(
1492 'The value specified, %s, using option "--highlightValuesClasses" is not allowed in conjunction with "--highlightValues" option.'
1493 % HighlightClasses
1494 )
1495 if OptionsInfo["HighlightRanges"] is not None:
1496 MiscUtil.PrintError(
1497 'The value specified, %s, using option "--highlightValuesClasses" is not allowed in conjunction with "--highlightRanges" option .'
1498 % HighlightClasses
1499 )
1500
1501 OptionsInfo["HighlightClassesRandom"] = True
1502 return
1503 else:
1504 MiscUtil.PrintError(
1505 'The value specified, %d, using option "--highlightValuesClasses" is not supported.' % HighlightClasses
1506 )
1507 return
1508
1509 OptionsInfo["HighlightClassesRules"] = HighlightClassessRules
1510
1511 # Process rules for highlighting values...
1512 HighlightClassesWords = HighlightClassessRules.split(",")
1513 for Index in range(0, len(HighlightClassesWords), 4):
1514 DataLabel = HighlightClassesWords[Index].strip()
1515 DataType = HighlightClassesWords[Index + 1].strip()
1516 DataCriterion = HighlightClassesWords[Index + 2].strip()
1517 DataValue = HighlightClassesWords[Index + 3].strip()
1518
1519 DataValue = float(DataValue)
1520
1521 if DataLabel in OptionsInfo["HighlightClassesLabelsMap"]:
1522 MiscUtil.PrintWarning(
1523 'Ignoring duplicate datalabel, %s, specified in highlighting values rule for class, %s, in "--highlightClassesValue" option...'
1524 % (DataLabel, HighlightClasses)
1525 )
1526 continue
1527
1528 OptionsInfo["HighlightClassesLabels"].append(DataLabel)
1529 OptionsInfo["HighlightClassesLabelsMap"][DataLabel] = DataLabel
1530
1531 OptionsInfo["HighlightClassesTypesMap"][DataLabel] = DataType
1532 OptionsInfo["HighlightClassesCriteriaMap"][DataLabel] = DataCriterion
1533 OptionsInfo["HighlightClassesCriteriaValuesMap"][DataLabel] = DataValue
1534
1535 # Set up synonyms for data labels corresponding to physicochemical properties
1536 # calculated by MayaChemTools and RDKit...
1537 OptionsInfo["HighlightClassesSynonymsMap"] = {}
1538 OptionsInfo["HighlightClassesSynonymsMap"]["MolecularWeight"] = ["MolecularWeight", "MolWt"]
1539 OptionsInfo["HighlightClassesSynonymsMap"]["HydrogenBondDonors"] = ["HydrogenBondDonors", "NHOHCount"]
1540 OptionsInfo["HighlightClassesSynonymsMap"]["HydrogenBondAcceptors"] = ["HydrogenBondAcceptors", "NOCount"]
1541 OptionsInfo["HighlightClassesSynonymsMap"]["LogP"] = ["SLogP", "MolLogP"]
1542 OptionsInfo["HighlightClassesSynonymsMap"]["RotatableBonds"] = ["RotatableBonds", "NumRotatableBonds"]
1543 OptionsInfo["HighlightClassesSynonymsMap"]["TPSA"] = ["TPSA", "TPSA"]
1544
1545
1546 def ProcessHighlightColorsOption():
1547 """Process highlight colors option."""
1548
1549 OptionsInfo["HighlightColors"] = None
1550 OptionsInfo["HighlightColorsType"] = None
1551 OptionsInfo["HighlightColorsList"] = None
1552
1553 HighlightColors = "colorclass,table-success, table-danger"
1554 if not re.match("^auto$", Options["--highlightColors"], re.I):
1555 HighlightColors = Options["--highlightColors"].strip()
1556 if MiscUtil.IsEmpty(HighlightColors):
1557 MiscUtil.PrintError('The value specified using "--highlightColors" is empty.')
1558
1559 OptionsInfo["HighlightColors"] = re.sub(" ", "", HighlightColors)
1560 HighlightColorsList = [Color.lower() for Color in OptionsInfo["HighlightColors"].split(",")]
1561
1562 if len(HighlightColorsList) != 3:
1563 MiscUtil.PrintError(
1564 'The number of comma delimited paramater names and values, %d, specified using "--highlightColors" option must be 3.'
1565 % (len(HighlightColorsList))
1566 )
1567
1568 ColorsType, Color1, Color2 = HighlightColorsList
1569 if not re.match("^(colorclass|colorspec)$", ColorsType, re.I):
1570 MiscUtil.PrintError(
1571 'The color type, %s, specified using "--highlightColors" option is not valid. Supported values: colorclass or colorspec.'
1572 % ColorsType
1573 )
1574
1575 ColorsList = [Color1, Color2]
1576 if re.match("^colorclass$", ColorsType, re.I):
1577 CheckOptionTableClassColorValues("--highlightColors", ColorsList)
1578
1579 OptionsInfo["HighlightColorsList"] = ColorsList
1580 OptionsInfo["HighlightColorsType"] = ColorsType
1581
1582
1583 def ProcessHighlightColorsRangesOption():
1584 """Process highlight colors ranges option."""
1585
1586 OptionsInfo["HighlightColorsRanges"] = None
1587 OptionsInfo["HighlightColorsRangesType"] = None
1588 OptionsInfo["HighlightColorsRangesList"] = None
1589
1590 HighlightColors = "colorclass,table-success, table-warning, table-danger"
1591 if not re.match("^auto$", Options["--highlightColorsRanges"], re.I):
1592 HighlightColors = Options["--highlightColorsRanges"].strip()
1593 if MiscUtil.IsEmpty(HighlightColors):
1594 MiscUtil.PrintError('The value specified using "--highlightColorsRanges" is empty.')
1595
1596 OptionsInfo["HighlightColorsRanges"] = re.sub(" ", "", HighlightColors)
1597 HighlightColorsList = [Color.lower() for Color in OptionsInfo["HighlightColorsRanges"].split(",")]
1598
1599 if len(HighlightColorsList) != 4:
1600 MiscUtil.PrintError(
1601 'The number of comma delimited paramater names and values, %d, specified using "--highlightColorsRanges" option must be 4.'
1602 % (len(HighlightColorsList))
1603 )
1604
1605 ColorsType, Color1, Color2, Color3 = HighlightColorsList
1606 if not re.match("^(colorclass|colorspec)$", ColorsType, re.I):
1607 MiscUtil.PrintError(
1608 'The color type, %s, specified using "--highlightColorsRanges" option is not valid. Supported values: colorclass or colorspec.'
1609 % ColorsType
1610 )
1611
1612 ColorsList = [Color1, Color2, Color3]
1613 if re.match("^colorclass$", ColorsType, re.I):
1614 CheckOptionTableClassColorValues("--highlightColorsRanges", ColorsList)
1615
1616 OptionsInfo["HighlightColorsRangesList"] = ColorsList
1617 OptionsInfo["HighlightColorsRangesType"] = ColorsType
1618
1619
1620 def ProcessHighlightColorsRandomOption():
1621 """Process highlight colors random option."""
1622
1623 OptionsInfo["HighlightColorsRandom"] = None
1624 OptionsInfo["HighlightColorsRandomType"] = None
1625 OptionsInfo["HighlightColorsRandomList"] = None
1626
1627 HighlightColors = "colorclass,table-primary,table-success,table-danger,table-info,table-warning,table-secondary"
1628 if not re.match("^auto$", Options["--highlightColorsRandom"], re.I):
1629 HighlightColors = Options["--highlightColorsRandom"].strip()
1630 if MiscUtil.IsEmpty(HighlightColors):
1631 MiscUtil.PrintError('The value specified using "--highlightColorsRandom" is empty.')
1632
1633 OptionsInfo["HighlightColorsRandom"] = re.sub(" ", "", HighlightColors)
1634 HighlightColorsList = [Color.lower() for Color in OptionsInfo["HighlightColorsRandom"].split(",")]
1635
1636 if len(HighlightColorsList) <= 1:
1637 MiscUtil.PrintError(
1638 'The number of comma delimited paramater names and values, %d, specified using "--highlightColorsRandom" option must be > 1.'
1639 % (len(HighlightColorsList))
1640 )
1641
1642 ColorsType = HighlightColorsList[0]
1643 ColorsList = HighlightColorsList[1:]
1644
1645 if not re.match("^(colorclass|colorspec)$", ColorsType, re.I):
1646 MiscUtil.PrintError(
1647 'The color type, %s, specified using "--highlightColorsRandim" option is not valid. Supported values: colorclass or colorspec.'
1648 % ColorsType
1649 )
1650
1651 if re.match("^colorclass$", ColorsType, re.I):
1652 CheckOptionTableClassColorValues("--highlightColorsRandom", ColorsList)
1653
1654 OptionsInfo["HighlightColorsRandomList"] = ColorsList
1655 OptionsInfo["HighlightColorsRandomType"] = ColorsType
1656
1657
1658 def CheckOptionTableClassColorValues(OptionName, ColorsList):
1659 """Check names of table color classes and issue a warning for unknown names."""
1660
1661 TableClassColors = [
1662 "thead-dark",
1663 "thead-light",
1664 "table-primary",
1665 "table-success",
1666 "table-danger",
1667 "table-info",
1668 "table-warning",
1669 "table-active",
1670 "table-secondary",
1671 "table-light",
1672 "table-dark",
1673 "bg-primary",
1674 "bg-success",
1675 "bg-danger",
1676 "bg-info",
1677 "bg-warning",
1678 "bg-secondary",
1679 "bg-dark",
1680 "bg-light",
1681 ]
1682
1683 for Color in ColorsList:
1684 if Color not in TableClassColors:
1685 MiscUtil.PrintWarning(
1686 'The color class name, %s, specified using option "%s" appears to be a unknown name...'
1687 % (Color, OptionName)
1688 )
1689
1690
1691 def ProcessOptions():
1692 """Process and validate command line arguments and options."""
1693
1694 MiscUtil.PrintInfo("Processing options...")
1695
1696 # Validate options...
1697 ValidateOptions()
1698
1699 OptionsInfo["Infile"] = Options["--infile"]
1700 OptionsInfo["Outfile"] = Options["--outfile"]
1701 OptionsInfo["Overwrite"] = Options["--overwrite"]
1702
1703 # No need for any RDKit specific --outfileParams....
1704 OptionsInfo["InfileParams"] = MiscUtil.ProcessOptionInfileParameters(
1705 "--infileParams", Options["--infileParams"], OptionsInfo["Infile"]
1706 )
1707
1708 AlignmentSMARTSPattern = None
1709 if not re.match("^None$", Options["--alignmentSMARTS"], re.I):
1710 AlignmentSMARTSPattern = Options["--alignmentSMARTS"]
1711 OptionsInfo["AlignmentSMARTSPattern"] = AlignmentSMARTSPattern
1712
1713 Compute2DCoords = True
1714 if re.match("^no$", Options["--compute2DCoords"], re.I):
1715 Compute2DCoords = False
1716 OptionsInfo["Compute2DCoords"] = Compute2DCoords
1717
1718 CounterCol = True
1719 if re.match("^no$", Options["--counterCol"], re.I):
1720 CounterCol = False
1721 OptionsInfo["CounterCol"] = CounterCol
1722
1723 ColVisibility = True
1724 if re.match("^no$", Options["--colVisibility"], re.I):
1725 ColVisibility = False
1726 OptionsInfo["ColVisibility"] = ColVisibility
1727
1728 OptionsInfo["ColVisibilityCtrlMax"] = int(Options["--colVisibilityCtrlMax"])
1729
1730 Footer = None
1731 if not re.match("^None$", Options["--footer"], re.I):
1732 Footer = Options["--footer"]
1733 OptionsInfo["Footer"] = Footer
1734
1735 FooterClass = Options["--footerClass"].strip()
1736 if MiscUtil.IsEmpty(FooterClass):
1737 MiscUtil.PrintError('The value specified using option "--footerClass" is empty.')
1738 OptionsInfo["FooterClass"] = FooterClass
1739
1740 FreezeCols = True
1741 if re.match("^no$", Options["--freezeCols"], re.I):
1742 FreezeCols = False
1743 OptionsInfo["FreezeCols"] = FreezeCols
1744
1745 Header = None
1746 if not re.match("^None$", Options["--header"], re.I):
1747 Header = Options["--header"]
1748 OptionsInfo["Header"] = Header
1749
1750 HeaderStyle = Options["--headerStyle"].strip()
1751 if MiscUtil.IsEmpty(HeaderStyle):
1752 MiscUtil.PrintError('The value specified using option "--headerStyle" is empty.')
1753 OptionsInfo["HeaderStyle"] = HeaderStyle
1754
1755 ProcessHighlightSMARTSOption()
1756 ProcessHighlightDataOptions()
1757
1758 OptionsInfo["KeysNavigation"] = True
1759 if re.match("^no$", Options["--keysNavigation"], re.I):
1760 OptionsInfo["KeysNavigation"] = False
1761
1762 SizeValues = Options["--molImageSize"].split(",")
1763 OptionsInfo["MolImageWidth"] = int(SizeValues[0])
1764 OptionsInfo["MolImageHeight"] = int(SizeValues[1])
1765
1766 OptionsInfo["MolImageEncoded"] = True
1767 if re.match("^no$", Options["--molImageEncoded"], re.I):
1768 OptionsInfo["MolImageEncoded"] = False
1769
1770 OptionsInfo["Paging"] = True
1771 if re.match("^no$", Options["--paging"], re.I):
1772 OptionsInfo["Paging"] = False
1773
1774 PagingType = Options["--pagingType"]
1775 if not re.match("^(numbers|simple|simple_numbers|full|full_numbers|simple_number)$", Options["--pagingType"], re.I):
1776 MiscUtil.PrintWarning(
1777 'The paging type name, %s, specified using option "--pagingType" appears to be a unknown type...'
1778 % (PagingType)
1779 )
1780 OptionsInfo["PagingType"] = PagingType.lower()
1781
1782 OptionsInfo["PageLength"] = int(Options["--pageLength"])
1783
1784 OptionsInfo["RegexSearch"] = True
1785 if re.match("^no$", Options["--regexSearch"], re.I):
1786 OptionsInfo["RegexSearch"] = False
1787
1788 OptionsInfo["ShowMolName"] = True
1789 OptionsInfo["ShowMolNameDataLabel"] = "Name"
1790 if re.match("^no$", Options["--showMolName"], re.I):
1791 OptionsInfo["ShowMolName"] = False
1792
1793 OptionsInfo["ShowMolNameAuto"] = True if re.match("^auto$", Options["--showMolName"], re.I) else False
1794
1795 OptionsInfo["ScrollX"] = True
1796 if re.match("^no$", Options["--scrollX"], re.I):
1797 OptionsInfo["ScrollX"] = False
1798
1799 OptionsInfo["ScrollY"] = True
1800 if re.match("^no$", Options["--scrollY"], re.I):
1801 OptionsInfo["ScrollY"] = False
1802
1803 OptionsInfo["ScrollYSize"] = Options["--scrollYSize"]
1804 if re.match("vh$", Options["--scrollYSize"], re.I):
1805 ScrollYSize = int(re.sub("vh$", "", Options["--scrollYSize"]))
1806 if ScrollYSize <= 0:
1807 MiscUtil.PrintError(
1808 'The value specified, %s, for option "--scrollYSize" is not valid. Supported value: > 0 followed by "vh"'
1809 % Options["--scrollYSize"]
1810 )
1811
1812 TableStyle = None
1813 if not re.match("^None$", Options["--tableStyle"], re.I):
1814 if re.match("^All$", Options["--tableStyle"], re.I):
1815 TableStyle = "table table-striped table-bordered table-hover table-dark"
1816 else:
1817 TableStyle = re.sub(" ", "", Options["--tableStyle"])
1818 for Style in [Style for Style in TableStyle.split(",")]:
1819 if not re.match("^(table|table-striped|table-bordered|table-hover|table-dark|table-sm)$", Style, re.I):
1820 MiscUtil.PrintWarning(
1821 'The table style name, %s, specified using option "-t, --tableStyle" appears to be a unknown style...'
1822 % (Style)
1823 )
1824 TableStyle = re.sub(",", " ", TableStyle.lower())
1825 OptionsInfo["TableStyle"] = TableStyle
1826
1827 TableHeaderStyle = None
1828 if not re.match("^None$", Options["--tableHeaderStyle"], re.I):
1829 TableHeaderStyle = Options["--tableHeaderStyle"]
1830 TableHeaderStyle = TableHeaderStyle.lower()
1831 CheckOptionTableClassColorValues("--tableHeaderStyle", [TableHeaderStyle])
1832 OptionsInfo["TableHeaderStyle"] = TableHeaderStyle
1833
1834 OptionsInfo["TableFooter"] = True
1835 if re.match("^no$", Options["--tableFooter"], re.I):
1836 OptionsInfo["TableFooter"] = False
1837
1838 OptionsInfo["WrapText"] = True
1839 if re.match("^no$", Options["--wrapText"], re.I):
1840 OptionsInfo["WrapText"] = False
1841
1842 OptionsInfo["WrapTextWidth"] = int(Options["--wrapTextWidth"])
1843
1844
1845 def RetrieveOptions():
1846 """Retrieve command line arguments and options."""
1847
1848 # Get options...
1849 global Options
1850 Options = docopt(_docoptUsage_)
1851
1852 # Set current working directory to the specified directory...
1853 WorkingDir = Options["--workingdir"]
1854 if WorkingDir:
1855 os.chdir(WorkingDir)
1856
1857 # Handle examples option...
1858 if "--examples" in Options and Options["--examples"]:
1859 MiscUtil.PrintInfo(MiscUtil.GetExamplesTextFromDocOptText(_docoptUsage_))
1860 sys.exit(0)
1861
1862
1863 def ValidateOptions():
1864 """Validate option values."""
1865
1866 if not re.match("^None$", Options["--alignmentSMARTS"], re.I):
1867 PatternMol = Chem.MolFromSmarts(Options["--alignmentSMARTS"])
1868 if PatternMol is None:
1869 MiscUtil.PrintError(
1870 'The value specified, %s, using option "--alignmentSMARTS" is not a valid SMARTS: Failed to create pattern molecule'
1871 % Options["--alignmentSMARTS"]
1872 )
1873
1874 MiscUtil.ValidateOptionTextValue("-c, --compute2DCoords", Options["--compute2DCoords"], "yes no auto")
1875
1876 MiscUtil.ValidateOptionTextValue("--counterCol", Options["--counterCol"], "yes no")
1877 MiscUtil.ValidateOptionTextValue("--colVisibility", Options["--colVisibility"], "yes no")
1878 MiscUtil.ValidateOptionIntegerValue("--colVisibilityCtrlMax", Options["--colVisibilityCtrlMax"], {">": 0})
1879
1880 MiscUtil.ValidateOptionTextValue("--freezeCols", Options["--freezeCols"], "yes no")
1881 MiscUtil.ValidateOptionTextValue(
1882 "--highlightValuesClasses", Options["--highlightValuesClasses"], "RuleOf5 RuleOf3 DrugLike Random None"
1883 )
1884
1885 MiscUtil.ValidateOptionFilePath("-i, --infile", Options["--infile"])
1886 MiscUtil.ValidateOptionFileExt("-i, --infile", Options["--infile"], "sdf sd mol smi csv tsv txt")
1887
1888 MiscUtil.ValidateOptionFileExt("-o, --outfile", Options["--outfile"], "html")
1889 MiscUtil.ValidateOptionsOutputFileOverwrite(
1890 "-o, --outfile", Options["--outfile"], "--overwrite", Options["--overwrite"]
1891 )
1892 MiscUtil.ValidateOptionsDistinctFileNames(
1893 "-i, --infile", Options["--infile"], "-o, --outfile", Options["--outfile"]
1894 )
1895
1896 MiscUtil.ValidateOptionTextValue("-k, --keysNavigation", Options["--keysNavigation"], "yes no")
1897
1898 MiscUtil.ValidateOptionNumberValues("-m, --molImageSize", Options["--molImageSize"], 2, ",", "integer", {">": 0})
1899 MiscUtil.ValidateOptionTextValue("--molImageEncoded", Options["--molImageEncoded"], "yes no")
1900
1901 MiscUtil.ValidateOptionTextValue("-p, --paging", Options["--paging"], "yes no")
1902 MiscUtil.ValidateOptionIntegerValue("--pageLength", Options["--pageLength"], {">": 0})
1903 MiscUtil.ValidateOptionTextValue("-r, --regexSearch", Options["--regexSearch"], "yes no")
1904
1905 MiscUtil.ValidateOptionTextValue("--showMolName", Options["--showMolName"], "yes no auto")
1906
1907 MiscUtil.ValidateOptionTextValue("--scrollX", Options["--scrollX"], "yes no")
1908 MiscUtil.ValidateOptionTextValue("--scrollY", Options["--scrollY"], "yes no")
1909 if not re.search("vh$", Options["--scrollYSize"], re.I):
1910 MiscUtil.ValidateOptionIntegerValue("--scrollYSize", Options["--scrollYSize"], {">": 0})
1911
1912 MiscUtil.ValidateOptionTextValue("--tableFooter", Options["--tableFooter"], "yes no")
1913
1914 MiscUtil.ValidateOptionTextValue("--wrapText", Options["--wrapText"], "yes no")
1915 MiscUtil.ValidateOptionIntegerValue("--wrapTextWidth", Options["--wrapTextWidth"], {">": 0})
1916
1917
1918 # Setup a usage string for docopt...
1919 _docoptUsage_ = """
1920 RDKitDrawMoleculesAndDataTable.py - Generate a HTML data table
1921
1922 Usage:
1923 RDKitDrawMoleculesAndDataTable.py [--alignmentSMARTS <SMARTS>]
1924 [--compute2DCoords <yes or no>] [--counterCol <yes or no>]
1925 [--colVisibility <yes or no>] [--colVisibilityCtrlMax <number>] [--footer <text>]
1926 [--footerClass <text>] [--freezeCols <yes or no>] [--header <text>]
1927 [--headerStyle <text>] [--highlightSMARTS <SMARTS,...>] [--highlightSMARTSDelim <text>]
1928 [--highlightValues <datalabel,datatype,criterion,value,...>]
1929 [--highlightValuesRanges <datalabel,datatype,criterion1,vaue1,criterion2,value2...>]
1930 [--highlightValuesClasses <RuleOf5,RuleOf3,...>]
1931 [--highlightColors <colortype,color1,color2>]
1932 [--highlightColorsRanges <colortype,color1,color2,color3>]
1933 [--highlightColorsRandom <colottype,color1,color2,...>]
1934 [--infileParams <Name,Value,...>] [--keysNavigation <yes or no>]
1935 [--molImageSize <width,height>] [--molImageEncoded <yes or no> ] [--overwrite]
1936 [--paging <yes or no>] [--pagingType <numbers,simple, ...>] [--pageLength <number>]
1937 [--regexSearch <yes or no>] [--showMolName <yes or no>]
1938 [--scrollX <yes or no>] [--scrollY <yes or no>] [--scrollYSize <number>]
1939 [--tableStyle <table,table-striped,...>] [--tableFooter <yes or no>]
1940 [--tableHeaderStyle <thead-dark,thead-light,...>] [--wrapText <yes or no>]
1941 [--wrapTextWidth <number>] [-w <dir>] -i <infile> -o <outfile>
1942 RDKitDrawMoleculesAndDataTable.py -h | --help | -e | --examples
1943
1944 Description:
1945 Generate an interactive HTML table with columns corresponding to molecules
1946 and available alphanumerical data in an input file. The drawing of molecules are
1947 embedded in the columns as in line SVG images.
1948
1949 The interactive HTML table may contain multiple columns with drawing of
1950 molecules. These columns are automatically generated for each data field in SD
1951 file or a column name in SMILES and CSV/TSV file containing SMILES
1952 string in their names. The first molecular drawing column in the HTML table
1953 represents primary molecular structure data available in an input file. It
1954 corresponds to MOL block is SD file or a first column containing SMILES string
1955 in its name in SMILES and CSV/TSV files.
1956
1957 The interactive table requires internet access for viewing in a browser and
1958 employs the following frameworks: JQuery, Bootstrap, and DataTable. It provides
1959 the following functionality: sorting by columns, page length control, page
1960 navigation, searching data with regular expressions, and horizontal/vertical
1961 scrolling, row highlighting during hovering, a counter column, freezing of primary
1962 structure and counter columns, and column visibility control.
1963
1964 The supported input file formats are: Mol (.mol), SD (.sdf, .sd), SMILES (.smi),
1965 CSV/TSV (.csv, .tsv, .txt)
1966
1967 The supported output file format is HTML (.html).
1968
1969 Options:
1970 -a, --alignmentSMARTS <SMARTS> [default: none]
1971 SMARTS pattern for aligning molecules to a common template. This option is
1972 only used for primary molecular data in SD, SMILES and CSV/TSV files. It is
1973 ignored for all other molecular coordinates corresponding to data fields in SD
1974 file or columns in SMILES and CSV/TSV files containing SMILES string in their
1975 names.
1976 -c, --compute2DCoords <yes or no> [default: yes]
1977 Compute 2D coordinates of molecules before drawing. Default: yes for SMILES
1978 strings in SMILES, CSV/TSV, and SD file data fields. In addition, 2D coordinated are
1979 always calculated for molecules corresponding to data fields in SD file or columns
1980 in SMILES and CSV/TSV files containing SMILES string in their names.
1981 --counterCol <yes or no> [default: yes]
1982 Show a counter column as the first column in the table. It contains the position
1983 for each row in the table.
1984 --colVisibility <yes or no> [default: yes]
1985 Show a dropdown button to toggle visibility of columns in the table. The counter
1986 and primary structure columns are excluded from the list.
1987 --colVisibilityCtrlMax <number> [default: 25]
1988 Maximum number of columns to show in column visibility dropdown button. The
1989 rest of the data columns are not listed in the dropdown and are shown in the table.
1990 A word to the wise: The display of too many columns appear to hang interactive
1991 Javascript framework for Bootstrap and DataTables.
1992 --freezeCols <yes or no> [default: yes]
1993 Lock counter and primary structure columns in place during horizontal scrolling.
1994 --footer <text> [default: none]
1995 Footer text to insert at the bottom of the HTML page after the table.
1996 --footerClass <text> [default: small text-center text-muted]
1997 Footer class style to use with <p> tag.
1998 -e, --examples
1999 Print examples.
2000 -h, --help
2001 Print this help message.
2002 --header <text> [default: none]
2003 Header text to insert at the top of the HTML page before the table.
2004 --headerStyle <text> [default: h5]
2005 Header style to use. Possible values: h1 to h6.
2006 --highlightSMARTS <SMARTS,...> [default: none]
2007 SMARTS pattern for highlighting atoms and bonds in molecules. All matched
2008 substructures are highlighted.
2009
2010 The SMARTS string is used to highlight atoms and bonds in drawing of
2011 molecules present in a HTML table across multiple columns. These columns
2012 correspond to data field labels in SD file or a column name in SMILES and
2013 CSV/TSV file containing SMILES string in their names. The first molecular
2014 drawing column in HTML table corresponds to primary molecular structure
2015 data available in an input file. It is identified by a label 'Structure' across
2016 all input formats.
2017
2018 A single SMARTS string is used to highlight a common substructure across
2019 all columns containing drawing of molecules in HTML table.
2020
2021 Format:
2022
2023 SMARTS
2024 Structure,SMARTS1,DataLabel,SMARTS2,...
2025 Structure,SMARTS1,Collabel,SMARTS2,...
2026
2027 Example:
2028
2029 c1ccccc1
2030 Structure,c1ccccc1,SMILESR1,c1ccccc1,SMILESR2,c1ccccc1
2031
2032 --highlightSMARTSDelim <text> [default: ,]
2033 Delimiter for parsing SMARTS patterns specified using '--highlightSMARTS'
2034 option. Default: ',' comma character. Possible value: Any arbitrary text or
2035 a valid character. You may use arbitrary text as a delimiter to handle
2036 presence of special characters such as comma, semicolon, tilde etc. in
2037 SMARTS patterns.
2038 --highlightValues <datalabel,datatype,criterion,value,...> [default: none]
2039 Highlighting methodology to use for highlighting alphanumerical data
2040 corresponding to data fields in SD file or column names in SMILES and
2041 CSV/TSV text files.
2042
2043 Input text contains these quartets: DataLabel, DataType, Criterion, Value.
2044 Possible datatype values: numeric, text. Possible criterion values for numeric
2045 and text: gt, lt, ge, le.
2046
2047 The 'datalabel' corresponds to either data field label in SD file or column name
2048 in SMILES and CSV/TSV text files.
2049
2050 Examples:
2051
2052 MolecularWeight,numeric,le,500
2053 MolecularWeight,numeric,le,450,SLogP,numeric,le,5
2054 Name,text,eq,Aspirin
2055 Name,regex,eq,acid|amine
2056
2057 --highlightValuesRanges <datalabel,datatype,...> [default: none]
2058 Highlighting methodology to use for highlighting ranges of alphanumerical
2059 data corresponding to data fields in SD file or column names in SMILES and
2060 CSV/TSV text files.
2061
2062 Input text contains these sextets: DataLabel, DataType, CriterionLowerBound,
2063 LowerBoundValue, CriterionUpperBound, UpperBoundValue.
2064
2065 Possible datatype values: numeric or text. Possible criterion values: Lower
2066 bound value - lt, le; Upper bound value: gt, ge.
2067
2068 The 'datalabel' corresponds to either data field label in SD file or column name
2069 in SMILES and CSV/TSV text files.
2070
2071 Examples:
2072
2073 MolecularWeight,numeric,lt,450,gt,1000
2074 MolecularWeight,numeric,lt,450,gt,1000,SLogP,numeric,lt,0,gt,5
2075
2076 --highlightValuesClasses <RuleOf5,RuleOf3,...> [default: none]
2077 Highlighting methodology to use for highlighting ranges of numerical data
2078 data corresponding to specific set of data fields in SD file or column names in
2079 SMILES and CSV/TSV text files. Possible values: RuleOf5, RuleOf3, DrugLike,
2080 Random.
2081
2082 The following value classes are supported: RuleOf5, RuleOf3, LeadLike, DrugLike.
2083 LeadLike is equivalent to RuleOf3.
2084
2085 Each supported class encompasses a specific set of data labels along with
2086 appropriate criteria to compare and highlight column values, except for
2087 'Random' class. The data labels in these classes are automatically associated
2088 with appropriate data fields in SD file or column names in SMILES and CSV/TSV
2089 text files.
2090
2091 No data labels are associated with 'Random' class. It is used to highlight
2092 available alphanumeric data by randomly selecting a highlight color from the
2093 list of colors specified using '--highlightColorsRandom' option. The 'Random'
2094 class value is not allowed in conjunction with '--highlightValues' or
2095 '--highlightValuesRanges'.
2096
2097 The rules to highlight values for the supported classes are as follows.
2098
2099 RuleOf5 [ Ref 91 ]:
2100
2101 MolecularWeight,numeric,le,500 (MolecularWeight <= 500)
2102 HydrogenBondDonors,numeric,le,5 (HydrogenBondDonors <= 5)
2103 HydrogenBondAcceptors,numeric,le,10 (HydrogenBondAcceptors <= 10)
2104 LogP,numeric,le,5 (LogP <= 5)
2105
2106 RuleOf3 or LeadLike [ Ref 92 ]:
2107
2108 MolecularWeight,numeric,le,300 (MolecularWeight <= 300)
2109 HydrogenBondDonors,numeric,le,3 (HydrogenBondDonors <= 3)
2110 HydrogenBondAcceptors,numeric,le,3 (HydrogenBondAcceptors <= 3)
2111 LogP,numeric,le,3 (LogP <= 3)
2112 RotatableBonds,numeric,le,3 (RotatableBonds <= 3)
2113 TPSA,numeric,le,60 (TPSA <= 60)
2114
2115 DrugLike:
2116
2117 MolecularWeight,numeric,le,500 (MolecularWeight <= 500)
2118 HydrogenBondDonors,numeric,le,5 (HydrogenBondDonors <= 5)
2119 HydrogenBondAcceptors,numeric,le,10 (HydrogenBondAcceptors <= 10)
2120 LogP,numeric,le,5 (LogP <= 5)
2121 RotatableBonds,numeric,le,10 (RotatableBonds <= 10)
2122 TPSA,numeric,le,140 (TPSA <= 140)
2123
2124 The following synonyms are automatically detected for data labels used
2125 by MayaChemTools and RDKit packages during the calculation of
2126 physicochemical properties.
2127
2128 MayaChemTools: MolecularWeight, HydrogenBondDonors, HydrogenBondAcceptors,
2129 SLogP, RotatableBonds, TPSA.
2130
2131 RDKit: MolWt, NHOHCount, NOCount, MolLogP, NumRotatableBonds, TPSA
2132
2133 --highlightColors <colortype,color1,color2> [default: auto]
2134 Background colors used to highlight column values based on criterion
2135 specified by '--highlightValues' and '--highlightColorsClasses' option. Default
2136 value: colorclass,table-success, table-danger.
2137
2138 The first color is used to highlight column values that satisfy the specified
2139 criterion for the column. The second color highlights the rest of the values
2140 in the column.
2141
2142 Possible values for colortype: colorclass or colorspec.
2143
2144 Any valid bootstrap contextual color class is supported for 'colorclass'
2145 color type. For example: table-primary (Blue), table-success (Green),
2146 table-danger (Red), table-info (Light blue), table-warning (Orange),
2147 table-secondary (Grey), table-light (Light grey), and table-dark (Dark grey).
2148
2149 The following bootstrap color classes may also used: bg-primary bg-success,
2150 bg-danger bg-info, bg-warning, bg-secondary.
2151
2152 Any valid color name or hexadecimal color specification is supported for
2153 'colorspec' color type: For example: red, green, blue, #ff000, #00ff00, #0000ff.
2154 --highlightColorsRanges <colortype,color1,color2,color3> [default: auto]
2155 Background colors used to highlight column values using criteria specified
2156 by '--highlightValuesRanges' option. Default value: colorclass, table-success,
2157 table-warning, table-danger.
2158
2159 The first and third color are used to highlight column values lower and higher
2160 than the specified values for the lower and upper bound. The middle color highlights
2161 the rest of the values in the column.
2162
2163 The supported color type and values are explained in the section for '--highlightColors'.
2164 --highlightColorsRandom <colortype,color1,color2,...> [default: auto]
2165 Background color list to use for randomly selecting a color to highlight
2166 column values during 'Random" value of '--highlightValuesClasses' option.
2167
2168 Default value: colorclass,table-primary,table-success,table-danger,table-info,
2169 table-warning,table-secondary.
2170
2171 The supported color type and values are explained in the section for '--highlightColors'.
2172 -i, --infile <infile>
2173 Input file name.
2174 --infileParams <Name,Value,...> [default: auto]
2175 A comma delimited list of parameter name and value pairs for reading
2176 molecules from files. The supported parameter names for different file
2177 formats, along with their default values, are shown below:
2178
2179 SD, MOL: removeHydrogens,yes,sanitize,yes,strictParsing,yes
2180 SMILES: smilesColumn,1,smilesNameColumn,2,smilesDelimiter,space,
2181 sanitize,yes
2182
2183 Possible values for smilesDelimiter: space, comma or tab.
2184 -k, --keysNavigation <yes or no> [default: yes]
2185 Provide Excel like keyboard cell navigation for the table.
2186 -m, --molImageSize <width,height> [default: 200,150]
2187 Image size of a molecule in pixels.
2188 --molImageEncoded <yes or no> [default: yes]
2189 Base64 encode SVG image of a molecule for inline embedding in a HTML page.
2190 The inline SVG image may fail to display in browsers without encoding.
2191 -o, --outfile <outfile>
2192 Output file name.
2193 --overwrite
2194 Overwrite existing files.
2195 -p, --paging <yes or no> [default: yes]
2196 Provide page navigation for browsing data in the table.
2197 --pagingType <numbers, simple, ...> [default: full_numbers]
2198 Type of page navigation. Possible values: numbers, simple, simple_numbers,
2199 full, full_numbers, or first_last_numbers.
2200
2201 numbers - Page number buttons only
2202 simple - 'Previous' and 'Next' buttons only
2203 simple_numbers - 'Previous' and 'Next' buttons, plus page numbers
2204 full - 'First', 'Previous', 'Next' and 'Last' buttons
2205 full_numbers - 'First', 'Previous', 'Next' and 'Last' buttons, plus
2206 page numbers
2207 first_last_numbers - 'First' and 'Last' buttons, plus page numbers
2208
2209 --pageLength <number> [default: 15]
2210 Number of rows to show per page.
2211 -r, --regexSearch <yes or no> [default: yes]
2212 Allow regular expression search through alphanumerical data in the table.
2213 -s, --showMolName <yes or no> [default: auto]
2214 Show molecule names in a column next to the column corresponding to primary
2215 structure data in SD and SMILES file. The default value is yes for SD and SMILES file.
2216 This option is ignored for CSV/TSV text files.
2217 --scrollX <yes or no> [default: yes]
2218 Provide horizontal scroll bar in the table as needed.
2219 --scrollY <yes or no> [default: yes]
2220 Provide vertical scroll bar in the table as needed.
2221 --scrollYSize <number> [default: 75vh]
2222 Maximum height of table viewport either in pixels or percentage of the browser
2223 window height before providing a vertical scroll bar. Default: 75% of the height of
2224 browser window.
2225 -t, --tableStyle <table,table-striped,...> [default: table,table-hover,table-sm]
2226 Style of table. Possible values: table, table-striped, table-bordered,
2227 table-hover, table-dark, table-sm, none, or All. Default: 'table,table-hover'. A
2228 comma delimited list of any valid Bootstrap table styles is also supported.
2229 --tableFooter <yes or no> [default: yes]
2230 Show column headers at the end of the table.
2231 --tableHeaderStyle <thead-dark,thead-light,...> [default: thead-dark]
2232 Style of table header. Possible values: thead-dark, thead-light, or none.
2233 The names of the following contextual color classes are also supported:
2234 table-primary (Blue), table-success (Green), table-danger (Red), table-info
2235 (Light blue), table-warning (Orange), table-active (Grey), table-light (Light
2236 grey), and table-dark (Dark grey).
2237 -w, --workingdir <dir>
2238 Location of working directory which defaults to the current directory.
2239 --wrapText <yes or no> [default: yes]
2240 Wrap alphanumeric text using <br/> delimiter for display in a HTML table.
2241 --wrapTextWidth <number> [default: 40]
2242 Maximum width in characters before wraping alphanumeric text for display
2243 in a HTML table.
2244
2245 Examples:
2246 To generate a HTML table containing structure and alphanumeric data for
2247 molecules in a SD file along with all the bells and whistles to interact with
2248 the table, type:
2249
2250 % RDKitDrawMoleculesAndDataTable.py -i Sample.sdf -o SampleOut.html
2251
2252 To generate a HTML table containing structure and alphanumeric data for
2253 molecules in a SMILES file along with all the bells and whistles to interact
2254 with the table, type:
2255
2256 % RDKitDrawMoleculesAndDataTable.py -i Sample.smi -o SampleOut.html
2257
2258 To generate a HTML table containing multiple structure columns for molecules
2259 in a CSV file along with all the bells and whistles to interact with the table, type:
2260
2261 % RDKitDrawMoleculesAndDataTable.py -i SampleSeriesRGroupsD3R.csv
2262 -o SampleSeriesRGroupsD3ROut.html
2263
2264 To generate a HTML table containing structure and alphanumeric data for
2265 molecules in a SD file along without any bells and whistles to interact with
2266 the table, type:
2267
2268 % RDKitDrawMoleculesAndDataTable.py --colVisibility no --freezeCols no
2269 --keysNavigation no --paging no --regexSearch no --scrollX no
2270 --scrollY no -i Sample.sdf -o SampleOut.html
2271
2272 To generate a HTML table containing structure and alphanumeric data for
2273 molecules in a SD file along with highlighting molecular weight values
2274 using a specified criterion, type:
2275
2276 % RDKitDrawMoleculesAndDataTable.py --highlightValues
2277 "MolecularWeight,numeric,le,500" -i Sample.sdf -o SampleOut.html
2278
2279 To generate a HTML table containing structure and alphanumeric data for
2280 molecules in a SD file along with highlighting range of molecular weight values
2281 using a specified criterion, type:
2282
2283 % RDKitDrawMoleculesAndDataTable.py --highlightValuesRanges
2284 "MolecularWeight,numeric,lt,400,gt,500" -i Sample.sdf -o SampleOut.html
2285
2286 To generate a HTML table containing structure and alphanumeric data for
2287 molecules in a SD file along with highlighting molecular weight values and
2288 ranges of SLogP values using a specified criterion and color schemes, type:
2289
2290 % RDKitDrawMoleculesAndDataTable.py --highlightValues
2291 "MolecularWeight,numeric,le,500" --highlightValuesRanges
2292 "SLogP,numeric,lt,0,gt,5" --highlightColors "colorclass,table-success,
2293 table-danger" --highlightColorsRanges "colorclass,table-danger,
2294 table-success,table-warning" -i Sample.sdf -o SampleOut.html
2295
2296 To generate a HTML table containing structure and alphanumeric data for
2297 molecules in a SD file along with highlighting RuleOf5 physicochemical
2298 properties using a pre-defined set of criteria, type:
2299
2300 % RDKitDrawMoleculesAndDataTable.py --highlightValuesClasses RuleOf5
2301 -i Sample.sdf -o SampleOut.html
2302
2303 To generate a HTML table containing structure and alphanumeric data for
2304 molecules in a SD file along with all the bells and whistles to interact
2305 with the table and highlight a specific SMARTS pattern in molecules, type:
2306
2307 % RDKitDrawMoleculesAndDataTable.py --highlightSMARTS "c1ccccc1"
2308 -i Sample.sdf -o SampleOut.html
2309
2310 To generate a HTML table containing structure and alphanumeric data for
2311 molecules in a SD file along with highlighting of values using random colors
2312 from a default list of colors, type:
2313
2314 % RDKitDrawMoleculesAndDataTable.py --highlightValuesClasses Random
2315 -i Sample.sdf -o SampleOut.html
2316
2317 To generate a HTML table containing structure and alphanumeric data for
2318 molecules in a SD file along with highlighting of values using random colors
2319 from a specified list of colors, type:
2320
2321 % RDKitDrawMoleculesAndDataTable.py --highlightValuesClasses Random
2322 --highlightColorsRandom "colorspec,Lavendar,MediumPurple,SkyBlue,
2323 CornflowerBlue,LightGreen,MediumSeaGreen,Orange,Coral,Khaki,Gold,
2324 Salmon,LightPink,Aquamarine,MediumTurquoise,LightGray"
2325 -i Sample.sdf -o SampleOut.html
2326
2327 To generate a HTML table containing structure and alphanumeric data for
2328 molecules in a SMILES file specific columns, type:
2329
2330 % RDKitDrawMoleculesAndDataTable.py --infileParams "smilesDelimiter,
2331 comma, smilesColumn,1,smilesNameColumn,2"
2332 -i SampleSMILES.csv -o SampleOut.html
2333
2334 Author:
2335 Manish Sud(msud@san.rr.com)
2336
2337 See also:
2338 RDKitConvertFileFormat.py, RDKitDrawMolecules.py, RDKitRemoveDuplicateMolecules.py,
2339 RDKitSearchFunctionalGroups.py, RDKitSearchSMARTS.py
2340
2341 Copyright:
2342 Copyright (C) 2026 Manish Sud. All rights reserved.
2343
2344 The functionality available in this script is implemented using RDKit, an
2345 open source toolkit for cheminformatics developed by Greg Landrum.
2346
2347 This file is part of MayaChemTools.
2348
2349 MayaChemTools is free software; you can redistribute it and/or modify it under
2350 the terms of the GNU Lesser General Public License as published by the Free
2351 Software Foundation; either version 3 of the License, or (at your option) any
2352 later version.
2353
2354 """
2355
2356 if __name__ == "__main__":
2357 main()