rdkit.Chem.PandasTools

Package rdkit :: Package Chem :: Module PandasTools |
[hide private]
[frames] | no frames]

# Source Code for Module rdkit.Chem.PandasTools

      1  '''
      2  Importing pandasTools enables several features that allow for using RDKit molecules as columns of a Pandas dataframe.
      3  If the dataframe is containing a molecule format in a column (e.g. smiles), like in this example:
      4  >>> from rdkit.Chem import PandasTools
      5  >>> import pandas as pd
      6  >>> import os
      7  >>> from rdkit import RDConfig
      8  >>> antibiotics = pd.DataFrame(columns=['Name','Smiles'])
      9  >>> antibiotics = antibiotics.append({'Smiles':'CC1(C(N2C(S1)C(C2=O)NC(=O)CC3=CC=CC=C3)C(=O)O)C','Name':'Penicilline G'}, ignore_index=True)#Penicilline G
     10  >>> antibiotics = antibiotics.append({'Smiles':'CC1(C2CC3C(C(=O)C(=C(C3(C(=O)C2=C(C4=C1C=CC=C4O)O)O)O)C(=O)N)N(C)C)O','Name':'Tetracycline'}, ignore_index=True)#Tetracycline
     11  >>> antibiotics = antibiotics.append({'Smiles':'CC1(C(N2C(S1)C(C2=O)NC(=O)C(C3=CC=CC=C3)N)C(=O)O)C','Name':'Ampicilline'}, ignore_index=True)#Ampicilline
     12  >>> print([str(x) for x in  antibiotics.columns])
     13  ['Name', 'Smiles']
     14  >>> print(antibiotics)
     15              Name                                             Smiles
     16  0  Penicilline G    CC1(C(N2C(S1)C(C2=O)NC(=O)CC3=CC=CC=C3)C(=O)O)C
     17  1   Tetracycline  CC1(C2CC3C(C(=O)C(=C(C3(C(=O)C2=C(C4=C1C=CC=C4...
     18  2  Ampicilline  CC1(C(N2C(S1)C(C2=O)NC(=O)C(C3=CC=CC=C3)N)C(=O...
     19
     20  a new column can be created holding the respective RDKit molecule objects. The fingerprint can be included to accelerate substructure searches on the dataframe.
     21
     22  >>> PandasTools.AddMoleculeColumnToFrame(antibiotics,'Smiles','Molecule',includeFingerprints=True)
     23  >>> print([str(x) for x in  antibiotics.columns])
     24  ['Name', 'Smiles', 'Molecule']
     25
     26  A substructure filter can be applied on the dataframe using the RDKit molecule column, because the ">=" operator has been modified to work as a substructure check.
     27  Such the antibiotics containing the beta-lactam ring "C1C(=O)NC1" can be obtained by
     28
     29  >>> beta_lactam = Chem.MolFromSmiles('C1C(=O)NC1')
     30  >>> beta_lactam_antibiotics = antibiotics[antibiotics['Molecule'] >= beta_lactam]
     31  >>> print(beta_lactam_antibiotics[['Name','Smiles']])
     32              Name                                             Smiles
     33  0  Penicilline G    CC1(C(N2C(S1)C(C2=O)NC(=O)CC3=CC=CC=C3)C(=O)O)C
     34  2  Ampicilline  CC1(C(N2C(S1)C(C2=O)NC(=O)C(C3=CC=CC=C3)N)C(=O...
     35
     36
     37  It is also possible to load an SDF file can be load into a dataframe.
     38
     39  >>> sdfFile = os.path.join(RDConfig.RDDataDir,'NCI/first_200.props.sdf')
     40  >>> frame = PandasTools.LoadSDF(sdfFile,smilesName='SMILES',molColName='Molecule',includeFingerprints=True)
     41  >>> frame.info # doctest: +SKIP
     42  <bound method DataFrame.info of <class 'pandas.core.frame.DataFrame'>
     43  Int64Index: 200 entries, 0 to 199
     44  Data columns:
     45  AMW                       200  non-null values
     46  CLOGP                     200  non-null values
     47  CP                        200  non-null values
     48  CR                        200  non-null values
     49  DAYLIGHT.FPG              200  non-null values
     50  DAYLIGHT_CLOGP            200  non-null values
     51  FP                        200  non-null values
     52  ID                        200  non-null values
     53  ISM                       200  non-null values
     54  LIPINSKI_VIOLATIONS       200  non-null values
     55  NUM_HACCEPTORS            200  non-null values
     56  NUM_HDONORS               200  non-null values
     57  NUM_HETEROATOMS           200  non-null values
     58  NUM_LIPINSKIHACCEPTORS    200  non-null values
     59  NUM_LIPINSKIHDONORS       200  non-null values
     60  NUM_RINGS                 200  non-null values
     61  NUM_ROTATABLEBONDS        200  non-null values
     62  P1                        30  non-null values
     63  SMILES                    200  non-null values
     64  Molecule                  200  non-null values
     65  dtypes: object(20)>
     66
     67  In order to support rendering the molecules as images in the HTML export of the dataframe, the __str__ method is monkey-patched to return a base64 encoded PNG:
     68  >>> molX = Chem.MolFromSmiles('Fc1cNc2ccccc12')
     69  >>> print(molX) # doctest: +SKIP
     70  <img src="data:image/png;base64,..." alt="Mol"/>
     71  This can be reverted using the ChangeMoleculeRendering method
     72  >>> ChangeMoleculeRendering(renderer='String')
     73  >>> print(molX) # doctest: +SKIP
     74  <rdkit.Chem.rdchem.Mol object at 0x10d179440>
     75  >>> ChangeMoleculeRendering(renderer='PNG')
     76  >>> print(molX) # doctest: +SKIP
     77  <img src="data:image/png;base64,..." alt="Mol"/>
     78  '''
     79  from __future__ import print_function
     80
     81  from base64 import b64encode
     82  import types,copy
     83
     84  from rdkit.six import BytesIO, string_types
     85  from rdkit import Chem
     86  from rdkit.Chem import Draw
     87
     88  try:
     89    import pandas as pd
     90    try:
     91      v = pd.__version__.split('.')
     92    except AttributeError:
     93      # support for older versions of pandas
     94      v = pd.version.version.split('.')
     95
     96
     97    if v[0]=='0' and int(v[1])<10:
     98      print("Pandas version %s not compatible with tests"%v, file=sys.stderr)
     99      pd = None
    100    else:
    101      if 'display.width' in  pd.core.config._registered_options:
    102        pd.set_option('display.width',1000000000)
    103      if 'display.max_rows' in  pd.core.config._registered_options:
    104        pd.set_option('display.max_rows',1000000000)
    105      elif 'display.height' in  pd.core.config._registered_options:
    106        pd.set_option('display.height',1000000000)
    107      if 'display.max_colwidth' in  pd.core.config._registered_options:
    108        pd.set_option('display.max_colwidth',1000000000)
    109      #saves the default pandas rendering to allow restauration
    110      defPandasRendering = pd.core.frame.DataFrame.to_html
    111  except ImportError:
    112    import traceback
    113    traceback.print_exc()
    114    pd = None
    115
    116  except Exception as e:
    117    import sys
    118    import traceback
    119    traceback.print_exc()
    120    pd = None
    121
    122  highlightSubstructures=True
    123  molRepresentation = 'png' # supports also SVG
    124  molSize = (200,200)
    125
    126
    127 -def patchPandasHTMLrepr(self,**kwargs):
    128    '''
    129    Patched default escaping of HTML control characters to allow molecule image rendering dataframes
    130    '''
    131    formatter = pd.core.format.DataFrameFormatter(self,buf=None,columns=None,col_space=None,colSpace=None,header=True,index=True,
    132                                                 na_rep='NaN',formatters=None,float_format=None,sparsify=None,index_names=True,
    133                                                 justify = None, force_unicode=None,bold_rows=True,classes=None,escape=False)
    134    formatter.to_html()
    135    html = formatter.buf.getvalue()
    136    return html
    137
    138 -def patchPandasHeadMethod(self,n=5):
    139    '''Ensure inheritance of patched to_html in "head" subframe
    140    '''
    141    df = self[:n]
    142    df.to_html = types.MethodType(patchPandasHTMLrepr,df)
    143    df.head = types.MethodType(patchPandasHeadMethod,df)
    144    return df
    145
    146 -def _get_image(x):
    147    """displayhook function for PIL Images, rendered as PNG"""
    148    import pandas as pd
    149    bio = BytesIO()
    150    x.save(bio,format='PNG')
    151    s = b64encode(bio.getvalue()).decode('ascii')
    152    pd.set_option('display.max_columns',len(s)+1000)
    153    pd.set_option('display.max_rows',len(s)+1000)
    154    if len(s)+100 > pd.get_option("display.max_colwidth"):
    155      pd.set_option("display.max_colwidth",len(s)+1000)
    156    return s
    157
    158 -def _get_svg_image(mol, size=(200,200), highlightAtoms=[]):
    159    """ mol rendered as SVG """
    160    from IPython.display import SVG
    161    from rdkit.Chem import rdDepictor
    162    from rdkit.Chem.Draw import rdMolDraw2D
    163    try:
    164      # If no coordinates, calculate 2D
    165      mol.GetConformer(-1)
    166    except ValueError:
    167      rdDepictor.Compute2DCoords(mol)
    168    drawer = rdMolDraw2D.MolDraw2DSVG(*size)
    169    drawer.DrawMolecule(mol,highlightAtoms=highlightAtoms)
    170    drawer.FinishDrawing()
    171    svg = drawer.GetDrawingText().replace('svg:','')
    172    return SVG(svg).data # IPython's SVG clears the svg text
    173
    174  from rdkit import DataStructs
    175
    176  try:
    177    from rdkit.Avalon import pyAvalonTools as pyAvalonTools
    178    _fingerprinter=lambda x,y:pyAvalonTools.GetAvalonFP(x,isQuery=y,bitFlags=pyAvalonTools.avalonSSSBits)
    179  except ImportError:
    180    _fingerprinter=lambda x,y:Chem.PatternFingerprint(x,fpSize=2048)
    181
    182 -def _molge(x,y):
    183    """Allows for substructure check using the >= operator (X has substructure Y -> X >= Y) by
    184    monkey-patching the __ge__ function
    185    This has the effect that the pandas/numpy rowfilter can be used for substructure filtering (filtered = dframe[dframe['RDKitColumn'] >= SubstructureMolecule])
    186    """
    187    if x is None or y is None: return False
    188    if hasattr(x,'_substructfp'):
    189      if not hasattr(y,'_substructfp'):
    190        y._substructfp=_fingerprinter(y,True)
    191      if not DataStructs.AllProbeBitsMatch(y._substructfp,x._substructfp):
    192        return False
    193    match = x.GetSubstructMatch(y)
    194    if match:
    195      if highlightSubstructures:
    196          x.__sssAtoms=list(match)
    197      else:
    198          x.__sssAtoms=[]
    199      return True
    200    else:
    201      return False
    202
    203
    204  Chem.Mol.__ge__ = _molge # lambda x,y: x.HasSubstructMatch(y)
    205
    206 -def PrintAsBase64PNGString(x,renderer = None):
    207    '''returns the molecules as base64 encoded PNG image
    208    '''
    209    if highlightSubstructures and hasattr(x,'__sssAtoms'):
    210        highlightAtoms=x.__sssAtoms
    211    else:
    212        highlightAtoms=[]
    213    if molRepresentation.lower() == 'svg':
    214      return _get_svg_image(x, highlightAtoms=highlightAtoms, size=molSize)
    215    else:
    216      return '<img src="data:image/png;base64,%s" alt="Mol"/>'%_get_image(Draw.MolToImage(x,highlightAtoms=highlightAtoms, size=molSize))
    217
    218
    219 -def PrintDefaultMolRep(x):
    220    return str(x.__repr__())
    221
    222  #Chem.Mol.__str__ = lambda x: '<img src="data:image/png;base64,%s" alt="Mol"/>'%get_image(Draw.MolToImage(x))
    223  Chem.Mol.__str__ = PrintAsBase64PNGString
    224
    225 -def _MolPlusFingerprint(m):
    226    '''Precomputes fingerprints and stores results in molecule objects to accelerate substructure matching
    227    '''
    228    #m = Chem.MolFromSmiles(smi)
    229    if m is not None:
    230      m._substructfp=_fingerprinter(m,False)
    231    return m
    232
    233 -def RenderImagesInAllDataFrames(images=True):
    234    '''Changes the default dataframe rendering to not escape HTML characters, thus allowing rendered images in all dataframes.
    235    IMPORTANT: THIS IS A GLOBAL CHANGE THAT WILL AFFECT TO COMPLETE PYTHON SESSION. If you want to change the rendering only
    236    for a single dataframe use the "ChangeMoleculeRendering" method instead.
    237    '''
    238    if images:
    239      pd.core.frame.DataFrame.to_html = patchPandasHTMLrepr
    240    else:
    241      pd.core.frame.DataFrame.to_html = defPandasRendering
    242
    243
    244 -def AddMoleculeColumnToFrame(frame, smilesCol='Smiles', molCol = 'ROMol',includeFingerprints=False):
    245    '''Converts the molecules contains in "smilesCol" to RDKit molecules and appends them to the dataframe "frame" using the specified column name.
    246    If desired, a fingerprint can be computed and stored with the molecule objects to accelerate substructure matching
    247    '''
    248    if not includeFingerprints:
    249      frame[molCol]=frame[smilesCol].map(Chem.MolFromSmiles)
    250    else:
    251      frame[molCol]=frame[smilesCol].map(lambda smiles: _MolPlusFingerprint(Chem.MolFromSmiles(smiles)))
    252    RenderImagesInAllDataFrames(images=True)
    253    #frame.to_html = types.MethodType(patchPandasHTMLrepr,frame)
    254    #frame.head = types.MethodType(patchPandasHeadMethod,frame)
    255
    256
    257 -def ChangeMoleculeRendering(frame=None, renderer='PNG'):
    258    '''Allows to change the rendering of the molecules between base64 PNG images and string representations.
    259    This serves two purposes: First it allows to avoid the generation of images if this is not desired and, secondly, it allows to enable image rendering for
    260    newly created dataframe that already contains molecules, without having to rerun the time-consuming AddMoleculeColumnToFrame. Note: this behaviour is, because some pandas methods, e.g. head()
    261    returns a new dataframe instance that uses the default pandas rendering (thus not drawing images for molecules) instead of the monkey-patched one.
    262    '''
    263    if renderer == 'String':
    264      Chem.Mol.__str__ = PrintDefaultMolRep
    265    else:
    266      Chem.Mol.__str__ = PrintAsBase64PNGString
    267    if frame is not None:
    268      frame.to_html = types.MethodType(patchPandasHTMLrepr,frame)
    269
    270 -def LoadSDF(filename, idName='ID',molColName = 'ROMol',includeFingerprints=False, isomericSmiles=False, smilesName=None, embedProps=False):
    271    """ Read file in SDF format and return as Pandas data frame. If embedProps=True all properties also get embedded in Mol objects in the molecule column. """
    272    df = None
    273    if isinstance(filename, string_types):
    274      if filename.lower()[-3:] == ".gz":
    275        import gzip
    276        f = gzip.open(filename, "rb")
    277      else:
    278        f = open(filename, 'rb')
    279      close = f.close
    280    else:
    281      f = filename
    282      close = None # don't close an open file that was passed in
    283    records = []
    284    indices = []
    285    for i, mol in enumerate(Chem.ForwardSDMolSupplier(f)):
    286      if mol is None: continue
    287      row = dict((k, mol.GetProp(k)) for k in mol.GetPropNames())
    288      if not embedProps:
    289        for prop in mol.GetPropNames():
    290          mol.ClearProp(prop)
    291      if mol.HasProp('_Name'): row[idName] = mol.GetProp('_Name')
    292      if smilesName is not None:
    293        row[smilesName] = Chem.MolToSmiles(mol, isomericSmiles=isomericSmiles)
    294      if not includeFingerprints:
    295          row[molColName] = mol
    296      else:
    297          row[molColName] = _MolPlusFingerprint(mol)
    298      records.append(row)
    299      indices.append(i)
    300
    301    if close is not None: close()
    302    RenderImagesInAllDataFrames(images=True)
    303    return pd.DataFrame(records, index=indices)
    304
    305  from rdkit.Chem import SDWriter
    306
    307 -def WriteSDF(df, out, molColName='ROMol', idName=None, properties=None, allNumeric=False):
    308    '''Write an SD file for the molecules in the dataframe. Dataframe columns can be exported as SDF tags if specified in the "properties" list. "properties=list(df.columns)" would export all columns.
    309    The "allNumeric" flag allows to automatically include all numeric columns in the output. User has to make sure that correct data type is assigned to column.
    310    "idName" can be used to select a column to serve as molecule title. It can be set to "RowID" to use the dataframe row key as title.
    311    '''
    312
    313    close = None
    314    if isinstance(out, string_types):
    315      if out.lower()[-3:] == ".gz":
    316        import gzip
    317        out = gzip.open(out, "wb")
    318        close = out.close
    319
    320    writer = SDWriter(out)
    321    if properties is None:
    322      properties=[]
    323    else:
    324      properties=list(properties)
    325    if allNumeric:
    326      properties.extend([dt for dt in df.dtypes.keys() if (np.issubdtype(df.dtypes[dt],float) or np.issubdtype(df.dtypes[dt],int))])
    327
    328    if molColName in properties:
    329      properties.remove(molColName)
    330    if idName in properties:
    331      properties.remove(idName)
    332    writer.SetProps(properties)
    333    for row in df.iterrows():
    334      # make a local copy I can modify
    335      mol = Chem.Mol(row[1][molColName])
    336
    337      if idName is not None:
    338        if idName == 'RowID':
    339          mol.SetProp('_Name',str(row[0]))
    340        else:
    341          mol.SetProp('_Name',str(row[1][idName]))
    342      for p in properties:
    343        cell_value = row[1][p]
    344        # Make sure float does not get formatted in E notation
    345        if np.issubdtype(type(cell_value),float):
    346          s = '{:f}'.format(cell_value).rstrip("0") # "f" will show 7.0 as 7.00000
    347          if s[-1] == ".":
    348            s += "0"  # put the "0" back on if it's something like "7."
    349          mol.SetProp(p, s)
    350        else:
    351          mol.SetProp(p,str(cell_value))
    352      writer.write(mol)
    353    writer.close()
    354    if close is not None: close()
    355
    356  _saltRemover = None
    357 -def RemoveSaltsFromFrame(frame, molCol = 'ROMol'):
    358    '''
    359    Removes salts from mols in pandas DataFrame's ROMol column
    360    '''
    361    global _saltRemover
    362    if _saltRemover is None:
    363        from rdkit.Chem import SaltRemover
    364        _saltRemover = SaltRemover.SaltRemover()
    365    frame[molCol] = frame.apply(lambda x: _saltRemover.StripMol(x[molCol]), axis = 1)
    366
    367 -def SaveSMILESFromFrame(frame, outFile, molCol='ROMol', NamesCol='', isomericSmiles=False):
    368    '''
    369    Saves smi file. SMILES are generated from column with RDKit molecules. Column with names is optional.
    370    '''
    371    w = Chem.SmilesWriter(outFile, isomericSmiles=isomericSmiles)
    372    if NamesCol != '':
    373      for m,n in zip(frame[molCol], map(str,frame[NamesCol])):
    374        m.SetProp('_Name',n)
    375        w.write(m)
    376      w.close()
    377    else:
    378      for m in frame[molCol]:
    379        w.write(m)
    380      w.close()
    381
    382  import numpy as np
    383  import os
    384  from rdkit.six.moves import cStringIO as StringIO
    385
    386 -def SaveXlsxFromFrame(frame, outFile, molCol='ROMol', size=(300,300)):
    387      """
    388      Saves pandas DataFrame as a xlsx file with embedded images.
    389      It maps numpy data types to excel cell types:
    390      int, float -> number
    391      datetime -> datetime
    392      object -> string (limited to 32k character - xlsx limitations)
    393
    394      Cells with compound images are a bit larger than images due to excel.
    395      Column width weirdness explained (from xlsxwriter docs):
    396      The width corresponds to the column width value that is specified in Excel.
    397      It is approximately equal to the length of a string in the default font of Calibri 11.
    398      Unfortunately, there is no way to specify "AutoFit" for a column in the Excel file format.
    399      This feature is only available at runtime from within Excel.
    400      """
    401
    402      import xlsxwriter # don't want to make this a RDKit dependency
    403
    404      cols = list(frame.columns)
    405      cols.remove(molCol)
    406      dataTypes = dict(frame.dtypes)
    407
    408      workbook = xlsxwriter.Workbook(outFile) # New workbook
    409      worksheet = workbook.add_worksheet() # New work sheet
    410      worksheet.set_column('A:A', size[0]/6.) # column width
    411
    412      # Write first row with column names
    413      c2 = 1
    414      for x in cols:
    415          worksheet.write_string(0, c2, x)
    416          c2 += 1
    417
    418      c = 1
    419      for index, row in frame.iterrows():
    420          image_data = StringIO()
    421          img = Draw.MolToImage(row[molCol], size=size)
    422          img.save(image_data, format='PNG')
    423
    424          worksheet.set_row(c, height=size[1]) # looks like height is not in px?
    425          worksheet.insert_image(c, 0, "f", {'image_data': image_data})
    426
    427          c2 = 1
    428          for x in cols:
    429              if str(dataTypes[x]) == "object":
    430                  worksheet.write_string(c, c2, str(row[x])[:32000]) # string length is limited in xlsx
    431              elif ('float' in str(dataTypes[x])) or ('int' in str(dataTypes[x])):
    432                  if (row[x] != np.nan) or (row[x] != np.inf):
    433                      worksheet.write_number(c, c2, row[x])
    434              elif 'datetime' in str(dataTypes[x]):
    435                  worksheet.write_datetime(c, c2, row[x])
    436              c2 += 1
    437          c += 1
    438
    439      workbook.close()
    440      image_data.close()
    441
    442
    443 -def FrameToGridImage(frame, column = 'ROMol', legendsCol=None, **kwargs):
    444    '''
    445    Draw grid image of mols in pandas DataFrame.
    446    '''
    447    if legendsCol:
    448      if legendsCol == frame.index.name:
    449        img = Draw.MolsToGridImage(frame[column], legends=list(map(str, list(frame.index))), **kwargs)
    450      else:
    451        img = Draw.MolsToGridImage(frame[column], legends=list(map(str, list(frame[legendsCol]))), **kwargs)
    452    else:
    453      img = Draw.MolsToGridImage(frame[column], **kwargs)
    454    return img
    455
    456  from rdkit.Chem.Scaffolds import MurckoScaffold
    457
    458 -def AddMurckoToFrame(frame, molCol = 'ROMol', MurckoCol = 'Murcko_SMILES', Generic = False):
    459    '''
    460    Adds column with SMILES of Murcko scaffolds to pandas DataFrame. Generic set to true results in SMILES of generic framework.
    461    '''
    462    if Generic:
    463      frame[MurckoCol] = frame.apply(lambda x: Chem.MolToSmiles(MurckoScaffold.MakeScaffoldGeneric(MurckoScaffold.GetScaffoldForMol(x[molCol]))), axis=1)
    464    else:
    465      frame[MurckoCol] = frame.apply(lambda x: Chem.MolToSmiles(MurckoScaffold.GetScaffoldForMol(x[molCol])), axis=1)
    466
    467
    468  from rdkit.Chem import AllChem
    469
    470 -def AlignMol(mol,scaffold):
    471    """
    472    Aligns mol (RDKit mol object) to scaffold (SMILES string)
    473    """
    474    scaffold = Chem.MolFromSmiles(scaffold)
    475    AllChem.Compute2DCoords(scaffold)
    476    AllChem.GenerateDepictionMatching2DStructure(mol,scaffold)
    477    return mol
    478
    479 -def AlignToScaffold(frame, molCol='ROMol', scaffoldCol='Murcko_SMILES'):
    480    '''
    481    Aligns molecules in molCol to scaffolds in scaffoldCol
    482    '''
    483    frame[molCol] = frame.apply(lambda x: AlignMol(x[molCol],x[scaffoldCol]), axis=1)
    484
    485
    486  if __name__ == "__main__":
    487    import sys
    488    if pd is None:
    489      print("pandas installation not found, skipping tests", file=sys.stderr)
    490    else:
    491      # version check
    492      try:
    493        v = pd.__version__.split('.')
    494      except AttributeError:
    495        # support for older versions of pandas
    496        v = pd.version.version.split('.')
    497
    498      if v[0]=='0' and int(v[1])<10:
    499        print("pandas installation >=0.10 not found, skipping tests",
    500              file=sys.stderr)
    501      else:
    502        import doctest
    503        failed,tried=doctest.testmod(optionflags=doctest.ELLIPSIS+doctest.NORMALIZE_WHITESPACE)
    504        if failed:
    505          sys.exit(failed)
    506
    507  # $Id$
    508  #
    509  #  Copyright (c) 2013, Novartis Institutes for BioMedical Research Inc.
    510  #  All rights reserved.
    511  #
    512  # Redistribution and use in source and binary forms, with or without
    513  # modification, are permitted provided that the following conditions are
    514  # met:
    515  #
    516  #     * Redistributions of source code must retain the above copyright
    517  #       notice, this list of conditions and the following disclaimer.
    518  #     * Redistributions in binary form must reproduce the above
    519  #       copyright notice, this list of conditions and the following
    520  #       disclaimer in the documentation and/or other materials provided
    521  #       with the distribution.
    522  #     * Neither the name of Novartis Institutes for BioMedical Research Inc.
    523  #       nor the names of its contributors may be used to endorse or promote
    524  #       products derived from this software without specific prior written permission.
    525  #
    526  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    527  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    528  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    529  # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    530  # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    531  # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    532  # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    533  # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    534  # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    535  # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    536  # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    537  #
    538