Source code for ihm_validation.precision

# -*- coding: utf-8 -*-
#
# precision.py - Run local precision assessment (PrISM)
#
# Copyright (C) 2025 Arthur Zalevsky <aozalevsky@gmail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""
Run local precision assessment (PrISM)
"""
from mmcif_io import GetInputInformation
import pandas as pd
import logging
from pathlib import Path
import pickle
import time
import utility
# import pymol
import sys

pd.options.mode.chained_assignment = None

p = Path(__file__).absolute().parent.parent.parent
sys.path.append(str(p))
p = Path(p, 'prism', 'src')
sys.path.append(str(p))
# Path to pymol
sys.path.append('/usr/lib/python3/dist-packages')

import prism.src.ihm_parser
import prism.src.main
from pymol import cmd

[docs] class PRISM(GetInputInformation): timeout = 120 data = {} def __init__(self, *args, timeout=120, **kwargs): super().__init__(*args, **kwargs) self.timeout = timeout
[docs] def get_data(self): cache_fn = Path(self.cache, f'{self.stem}.prism.pkl') data = {} # Check if we already requested the data if Path(cache_fn).is_file() and not self.nocache: logging.info(f'Found {self.stem} in cache: {cache_fn}') with open(cache_fn, 'rb') as f: data = pickle.load(f) elif not Path(cache_fn).is_file() or self.nocache: logging.info("PrISM analysis is being calculated...") data = self._get_data() with open(cache_fn, 'wb') as f: pickle.dump(data, f) self.data = data return data
def _get_data(self): data = {} for ens in self.system.ensembles: mg = ens.model_group if mg is None: logging.error('ModelGroup is missing for Ensemble {ens._id}. Skipping.') continue try: e_total = ens.num_models e_deposited = ens.num_models_deposited except (AttributeError, TypeError) as e: logging.error('Missing Ensemble {ens._id} attributes') logging.error(e) continue try: assert e_deposited == len(mg) except AssertionError as e: logging.warning('Number of deposited models and size of a model group are different for Ensemble {ens._id}. Using model group size') e_deposited = len(mg) # Only collect information for multimodel model groups if e_deposited < 2: continue coords, radius, mass, ps_names = prism.src.ihm_parser.get_all_attributes_ihm(mg) try: # PrISM excecution time is unpredictable with utility.timeout(self.timeout): df = prism.src.main.run_prism_ihm( coords, mass, radius, ps_names, classes=3) except TimeoutError as e: logging.error(f'PrISM calculation timed out for ensemble {ens._id}') continue except ValueError as e: logging.error(f'PrISM calculation failed out for ensemble {ens._id}') logging.error(e) continue data[ens._id] = { 'total': e_total, 'deposited': e_deposited, 'prism': df } self.data = data return data
[docs] def get_plots(self, imgDirname='.'): """Render PrISM images with PyMOL""" data = self.data plots = {} def init_colors(cmd): # Original PrISM colors # cmd.set_color('low_1', [1.00, 0.00, 0.00]) # cmd.set_color('low_2', [1.00, 0.50, 0.50]) # cmd.set_color('low_3', [1.00, 0.75, 0.75]) # cmd.set_color('mid_1', [1.00, 1.00, 1.00]) # cmd.set_color('high_3', [0.56, 0.93, 0.56]) # cmd.set_color('high_2', [0.20, 0.80, 0.20]) # cmd.set_color('high_1', [0.00, 0.39, 0.00]) # https://colorbrewer2.org/#type=diverging&scheme=RdBu&n=7 cmd.set_color('low_1', [0.70, 0.09, 0.17]) cmd.set_color('low_2', [0.94, 0.54, 0.38]) cmd.set_color('low_3', [0.99, 0.86, 0.78]) cmd.set_color('mid_1', [0.97, 0.97, 0.97]) cmd.set_color('high_3', [0.82, 0.89, 0.94]) cmd.set_color('high_2', [0.40, 0.66, 0.81]) cmd.set_color('high_1', [0.13, 0.40, 0.68]) for k, v in data.items(): cmd.reinitialize() init_colors(cmd) fn_stem = f'{self.ID_f}_prism_{k}' plots_ = [] for index, row in v['prism'].iterrows(): x = row['x'] y = row['y'] z = row['z'] r = row['r'] c = f'{row["Type"]}_{row["Class"]}' a = cmd.pseudoatom('', pos=[x, y, z], color=c, vdw=r) cmd.show('spheres') cmd.bg_color('white') cmd.set('opaque_background', 1) cmd.orient() cmd.zoom(buffer=10) cmd.clip('slab', 1000) cmd.set('ray_shadow', 0) cmd.rotate('z', 180) fn = f'{fn_stem}_p1.png' cmd.png(str(Path(imgDirname, fn)), ray=False) plots_.append(fn) cmd.rotate('x', 90) fn = f'{fn_stem}_p2.png' cmd.png(str(Path(imgDirname, fn)), ray=False) plots_.append(fn) cmd.rotate('y', 90) fn = f'{fn_stem}_p3.png' cmd.png(str(Path(imgDirname, fn)), ray=False) plots_.append(fn) fn = f'{fn_stem}.pse' cmd.save(Path(imgDirname, fn)) plots[k] = { 'total': v['total'], 'deposited': v['deposited'], 'plots': plots_} return plots
@property def pymol_version(self): return cmd.get_version()[0] @property def prism_version(self): '''This is a stub. Using hardoced commit id for now.''' return 'dbe5a41'