MayaChemTools

    1 #!/bin/env python
    2 #
    3 # File: PyMOLGenerateRamachandranPlots.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 PyMOL, a
    9 # molecular visualization system on an open source foundation originally
   10 # developed by Warren DeLano.
   11 #
   12 # This file is part of MayaChemTools.
   13 #
   14 # MayaChemTools is free software; you can redistribute it and/or modify it under
   15 # the terms of the GNU Lesser General Public License as published by the Free
   16 # Software Foundation; either version 3 of the License, or (at your option) any
   17 # later version.
   18 #
   19 # MayaChemTools is distributed in the hope that it will be useful, but without
   20 # any warranty; without even the implied warranty of merchantability of fitness
   21 # for a particular purpose.  See the GNU Lesser General Public License for more
   22 # details.
   23 #
   24 # You should have received a copy of the GNU Lesser General Public License
   25 # along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or
   26 # write to the Free Software Foundation Inc., 59 Temple Place, Suite 330,
   27 # Boston, MA, 02111-1307, USA.
   28 #
   29 
   30 from __future__ import print_function
   31 
   32 import os
   33 import sys
   34 import time
   35 import re
   36 import csv
   37 import matplotlib.pyplot as plt
   38 import numpy as np
   39 
   40 # PyMOL imports...
   41 try:
   42     import pymol
   43 
   44     # Finish launching PyMOL in  a command line mode for batch processing (-c)
   45     # along with the following options:  disable loading of pymolrc and plugins (-k);
   46     # suppress start up messages (-q)
   47     pymol.finish_launching(["pymol", "-ckq"])
   48 except ImportError as ErrMsg:
   49     sys.stderr.write("\nFailed to import PyMOL module/package: %s\n" % ErrMsg)
   50     sys.stderr.write("Check/update your PyMOL environment and try again.\n\n")
   51     sys.exit(1)
   52 
   53 # MayaChemTools imports...
   54 sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), "..", "lib", "Python"))
   55 try:
   56     from docopt import docopt
   57     import MiscUtil
   58     import PyMOLUtil
   59 except ImportError as ErrMsg:
   60     sys.stderr.write("\nFailed to import MayaChemTools module/package: %s\n" % ErrMsg)
   61     sys.stderr.write("Check/update your MayaChemTools environment and try again.\n\n")
   62     sys.exit(1)
   63 
   64 ScriptName = os.path.basename(sys.argv[0])
   65 Options = {}
   66 OptionsInfo = {}
   67 
   68 
   69 def main():
   70     """Start execution of the script."""
   71 
   72     MiscUtil.PrintInfo(
   73         "\n%s (PyMOL v%s; MayaChemTools v%s; %s): Starting...\n"
   74         % (ScriptName, pymol.cmd.get_version()[0], MiscUtil.GetMayaChemToolsVersion(), time.asctime())
   75     )
   76 
   77     (WallClockTime, ProcessorTime) = MiscUtil.GetWallClockAndProcessorTime()
   78 
   79     # Retrieve command line arguments and options...
   80     RetrieveOptions()
   81 
   82     # Process and validate command line arguments and options...
   83     ProcessOptions()
   84 
   85     # Perform actions required by the script...
   86     GenerateRamachandranPlots()
   87 
   88     MiscUtil.PrintInfo("\n%s: Done...\n" % ScriptName)
   89     MiscUtil.PrintInfo("Total time: %s" % MiscUtil.GetFormattedElapsedTime(WallClockTime, ProcessorTime))
   90 
   91 
   92 def GenerateRamachandranPlots():
   93     """Calculate phi and psi angles for macromolecules containing amino acids
   94     and generate Ramachandran plots.
   95     """
   96 
   97     # Calculate phi and psi angles..
   98     CalculatePhiPsiAngles()
   99 
  100     # Read phi and psi densities...
  101     ReadPhiAndPsiDensities()
  102 
  103     # Setup contour info...
  104     SetupContoursInfo()
  105 
  106     # Generate plots...
  107     if OptionsInfo["MultipleOutFiles"]:
  108         GenerateMultiplePlotFiles()
  109     else:
  110         GenerateSinglePlotFile()
  111 
  112 
  113 def GenerateSinglePlotFile():
  114     """Generate a single plot file containg all four types of Ramachandran plots."""
  115 
  116     Outfile = OptionsInfo["Outfile"]
  117     MiscUtil.PrintInfo("\nGenerating output file %s..." % (Outfile))
  118 
  119     SetupFontFamily()
  120 
  121     # Setup figure...
  122     PlotFigure, Axes = plt.subplots(
  123         2, 2, figsize=(OptionsInfo["FigWidth"], OptionsInfo["FigHeight"]), dpi=OptionsInfo["FigDPI"]
  124     )
  125     PlotAxes = [Axis for RowAxes in Axes for Axis in RowAxes]
  126 
  127     # Adjust space between subplots...
  128     plt.subplots_adjust(left=0.1, right=0.9, bottom=0.1, top=0.9, wspace=0.3, hspace=0.25)
  129 
  130     for PlotIndex, PlotType in enumerate(OptionsInfo["PlotTypesInfo"]["Types"]):
  131         DrawPlot(PlotAxes[PlotIndex], PlotType)
  132 
  133     # Save figure...
  134     plt.savefig(Outfile)
  135 
  136 
  137 def GenerateMultiplePlotFiles():
  138     """Generate multiple plot files corresponding to four types of Ramachandran plots."""
  139 
  140     MiscUtil.PrintInfo("\nGenerating multiple output files...")
  141 
  142     SetupFontFamily()
  143 
  144     for PlotType in OptionsInfo["PlotTypesInfo"]["Types"]:
  145         Outfile = OptionsInfo["PlotTypesInfo"]["Outfiles"][PlotType]
  146         MiscUtil.PrintInfo("Generating  output file %s..." % Outfile)
  147 
  148         PlotFigure, PlotAxis = plt.subplots(
  149             1, 1, figsize=(OptionsInfo["FigWidth"], OptionsInfo["FigHeight"]), dpi=OptionsInfo["FigDPI"]
  150         )
  151         DrawPlot(PlotAxis, PlotType)
  152 
  153         # Save figure...
  154         plt.savefig(Outfile)
  155 
  156         # Get ready for the next figure...
  157         plt.clf()
  158 
  159 
  160 def DrawPlot(PlotAxis, PlotType):
  161     """Draw contour and scatter plot."""
  162 
  163     PlotTypesInfo = OptionsInfo["PlotTypesInfo"]
  164 
  165     # Draw filled contours...
  166     PhiPsiContourInfo = PlotTypesInfo["PhiPsiContourInfo"][PlotType]
  167     PlotAxis.contourf(
  168         PhiPsiContourInfo["X"],
  169         PhiPsiContourInfo["Y"],
  170         PhiPsiContourInfo["Z"],
  171         levels=PlotTypesInfo["Levels"][PlotType],
  172         colors=PlotTypesInfo["Colors"][PlotType],
  173     )
  174 
  175     # Draw scatter plot for phi and psi angles...
  176     if PlotTypesInfo["ResCount"][PlotType]:
  177         PlotAxis.scatter(
  178             PlotTypesInfo["PhiAngles"][PlotType],
  179             PlotTypesInfo["PsiAngles"][PlotType],
  180             s=OptionsInfo["ScatterMarkerSize"],
  181             c=OptionsInfo["ScatterMarkerColor"],
  182             marker=OptionsInfo["ScatterMarkerStyle"],
  183         )
  184 
  185     # Setup limits...
  186     PlotAxis.set_xlim(PlotTypesInfo["Limits"][PlotType])
  187     PlotAxis.set_ylim(PlotTypesInfo["Limits"][PlotType])
  188 
  189     # Setup major tick marks...
  190     PlotAxis.set_xticks(PlotTypesInfo["MajorTickPositions"][PlotType])
  191     PlotAxis.set_xticklabels(
  192         PlotTypesInfo["MajorTickLabels"][PlotType],
  193         fontdict={"fontsize": OptionsInfo["FontTicksSize"], "fontweight": OptionsInfo["FontTicksWeight"]},
  194     )
  195     PlotAxis.set_yticks(PlotTypesInfo["MajorTickPositions"][PlotType])
  196     PlotAxis.set_yticklabels(
  197         PlotTypesInfo["MajorTickLabels"][PlotType],
  198         fontdict={"fontsize": OptionsInfo["FontTicksSize"], "fontweight": OptionsInfo["FontTicksWeight"]},
  199     )
  200 
  201     # Set up minor ticks...
  202     if OptionsInfo["TicksMinor"]:
  203         PlotAxis.set_xticks(PlotTypesInfo["MinorTickPositions"][PlotType], minor=True)
  204         PlotAxis.set_yticks(PlotTypesInfo["MinorTickPositions"][PlotType], minor=True)
  205 
  206     # Setup grid...
  207     if OptionsInfo["Grid"]:
  208         PlotAxis.grid(
  209             True,
  210             color=OptionsInfo["GridLineColor"],
  211             linestyle=OptionsInfo["GridLineStyle"],
  212             linewidth=OptionsInfo["GridLineWidth"],
  213         )
  214 
  215     # Setup title...
  216     PlotAxis.set_title(
  217         PlotTypesInfo["Titles"][PlotType],
  218         fontsize=OptionsInfo["FontTitleSize"],
  219         fontweight=OptionsInfo["FontTitleWeight"],
  220     )
  221 
  222     # Setup axes labels...
  223     if PlotTypesInfo["DrawXLabel"][PlotType]:
  224         XLabel = r"$\Phi$" if OptionsInfo["Greek"] else "Phi"
  225         PlotAxis.set_xlabel(XLabel, fontsize=OptionsInfo["FontAxesSize"], fontweight=OptionsInfo["FontAxesWeight"])
  226     if PlotTypesInfo["DrawYLabel"][PlotType]:
  227         # Setup a horizontal ylabel close to the axis...
  228         YLabel = r"$\Psi$" if OptionsInfo["Greek"] else "Psi"
  229         YRotation = 0 if OptionsInfo["Greek"] else 90
  230         PlotAxis.set_ylabel(
  231             YLabel,
  232             fontsize=OptionsInfo["FontAxesSize"],
  233             fontweight=OptionsInfo["FontAxesWeight"],
  234             rotation=YRotation,
  235             labelpad=0,
  236         )
  237 
  238 
  239 def SetupFontFamily():
  240     """Setuo global font family."""
  241 
  242     if re.match("^auto$", OptionsInfo["FontFamily"], re.I):
  243         return
  244     plt.rcParams["font.family"] = OptionsInfo["FontFamily"]
  245 
  246 
  247 def SetupContoursInfo():
  248     """Setup contour info for generating contour plots."""
  249 
  250     MiscUtil.PrintInfo("\nProcessing phi and psi densities for contour plots...")
  251 
  252     OptionsInfo["PlotTypesInfo"]["PhiPsiContourInfo"] = {}
  253     for PlotType in OptionsInfo["PlotTypesInfo"]["Types"]:
  254         PhiPsiContourInfo = SetupPhiAndPsiContourInfo(PlotType)
  255         OptionsInfo["PlotTypesInfo"]["PhiPsiContourInfo"][PlotType] = PhiPsiContourInfo
  256 
  257 
  258 def SetupPhiAndPsiContourInfo(PlotType):
  259     """Setup X, Y and Z contour arrays for generating contour plots."""
  260 
  261     DensityInfo = OptionsInfo["PlotTypesInfo"]["PhiPsiDensityInfo"][PlotType]
  262 
  263     X, Y = np.meshgrid(DensityInfo["PhiValues"], DensityInfo["PsiValues"])
  264     Z = np.zeros((len(DensityInfo["PhiValues"]), len(DensityInfo["PsiValues"])))
  265 
  266     # Initialize X, Y, and Z arrays for contour plots...
  267     for ZRowIndex, PsiID in enumerate(DensityInfo["PhiIDs"]):
  268         for ZColIndex, PhiID in enumerate(DensityInfo["PsiIDs"]):
  269             Z[ZRowIndex][ZColIndex] = DensityInfo["Density"][PhiID][PsiID]
  270 
  271     # Track contour data...
  272     ContourInfo = {}
  273     ContourInfo["X"] = X
  274     ContourInfo["Y"] = Y
  275     ContourInfo["Z"] = Z
  276 
  277     return ContourInfo
  278 
  279 
  280 def ReadPhiAndPsiDensities():
  281     """Read phi and psi densities for generating filled contours."""
  282 
  283     OptionsInfo["PlotTypesInfo"]["PhiPsiDensityInfo"] = {}
  284     for PlotType in OptionsInfo["PlotTypesInfo"]["Types"]:
  285         DensityFile = OptionsInfo["PlotTypesInfo"]["PhiPsiDensityFiles"][PlotType]
  286         PhiPsiDensityInfo = ReadPhiAndPsiDensityFile(DensityFile)
  287         OptionsInfo["PlotTypesInfo"]["PhiPsiDensityInfo"][PlotType] = PhiPsiDensityInfo
  288 
  289 
  290 def ReadPhiAndPsiDensityFile(DensityFile):
  291     """Read phi and psi desnsity file.
  292 
  293     Format:
  294     Phi,Psi,Density
  295     -179.0,-179.0,0.00782923406455425
  296     -179.0,-177.0,0.00641357067237856
  297     ... ... ...
  298     """
  299 
  300     MiscUtil.PrintInfo("\nReading psi and psi density grid file %s..." % DensityFile)
  301 
  302     DensityFH = open(DensityFile, "r")
  303     if DensityFH is None:
  304         MiscUtil.PrintError("Couldn't open phi and psi density file: %s.\n" % (DensityFile))
  305 
  306     HeaderLine = True
  307     DensityLines = []
  308     for Line in DensityFH:
  309         Line = Line.rstrip()
  310         # Ignore comments...
  311         if re.match("^#", Line, re.I):
  312             continue
  313         # Ignore header line...
  314         if HeaderLine:
  315             HeaderLine = False
  316             continue
  317         DensityLines.append(Line)
  318 
  319     DensityInfo = {}
  320     DensityInfo["Density"] = {}
  321 
  322     DensityInfo["PhiIDs"] = []
  323     DensityInfo["PhiValues"] = []
  324 
  325     DensityInfo["PsiIDs"] = []
  326     DensityInfo["PsiValues"] = []
  327 
  328     PhiValuesMap = {}
  329     PsiValuesMap = {}
  330 
  331     Count = 0
  332     MinDensity = 99999.0
  333     MaxDensity = -MinDensity
  334 
  335     DensityReader = csv.reader(DensityLines, delimiter=",", quotechar='"')
  336     for LineWords in DensityReader:
  337         Count += 1
  338 
  339         Phi = LineWords[0]
  340         Psi = LineWords[1]
  341         Density = LineWords[2]
  342 
  343         # Track unique phi and psi value...
  344         if Phi not in PhiValuesMap:
  345             PhiValuesMap[Phi] = float(Phi)
  346         if Psi not in PsiValuesMap:
  347             PsiValuesMap[Psi] = float(Psi)
  348 
  349         # Track density data...
  350         if Phi not in DensityInfo["Density"]:
  351             DensityInfo["Density"][Phi] = {}
  352 
  353         Density = float(Density)
  354         DensityInfo["Density"][Phi][Psi] = Density
  355         if Density < MinDensity:
  356             MinDensity = Density
  357         if Density > MaxDensity:
  358             MaxDensity = Density
  359 
  360     # Sort and track values for phi and psi angles...
  361     DensityInfo["PhiIDs"] = sorted(PhiValuesMap.keys(), key=lambda Phi: PhiValuesMap[Phi])
  362     DensityInfo["PhiValues"] = [PhiValuesMap[Phi] for Phi in DensityInfo["PhiIDs"]]
  363 
  364     DensityInfo["PsiIDs"] = sorted(PsiValuesMap.keys(), key=lambda Psi: PsiValuesMap[Psi])
  365     DensityInfo["PsiValues"] = [PsiValuesMap[Psi] for Psi in DensityInfo["PsiIDs"]]
  366 
  367     MiscUtil.PrintInfo("Minimum density: %.4f; Maximum density: %.4f" % (MinDensity, MaxDensity))
  368     MiscUtil.PrintInfo("Number of phi and psi angles: %s" % Count)
  369 
  370     MiscUtil.PrintInfo("\nDimensions of phi and psi grid angles:")
  371     MiscUtil.PrintInfo(
  372         "Phi - Min: %s; Max: %s; Bin size: %s; Count: %s"
  373         % (
  374             DensityInfo["PhiValues"][0],
  375             DensityInfo["PhiValues"][-1],
  376             abs(DensityInfo["PhiValues"][1] - DensityInfo["PhiValues"][0]),
  377             len(DensityInfo["PhiValues"]),
  378         )
  379     )
  380     MiscUtil.PrintInfo(
  381         "Psi - Min: %s; Max: %s; Bin size: %s; Count: %s"
  382         % (
  383             DensityInfo["PsiValues"][0],
  384             DensityInfo["PsiValues"][-1],
  385             abs(DensityInfo["PsiValues"][1] - DensityInfo["PsiValues"][0]),
  386             len(DensityInfo["PsiValues"]),
  387         )
  388     )
  389 
  390     return DensityInfo
  391 
  392 
  393 def CalculatePhiPsiAngles():
  394     """Calculate phi and psi angles for scatter plots."""
  395 
  396     Infile = OptionsInfo["Infile"]
  397     MolName = OptionsInfo["InfileRoot"]
  398 
  399     # Load molecule...
  400     pymol.cmd.reinitialize()
  401     pymol.cmd.load(Infile, MolName)
  402 
  403     MiscUtil.PrintInfo("\nCalculating phi and psi torsion angles for input file %s..." % Infile)
  404 
  405     # Initialize...
  406     OptionsInfo["PlotTypesInfo"]["PhiAngles"] = {}
  407     OptionsInfo["PlotTypesInfo"]["PsiAngles"] = {}
  408     OptionsInfo["PlotTypesInfo"]["ResCount"] = {}
  409     for PlotType in OptionsInfo["PlotTypesInfo"]["Types"]:
  410         OptionsInfo["PlotTypesInfo"]["PhiAngles"][PlotType] = []
  411         OptionsInfo["PlotTypesInfo"]["PsiAngles"][PlotType] = []
  412         OptionsInfo["PlotTypesInfo"]["ResCount"][PlotType] = 0
  413 
  414     Precision = OptionsInfo["Precision"]
  415 
  416     TotalResCount = 0
  417     # Go over specified chain IDs..
  418     for ChainID in OptionsInfo["SpecifiedChainsAndLigandsInfo"]["ChainIDs"]:
  419         PhiPsiInfoList = []
  420         GeneralPhiPsiInfo, GlycinePhiPsiInfo, ProlinePhiPsiInfo, PreProlinePhiPsiInfo = (
  421             PyMOLUtil.GetPhiPsiCategoriesResiduesInfo(MolName, ChainID)
  422         )
  423         PhiPsiInfoList.extend([GeneralPhiPsiInfo, GlycinePhiPsiInfo, ProlinePhiPsiInfo, PreProlinePhiPsiInfo])
  424 
  425         for Index, PlotType in enumerate(OptionsInfo["PlotTypesInfo"]["Types"]):
  426             PhiPsiInfo = PhiPsiInfoList[Index]
  427             ResCount = len(PhiPsiInfo["ResNums"])
  428             if not ResCount:
  429                 continue
  430 
  431             TotalResCount += ResCount
  432             OptionsInfo["PlotTypesInfo"]["ResCount"][PlotType] += ResCount
  433 
  434             PhiAngles, PsiAngles = ProcessPsiInfo(PhiPsiInfo, Precision)
  435             OptionsInfo["PlotTypesInfo"]["PhiAngles"][PlotType].extend(PhiAngles)
  436             OptionsInfo["PlotTypesInfo"]["PsiAngles"][PlotType].extend(PsiAngles)
  437 
  438     # Delete MolName object
  439     pymol.cmd.delete(MolName)
  440 
  441     MiscUtil.PrintInfo("\nTotal number of phi and psi angles: %d" % TotalResCount)
  442 
  443     MiscUtil.PrintInfo("")
  444     for PlotType in OptionsInfo["PlotTypesInfo"]["Types"]:
  445         MiscUtil.PrintInfo(
  446             'Number of "%s" phi and psi angles: %s' % (PlotType, OptionsInfo["PlotTypesInfo"]["ResCount"][PlotType])
  447         )
  448 
  449     if not TotalResCount:
  450         MiscUtil.PrintInfo("")
  451         MiscUtil.PrintWarning(
  452             "No valid phi and psi angles found in input file. Ramachandran plots will be generated without phi and psi scatter plots..."
  453         )
  454 
  455 
  456 def ProcessPsiInfo(PhiPsiInfo, Precision):
  457     """Process phi and psi angels for scatter plots."""
  458 
  459     PhiAngles = []
  460     PsiAngles = []
  461     for ResNum in PhiPsiInfo["ResNums"]:
  462         Phi = "%.*f" % (Precision, PhiPsiInfo["Phi"][ResNum])
  463         Psi = "%.*f" % (Precision, PhiPsiInfo["Psi"][ResNum])
  464         PhiAngles.append(float(Phi))
  465         PsiAngles.append(float(Psi))
  466 
  467     return PhiAngles, PsiAngles
  468 
  469 
  470 def RetrieveInfileInfo():
  471     """Retrieve information for input file."""
  472 
  473     Infile = OptionsInfo["Infile"]
  474     InfileRoot = OptionsInfo["InfileRoot"]
  475 
  476     ChainsAndLigandsInfo = PyMOLUtil.GetChainsAndLigandsInfo(Infile, InfileRoot)
  477     OptionsInfo["ChainsAndLigandsInfo"] = ChainsAndLigandsInfo
  478 
  479 
  480 def ProcessChainIDs():
  481     """Process specified chain IDs for infile."""
  482 
  483     MiscUtil.PrintInfo("\nProcessing specified chain IDs for input file %s..." % OptionsInfo["Infile"])
  484     ChainsAndLigandsInfo = OptionsInfo["ChainsAndLigandsInfo"]
  485     SpecifiedChainsAndLigandsInfo = PyMOLUtil.ProcessChainsAndLigandsOptionsInfo(
  486         ChainsAndLigandsInfo, "-c, --chainIDs", OptionsInfo["ChainIDs"], None, None
  487     )
  488 
  489     OptionsInfo["SpecifiedChainsAndLigandsInfo"] = SpecifiedChainsAndLigandsInfo
  490 
  491     MiscUtil.PrintInfo("Specified chain IDs: %s" % (", ".join(SpecifiedChainsAndLigandsInfo["ChainIDs"])))
  492 
  493 
  494 def SetupPlotsInfo():
  495     """Setup information for generating plots."""
  496 
  497     InitializePlotTypesInfo()
  498     SetupPlotTypesOutfiles()
  499 
  500     ProessContourLevelsAndColors()
  501 
  502 
  503 def ProessContourLevelsAndColors():
  504     """Process specified contour levels and colors."""
  505 
  506     if re.match("^auto$", OptionsInfo["LevelsAndColors"], re.I):
  507         return
  508 
  509     # Setup canonical plot types for validation...
  510     CanonicalPlotTypes = {}
  511     for PlotType in OptionsInfo["PlotTypesInfo"]["Types"]:
  512         CanonicalPlotTypes[PlotType.lower()] = PlotType
  513 
  514     LevelsAndColors = re.sub(" ", "", OptionsInfo["LevelsAndColors"])
  515     if not len(LevelsAndColors):
  516         MiscUtil.PrintError('The levels and colors specified using "-l, --levelsAndColors" option are empty.')
  517 
  518     for TypeLevelsColorsWord in LevelsAndColors.split(";"):
  519         if not len(TypeLevelsColorsWord):
  520             MiscUtil.PrintError(
  521                 'The plot types, levels, and colors, "%s" specified using "-l, --levelsAndColors" option in, "%s", is empty.'
  522                 % (TypeLevelsColorsWord, LevelsAndColors)
  523             )
  524 
  525         TypeLevelsColorsWords = TypeLevelsColorsWord.split(":")
  526         if len(TypeLevelsColorsWords) != 2:
  527             MiscUtil.PrintError(
  528                 'The format of plot type, levels, and colors specification, "%s", specified using "-l, --levelsAndColors" option, in "%s",  is not valid: Supported format: <PlotType>: <Level>, <Color>, <Level>,...'
  529                 % (TypeLevelsColorsWord, OptionsInfo["LevelsAndColors"])
  530             )
  531 
  532         PlotType = TypeLevelsColorsWords[0]
  533         if not len(PlotType):
  534             MiscUtil.PrintError(
  535                 'The plot type, "%s" specified using "-l, --levelsAndColors" option in, "%s", is empty.'
  536                 % (PlotType, TypeLevelsColorsWord)
  537             )
  538         CanonicalPlotType = PlotType.lower()
  539 
  540         if CanonicalPlotType not in CanonicalPlotTypes:
  541             MiscUtil.PrintError(
  542                 'The plot type, "%s" specified using "-l, --levelsAndColors" option in, "%s", is not valid. Supported valus: %s'
  543                 % (PlotType, TypeLevelsColorsWord, ", ".join(OptionsInfo["PlotTypesInfo"]["Types"]))
  544             )
  545         PlotType = CanonicalPlotTypes[CanonicalPlotType]
  546 
  547         LevelsColorsWords = TypeLevelsColorsWords[1].split(",")
  548         if not (len(LevelsColorsWords) % 2):
  549             MiscUtil.PrintError(
  550                 'The format of levels and colors specifification, "%s", specified using "-l, --levelsAndColors" option in, "%s",  is not valid. It must contain odd number of values. Supported format: <PlotType>: <Level>, <Color>, <Level>,...'
  551                 % (", ".join(LevelsColorsWords), TypeLevelsColorsWord)
  552             )
  553 
  554         # Retrieve levels and colors...
  555         Levels = []
  556         Colors = []
  557         for Index, SpecWord in enumerate(LevelsColorsWords):
  558             if not len(SpecWord):
  559                 MiscUtil.PrintError(
  560                     'The level or color, "%s" specified using "-l, --levelsAndColors" option in, "%s", is empty.'
  561                     % (SpecWord, TypeLevelsColorsWord)
  562                 )
  563 
  564             if Index % 2:
  565                 Colors.append(SpecWord)
  566                 continue
  567 
  568             # Process level...
  569             if not MiscUtil.IsFloat(SpecWord):
  570                 MiscUtil.PrintError(
  571                     'The level, "%s" specified using "-l, --levelsAndColors" option in, "%s", must be a number.'
  572                     % (SpecWord, TypeLevelsColorsWord)
  573                 )
  574 
  575             Level = float(SpecWord)
  576             if len(Levels):
  577                 # The current level must be greater than all previous levels..
  578                 for PreviousLevel in Levels:
  579                     if Level <= PreviousLevel:
  580                         MiscUtil.PrintError(
  581                             'The level, "%s" specified using "-l, --levelsAndColors" option in, "%s", must be greater than all previous levels.'
  582                             % (SpecWord, TypeLevelsColorsWord)
  583                         )
  584 
  585             Levels.append(Level)
  586 
  587         OptionsInfo["PlotTypesInfo"]["Levels"][PlotType] = Levels
  588         OptionsInfo["PlotTypesInfo"]["Colors"][PlotType] = Colors
  589 
  590 
  591 def InitializePlotTypesInfo():
  592     """Initialize information for generating plots."""
  593 
  594     PlotTypesInfo = {}
  595     PlotTypesInfo["Types"] = []
  596     PlotTypesInfo["PhiPsiDensityFiles"] = {}
  597     PlotTypesInfo["PhiPsiDensityInfo"] = {}
  598     PlotTypesInfo["PhiPsiContourInfo"] = {}
  599 
  600     PlotTypesInfo["Titles"] = {}
  601     PlotTypesInfo["DrawXLabel"] = {}
  602     PlotTypesInfo["DrawYLabel"] = {}
  603 
  604     PlotTypesInfo["Limits"] = {}
  605     PlotTypesInfo["MajorTickPositions"] = {}
  606     PlotTypesInfo["MajorTickLabels"] = {}
  607     PlotTypesInfo["MinorTickPositions"] = {}
  608 
  609     PlotTypesInfo["Levels"] = {}
  610     PlotTypesInfo["Colors"] = {}
  611     PlotTypesInfo["Outfiles"] = {}
  612     PlotTypesInfo["PhiAngles"] = {}
  613     PlotTypesInfo["PsiAngles"] = {}
  614 
  615     MayaChemToolsDataDir = MiscUtil.GetMayaChemToolsLibDataPath()
  616 
  617     # Setup contour colors for supported default schemes...
  618     ContourColorSchemes = {}
  619     ContourColorSchemes["General"] = {
  620         "MuttedColorShades1": ["#FFFFFF", "#EBF1DE", "#C3D69B"],
  621         "MuttedColorShades2": ["#FFFFFF", "#EBF1DE", "#D7E4BD"],
  622         "BrightColorShades": ["#FFFFFF", "#B3E8FF", "#7FD9FF"],
  623     }
  624     ContourColorSchemes["Glycine"] = {
  625         "MuttedColorShades1": ["#FFFFFF", "#FDEADA", "#FAC090"],
  626         "MuttedColorShades2": ["#FFFFFF", "#FDEADA", "#FCD5B5"],
  627         "BrightColorShades": ["#FFFFFF", "#FFE8C5", "#FFCC7F"],
  628     }
  629     ContourColorSchemes["Proline"] = {
  630         "MuttedColorShades1": ["#FFFFFF", "#E6E0EC", "#B3A2C7"],
  631         "MuttedColorShades2": ["#FFFFFF", "#E6E0EC", "#CCC1DA"],
  632         "BrightColorShades": ["#FFFFFF", "#D0FFC5", "#7FFF8C"],
  633     }
  634     ContourColorSchemes["PreProline"] = {
  635         "MuttedColorShades1": ["#FFFFFF", "#DCE6F2", "#95B3D7"],
  636         "MuttedColorShades2": ["#FFFFFF", "#DCE6F2", "#B9CDE5"],
  637         "BrightColorShades": ["#FFFFFF", "#B3E8FF", "#7FD9FF"],
  638     }
  639 
  640     if re.match("^MuttedColorShades1$", OptionsInfo["LevelsAndColorsScheme"], re.I):
  641         DefaultColorScheme = "MuttedColorShades1"
  642     elif re.match("^MuttedColorShades2$", OptionsInfo["LevelsAndColorsScheme"], re.I):
  643         DefaultColorScheme = "MuttedColorShades2"
  644     elif re.match("^BrightColorShades$", OptionsInfo["LevelsAndColorsScheme"], re.I):
  645         DefaultColorScheme = "BrightColorShades"
  646     else:
  647         MiscUtil.PrintError(
  648             'The color scheme, %s, specified using "--levelsAndColorsScheme" option is not supported.'
  649             % (OptionsInfo["LevelsAndColorsScheme"])
  650         )
  651 
  652     for Type in ["General", "Glycine", "Proline", "PreProline"]:
  653         PlotTypesInfo["Types"].append(Type)
  654 
  655         # Setup phi and psi density file...
  656         DensityFile = os.path.join(MayaChemToolsDataDir, "PhiPsiDensity%s.csv" % (Type))
  657         if not os.path.exists(DensityFile):
  658             MiscUtil.PrintError(
  659                 "The phi and psi density file file, %s, doesn't exist. This is required for generating contour plots.\n"
  660                 % (DensityFile)
  661             )
  662         PlotTypesInfo["PhiPsiDensityFiles"][Type] = DensityFile
  663 
  664         # Setup plot title...
  665         Title = Type
  666         if re.match("^PreProline$", Type, re.I):
  667             Title = "pre-Proline"
  668         PlotTypesInfo["Titles"][Type] = Title
  669 
  670         # Setup flags for drawing axis labels...
  671         DrawXLabel, DrawYLabel = [True] * 2
  672         if not OptionsInfo["MultipleOutFiles"]:
  673             # Turn off XLabel for plots in first row...
  674             DrawXLabel = False if re.match("^(General|Glycine)$", Type, re.I) else True
  675 
  676             # Turn off YLabel for plots in second column...
  677             DrawYLabel = False if re.match("^(Glycine|PreProline)$", Type, re.I) else True
  678         PlotTypesInfo["DrawXLabel"][Type] = DrawXLabel
  679         PlotTypesInfo["DrawYLabel"][Type] = DrawYLabel
  680 
  681         # Setup limits...
  682         (MinLimit, MaxLimit) = [-180, 180]
  683         PlotTypesInfo["Limits"][Type] = [MinLimit, MaxLimit]
  684 
  685         # Setup major tick labels and positions...
  686         MajorTickPositions = list(range(MinLimit, MaxLimit, OptionsInfo["TicksMajorInterval"]))
  687         MajorTickPositions.append(MaxLimit)
  688         MajorTickLabels = ["%s" % Position for Position in MajorTickPositions]
  689         PlotTypesInfo["MajorTickPositions"][Type] = MajorTickPositions
  690         PlotTypesInfo["MajorTickLabels"][Type] = MajorTickLabels
  691 
  692         # Setup minor tick positions without any labels...
  693         MinorTickPositions = list(range(MinLimit, MaxLimit, OptionsInfo["TicksMinorInterval"]))
  694         MinorTickPositions.append(MaxLimit)
  695         PlotTypesInfo["MinorTickPositions"][Type] = MinorTickPositions
  696 
  697         # Setup contour levels and colors...
  698         Levels = []
  699         Colors = []
  700         if re.match("^General$", Type, re.I):
  701             Levels = [0.0, 0.0005, 0.02, 1.0]
  702             Colors = ContourColorSchemes[Type][DefaultColorScheme]
  703         elif re.match("^Glycine$", Type, re.I):
  704             Levels = [0.0, 0.002, 0.02, 1.0]
  705             Colors = ContourColorSchemes[Type][DefaultColorScheme]
  706         elif re.match("^Proline$", Type, re.I):
  707             Levels = [0.0, 0.002, 0.02, 1.0]
  708             Colors = ContourColorSchemes[Type][DefaultColorScheme]
  709         elif re.match("^PreProline$", Type, re.I):
  710             Levels = [0.0, 0.002, 0.02, 1.0]
  711             Colors = ContourColorSchemes[Type][DefaultColorScheme]
  712 
  713         PlotTypesInfo["Levels"][Type] = Levels
  714         PlotTypesInfo["Colors"][Type] = Colors
  715 
  716     OptionsInfo["PlotTypesInfo"] = PlotTypesInfo
  717 
  718 
  719 def SetupPlotTypesOutfiles():
  720     """Setup output file names for plot types."""
  721 
  722     OptionsInfo["OutfilesList"] = []
  723     OptionsInfo["OutfilesList"].append(OptionsInfo["Outfile"])
  724 
  725     OptionsInfo["PlotTypesInfo"]["Outfiles"] = {}
  726     for PlotType in OptionsInfo["PlotTypesInfo"]["Types"]:
  727         OptionsInfo["PlotTypesInfo"]["Outfiles"][PlotType] = None
  728 
  729     if not OptionsInfo["MultipleOutFiles"]:
  730         return
  731 
  732     FileDir, FileName, FileExt = MiscUtil.ParseFileName(OptionsInfo["Outfile"])
  733     OutfileRoot = FileName
  734     OutfileExt = FileExt
  735 
  736     for PlotType in OptionsInfo["PlotTypesInfo"]["Types"]:
  737         PlotOutfile = "%s_%s.%s" % (OutfileRoot, PlotType, OutfileExt)
  738         if os.path.exists(PlotOutfile):
  739             if not OptionsInfo["Overwrite"]:
  740                 MiscUtil.PrintError(
  741                     'The plot output file, %s, already exist. Use option "--ov" or "--overwrite" and try again.\n'
  742                     % (PlotOutfile)
  743                 )
  744 
  745         OptionsInfo["PlotTypesInfo"]["Outfiles"][PlotType] = PlotOutfile
  746         OptionsInfo["OutfilesList"].append(PlotOutfile)
  747 
  748 
  749 def ProcessOptions():
  750     """Process and validate command line arguments and options."""
  751 
  752     MiscUtil.PrintInfo("Processing options...")
  753 
  754     # Validate options...
  755     ValidateOptions()
  756 
  757     OptionsInfo["OutMode"] = Options["--outMode"]
  758     OptionsInfo["MultipleOutFiles"] = True if re.match("^MultipleFiles$", OptionsInfo["OutMode"], re.I) else False
  759     MultipleOutFiles = OptionsInfo["MultipleOutFiles"]
  760 
  761     OptionsInfo["FigDPI"] = int(Options["--figDPI"])
  762 
  763     FigSize = Options["--figSize"]
  764     Width = 6.4
  765     Height = 4.8 if MultipleOutFiles else 6.4
  766 
  767     if not re.match("^auto$", FigSize, re.I):
  768         FigSizeWords = FigSize.split(",")
  769         Width = float(FigSizeWords[0])
  770         Height = float(FigSizeWords[1])
  771     OptionsInfo["FigSize"] = FigSize
  772     OptionsInfo["FigWidth"] = Width
  773     OptionsInfo["FigHeight"] = Height
  774 
  775     OptionsInfo["FontFamily"] = Options["--fontFamily"]
  776     OptionsInfo["FontAxesSize"] = Options["--fontAxesSize"]
  777     OptionsInfo["FontAxesWeight"] = Options["--fontAxesWeight"]
  778     OptionsInfo["FontTicksSize"] = Options["--fontTicksSize"]
  779     OptionsInfo["FontTicksWeight"] = Options["--fontTicksWeight"]
  780     OptionsInfo["FontTitleSize"] = Options["--fontTitleSize"]
  781     OptionsInfo["FontTitleWeight"] = Options["--fontTitleWeight"]
  782 
  783     OptionsInfo["Greek"] = True if re.match("^Yes$", Options["--greek"], re.I) else False
  784 
  785     OptionsInfo["Grid"] = True if re.match("^Yes$", Options["--grid"], re.I) else False
  786     OptionsInfo["GridLineColor"] = Options["--gridLineColor"]
  787     OptionsInfo["GridLineStyle"] = Options["--gridLineStyle"]
  788     OptionsInfo["GridLineWidth"] = float(Options["--gridLineWidth"])
  789 
  790     OptionsInfo["Infile"] = Options["--infile"]
  791     FileDir, FileName, FileExt = MiscUtil.ParseFileName(OptionsInfo["Infile"])
  792     OptionsInfo["InfileRoot"] = FileName
  793 
  794     OptionsInfo["LevelsAndColorsScheme"] = Options["--levelsAndColorsScheme"]
  795 
  796     OptionsInfo["Outfile"] = Options["--outfile"]
  797     FileDir, FileName, FileExt = MiscUtil.ParseFileName(OptionsInfo["Outfile"])
  798     OptionsInfo["OutfileRoot"] = FileName
  799 
  800     OptionsInfo["Overwrite"] = Options["--overwrite"]
  801     OptionsInfo["Precision"] = int(Options["--precision"])
  802 
  803     OptionsInfo["ScatterMarkerColor"] = Options["--scatterMarkerColor"]
  804     OptionsInfo["ScatterMarkerSize"] = float(Options["--scatterMarkerSize"])
  805     OptionsInfo["ScatterMarkerStyle"] = Options["--scatterMarkerStyle"]
  806 
  807     TicksMajorInterval = 90 if MultipleOutFiles else 180
  808     if not re.match("^auto$", Options["--ticksMajorInterval"], re.I):
  809         TicksMajorInterval = int(Options["--ticksMajorInterval"])
  810     OptionsInfo["TicksMajorInterval"] = TicksMajorInterval
  811 
  812     OptionsInfo["TicksMinor"] = True if re.match("^Yes$", Options["--ticksMinor"], re.I) else False
  813     TicksMinorInterval = 10 if MultipleOutFiles else 45
  814     if not re.match("^auto$", Options["--ticksMinorInterval"], re.I):
  815         TicksMinorInterval = int(Options["--ticksMinorInterval"])
  816     OptionsInfo["TicksMinorInterval"] = TicksMinorInterval
  817 
  818     RetrieveInfileInfo()
  819     OptionsInfo["ChainIDs"] = Options["--chainIDs"]
  820 
  821     ProcessChainIDs()
  822 
  823     OptionsInfo["LevelsAndColors"] = Options["--levelsAndColors"]
  824     SetupPlotsInfo()
  825 
  826 
  827 def RetrieveOptions():
  828     """Retrieve command line arguments and options."""
  829 
  830     # Get options...
  831     global Options
  832     Options = docopt(_docoptUsage_)
  833 
  834     # Set current working directory to the specified directory...
  835     WorkingDir = Options["--workingdir"]
  836     if WorkingDir:
  837         os.chdir(WorkingDir)
  838 
  839     # Handle examples option...
  840     if "--examples" in Options and Options["--examples"]:
  841         MiscUtil.PrintInfo(MiscUtil.GetExamplesTextFromDocOptText(_docoptUsage_))
  842         sys.exit(0)
  843 
  844 
  845 def ValidateOptions():
  846     """Validate option values."""
  847 
  848     MiscUtil.ValidateOptionIntegerValue("--figDPI", Options["--figDPI"], {">": 0})
  849     if not re.match("^auto$", Options["--figSize"], re.I):
  850         MiscUtil.ValidateOptionNumberValues("--figSize", Options["--figSize"], 2, ",", "float", {">": 0})
  851 
  852     MiscUtil.ValidateOptionFilePath("-i, --infile", Options["--infile"])
  853     MiscUtil.ValidateOptionFileExt("-i, --infile", Options["--infile"], "pdb cif")
  854 
  855     MiscUtil.ValidateOptionTextValue("-g, --greek", Options["--greek"], "yes no")
  856 
  857     MiscUtil.ValidateOptionTextValue("--grid", Options["--grid"], "yes no")
  858     MiscUtil.ValidateOptionFloatValue("--gridLineWidth", Options["--gridLineWidth"], {">": 0})
  859 
  860     MiscUtil.ValidateOptionTextValue(
  861         "--levelsAndColorsScheme",
  862         Options["--levelsAndColorsScheme"],
  863         "MuttedColorShades1 MuttedColorShades2 BrightColorShades",
  864     )
  865 
  866     MiscUtil.ValidateOptionsOutputFileOverwrite(
  867         "-o, --outfile", Options["--outfile"], "--overwrite", Options["--overwrite"]
  868     )
  869 
  870     MiscUtil.ValidateOptionTextValue("--outMode", Options["--outMode"], "SingleFile MultipleFiles")
  871     MiscUtil.ValidateOptionIntegerValue("-p, --precision", Options["--precision"], {">": 0})
  872 
  873     MiscUtil.ValidateOptionFloatValue("--scatterMarkerSize", Options["--scatterMarkerSize"], {">": 0})
  874 
  875     if not re.match("^auto$", Options["--ticksMajorInterval"], re.I):
  876         MiscUtil.ValidateOptionIntegerValue("--ticksMajorInterval", Options["--ticksMajorInterval"], {">": 0, "<": 360})
  877 
  878     MiscUtil.ValidateOptionTextValue("--ticksMinor", Options["--ticksMinor"], "yes no")
  879     if not re.match("^auto$", Options["--ticksMinorInterval"], re.I):
  880         MiscUtil.ValidateOptionIntegerValue("--ticksMinorInterval", Options["--ticksMinorInterval"], {">": 0, "<": 360})
  881 
  882 
  883 # Setup a usage string for docopt...
  884 _docoptUsage_ = """
  885 PyMOLGenerateRamachandranPlots.py - Generate Ramachandran plots
  886 
  887 Usage:
  888     PyMOLGenerateRamachandranPlots.py [--chainIDs <First, All or ID1,ID2...>]
  889                                       [--figDPI <number>] [--figSize <width, height>] [--fontFamily <text>]
  890                                       [--fontAxesSize <number or text>] [--fontAxesWeight <number or text>]
  891                                       [--fontTicksSize <number or text>] [--fontTicksWeight <number or text>]
  892                                       [--fontTitleSize <number or text>] [--fontTitleWeight <number or text>] [--greek <yes or no>]
  893                                       [--grid <yes or no>] [--gridLineColor <text>] [--gridLineStyle <text>] [--gridLineWidth <number>]
  894                                       [--levelsAndColors <PlotType:Level,color,Level,...;...>] [--levelsAndColorsScheme <text>]
  895                                       [--outMode <SingleFile or MultipleFiles>] [--overwrite]  [--precision <number>]
  896                                       [--scatterMarkerColor <text>] [--scatterMarkerSize <number>] [--scatterMarkerStyle <text>]
  897                                       [--ticksMajorInterval <number>] [--ticksMinor <yes or no>] [--ticksMinorInterval <number>]
  898                                       [-w <dir>] -i <infile> -o <outfile>
  899     PyMOLGenerateRamachandranPlots.py -h | --help | -e | --examples
  900 
  901 Description:
  902     Generate Ramachandran plots for amino acid residues present in macromolecules.
  903     
  904     The Ramachandran plots are generated by plotting phi and psi backbone angles
  905     corresponding to the following four categories of amino acids:
  906     
  907         General: All residues except glycine, proline, or pre-proline
  908         Glycine: Only glycine residues
  909         Proline: Only proline residues
  910         PreProline: Only residues before proline not including glycine or
  911             proline
  912     
  913     In addition to the scatter plots for phi and psi angles, the filled contours
  914     are generated for the density of phi and psi angles [ Ref 144 ] for the
  915     Ramachandran plots. The contours are generated for "favored" and "allowed"
  916     regions. The phi and psi density is retrieved from the following density files
  917     available in MAYACHEMTOOLS/lib/data/ directory:
  918     
  919         General: PhiPsiDensityGeneral.csv 
  920         Glycine: PhiPsiDensityGlycine.csv
  921         Proline: PhiPsiDensityProline.csv
  922         PreProline: PhiPsiDensityPreProline.csv
  923     
  924     The supported input  file format are: PDB (.pdb), mmCIF (.cif)
  925     
  926     The output image file can be saved in any format supported by Python
  927     module Matplotlib. The image format is automatically detected from the
  928     output file extension. 
  929     
  930     Some of the most common output image file formats are: EPS (.eps), PDF (.pdf),
  931     PNG (.png), PS (.ps), SVG (.svg).
  932 
  933 Options:
  934     -c, --chainIDs <First, All or ID1,ID2...>  [default: All]
  935         List of chain IDs to use for calculating phi and psi angles for residues
  936         in chains. Possible values: First, All, or a comma delimited list of chain
  937         IDs. The default is to use all chain IDs in input file.
  938     -e, --examples
  939         Print examples.
  940     --figDPI <number>  [default: 300]
  941         Figure resolution in dots per inches. The DPI value must be supported
  942         by Matplotlib during generation of an image of a specific format. No
  943         validation is performed.
  944     --figSize <width, height>  [default: auto]
  945         Figure dimensions in inches. The default values are dependent on the
  946         the value of '--outMode' option as shown below:
  947         
  948             SingleFile: 6.4, 6.4
  949             MultipleFiles: 6.4, 4.8
  950         
  951     --fontFamily <text>  [default: auto]
  952         Font family to use for title, axes labels, and tick marks. It must be a
  953         valid Matplotlib value. The default value corresponds to the value 
  954         plt.rcParams["font.family"] in your environment. For example: serif,
  955         sans-serif, cursive, etc. 
  956     --fontAxesSize <number or text>  [default: 10]
  957         Font size for labels on axes. It must be valid Matplotlib font size. For
  958         example: size in points, xx-small, x-small, small, medium, etc.
  959     --fontAxesWeight <number or text>  [default: regular]
  960         Font weight for labels on axes. It must be valid Matplotlib value. For
  961         example: a numeric value in range 0-1000, ultralight, light, normal,
  962         regular, book, medium, etc.
  963     --fontTicksSize <number or text>  [default: 8]
  964         Font size for tick labels. It must be a valid Matplotlib font size. For
  965         example: size in points, xx-small, x-small, small, medium, etc.
  966     --fontTicksWeight <number or text>  [default: regular]
  967         Font weight for tick labels. It must be valid Matplotlib value. For
  968         example: a numeric value in range 0-1000, ultralight, light,
  969         normal, regular, book, medium, etc.
  970     --fontTitleSize <number or text>  [default: 10]
  971         Font size for title. It must be a valid Matplotlib font size. For example:
  972         size in points, xx-small, x-small, small, medium, etc.
  973     --fontTitleWeight <number or text>  [default: bold]
  974         Font weight for title. It must be a valid Matplotlib value. For example: a
  975         numeric value in range 0-1000, ultralight, light, normal, regular, book,
  976         medium, etc.
  977     -g, --greek <yes or no>  [default: yes]
  978         Show phi and psi labels as greek characters.
  979     --grid <yes or no>  [default: yes]
  980         Display grid lines at major tick marks.
  981     --gridLineColor <text>  [default: #b0b0b0]
  982         Grid line color. It must be a valid Matplotlib value. The default color
  983         is light gray.
  984     --gridLineStyle <text>  [default: dotted]
  985         Grid line style. It must be a valid Matplotlib value. For example:
  986         '-' or 'solid', --' or 'dashed', '-.' or 'dashdot', ':' or 'dotted' etc.
  987     --gridLineWidth <number>  [default: 0.8]
  988         Grid line width. It must be a valid Matplotlib value.
  989     -h, --help
  990         Print this help message.
  991     -i, --infile <infile>
  992         Input file name.
  993     -l, --levelsAndColors <PlotType:Level,color,Level,...;...>  [default: auto]
  994         Semicolon delimited list of contour levels and colors for four types
  995         of Ramachandran plots.
  996         
  997         Three default contour level and color scheme may be specified by
  998         '--levelsAndColorsScheme' option. By default, the 'MuttedColorShades1'
  999         scheme is used. The default contour levels correspond to 'favored' and
 1000         'allowed' regions [ Ref 144 ] for phi and psi angles.
 1001         
 1002         The colors are used to fill spaces between contour levels. The values
 1003         for contour levels must be ascending order. The number of colors
 1004         must be one less than the number contour levels.
 1005         
 1006         The format of contour level and color specification is as follows:
 1007         
 1008             PlotType:Level,Color,Level,...;PlotType:Level,Color,Level,...
 1009         
 1010         The valid values for plot type are:
 1011         
 1012             General, Glycine, Proline, or PreProline
 1013         
 1014         The contour level must be a number. The color value must be a valid color
 1015         name or a hexadecimal color string supported by Matplotlib. No validation
 1016         is performed.
 1017         
 1018         For example:
 1019         
 1020             General: 0.0, #FFFFFF, 0.0005, #EBF1DE, 0.02, #C3D69B, 1.0
 1021         
 1022     --levelsAndColorsScheme <text>  [default: MuttedColorShades1]
 1023         Default contour levels and colors scheme.  Possible values:
 1024         MuttedColorShades1, MuttedColorShades2, or BrightColorShades.
 1025         
 1026         This option is only used during 'auto' value of '--levelsAndColors' option.
 1027         The default contour levels correspond to 'favored' and 'allowed' regions
 1028         [ Ref 144 ] for phi and psi angles.
 1029         
 1030         The default contour and color values for different default schemes are
 1031         shown below:
 1032         
 1033         MuttedColorShades1:
 1034         
 1035             General: 0.0, #FFFFFF, 0.0005, #EBF1DE, 0.02, #C3D69B, 1.0
 1036             Glycine: 0.0, #FFFFFF, 0.002, #7FD9FF, 0.02, #FAC090, 1.0
 1037             Proline: 0.0, #FFFFFF, 0.002, #E6E0EC, 0.02, #B3A2C7, 1.0
 1038             PreProline: 0.0, #FFFFFF, 0.002, #DCE6F2, 0.02, #95B3D7, 1.0
 1039         
 1040         MuttedColorShades2:
 1041         
 1042             General: 0.0, #FFFFFF, 0.0005, #EBF1DE, 0.02, #D7E4BD, 1.0
 1043             Glycine: 0.0, #FFFFFF, 0.002, #FDEADA, 0.02, #FCD5B5, 1.0
 1044             Proline: 0.0, #FFFFFF, 0.002, #E6E0EC, 0.02, #CCC1DA, 1.0
 1045             PreProline: 0.0, #FFFFFF, 0.002, #DCE6F2, 0.02, #B9CDE5, 1.0
 1046         
 1047         BrightColorShades: [ Ref 145 ]
 1048         
 1049             General: 0.0, #FFFFFF, 0.0005, #B3E8FF, 0.02, #7FD9FF, 1.0
 1050             Glycine: 0.0, #FFFFFF, 0.002, #FFE8C5, 0.02, #FFCC7F, 1.0
 1051             Proline: 0.0, #FFFFFF, 0.002, #D0FFC5, 0.02, #7FFF8C, 1.0
 1052             PreProline: 0.0, #FFFFFF, 0.002, #B3E8FF, 0.02, #7FD9FF, 1.0
 1053         
 1054     -o, --outfile <outfile>
 1055         Output image file name.
 1056         
 1057         A set of output files is optionally generated for 'MultipleFiles' value of
 1058         '--outMode' option. The names of these output files are automatically
 1059         generated from the the name of the specified output file as shown
 1060         below:
 1061         
 1062             General: <OutfileRoot>_General.<OutfileExt>
 1063             Glycine: <OutfileRoot>_Glycine.<OutfileExt>
 1064             Proline: <OutfileRoot>_Proline.<OutfileExt>
 1065             PreProline: <OutfileRoot>_PreProline.<OutfileExt>
 1066         
 1067     --outMode <Single or Multiple>  [default: SingleFile]
 1068         A single output file containing all four Ramachandran plots or multiple
 1069         output files corresponding to different types of Ramachandran plots.
 1070         
 1071         The phi and psi angles are categorized into the following groups
 1072         corresponding to four types of Ramachandran plots:
 1073         
 1074             General: All residues except glycine, proline, or pre-proline
 1075             Glycine: Only glycine residues
 1076             Proline: Only proline residues
 1077             PreProline: Only residues before proline not including glycine or
 1078                 proline
 1079         
 1080     --overwrite
 1081         Overwrite existing files.
 1082     -p, --precision <number>  [default: 2]
 1083         Floating point precision for plotting the calculated phi and psi angles.
 1084     --scatterMarkerColor <text>  [default: #1f77b4]
 1085         Scatter marker color for plotting to phi and psi angles. It must be a
 1086         valid Matplotlib value. The default color is dark blue.
 1087     --scatterMarkerSize <number>  [default: 1.0]
 1088         Scatter marker size for piloting phi and psi angles. It must be a valid
 1089         Matplotlib value.
 1090     --scatterMarkerStyle <text>  [default: .]
 1091         Scatter marker style for piloting phi and psi angles. It must be a valid
 1092         Matplotlib value. For example: '.' (point), ',' (pixel), 'o' (circle), etc.
 1093     --ticksMajorInterval <number>  [default: auto]
 1094         Display major marks on axes at intervals specified in degrees for phi and
 1095         psi angles. The default value is dependent on the the value of '--outMode'
 1096         option: SingleFile: 180; MultipleFiles: 90
 1097         
 1098         The grid lines are drawn at the locations of major tick marks.
 1099     --ticksMinor <yes or no>  [default: yes]
 1100         Display minor tick marks. The major tick mark are always displayed.
 1101     --ticksMinorInterval <number>  [default: auto]
 1102         Display minor marks on axes at intervals specified in degrees for phi and
 1103         psi angles. The default value is dependent on the the value of '--outMode'
 1104         option: SingleFile:  45; MultipleFiles: 10
 1105     -w, --workingdir <dir>
 1106         Location of working directory which defaults to the current directory.
 1107 
 1108 Examples:
 1109     To generate Ramachandran plot for all residues across all chains in input
 1110     file and write out a single SVG file containing all four types of plots, type:
 1111 
 1112         % PyMOLGenerateRamachandranPlots.py -i Sample3.pdb -o Sample3Out.svg
 1113 
 1114     To generate Ramachandran plot for all residues across all chains in input
 1115     file and write out four SVG files corresponding to four types of plots, type:
 1116 
 1117         % PyMOLGenerateRamachandranPlots.py --outMode MultipleFiles
 1118           -i Sample3.pdb -o Sample3Out.svg
 1119 
 1120     To generate Ramachandran plot for all residues in a specific chain in input
 1121     file and write out a single PDF file containing all four types of plots, type:
 1122 
 1123         % PyMOLGenerateRamachandranPlots.py -c E -i Sample3.pdb
 1124           -o Sample3Out.pdf
 1125 
 1126     To generate Ramachandran plot for all residues across all chains in input
 1127     file using specific options and write out four PNG files containing all four
 1128     types of plots, type:
 1129 
 1130         % PyMOLGenerateRamachandranPlots.py --outMode MultipleFiles
 1131           --figSize "6,4" --figDPI 600 --fontTitleSize 10 --fontTitleWeight
 1132           normal --greek no --grid no --levelsAndColors
 1133           "General: 0.0, #FFFFFF, 0.0005, #B3E8FF, 0.02, #7FD9FF, 1.0"
 1134           -i Sample3.pdb -o Sample3Out.png
 1135 
 1136 Author:
 1137     Manish Sud(msud@san.rr.com)
 1138 
 1139 See also:
 1140     DownloadPDBFiles.pl, PyMOLCalculatePhiPsiAngles.py, PyMOLCalculateRMSD.py,
 1141     PyMOLCalculateProperties.py
 1142 
 1143 Copyright:
 1144     Copyright (C) 2026 Manish Sud. All rights reserved.
 1145 
 1146     The functionality available in this script is implemented using PyMOL, a
 1147     molecular visualization system on an open source foundation originally
 1148     developed by Warren DeLano.
 1149 
 1150     This file is part of MayaChemTools.
 1151 
 1152     MayaChemTools is free software; you can redistribute it and/or modify it under
 1153     the terms of the GNU Lesser General Public License as published by the Free
 1154     Software Foundation; either version 3 of the License, or (at your option) any
 1155     later version.
 1156 
 1157 """
 1158 
 1159 if __name__ == "__main__":
 1160     main()