├── .gitignore ├── GUIs ├── correlated.ui ├── custom.scss ├── frequency.ui ├── img │ ├── biocompup.jpg │ ├── restart_alt_black_24dp.svg │ ├── ring_col.svg │ ├── ring_gray.svg │ └── unipd800.png └── plugin.ui ├── LICENCE ├── README.md ├── __init__.py ├── assets ├── hetero_dict.pkl └── hetero_dict_keys.pkl ├── correlation_window.py ├── doc_imgs ├── clusters.png ├── contact_map.png └── edges.png ├── environment-open-source.yml ├── environment-schrodinger.yml ├── frequency_window.py ├── main_window.py ├── ring_api.py ├── ring_local.py ├── rmsd_clustering.py ├── singularity.def └── utilities.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | .idea/ 132 | 133 | *.cif 134 | *.json 135 | 136 | *.sif 137 | -------------------------------------------------------------------------------- /GUIs/correlated.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 832 10 | 575 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | 21 | 730 22 | 400 23 | 24 | 25 | 26 | Pairwise interaction correlation analysis 27 | 28 | 29 | 30 | QLayout::SetDefaultConstraint 31 | 32 | 33 | 34 | 35 | 36 | 0 37 | 0 38 | 39 | 40 | 41 | Filter table (use space to set more filters at the same time) 42 | 43 | 44 | 45 | 46 | 47 | 48 | 0 49 | 0 50 | 51 | 52 | 53 | 54 | 0 55 | 30 56 | 57 | 58 | 59 | 60 | 16777215 61 | 30 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | Select the rows to see the interactions in Pymol 73 | 74 | 75 | 76 | 77 | 78 | 79 | 0 80 | 0 81 | 82 | 83 | 84 | QTableWidget::item:selected{ background-color: rgb(137, 137, 235); 85 | color: white; } 86 | 87 | 88 | 89 | QAbstractItemView::NoEditTriggers 90 | 91 | 92 | false 93 | 94 | 95 | QAbstractItemView::SelectRows 96 | 97 | 98 | false 99 | 100 | 101 | true 102 | 103 | 104 | false 105 | 106 | 107 | true 108 | 109 | 110 | false 111 | 112 | 113 | false 114 | 115 | 116 | false 117 | 118 | 119 | 120 | Edge_1 121 | 122 | 123 | 124 | 125 | Frequency 126 | 127 | 128 | 129 | 130 | Interaction 131 | 132 | 133 | 134 | 135 | Edge_2 136 | 137 | 138 | 139 | 140 | Frequency 141 | 142 | 143 | 144 | 145 | Corr_val 146 | 147 | 148 | 149 | 150 | P_val 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 0 160 | 0 161 | 162 | 163 | 164 | 165 | 0 166 | 167 | 168 | 0 169 | 170 | 171 | 0 172 | 173 | 174 | 0 175 | 176 | 177 | 0 178 | 179 | 180 | 181 | 182 | Qt::Horizontal 183 | 184 | 185 | 186 | 40 187 | 20 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 0 197 | 0 198 | 199 | 200 | 201 | 202 | 200 203 | 0 204 | 205 | 206 | 207 | Visualize selected 208 | 209 | 210 | false 211 | 212 | 213 | 214 | 215 | 216 | 217 | Qt::Horizontal 218 | 219 | 220 | 221 | 40 222 | 20 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | text_filter 237 | corrTable 238 | show_corr 239 | 240 | 241 | 242 | 243 | -------------------------------------------------------------------------------- /GUIs/custom.scss: -------------------------------------------------------------------------------- 1 | .QPushButton {{ 2 | color: white; 3 | text-transform: none; 4 | font-weight: 400; 5 | font-size: 13px; 6 | background-color: #2195fb; 7 | border: unset; 8 | min-height: 24px; 9 | }} 10 | 11 | .QTabWidget::pane {{ 12 | border: 0 solid white; 13 | margin-top: -13px; 14 | margin-bottom: -13px; 15 | }} 16 | 17 | .QPushButton:disabled {{ 18 | color: rgba(83, 98, 104, 0.38); 19 | text-transform: none; 20 | background-color: rgba(0, 0, 0, 0.12); 21 | border: unset; 22 | min-height: 24px; 23 | }} 24 | 25 | .QSpinBox {{ 26 | color: #272d31; 27 | min-height: 24px; 28 | }} 29 | 30 | .QDoubleSpinBox {{ 31 | color: #272d31; 32 | min-height: 24px; 33 | }} 34 | 35 | .QDoubleSpinBox:disabled {{ 36 | color: #9ebcce; 37 | min-height: 24px; 38 | }} 39 | 40 | .QComboBox {{ 41 | color: #272d31; 42 | min-height: 24px; 43 | }} 44 | 45 | .QLineEdit {{ 46 | color: #272d31; 47 | }} 48 | 49 | .QProgressBar {{ 50 | height: 8px; 51 | margin-bottom: 4px; 52 | border-radius: 6px; 53 | }} 54 | 55 | .QProgressBar::chunk {{ 56 | border-radius: 8px; 57 | }} 58 | 59 | .QGroupBox::title {{ 60 | color: #43474d; 61 | }} 62 | 63 | #frame_2 {{ 64 | border: 1px solid #797981; 65 | }} 66 | 67 | #rmsd_box, #cluster_box, #frame_3,#frame_4, #frame {{ 68 | border: unset; 69 | }} 70 | -------------------------------------------------------------------------------- /GUIs/frequency.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 849 10 | 564 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | Residue based analysis 21 | 22 | 23 | 24 | QLayout::SetDefaultConstraint 25 | 26 | 27 | 28 | 29 | Percentage of the frequency of interaction for each residue between all the states 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 0 38 | 0 39 | 40 | 41 | 42 | QTableWidget::item:selected{ background-color: rgb(137, 137, 43 | 235); color: white; } 44 | 45 | 46 | 47 | QAbstractScrollArea::AdjustIgnored 48 | 49 | 50 | QAbstractItemView::NoEditTriggers 51 | 52 | 53 | QAbstractItemView::SingleSelection 54 | 55 | 56 | QAbstractItemView::SelectRows 57 | 58 | 59 | false 60 | 61 | 62 | true 63 | 64 | 65 | false 66 | 67 | 68 | 69 | Residue 70 | 71 | 72 | 73 | 74 | Ionic 75 | 76 | 77 | 78 | 79 | Metal Ion 80 | 81 | 82 | 83 | 84 | SS bond 85 | 86 | 87 | 88 | 89 | π-π stack 90 | 91 | 92 | 93 | 94 | π cation 95 | 96 | 97 | 98 | 99 | π-Hydrogen 100 | 101 | 102 | 103 | 104 | H-bond 105 | 106 | 107 | 108 | 109 | Halogen 110 | 111 | 112 | 113 | 114 | Van der Waals 115 | 116 | 117 | 118 | 119 | IAC 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /GUIs/img/biocompup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BioComputingUP/ring-pymol/1ef23a7e93c8661ddbcd3db8fcc08f4858ba588d/GUIs/img/biocompup.jpg -------------------------------------------------------------------------------- /GUIs/img/restart_alt_black_24dp.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /GUIs/img/ring_col.svg: -------------------------------------------------------------------------------- 1 | 2 | 15 | 17 | 21 | 25 | 26 | 30 | 34 | 35 | 39 | 43 | 44 | 53 | 54 | 56 | 57 | 59 | image/svg+xml 60 | 62 | 63 | 64 | 65 | 66 | 70 | 74 | 78 | 82 | 86 | 90 | 94 | 95 | -------------------------------------------------------------------------------- /GUIs/img/ring_gray.svg: -------------------------------------------------------------------------------- 1 | 2 | 18 | 39 | 41 | 46 | 49 | 53 | 54 | 62 | 66 | 67 | 77 | 87 | 97 | 100 | 107 | 108 | 109 | 111 | 112 | 114 | image/svg+xml 115 | 117 | 118 | 119 | 120 | 121 | 126 | 131 | 136 | 139 | 142 | 144 | 148 | 152 | 156 | 160 | 164 | 168 | 172 | 176 | 180 | 184 | 188 | 192 | 196 | 200 | 204 | 208 | 212 | 216 | 220 | 224 | 228 | 232 | 236 | 240 | 244 | 248 | 249 | 250 | 251 | 253 | 258 | 261 | 263 | 267 | 271 | 272 | 276 | 280 | 281 | 285 | 290 | 291 | 292 | -------------------------------------------------------------------------------- /GUIs/img/unipd800.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BioComputingUP/ring-pymol/1ef23a7e93c8661ddbcd3db8fcc08f4858ba588d/GUIs/img/unipd800.png -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | RING-PyMOL, analysis tools for structural ensembles and 635 | molecular dynamic (MD) simulations. 636 | Copyright (C) 2022 Damiano Piovesan. 637 | 638 | This program is free software: you can redistribute it and/or modify 639 | it under the terms of the GNU General Public License as published by 640 | the Free Software Foundation, either version 3 of the License, or 641 | (at your option) any later version. 642 | 643 | This program is distributed in the hope that it will be useful, 644 | but WITHOUT ANY WARRANTY; without even the implied warranty of 645 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 646 | GNU General Public License for more details. 647 | 648 | You should have received a copy of the GNU General Public License 649 | along with this program. If not, see . 650 | 651 | Also add information on how to contact you by electronic and paper mail. 652 | 653 | If the program does terminal interaction, make it output a short 654 | notice like this when it starts in an interactive mode: 655 | 656 | RING-PyMOL Copyright (C) 2022 Damiano Piovesan. 657 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 658 | This is free software, and you are welcome to redistribute it 659 | under certain conditions; type `show c' for details. 660 | 661 | The hypothetical commands `show w' and `show c' should show the appropriate 662 | parts of the General Public License. Of course, your program's commands 663 | might be different; for a GUI interface, you would use an "about box". 664 | 665 | You should also get your employer (if you work as a programmer) or school, 666 | if any, to sign a "copyright disclaimer" for the program, if necessary. 667 | For more information on this, and how to apply and follow the GNU GPL, see 668 | . 669 | 670 | The GNU General Public License does not permit incorporating your program 671 | into proprietary programs. If your program is a subroutine library, you 672 | may consider it more useful to permit linking proprietary applications with 673 | the library. If this is what you want to do, use the GNU Lesser General 674 | Public License instead of this License. But first, please read 675 | . 676 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RING PyMOL plugin 2 | 3 | 4 | * [RING PyMOL plugin](#ring-pymol-plugin) 5 | * [Install](#install) 6 | * [Installation of PyMOL and python dependencies](#installation-of-pymol-and-python-dependencies) 7 | * [Installation of PyMOL with apt](#installation-of-pymol-with-apt) 8 | * [Installation of PyMOL with Conda from yml file (RECOMMENDED)](#installation-of-pymol-with-conda-from-yml-file--recommended-) 9 | * [Installation of PyMOL with Conda](#installation-of-pymol-with-conda) 10 | * [NOTE](#note) 11 | * [Install the RING plugin](#install-the-ring-plugin) 12 | * [Singularity container](#singularity-container) 13 | * [Usage Instructions](#usage-instructions) 14 | * [Configuration](#configuration) 15 | * [RING](#ring) 16 | * [Visualization](#visualization) 17 | * [Executing RING and visualize the edges](#executing-ring-and-visualize-the-edges) 18 | * [Filtering the results](#filtering-the-results) 19 | * [Nodes](#nodes) 20 | * [Pairwise interaction plot](#pairwise-interaction-plot) 21 | * [Nodes interaction table](#nodes-interaction-table) 22 | * [Color nodes by interaction frequency](#color-nodes-by-interaction-frequency) 23 | * [Edges](#edges) 24 | * [Interaction plots](#interaction-plots) 25 | * [Chain interactions](#chain-interactions) 26 | * [Secondary structure interactions](#secondary-structure-interactions) 27 | * [Probabilistic interchain residue contact map](#probabilistic-interchain-residue-contact-map) 28 | * [Pairwise interaction correlation analysis](#pairwise-interaction-correlation-analysis) 29 | * [Clustering](#clustering) 30 | * [Calculation of the hierarchical clustering](#calculation-of-the-hierarchical-clustering) 31 | * [Clustering visualizations](#clustering-visualizations) 32 | * [Create object](#create-object) 33 | * [Example](#example) 34 | 35 | 36 | ## Install 37 | 38 | ### Installation of PyMOL and python dependencies 39 | 40 | #### Installation of PyMOL with apt 41 | 42 | - `sudo apt install pymol python3-pip python3-tk` 43 | - `pip install pmw networkx numpy scipy seaborn pandas qt-material biopython requests tqdm` 44 | 45 | #### Installation of PyMOL with Conda from yml file (RECOMMENDED) 46 | 47 | - Install conda, following the instructions on their website 48 | - Download the environment.yml file from this repository 49 | - Create the environment with `conda env create -f environment-open-source.yml` 50 | - To install the schrodinger version of PyMOL use the `environment-schrodinger.yml` file 51 | - Activate the environment with `conda activate ring-pymol` 52 | 53 | #### Installation of PyMOL with Conda 54 | 55 | - Install conda, following the instructions on their website 56 | - Create a new environment and switch to it 57 | - `conda create -n myenv` 58 | - `conda activate myenv` 59 | - Install PyMOL in the new environment 60 | - `conda install -c conda-forge -c schrodinger pymol-bundle` (schrodinger version) 61 | - `conda install -c conda-forge pymol-open-source` (open-source version) 62 | - Install python dependencies for the plugin 63 | - `conda install networkx numpy scipy seaborn pandas requests biopython tqdm` 64 | - `pip install qt-material` (This will be installed in the conda environment) 65 | 66 | ### NOTE 67 | 68 | Please check that the PyMOL executable that you are running is the one for which you installed all the dependencies. 69 | E.g. 70 | `which pymol` should return something like `/opt/miniconda3/envs/myenv/bin/pymol` if installed with the recommended 71 | conda installation. 72 | 73 | ### Install the RING plugin 74 | 75 | - Open PyMOL and go to Plugin > Plugin Manager > Install New Plugin > Install from Repository > Add.. 76 | - Add https://biocomputingup.it/shared/ring-plugin/ 77 | - Click on ring-plugin.zip in the right panel and then Install 78 | - Set the installation directory 79 | - 80 | - The plugin should now appear on the Plugin menu of PyMOL 81 | 82 | ### Singularity container 83 | 84 | Another option for installing the plugin is to use the singularity container definition file provided in this 85 | repository. 86 | To create the image file you can follow these steps: 87 | 88 | - `sudo singularity build -F ring-pymol-plugin.sif singularity.def` (this will create the image file) 89 | - `singularity shell --cleanenv --writable-tmpfs -B ~/.Xauthority ring-pymol-plugin.sif` (this will open a shell in the 90 | container). 91 | Note that the -B option is needed to allow the container to access the X server of the host machine for displaying the 92 | GUI. 93 | - Start PyMOL with `pymol` 94 | - Add a new directory where to find new plugins 95 | - Plugin > Plugin Manager > Settings > Add new directory... 96 | - Add `/opt` 97 | - Restart PyMOL 98 | - The plugin should now appear on the Plugin menu of PyMOL 99 | 100 | # Usage Instructions 101 | 102 | ## Configuration 103 | 104 | ### RING 105 | 106 | In the configuration tab you can configure the settings for the RING software 107 | 108 | - If you want to execute RING locally or on a remote server using the RING WS APIs (https://ring.biocomputingup.it). 109 | - The location of the RING executable, if it is placed on the default location it will be picked up automatically 110 | - The options for the edge filtering 111 | - Multi: Computes multiple edges for a pair of nodes, filtering out connections triangular connections like the one 112 | showed in the image below. It retains the connection with the lower distance computed between the interacting 113 | atoms. 114 | 115 | - One:Return only the most valuable connection between two residues. 116 | 117 | - All: Return all the connections found. 118 | - Sequence separation, the minimum distance that is required between two residues in the sequence to compute the 119 | interactions between them. 120 | - Distance thresholds, the maximum distance that can exist between two atoms (or residues) to have a specific 121 | interaction 122 | - Re-execute RING every time: use this checkbox if you want to override the save function for the RING results of the 123 | plugin and re-execute the software each time instead of filtering the stored result. Use this if you modified the 124 | object or if you loaded a different version of the same object. 125 | 126 | ### Visualization 127 | 128 | This tab is reserved for settings of the CGO (the edges) and we can find settings regarding 129 | 130 | - The width of the edge 131 | - The transparency of the edge 132 | - The color of the edge associated to each type of interaction calculated by RING 133 | 134 | ## Executing RING and visualize the edges 135 | 136 | To run RING on a structure you can fetch a PDB structure from the PDB repository with the command `fetch PDB_ID`. Then 137 | open the plugin and at the top check that the object you fetched is selected in the drop-down menu. 138 | 139 | To run RING on that object press the `Execute RING` button. The RING software will be executed on the selected object. 140 | You can see the execution progress on the progress bar that appears when the object is exported and loaded into RING. 141 | 142 | The drawing of the computed edges is automatic and they will be filtered in compliance with the selected filters. 143 | 144 | The edges are loaded as Compiled Graphic Objects (CGO) in PyMOL grouped by interaction type `obj_interactionType` and 145 | finally all the interactions for that objects are placed in a group called `obj_edges`. 146 | 147 | The shown interactions are shown state-by-state, meaning that the interactions are shown for a specific state. If you 148 | navigate to different states the interactions will change showing the interactions for that particular state. 149 | 150 | In the same way a selection for the nodes involved in a interaction is created, and all the selected nodes are placed in 151 | a group called `obj_nodes`. 152 | 153 | ### Filtering the results 154 | 155 | Use the switches and controls in the top bar to filter the edges computed by the RING software. If the analyzed object 156 | has multiple states then you can filter the interactions by their frequency, setting a minimum and maximum frequency. 157 | Interactions that have a frequency that is not in that range will be filtered out from the visualization. 158 | 159 | To visualize the results click on the `Show` button if RING was already launched, or `Execute RING` on the object. 160 | 161 | ## Nodes 162 | 163 | ### Pairwise interaction plot 164 | 165 | By creating a selection of exactly two residues, and selecting the selection in the top bar, it is possible to produce a 166 | plot showing the interaction occurring during an MD simulation (or in a multi-state structure) between the two residues. 167 | In this plot different series represent different types of interaction occurring. In the $x$ axis the number of states 168 | is represented, while in the $y$ axis there is the distance of interaction. 169 | 170 | ### Nodes interaction table 171 | 172 | Clicking on this button will open a new window containing a table where all the residues of the selected structure that 173 | are interacting are listed, with their frequency of interaction for each type of possible interaction. This is useful 174 | to see for example if a residue is involved in a certain type of interaction, and the frequency of this interaction 175 | (when the structure has multiple states). 176 | 177 | The rows of the table can be selected in order to create a new selection (`sele_rows`) in PyMOL with the selected 178 | residues. 179 | The selection can be expanded by clicking on multiple rows. To reset the selection one can delete it 180 | from PyMOL and then restart to select new rows. 181 | 182 | ### Color nodes by interaction frequency 183 | 184 | This option lets you color the residues of the selected object based on the frequency of interaction of a certain type. 185 | The coloring is done using a heatmap, where the residues with the highest frequency are colored in blue, while the 186 | residues with the lowest frequency are colored in white. 187 | The frequency is computed on the number of states that a residue present the selected type of interaction over the 188 | total number of states. 189 | 190 | Select the interaction of interest and then click on `Color nodes` to color the nodes in the structure. 191 | 192 | ## Edges 193 | 194 | ### Interaction plots 195 | 196 | The following two graphs are intended for giving a quick overview of the interactions occurring in the structure. 197 | 198 | #### Chain interactions 199 | 200 | This graph shows the interactions occurring between the different chains of the structure. The nodes of the graph are 201 | the chains, while the edges are the interactions between the chains. 202 | 203 | #### Secondary structure interactions 204 | 205 | This graph shows the interactions occurring between the different secondary structure elements of the protein. 206 | The nodes of the graph are the secondary structure elements, identified with $\alpha$ helices and $\beta$ strands. 207 | The numbering of these elements is given starting from the N-terminus of the protein. 208 | The edges are the interactions between the secondary structure elements. 209 | 210 | ### Probabilistic interchain residue contact map 211 | 212 | This heatmap represents the probability of interaction between residues of different chains. The probability is computed 213 | as the number of states in which the two residues are interacting over the total number of states. 214 | The user can select different types of interactions using the drop-down menu. Showing `all` interactions will also show 215 | intrachain interactions probabilities. Moreover, with `all` interactions, the probability of interaction between two 216 | residues is computed as the number of any type of interaction between the two residues over the total number of states. 217 | The heatmap can be zoomed and panned using the controls in the top bar, this can be useful to concentrate the plot in a 218 | specific region (e.g. chain A interactions with chain B). 219 | 220 | ### Pairwise interaction correlation analysis 221 | 222 | Correlation analysis can be performed by the plugin on the results of RING, to study correlations between contacts found 223 | in a multi-state structure. Indeed, the correlation that the plugin computes is a correlation of the contacts over the 224 | time variable. It is interesting to see if some contacts are in correlation over time because this can be a clear signal 225 | of an allostery mechanism that is present in the protein of study, especially if the two contacts are located in 226 | different regions in the protein. 227 | 228 | With correlation one can see if two contacts are present in the structure at the same time for a repeated number of 229 | times, or, if they anti-correlate, they are present most of the times alternatively. The plugin computes the correlation 230 | matrix given the contact maps produced by RING, and produce a table that summarize the results. 231 | 232 | For this analysis different settings for the creation and filter of the correlation matrix are available. One important 233 | thing is to limit the number of spurious contacts by setting a minimum frequency of contact, and at the same time 234 | removing the ones that are constantly present in the structure. This limits the number of results that would have small 235 | significance. Once the correlation matrix is produced more filters can be applied to it, removing points where the 236 | correlation coefficient is not high or low enough, or filtering on the p-value of the correlation. For further reading 237 | on how the coefficient is computed and what the p-value is please refer 238 | to https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.pearsonr.html 239 | 240 | The results of the calculation is a table composed by various column, from left to right we find: 241 | 242 | - The first edge composed by the two residue in contact 243 | - The frequency of the first edge in the multi-state structure, for the indicated type 244 | - The type of interaction of the first **and** second edge, when the interaction type is *ALL* this means that all the 245 | interactions between the two edges were taken in consideration to compute the correlation value and p-value. 246 | - The second edge composed by the two residue in contact 247 | - The frequency of the second edge in the multi-state structure, for the indicated type 248 | - The correlation value of the two edges 249 | - The p-value of the two edges 250 | 251 | Rows of the table can be filtered on all the columns, multiple additive filters can be set using the space as separator. 252 | So if one wants to filter on rows that contains as part of edge_1 or edge_2 the chain A and have a correlation value of 253 | 0.8 for HBOND interactions can write: `A/ 0.8 HBOND` 254 | 255 | Rows can also be selected (with multi-selection active, with `↑Shift` or `ctrl` ), and by pressing on 256 | the `Visualize selected` button the two selected edges (if only one row selected) will be created in the PyMOL 257 | interface, and two selections will be created containing the residues of edge_1 and edge_2. This has been done to 258 | highlight the correlating interactions, that can be further analyzed with other features of the plugin, such as the 259 | residue pair interaction plot on the two edges to confirm that the correlation or anti-correlation is present. 260 | 261 | ## Clustering 262 | 263 | When dealing with large multi-state structures like molecular dynamics simulations it can be helpful to reduce the 264 | number of states in the structure to better analyze some feature of the simulation. Moreover, with clustering one can 265 | see if there is some kind of pattern in the simulation, and some interesting to study conformational states are emerging 266 | during the simulation. 267 | 268 | ### Calculation of the hierarchical clustering 269 | 270 | The plugin provides a simple way to perform clustering analysis on the structures loaded into PyMOL. The clustering that 271 | the plugin provides is a hierarchical clustering based on RMSD distances calculated between all the states ( $n\times 272 | n$ ). By default the clustering is done only on the C $\alpha$ atoms, so it can be faster to compute, but if it is 273 | necessary one can change it taking in consideration all the atoms in the structure. 274 | 275 | The clustering method to compute the linkage matrix can be changed, and it can be one of the ones described 276 | here: https://docs.scipy.org/doc/scipy/reference/generated/scipy.cluster.hierarchy.linkage.html 277 | 278 | The clustering will be computed on demand, when the user wants to get one of the two plots to visualize the clustering 279 | or wants to generate a new object with the representative states of the clustering. Once the hierarchical clustering is 280 | computed, the hierarchical tree can be cut in two ways, by RMSD value or by number of desired clusters. Different cuts 281 | produce different results, that can be confronted with the two proposed visualizations. 282 | 283 | ### Clustering visualizations 284 | 285 | Two different plots can be produced with the results of the clustering, giving the user an idea of the distribution of 286 | the clusters, their densities and separations. 287 | 288 | The first plot is the Hierarchical Clustering Plot, which shows the hierarchical tree of the computed clustering, 289 | relative to the cut that the user selected. In the **x** axis the labels are composed by **cluster representative -(# 290 | states in cluster)**, where the cluster representative is the state that has the lower sum of distances between all the 291 | other states in the same cluster. 292 | 293 | The second plot represent each state individually as a cell, and a color is assigned to it based on the membership 294 | cluster. This can show if there are stable conformation during the simulation, and if some conformation is repeated 295 | during time. Moreover, each cluster is represented by a color, and to that color the representative state of that 296 | cluster is associated. The user can interact with plot by positioning the mouse over a cell, and a label will appear, 297 | showing the state number of the selected cell and the relative cluster representative. 298 | 299 | Once the appropriate number of cluster is selected (via RMSD or explicitly), the user can press the "Create object" 300 | button. This will extract the representative cluster states and stitch them together in a new PyMOL object. This new 301 | object can be used for further analysis with other features of the plugin. 302 | 303 | ### Create object 304 | 305 | Clicking on this button will apply the previously selected clustering and will create a new object with only the 306 | clustering representative states of the original object. 307 | 308 | ## Example 309 | 310 | The plugin can be tested with the following example: 311 | 312 | 1. Download the multi-state structure 2H9R from PDBe-KB 313 | - Type `fetch 2h9r` in the PyMOL command line 314 | 2. Load the plugin in PyMOL 315 | - `Plugin` $\rightarrow$ `Ring plugin` or type in the command line `ring_plugin` 316 | 3. Now the plugin should be loaded, and in its top bar there should be already selected the PyMOL object 2h9r, the 317 | structure that you previously loaded. 318 | 4. Now you can execute RING with the button `Execute RING` 319 | - This will run the RING executable (if present) on the selected object, or it will try to execute RING on the 320 | selected structure on a web server using the APIs. 321 | 5. Once the results are ready they will be parsed and visualized on the structure in the PyMOL interface. 322 | 323 |

324 | 325 |

326 | 327 | 6. Now the edges can be filtered based on the type of interaction, frequency, and all the various tools can be used. 328 | E.g. we can see the `probabilistic interchain residue contact map` of the $\pi-\pi$ stack interactions: 329 | 330 |

331 | 332 |

333 | 334 | 7. Finally, we can also try to cluster a multi-state object, with a RMSD-based clustering. We can for example set a 335 | threshold criterion on the RMSD value for cutting the hierarchical clustering, yielding a certain number of clusters. 336 | Otherwise, one can select the exact number of clusters, and the RMSD value for the cut will be calculated 337 | automatically. One example is the following, were we set a RMSD cut value of 3.5 $\AA$, yielding 5 different 338 | clusters: 339 | 340 |

341 | 342 |

343 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | # Avoid importing "expensive" modules here (e.g. scipy), since this code is 2 | # executed on PyMOL's startup. Only import such modules inside functions. 3 | import os 4 | import sys 5 | from pathlib import Path 6 | 7 | from pymol import cmd 8 | 9 | BASE_DIR = Path(__file__).resolve().parent 10 | 11 | 12 | def __init_plugin__(app=None): 13 | """ 14 | Add an entry to the PyMOL "Plugin" menu 15 | """ 16 | from pymol.plugins import addmenuitemqt 17 | from pathlib import Path 18 | 19 | sys.path.append(str(Path(__file__).absolute().parent)) 20 | 21 | addmenuitemqt('Ring plugin', ring_plugin) 22 | 23 | 24 | # global reference to avoid garbage collection of our dialog 25 | dialog = None 26 | 27 | 28 | def ring_plugin(test=False): 29 | from pymol import Qt 30 | from qt_material import apply_stylesheet 31 | 32 | from main_window import MainDialog 33 | 34 | global dialog 35 | 36 | # don't let exceptions stop PyMOL 37 | import traceback 38 | sys.excepthook = traceback.print_exception 39 | 40 | app = Qt.QtWidgets.QApplication([]) 41 | 42 | if "Fusion" in Qt.QtWidgets.QStyleFactory.keys(): 43 | app.setStyle('Fusion') 44 | 45 | extra = { 46 | # Density Scale 47 | 'density_scale': '-1', 48 | 49 | 'font_family': 'Roboto', 50 | 51 | 'warning': '#ff821c', 52 | 53 | # environ 54 | 'pyside6': False, 55 | 'linux': True, 56 | } 57 | 58 | dialog = MainDialog(app=app) 59 | 60 | apply_stylesheet(dialog, theme='light_blue.xml', extra=extra, invert_secondary=False) 61 | 62 | stylesheet = dialog.styleSheet() 63 | with open(os.path.join(BASE_DIR, "GUIs", "custom.scss")) as file: 64 | dialog.setStyleSheet(stylesheet + file.read().format(**os.environ)) 65 | 66 | dialog.show() 67 | if test: 68 | sys.exit(app.exec_()) 69 | 70 | 71 | if __name__ == '__main__': 72 | import pymol 73 | 74 | pymol.finish_launching() 75 | cmd.set("defer_builds_mode", 3) 76 | cmd.load("/home/alessio/projects/ring-victor/assets/samples/test.cif") 77 | cmd.dss() 78 | # cmd.show_as("cartoon") 79 | cmd.util.cbc() 80 | cmd.do("ring_plugin") 81 | -------------------------------------------------------------------------------- /assets/hetero_dict.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BioComputingUP/ring-pymol/1ef23a7e93c8661ddbcd3db8fcc08f4858ba588d/assets/hetero_dict.pkl -------------------------------------------------------------------------------- /assets/hetero_dict_keys.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BioComputingUP/ring-pymol/1ef23a7e93c8661ddbcd3db8fcc08f4858ba588d/assets/hetero_dict_keys.pkl -------------------------------------------------------------------------------- /correlation_window.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | 3 | from PyQt5 import QtCore, QtWidgets 4 | from PyQt5.uic import loadUi 5 | 6 | from utilities import * 7 | 8 | 9 | class CorrelationDialog(QtWidgets.QDialog): 10 | def __init__(self, main_dialog, parent=None): 11 | super(CorrelationDialog, self).__init__(parent) 12 | 13 | self.setWindowFlags(self.windowFlags() & QtCore.Qt.WindowMinimizeButtonHint) 14 | 15 | # populate the Window from our *.ui file which was created with the Qt Designer 16 | uifile = path.join(path.dirname(__file__), 'GUIs/correlated.ui') 17 | loadUi(uifile, self) 18 | self.parent = main_dialog 19 | 20 | self.current_obj = None 21 | 22 | self.show_corr.clicked.connect(self.show_corr_fn) 23 | self.text_filter.textChanged.connect(self.filter_table) 24 | 25 | def create_table(self, obj): 26 | try: 27 | import numpy as np 28 | import pandas as pd 29 | except ImportError: 30 | self.parent.log("Please install numpy and pandas to use this plugin") 31 | return 32 | 33 | df = pd.DataFrame() 34 | 35 | freq_inter = get_freq(obj, self.parent.temp_dir.name) 36 | freq_combined = get_freq_combined_all_interactions(obj, self.parent.temp_dir.name) 37 | 38 | self.current_obj = obj 39 | 40 | tableWidget = self.corrTable 41 | for inter in list(intTypeMap.keys()) + ["ALL"]: 42 | edges, corr_matr, p_matr = self.parent.correlations[obj][inter] 43 | with np.errstate(divide='ignore', invalid='ignore'): 44 | indexes = np.argwhere(~np.isnan(corr_matr)) 45 | 46 | edge1s = [edges[x] for x in [y[0] for y in indexes]] 47 | freqs1 = [freq_inter[inter][edge] if inter != 'ALL' else freq_combined[edge] for edge in edge1s] 48 | inter_labels = [inter for _ in edge1s] 49 | edge2s = [edges[x] for x in [y[1] for y in indexes]] 50 | freqs2 = [freq_inter[inter][edge] if inter != 'ALL' else freq_combined[edge] for edge in edge2s] 51 | corr_vals = [corr_matr[i, j] for (i, j) in indexes] 52 | p_vals = [p_matr[i, j] for (i, j) in indexes] 53 | 54 | tableWidget.setRowCount(0) 55 | tableWidget.setSortingEnabled(False) 56 | 57 | rowPosition = tableWidget.rowCount() # necessary even when there are no rows in the table 58 | df_of_interaction = pd.DataFrame( 59 | [edge1s, freqs1, inter_labels, edge2s, freqs2, corr_vals, p_vals]).transpose() 60 | df = pd.concat([df, df_of_interaction]) 61 | 62 | self.parent.log("{} {} interactions correlates/anti-correlates".format(len(df_of_interaction), inter)) 63 | 64 | df = df.sort_values([0, 3, 5], ascending=(True, True, False)) 65 | 66 | prev_edge = None 67 | color = 2 68 | tableWidget.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch) 69 | for _, row in df.iterrows(): 70 | x, f1, p, y, f2, z, w, *_ = row.to_list() 71 | if x != prev_edge: 72 | bg_color, fg_color, color = get_bg_fg_colors(color) 73 | 74 | tableWidget.insertRow(rowPosition) 75 | 76 | tableWidget.setItem(rowPosition, 0, QtWidgets.QTableWidgetItem(str(x))) 77 | 78 | f1Item = QtWidgets.QTableWidgetItem() 79 | f1Item.setData(QtCore.Qt.EditRole, round(float(f1), 3)) 80 | tableWidget.setItem(rowPosition, 1, f1Item) 81 | 82 | tableWidget.setItem(rowPosition, 2, QtWidgets.QTableWidgetItem(p)) 83 | tableWidget.setItem(rowPosition, 3, QtWidgets.QTableWidgetItem(str(y))) 84 | 85 | f2Item = QtWidgets.QTableWidgetItem() 86 | f2Item.setData(QtCore.Qt.EditRole, round(float(f2), 3)) 87 | tableWidget.setItem(rowPosition, 4, f2Item) 88 | 89 | zItem = QtWidgets.QTableWidgetItem() 90 | zItem.setData(QtCore.Qt.EditRole, round(float(z), 2)) 91 | tableWidget.setItem(rowPosition, 5, zItem) 92 | 93 | wItem = QtWidgets.QTableWidgetItem() 94 | wItem.setData(QtCore.Qt.EditRole, float(w)) 95 | tableWidget.setItem(rowPosition, 6, wItem) 96 | for i in range(7): 97 | tableWidget.item(rowPosition, i).setBackground(bg_color) 98 | tableWidget.item(rowPosition, i).setForeground(fg_color) 99 | tableWidget.item(rowPosition, i).setTextAlignment(QtCore.Qt.AlignCenter) 100 | 101 | if z > 0: 102 | tableWidget.item(rowPosition, 5).setForeground(QColor(0, 0, 255)) 103 | else: 104 | tableWidget.item(rowPosition, 5).setForeground(QColor(255, 0, 0)) 105 | 106 | prev_edge = x 107 | rowPosition += 1 108 | tableWidget.setSortingEnabled(True) 109 | tableWidget.viewport().update() 110 | self.show() 111 | 112 | def filter_table(self): 113 | filters = self.text_filter.toPlainText().strip().split(' ') 114 | tableWidget = self.corrTable 115 | 116 | if len(filters) > 0: 117 | to_filter_total = set() 118 | 119 | for f in filters: 120 | to_filter = set() 121 | items = tableWidget.findItems(f, QtCore.Qt.MatchContains) 122 | for item in items: 123 | to_filter.add(item.row()) 124 | if len(to_filter_total) == 0: 125 | to_filter_total = to_filter 126 | else: 127 | to_filter_total.intersection_update(to_filter) 128 | 129 | for row in range(tableWidget.rowCount()): 130 | tableWidget.setRowHidden(row, row not in to_filter_total) 131 | 132 | def show_corr_fn(self): 133 | self.parent.disable_window() 134 | 135 | cmd.delete("edge1 edge2 edge1_cgo edge2_cgo") 136 | 137 | table = self.corrTable 138 | selection = table.selectionModel().selectedRows() 139 | 140 | if len(selection) == 0: 141 | self.parent.log("Please select at least one row first!", error=True) 142 | return 143 | 144 | for i in range(len(selection)): 145 | row = selection[i].row() 146 | edge1 = table.item(row, 0).text() 147 | edge2 = table.item(row, 3).text() 148 | 149 | edge1 = Edge([Node(node) for node in edge1.split(' - ')]) 150 | edge2 = Edge([Node(node) for node in edge2.split(' - ')]) 151 | 152 | print(edge1, edge2) 153 | 154 | cmd.select("edge1", "/{}//{}/{} or /{}//{}/{}".format(self.current_obj, edge1.node1.chain, edge1.node1.resi, 155 | self.current_obj, edge1.node2.chain, 156 | edge1.node2.resi), merge=1) 157 | 158 | cmd.select("edge2", "/{}//{}/{} or /{}//{}/{}".format(self.current_obj, edge2.node1.chain, edge2.node1.resi, 159 | self.current_obj, edge2.node2.chain, 160 | edge2.node2.resi), merge=1) 161 | 162 | inter = 'ALL' if len(selection) > 1 else table.item(selection[0].row(), 2).text() 163 | 164 | self.parent.visualize(selection="edge1", of_type=inter) 165 | self.parent.visualize(selection="edge2", of_type=inter) 166 | 167 | self.parent.log("Selection edge1 contains all the residues from the edges selected in the first column", 168 | timed=False) 169 | self.parent.log("Selection edge2 contains all the residues from the edges selected in the second column", 170 | timed=False) 171 | self.parent.log( 172 | "CGO objects edge1_cgo and edge2_cgo are the selected edges from the first and second column respectively", 173 | timed=False) 174 | 175 | self.parent.enable_window() 176 | -------------------------------------------------------------------------------- /doc_imgs/clusters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BioComputingUP/ring-pymol/1ef23a7e93c8661ddbcd3db8fcc08f4858ba588d/doc_imgs/clusters.png -------------------------------------------------------------------------------- /doc_imgs/contact_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BioComputingUP/ring-pymol/1ef23a7e93c8661ddbcd3db8fcc08f4858ba588d/doc_imgs/contact_map.png -------------------------------------------------------------------------------- /doc_imgs/edges.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BioComputingUP/ring-pymol/1ef23a7e93c8661ddbcd3db8fcc08f4858ba588d/doc_imgs/edges.png -------------------------------------------------------------------------------- /environment-open-source.yml: -------------------------------------------------------------------------------- 1 | name: biotest 2 | channels: 3 | - conda-forge 4 | - bioconda 5 | - defaults 6 | dependencies: 7 | - pymol-open-source 8 | - pandas 9 | - seaborn 10 | - requests 11 | - networkx==3.3 12 | - pip 13 | - scipy 14 | - biopython 15 | - numpy 16 | - tqdm 17 | - pip: 18 | - qt-material 19 | prefix: /opt/miniconda3/envs/bio24 20 | -------------------------------------------------------------------------------- /environment-schrodinger.yml: -------------------------------------------------------------------------------- 1 | name: ring-pymol 2 | channels: 3 | - conda-forge 4 | - bioconda 5 | - defaults 6 | - schrodinger 7 | dependencies: 8 | - pymol-bundle 9 | - pandas 10 | - seaborn 11 | - requests 12 | - networkx==3.3 13 | - pip 14 | - scipy 15 | - biopython 16 | - numpy 17 | - tqdm 18 | - pip: 19 | - qt-material 20 | prefix: /opt/miniconda3/envs/bio24 21 | -------------------------------------------------------------------------------- /frequency_window.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | 3 | import PyQt5.QtGui as QtGui 4 | import PyQt5.QtWidgets as QtWidgets 5 | from PyQt5 import QtCore 6 | from PyQt5.uic import loadUi 7 | from pymol import cmd 8 | 9 | from utilities import get_bg_fg_colors, get_freq_combined, intTypeMap 10 | 11 | 12 | class FreqDialog(QtWidgets.QWidget): 13 | def __init__(self, main_dialog, parent=None): 14 | super(FreqDialog, self).__init__(parent) 15 | 16 | self.setWindowFlags(QtCore.Qt.WindowCloseButtonHint | QtCore.Qt.WindowMinimizeButtonHint) 17 | 18 | # populate the Window from our *.ui file which was created with the Qt Designer 19 | uifile = path.join(path.dirname(__file__), 'GUIs/frequency.ui') 20 | loadUi(uifile, self) 21 | self.parent = main_dialog 22 | 23 | self.freqTable.itemClicked.connect(self.sele_selected_resi_freq_table) 24 | 25 | def inter_freq_table(self): 26 | try: 27 | import numpy as np 28 | import pandas as pd 29 | except ImportError: 30 | self.parent.log("Please install numpy and pandas to use this plugin") 31 | return 32 | 33 | obj = self.parent.widg.selections_list.currentText() 34 | if obj == '' or (obj[0] == "(" and obj[-1] == ")"): 35 | self.parent.log("Please select an object to use this feature", error=True) 36 | return 37 | self.parent.disable_window() 38 | self.parent.log("Creation of residue interaction frequency table") 39 | freq_bond = dict() 40 | for bondType in intTypeMap.keys(): 41 | try: 42 | freq_bond.setdefault(bondType, 43 | get_freq_combined(obj, bondType, self.parent.temp_dir.name, 44 | interchain=self.parent.widg.interchain.isChecked(), 45 | intrachain=self.parent.widg.intrachain.isChecked())) 46 | except FileNotFoundError: 47 | self.parent.log("Run Ring on the selected object first!", error=True) 48 | self.parent.enable_window() 49 | return 50 | 51 | all_resi = set() 52 | for bondType, freqs in freq_bond.items(): 53 | all_resi.update(set(freqs.keys())) 54 | 55 | mtr = np.zeros((len(all_resi), len(intTypeMap.keys()))) 56 | for i, resi in enumerate(all_resi): 57 | for j, bondType in enumerate(intTypeMap.keys()): 58 | try: 59 | mtr[i, j] = freq_bond[bondType][resi] * 100 60 | except KeyError: 61 | pass 62 | 63 | right = pd.DataFrame(mtr, columns=intTypeMap.keys()) 64 | center = pd.DataFrame([[x for x in all_resi]]).transpose() 65 | df = pd.concat([center, right], axis=1) 66 | tableWidget = self.freqTable 67 | tableWidget.setRowCount(0) 68 | tableWidget.setSortingEnabled(False) 69 | 70 | tableWidget.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch) 71 | 72 | df = df.sort_values(by=0, ascending=True).reset_index() 73 | 74 | prevEdge = None 75 | color = 2 76 | for rowPosition, row in df.iterrows(): 77 | tableWidget.insertRow(rowPosition) 78 | for i, item in enumerate(row.to_list()[1:]): 79 | if i == 0 and item != prevEdge: 80 | bg_color, fg_color, color = get_bg_fg_colors(color) 81 | 82 | if i == 0: 83 | tableWidget.setItem(rowPosition, i, QtWidgets.QTableWidgetItem(str(item))) 84 | else: 85 | wItem = QtWidgets.QTableWidgetItem() 86 | wItem.setData(QtCore.Qt.DisplayRole, round(float(item), 2)) 87 | tableWidget.setItem(rowPosition, i, wItem) 88 | tableWidget.item(rowPosition, i).setBackground(bg_color) 89 | tableWidget.item(rowPosition, i).setForeground(fg_color) 90 | tableWidget.item(rowPosition, i).setTextAlignment(QtCore.Qt.AlignCenter) 91 | if i == 0: 92 | prevEdge = item 93 | tableWidget.setSortingEnabled(True) 94 | tableWidget.viewport().update() 95 | self.show() 96 | self.parent.enable_window() 97 | 98 | def sele_selected_resi_freq_table(self): 99 | tableWidget = self.freqTable 100 | indexes = tableWidget.selectionModel().selectedRows() 101 | for index in sorted(indexes): 102 | chain, resi = tableWidget.item(index.row(), 0).text().split("/")[0:2] 103 | cmd.select("sele_row", selection="chain {} and resi {}".format(chain, resi), merge=1) 104 | self.parent.log("Updated selection sele_row with the residue selected in the frequency table") 105 | -------------------------------------------------------------------------------- /ring_api.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import threading 4 | import time 5 | from threading import Thread 6 | 7 | import requests 8 | 9 | drmaatic_url = "https://drmaatic.biocomputingup.it" 10 | list_job_url = "{}/job/".format(drmaatic_url) 11 | 12 | _log_f = lambda x: x 13 | 14 | 15 | class Status: 16 | statusMap = { 17 | "job has been rejected from the ws": "failed", 18 | "job has been received from the ws": "pending", 19 | "job has been created and sent to the DRM": "pending", 20 | "process status cannot be determined": "pending", 21 | "job is queued and active": "running", 22 | "job is queued and in system hold": "running", 23 | "job is queued and in user hold": "running", 24 | "job is queued and in user and system hold": "running", 25 | "job is running": "running", 26 | "job is system suspended": "pending", 27 | "job is user suspended": "pending", 28 | "job finished normally": "success", 29 | "job finished, but failed": "failed", 30 | "job has been deleted": "deleted" 31 | } 32 | 33 | def __init__(self, status): 34 | self.status = self.decode_status(status) 35 | 36 | def __repr__(self): 37 | return self.status 38 | 39 | def __eq__(self, other): 40 | return self.status == other 41 | 42 | def decode_status(self, status_long): 43 | return self.statusMap[status_long] 44 | 45 | 46 | class Job: 47 | _status: [Status, None] = None 48 | _uuid: [str, None] = None 49 | 50 | def __init__(self, uuid=None, status=None): 51 | self.uuid = uuid 52 | self.status = status 53 | 54 | def __repr__(self) -> str: 55 | return "{} - {}".format(self.uuid, self.status) 56 | 57 | @property 58 | def status(self): 59 | return self._status 60 | 61 | @status.setter 62 | def status(self, status): 63 | self._status = Status(status) if status is not None else status 64 | 65 | @property 66 | def uuid(self): 67 | return self._uuid 68 | 69 | @uuid.setter 70 | def uuid(self, uuid): 71 | self._uuid = uuid 72 | 73 | def is_finished(self) -> bool: 74 | return self._status == "failed" or self._status == "deleted" or self._status == "success" 75 | 76 | 77 | def check_for_job(job): 78 | try: 79 | job_url = "{}/{}".format(list_job_url, job.uuid) 80 | 81 | while not job.is_finished(): 82 | response = requests.get(job_url, timeout=500) 83 | response.raise_for_status() 84 | job.status = response.json()["status"] 85 | if not job.is_finished(): 86 | time.sleep(3) 87 | 88 | except requests.exceptions.RequestException as err: 89 | return err 90 | 91 | 92 | def post_job(job, file_pth, params): 93 | try: 94 | files = {'input_file': open(file_pth, 'rb')} 95 | 96 | response = requests.post(list_job_url, files=files, data=params, timeout=10000) 97 | response.raise_for_status() 98 | job.uuid = response.json()["uuid"] 99 | job.status = response.json()["status"] 100 | 101 | except requests.exceptions.RequestException as err: 102 | return err 103 | 104 | 105 | def download_results(job, extract_pth): 106 | if job.status == "failed": 107 | return 108 | try: 109 | output_url = "{}/{}/{}".format(list_job_url, job.uuid, "download") 110 | 111 | response = requests.get(output_url, timeout=5) 112 | response.raise_for_status() 113 | file_name = response.headers["content-disposition"].split("filename=")[1] 114 | file_pth = "{}/{}".format(extract_pth, file_name) 115 | 116 | with open(file_pth, "wb") as f: 117 | f.write(response.content) 118 | 119 | shutil.unpack_archive(file_pth, extract_pth) 120 | os.remove(file_pth) 121 | 122 | except requests.exceptions.RequestException as err: 123 | return err 124 | 125 | 126 | def config_to_parameters(config): 127 | convert = {"-g": "seq_sep", 128 | "-o": "len_salt", 129 | "-s": "len_ss", 130 | "-k": "len_pipi", 131 | "-a": "len_pica", 132 | "-b": "len_hbond", 133 | "-w": "len_vdw"} 134 | 135 | new_config = {} 136 | 137 | for key, value in config.items(): 138 | # replace key of config with value 139 | if key in convert: 140 | new_config[convert[key]] = value.strip("--") 141 | 142 | new_config[config["edges"].strip("--")] = True 143 | 144 | return new_config 145 | 146 | 147 | def run_ring_api(file_pth, run_config, tmp_dir, log_f, progress_f): 148 | global _log_f 149 | 150 | _log_f = log_f 151 | 152 | job: Job = Job() 153 | 154 | file_name = os.path.basename(file_pth) 155 | _log_f(file_pth, file_name) 156 | 157 | parameters = {"task": "ring-plugin-api", 158 | "original_name": file_name 159 | } 160 | 161 | parameters.update(config_to_parameters(run_config)) 162 | 163 | _log_f("Remote RING generation started") 164 | _log_f("Sending job to remote server") 165 | t_post_job = Thread(target=post_job, args=(job, file_pth, parameters)) 166 | t_post_job.start() 167 | 168 | prev_progress = 0 169 | while t_post_job.is_alive(): 170 | progress_f(min([prev_progress, 15])) 171 | prev_progress = (prev_progress + 0.01) 172 | 173 | t_check_job = Thread(target=check_for_job, args=(job,)) 174 | t_check_job.start() 175 | 176 | prev_progress = 15 177 | timer = time.time() - 5 178 | while t_check_job.is_alive(): 179 | if time.time() - timer > 5: 180 | timer = time.time() 181 | _log_f("Running job {}".format(job)) 182 | 183 | progress_f(min([prev_progress, 85])) 184 | prev_progress = (prev_progress + 0.00001) 185 | 186 | if job.status == "success": 187 | _log_f("Computation terminated, downloading results") 188 | else: 189 | _log_f("Error in the execution of RING, please retry later or launch locally", error=True) 190 | 191 | t_download_results = Thread(target=download_results, args=(job, tmp_dir)) 192 | t_download_results.start() 193 | 194 | prev_progress = 85 195 | 196 | _log_f("Downloading results...") 197 | while t_download_results.is_alive(): 198 | progress_f(min([prev_progress, 100])) 199 | prev_progress = (prev_progress + 0.01) 200 | 201 | _log_f("Download completed!") 202 | progress_f(100) 203 | -------------------------------------------------------------------------------- /ring_local.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import sys 4 | import time 5 | 6 | from pymol import cmd 7 | from tqdm import tqdm 8 | 9 | 10 | def run_ring_local(ring_pth, file_pth, obj_name, run_config, tmp_dir, log_f, progress_f, verbose=False): 11 | progress_f(1) 12 | 13 | p = subprocess.Popen( 14 | [ring_pth, "-i", file_pth, "--out_dir", tmp_dir, 15 | "-g", run_config["-g"], 16 | "-o", run_config["-o"], 17 | "-s", run_config["-s"], 18 | "-k", run_config["-k"], 19 | "-a", run_config["-a"], 20 | "-b", run_config["-b"], 21 | "-w", run_config["-w"], 22 | run_config["water"], 23 | run_config["add_h"], 24 | run_config["edges"], "--all_models", "--md", "-v", "--no_colors"], 25 | stdout=subprocess.PIPE, 26 | stderr=subprocess.STDOUT, universal_newlines=True) 27 | 28 | log_f("Local RING generation started") 29 | 30 | if verbose: 31 | log_f("Executing : " + ' '.join(p.args)) 32 | 33 | log_file = open(tmp_dir + '/ring.log', 'w') 34 | 35 | for line in iter(p.stdout.readline, ''): 36 | if line.startswith('{') and '}' in line: 37 | progress = float(line.split('%')[0].split('{')[1].strip()) 38 | progress_f(progress) 39 | if verbose and '(' in line and ')' in line: 40 | times = line.split('(')[1].split(')')[0].split('<') 41 | log_f('Elapsed : ' + times[0].strip() + ' | Remaining : ' + times[1].strip()) 42 | if 'warning:' in line: 43 | warn = line.split('warning:')[1].strip() 44 | log_file.write(warn + '\n') 45 | if verbose: 46 | log_f(warn, warning=True) 47 | 48 | p.wait() 49 | log_file.close() 50 | 51 | # Check if the log file is empty 52 | if os.stat(tmp_dir + '/ring.log').st_size != 0: 53 | log_f(f"Local RING generation finished with warnings, check log file {tmp_dir}/ring.log for details", warning=True) 54 | 55 | if p.returncode != 0: 56 | raise Exception("Local RING generation failed") 57 | -------------------------------------------------------------------------------- /rmsd_clustering.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import math 3 | import multiprocessing as mp 4 | import os 5 | import time 6 | 7 | import matplotlib 8 | import matplotlib.patches as mpatches 9 | import numpy as np 10 | import seaborn as sn 11 | from Bio.SVDSuperimposer import SVDSuperimposer 12 | from matplotlib import pyplot as plt 13 | from matplotlib.axes import Axes 14 | from matplotlib.colors import ListedColormap 15 | from pymol import cmd 16 | from scipy import cluster 17 | from scipy.spatial.distance import squareform 18 | 19 | from utilities import generate_colormap 20 | 21 | structure_coords = dict() 22 | counter: mp.Value 23 | 24 | 25 | def cm_to_inch(x): 26 | return x / 2.54 27 | 28 | 29 | def hierarchy_optimization(X, logger, height, desired_clusters, method='complete'): 30 | result_label = dict() 31 | cut_heights = dict() 32 | 33 | range_n_clusters = list(range(2, len(X))) 34 | Z = cluster.hierarchy.linkage(squareform(X), optimal_ordering=True, method=method) 35 | 36 | centroid_clusters = [] 37 | names = range(len(X)) 38 | clusters = cluster.hierarchy.cut_tree(Z, n_clusters=range_n_clusters) 39 | 40 | if desired_clusters is not None: 41 | cluster_labels = clusters[:, desired_clusters - 2].flatten() 42 | 43 | silhouette_avg = 1 44 | result_label.setdefault(desired_clusters, (silhouette_avg, cluster_labels)) 45 | tmp = cluster.hierarchy.dendrogram(Z, p=desired_clusters, truncate_mode='lastp', no_plot=True) 46 | last_h = min(list(filter(lambda x: x != 0, [item for sublist in tmp['dcoord'] for item in sublist]))) 47 | cut_heights.setdefault(desired_clusters, last_h) 48 | 49 | for cluster_id in range(desired_clusters): 50 | nameList = list(zip(names, cluster_labels)) 51 | mask = np.array([i == cluster_id for i in cluster_labels]) 52 | idx = np.argmin(sum(X[:, mask][mask, :])) 53 | sublist = [name for (name, label) in nameList if label == cluster_id] 54 | centroid_clusters.append(sublist[idx]) 55 | elif height is not None: 56 | for i, n_clusters in enumerate(range_n_clusters): 57 | logger.progress((i / len(range_n_clusters)) * 100) 58 | cluster_labels = clusters[:, i].flatten() 59 | 60 | silhouette_avg = 1 61 | result_label.setdefault(n_clusters, (silhouette_avg, cluster_labels)) 62 | 63 | tmp = cluster.hierarchy.dendrogram(Z, p=n_clusters, truncate_mode='lastp', no_plot=True) 64 | last_h = min(list(filter(lambda x: x != 0, [item for sublist in tmp['dcoord'] for item in sublist]))) 65 | cut_heights.setdefault(n_clusters, last_h) 66 | 67 | if last_h <= height or n_clusters == len(X) - 1: 68 | for cluster_id in range(n_clusters): 69 | nameList = list(zip(names, cluster_labels)) 70 | mask = np.array([i == cluster_id for i in cluster_labels]) 71 | idx = np.argmin(sum(X[:, mask][mask, :])) 72 | sublist = [name for (name, label) in nameList if label == cluster_id] 73 | centroid_clusters.append(sublist[idx]) 74 | logger.progress(100) 75 | break 76 | 77 | logger.close_progress() 78 | return result_label, Z, cut_heights, centroid_clusters 79 | 80 | 81 | def cluster_distribution_heatmap(logger, pdb_id, method, tmp_dir, rmsd_val=None, desired_clusters=None, x_len=50): 82 | logger.disable_window() 83 | 84 | X = load_rmsd_dis_matrix(logger, pdb_id, tmp_dir) 85 | 86 | if desired_clusters is not None and desired_clusters < 2: 87 | logger.log("The number of cluster has to be greater than 2", error=True) 88 | logger.enable_window() 89 | return 90 | 91 | if desired_clusters is not None and desired_clusters >= math.sqrt(X.size): 92 | logger.log("The number of cluster has to smaller than the maximum number of models", error=True) 93 | logger.enable_window() 94 | return 95 | 96 | labels, Z, cut_heights, centroid_cluster = hierarchy_optimization(X, logger, height=rmsd_val, 97 | desired_clusters=desired_clusters, method=method) 98 | 99 | if desired_clusters is not None and desired_clusters not in labels: 100 | logger.log("The number of cluster has to be in the range 2 - {} (inclusive)".format(max(labels))) 101 | logger.enable_window() 102 | return 103 | else: 104 | logger.log('Number of clusters for selected RMSD cut: {}'.format(len(centroid_cluster)), warning=True) 105 | 106 | n_clusters = desired_clusters if desired_clusters is not None else len(centroid_cluster) 107 | 108 | labels = labels[n_clusters][1] 109 | 110 | def remove_spines(ax): 111 | ax.spines['right'].set_color('none') 112 | ax.spines['top'].set_color('none') 113 | ax.spines['left'].set_color('none') 114 | ax.spines['bottom'].set_color('none') 115 | 116 | fig = plt.figure(figsize=(13, 6), dpi=70) 117 | plt.get_current_fig_manager().set_window_title("State clusters") 118 | plt.style.use('default') 119 | ax1 = plt.subplot() 120 | 121 | pad_size = x_len - len(labels) % x_len if len(labels) % x_len != 0 else 0 122 | labels = labels.astype('float32') 123 | best = np.pad(labels, (0, pad_size), mode='constant', constant_values=np.nan) 124 | best = np.reshape(best, (int(len(best) / x_len), x_len)) 125 | 126 | if n_clusters < 11: 127 | cmap = matplotlib.cm.get_cmap('tab10') 128 | cmap = ListedColormap(cmap.colors[:n_clusters], N=n_clusters) 129 | else: 130 | cmap = generate_colormap(n_clusters) 131 | 132 | sn.set(font_scale=0.8) 133 | ax1: Axes = sn.heatmap(best, cmap=cmap, linewidths=.5, linecolor='white', ax=ax1, square=True, cbar=False, 134 | annot=False) 135 | ax1.set_yticks(np.arange(0.5, best.shape[0] + 0.5)) 136 | ax1.set_yticklabels(np.arange(1, best.size + 1, x_len), rotation=0, fontsize=10) 137 | ax1.set_xticks(np.arange(4.5, best.shape[1] + 0.5, 5)) 138 | ax1.set_xticklabels(range(5, best.shape[1] + 1, 5)) 139 | 140 | handles = [] 141 | handles_labels = [] 142 | for i in range(n_clusters): 143 | handles.append(mpatches.Patch(color=cmap.colors[i])) 144 | handles_labels.append(centroid_cluster[i] + 1) 145 | 146 | def flip(items, ncol): 147 | return itertools.chain(*[items[i::ncol] for i in range(ncol)]) 148 | 149 | plt.xlabel('States', fontdict={'size': 13, }) 150 | plt.title("Structure {} - {} clusters".format(pdb_id, len(set(handles_labels))), 151 | fontsize=14) 152 | 153 | def legend(ax, x0=0.01, y0=0, pad=0.5, **kwargs): 154 | otrans = ax.figure.transFigure 155 | t = ax.legend(bbox_to_anchor=(x0, y0, .98, 1), loc='lower center', bbox_transform=otrans, **kwargs) 156 | ax.figure.tight_layout(pad=pad) 157 | ax.figure.canvas.draw() 158 | tbox = t.get_window_extent().transformed(ax.figure.transFigure.inverted()) 159 | bbox = ax.get_position() 160 | ax.set_position([bbox.x0, bbox.y0 + tbox.height, bbox.width, bbox.height - tbox.height]) 161 | 162 | legend(ax1, y0=0.03, pad=0, borderaxespad=0., handles=flip(handles, 20), labels=flip(handles_labels, 20), ncol=20, 163 | facecolor="white", handlelength=1.2, handleheight=1.2, mode='expand', 164 | fontsize='large', fancybox=True, shadow=True, handletextpad=0.15) 165 | 166 | annot = ax1.annotate("", xy=(0, 0), xycoords="figure points", 167 | xytext=(10, 20), textcoords="offset points", 168 | bbox=dict(boxstyle="round", fc="white", alpha=0.6), 169 | fontsize=12) 170 | annot.set_visible(False) 171 | 172 | def update_annot(label, x, y): 173 | annot.xy = (x, y) 174 | annot.set_text(label) 175 | 176 | def on_hover(event): 177 | visible = annot.get_visible() 178 | is_outside_of_stackplot = True 179 | inv = ax1.transData.inverted() 180 | x, y = inv.transform((event.x, event.y)) 181 | state_id = int(x) + x_len * int(y) + 1 182 | if event.inaxes == ax1 and state_id <= labels.size: 183 | relative_cluster = centroid_cluster[int(labels[state_id - 1])] + 1 184 | update_annot("{} - cl. {}".format(state_id, relative_cluster), event.x, event.y) 185 | annot.set_visible(True) 186 | is_outside_of_stackplot = False 187 | if is_outside_of_stackplot and visible: 188 | annot.set_visible(False) 189 | fig.canvas.draw_idle() 190 | 191 | plt.connect('motion_notify_event', on_hover) 192 | 193 | remove_spines(ax1) 194 | logger.enable_window() 195 | 196 | plt.tight_layout() 197 | plt.grid(False) 198 | plt.show(block=False) 199 | 200 | 201 | def hierarchy_cut_plot(logger, pdb_id, method, tmp_dir, rmsd_val=None, desired_clusters=None): 202 | X = load_rmsd_dis_matrix(logger, pdb_id, tmp_dir) 203 | 204 | if desired_clusters is not None and desired_clusters < 2: 205 | logger.log("The number of cluster has to be greater than 2", error=True) 206 | return 207 | 208 | result_labels, Z, cut_heights, centroids = hierarchy_optimization(X, logger, rmsd_val, desired_clusters, 209 | method=method) 210 | 211 | if desired_clusters is not None: 212 | if desired_clusters in result_labels: 213 | silh_val = result_labels[desired_clusters][0] 214 | y_val = cut_heights[desired_clusters] 215 | else: 216 | logger.log("The number of cluster has to be in the range 2 - {} (inclusive)".format(max(result_labels))) 217 | return 218 | else: 219 | logger.log('Number of clusters for selected RMSD cut: {}'.format(len(centroids)), warning=True) 220 | silh_val = result_labels[len(centroids)][0] 221 | y_val = cut_heights[len(centroids)] 222 | 223 | n_clusters = desired_clusters if desired_clusters is not None else len(centroids) 224 | result_labels = result_labels[n_clusters][1] 225 | plt.figure() 226 | plt.get_current_fig_manager().set_window_title("Hierarchical clusters") 227 | 228 | plt.style.use('default') 229 | plt.axhline(y=y_val, linestyle="--", zorder=0, linewidth=1.3, 230 | label="{} clusters".format(n_clusters)) 231 | 232 | R = cluster.hierarchy.dendrogram(Z, no_plot=True, p=n_clusters, truncate_mode='lastp', ) 233 | 234 | my_map = dict() 235 | for i in range(len(Z) + 1): 236 | my_map[i] = i 237 | for i in range(len(Z)): 238 | my_map[len(Z) + 1 + i] = [int(Z[i, 0]), int(Z[i, 1])] 239 | 240 | def f(m_id): 241 | if m_id <= len(Z): 242 | return [m_id] 243 | return f(my_map[m_id][0]) + f(my_map[m_id][1]) 244 | 245 | temp = {R["leaves"][ii]: ( 246 | centroids[result_labels[f(label)[0]]] + 1, 247 | R["ivl"][ii] if '(' in R["ivl"][ii] else '(1)') for ii, label in 248 | enumerate(R["leaves"])} 249 | 250 | def llf(xx): 251 | return "{} - {}".format(*temp[xx]) 252 | 253 | cluster.hierarchy.dendrogram(Z, p=n_clusters, truncate_mode='lastp', leaf_label_func=llf, leaf_rotation=90) 254 | 255 | plt.xlabel("Cluster center state - (n° states in cluster)") 256 | plt.ylim(bottom=y_val - 0.5) 257 | plt.ylabel('RMSD (Å)') 258 | plt.suptitle("Hierarchical clusters", fontsize=12) 259 | plt.legend(loc='upper right', framealpha=1, prop={'size': 9}) 260 | plt.tight_layout() 261 | plt.show(block=False) 262 | 263 | 264 | def get_rmsd(args): 265 | sup = SVDSuperimposer() 266 | i, j = args 267 | sup.set(structure_coords[i], structure_coords[j]) 268 | if os.name != 'nt': 269 | global counter 270 | with counter.get_lock(): 271 | counter.value += 1 272 | sup.run() 273 | return i, j, sup.get_rms() 274 | 275 | 276 | def load_structure_coords(filename): 277 | coords = dict() 278 | current_model = 0 279 | 280 | with open(filename, 'r') as file: 281 | file.readline() 282 | file.readline() 283 | for line in file: 284 | line = line.strip().split(' ') 285 | if len(line) == 4: 286 | x, y, z = line[1:] 287 | coords.setdefault(current_model, []) 288 | coords[current_model].append(np.asarray([float(x), float(y), float(z)], dtype=np.float32)) 289 | else: 290 | current_model += 1 291 | file.readline() # Skip second line of header 292 | 293 | for model in coords.keys(): 294 | coords[model] = np.asarray(coords[model], dtype=np.float32) 295 | 296 | return coords 297 | 298 | 299 | def init(args): 300 | global counter 301 | counter = args 302 | 303 | 304 | def compute_rmsd_dist_matrix(logger, pdb_id, tmp_dir): 305 | mtrx_file = os.path.join(tmp_dir, "{}.npy".format(pdb_id)) 306 | 307 | logger.log("Loading structure") 308 | 309 | filename = os.path.join(tmp_dir, "{}.xyz".format(pdb_id)) 310 | 311 | global structure_coords 312 | 313 | structure_coords = load_structure_coords(filename) 314 | 315 | n_models = len(structure_coords.keys()) 316 | X = np.zeros((n_models, n_models)) 317 | indexes = np.tril_indices(n_models) 318 | 319 | args = [(i, j,) for i, j in zip(indexes[0], indexes[1])] 320 | 321 | logger.log("Computing distance matrix") 322 | 323 | # If we are not in windows than the computation is done with multiprocessing, else without it 324 | if os.name != 'nt': 325 | counter = mp.Value('i', 0) 326 | with mp.Pool(mp.cpu_count() - 1, initializer=init, initargs=(counter,)) as p: 327 | results = p.map_async(get_rmsd, iterable=args, chunksize=100) 328 | while not results.ready(): 329 | val = counter.value / len(indexes[0]) * 100 330 | logger.progress(val) 331 | time.sleep(1) 332 | for i, j, v in results.get(): 333 | X[i, j] = v 334 | else: 335 | for step, (i, j, v) in enumerate(map(get_rmsd, args)): 336 | X[i, j] = v 337 | logger.progress(step / len(indexes[0]) * 100) 338 | 339 | logger.close_progress() 340 | del structure_coords 341 | 342 | X += X.transpose() 343 | np.fill_diagonal(X, 0) 344 | np.save(mtrx_file, X) 345 | logger.log('Done') 346 | 347 | 348 | def load_rmsd_dis_matrix(logger, pdb_id, tmp_dir): 349 | mtrx_file = os.path.join(tmp_dir, "{}.npy".format(pdb_id)) 350 | logger.log("Loading distance matrix") 351 | X = np.load(mtrx_file) 352 | return X 353 | 354 | 355 | def cluster_states_obj(logger, pdb_id, method, tmp_dir, rmsd_val=None, desired_clusters=None): 356 | logger.disable_window() 357 | X = load_rmsd_dis_matrix(logger, pdb_id, tmp_dir) 358 | 359 | logger.log("Operation started, please wait") 360 | 361 | result_labels, _, cut_heights, repr_labels = hierarchy_optimization(X, logger, height=rmsd_val, 362 | desired_clusters=desired_clusters, 363 | method=method) 364 | 365 | if desired_clusters is not None and desired_clusters not in result_labels: 366 | logger.log("The number of cluster has to be in the range 2 - {} (inclusive)".format(max(result_labels))) 367 | return 368 | 369 | obj_name = "{}_cl".format(pdb_id.strip('_ca')) 370 | cmd.delete(obj_name) 371 | 372 | repr_labels = np.asarray(repr_labels) + 1 373 | 374 | for state in repr_labels: 375 | cmd.create(obj_name, pdb_id.strip('_ca'), source_state=state, target_state=-1, copy_properties=True) 376 | 377 | logger.log("Created new object with representative states from original object") 378 | logger.log("States used from original object: {}".format(repr_labels)) 379 | logger.enable_window() 380 | 381 | 382 | if __name__ == '__main__': 383 | class Logger: 384 | def __init__(self): 385 | pass 386 | 387 | @staticmethod 388 | def log(s, warning=False, error=False): 389 | print(s) 390 | 391 | @staticmethod 392 | def progress(n): 393 | print(n) 394 | 395 | def close_progress(self): 396 | pass 397 | 398 | def disable_window(self): 399 | pass 400 | 401 | def enable_window(self): 402 | pass 403 | 404 | 405 | temporary = Logger() 406 | # cluster_distribution_heatmap(temporary, "trj_ca", 'complete', desired_clusters=20) 407 | cluster_distribution_heatmap(temporary, "2h9r_ca", 'complete', desired_clusters=9) 408 | hierarchy_cut_plot(temporary, "2h9r_ca", 'complete', desired_clusters=9) 409 | # hierarchy_cut_plot(temporary, "trj_ca", 'complete', desired_clusters=20) 410 | -------------------------------------------------------------------------------- /singularity.def: -------------------------------------------------------------------------------- 1 | Bootstrap: docker 2 | From: continuumio/miniconda3:latest 3 | 4 | %files 5 | 6 | %post 7 | cd /opt || exit 1 8 | 9 | git clone https://github.com/BioComputingUP/ring-pymol ./ring-pymol 10 | 11 | cd ring-pymol || exit 1 12 | 13 | conda env create -f environment-open-source.yml 14 | 15 | chmod 775 -R /opt 16 | 17 | %environment 18 | export DISPLAY=:0 19 | 20 | %help 21 | 22 | %runscript 23 | -------------------------------------------------------------------------------- /utilities.py: -------------------------------------------------------------------------------- 1 | import json 2 | import math 3 | import os.path 4 | import pathlib 5 | import threading 6 | from json import JSONEncoder 7 | from os.path import exists 8 | from typing import Dict, List, Union 9 | 10 | import matplotlib.cm as cm 11 | import networkx as nx 12 | import numpy as np 13 | import pandas as pd 14 | import pymol 15 | from PyQt5.QtGui import QColor 16 | from matplotlib.colors import ListedColormap 17 | from pymol.cgo import * 18 | from pymol.wizard.message import Message 19 | from scipy.stats import pearsonr 20 | 21 | intTypeMap = { 22 | "IONIC": (0.0, 0.0, 1.0), 23 | "METAL_ION": (1.0, 0.0, 1.0), 24 | "SSBOND": (1.0, 1.0, 0.0), 25 | "PIPISTACK": (1.0, 0.5, 0.0), 26 | "PICATION": (1.0, 0.0, 0.0), 27 | "PIHBOND": (0.0, 1.0, 0.0), 28 | "HBOND": (0.0, 1.0, 1.0), 29 | "HALOGEN": (1.0, 105 / 255, 180 / 255), 30 | "VDW": (0.5050504803657532, 0.5050504803657532, 0.5050504803657532), 31 | "IAC": (1.0, 1.0, 1.0) 32 | } 33 | 34 | originalIntTypeMap = { 35 | "IONIC": (0.0, 0.0, 1.0), 36 | "METAL_ION": (1.0, 0.0, 1.0), 37 | "SSBOND": (1.0, 1.0, 0.0), 38 | "PIPISTACK": (1.0, 0.5, 0.0), 39 | "PICATION": (1.0, 0.0, 0.0), 40 | "PIHBOND": (0.0, 1.0, 0.0), 41 | "HBOND": (0.0, 1.0, 1.0), 42 | "HALOGEN": (1.0, 105 / 255, 180 / 255), 43 | "VDW": (0.5050504803657532, 0.5050504803657532, 0.5050504803657532), 44 | "IAC": (1.0, 1.0, 1.0) 45 | } 46 | 47 | 48 | def _default(self, obj): 49 | return getattr(obj.__class__, "to_json", _default.default)(obj) 50 | 51 | 52 | _default.default = JSONEncoder().default 53 | JSONEncoder.default = _default 54 | 55 | 56 | class Node: 57 | chain: [str, None] = None 58 | # resi can contain the insertion code like 27A 59 | resi: [str, None] = None 60 | resn: [str, None] = None 61 | 62 | def __init__(self, *args): 63 | if len(args) == 1: 64 | self.init_string(*args) 65 | else: 66 | self.init_args(*args) 67 | 68 | def init_string(self, string_id: str): 69 | if ':' in string_id: 70 | ids = string_id.strip().split(':') 71 | else: 72 | ids = string_id.strip().split('/') 73 | 74 | self.chain: str = ids[0] 75 | self.resi: str = ids[1] 76 | self.resn = None 77 | 78 | if len(ids) > 2: 79 | if len(ids[2]) == 3: 80 | self.resn: str = ids[2] 81 | elif ids[2] != '_': 82 | self.resi += ids[2] 83 | if len(ids) > 3: 84 | self.resn: str = ids[3] 85 | 86 | def init_args(self, chain: str, resi: Union[int, str], resn: str = None): 87 | self.chain: str = chain 88 | self.resi: str = resi 89 | self.resn: str = resn 90 | 91 | def __lt__(self, other): 92 | return self.chain < other.chain or self.chain == other.chain and self.resi < other.resi 93 | 94 | def __le__(self, other): 95 | return self == other or self < other 96 | 97 | def __gt__(self, other): 98 | return other < self 99 | 100 | def __ge__(self, other): 101 | return self == other or other < self 102 | 103 | def __eq__(self, other): 104 | if not other: 105 | return False 106 | base = self.chain == other.chain and self.resi == other.resi 107 | if self.resn and other.resn: 108 | base = base and self.resn == other.resn 109 | return base 110 | 111 | def __ne__(self, other): 112 | return not self == other 113 | 114 | def __repr__(self): 115 | if self.resn: 116 | return "{}/{}/{}".format(self.chain, self.resi, self.resn) 117 | return "{}/{}".format(self.chain, self.resi) 118 | 119 | def __hash__(self): 120 | return hash((self.chain, self.resi)) 121 | 122 | def id_repr(self): 123 | return "{}/{}".format(self.chain, self.resi) 124 | 125 | def id_tuple(self): 126 | return self.chain, self.resi 127 | 128 | def to_json(self): 129 | return self.__repr__() 130 | 131 | 132 | class Edge: 133 | def __init__(self, *args): 134 | self.node1 = None 135 | self.node2 = None 136 | if len(args) == 2: 137 | self.init_nodes(*args) 138 | else: 139 | self.init_list(*args) 140 | 141 | def init_nodes(self, node1: Node, node2: Node): 142 | self.node1: Node = node1 143 | self.node2: Node = node2 144 | 145 | def init_list(self, sorted_node_list: List[Node]): 146 | if len(sorted_node_list) != 2: 147 | raise ValueError("Cannot create an Edge with more than two nodes") 148 | self.node1: Node = sorted_node_list[0] 149 | self.node2: Node = sorted_node_list[1] 150 | 151 | def __lt__(self, other): 152 | return self.node1 < other.node1 or (self.node1 == other.node1 and self.node2 < other.node2) 153 | 154 | def __le__(self, other): 155 | return self == other or self < other 156 | 157 | def __gt__(self, other): 158 | return other < self 159 | 160 | def __ge__(self, other): 161 | return self == other or other < self 162 | 163 | def __eq__(self, other): 164 | if not other: 165 | return False 166 | return (self.node1 == other.node1 and self.node2 == other.node2) or ( 167 | self.node1 == other.node2 and self.node2 == other.node1) 168 | 169 | def __ne__(self, other): 170 | return not self == other 171 | 172 | def __repr__(self): 173 | return "{} - {}".format(self.node1, self.node2) 174 | 175 | def __hash__(self): 176 | return hash((self.node1, self.node2)) 177 | 178 | 179 | def get_freq(obj, tmp_dir, interchain=False, intrachain=False) -> Dict[str, Dict[Edge, float]]: 180 | conn_freq = dict() 181 | for inter in intTypeMap.keys(): 182 | conn_freq.setdefault(inter, dict()) 183 | gfreq_file = tmp_dir + "/md/{}.gfreq_{}".format(obj, inter) 184 | if exists(gfreq_file): 185 | with open(gfreq_file, 'r') as f: 186 | for line in f: 187 | node1, _, node2, perc = line.split('\t') 188 | node1 = Node(node1) 189 | node2 = Node(node2) 190 | edge = Edge(node1, node2) 191 | 192 | if intrachain and node1.chain != node2.chain: 193 | continue 194 | if interchain and node1.chain == node2.chain: 195 | continue 196 | 197 | conn_freq[inter].setdefault(edge, float(perc)) 198 | return conn_freq 199 | 200 | 201 | def get_freq_combined(obj, bond, tmp_dir, interchain=False, intrachain=False, key_string=False): 202 | conn_freq = dict() 203 | try: 204 | with open(tmp_dir + "/md/{}.gfreq_{}".format(obj, bond), 'r') as f: 205 | for line in f: 206 | node1, _, node2, perc = line.split('\t') 207 | node1 = Node(node1) 208 | node2 = Node(node2) 209 | if intrachain and node1.chain != node2.chain: 210 | continue 211 | if interchain and node1.chain == node2.chain: 212 | continue 213 | if not key_string: 214 | conn_freq.setdefault(node1, []) 215 | conn_freq[node1].append(float(perc)) 216 | else: 217 | conn_freq.setdefault(str(node1), []) 218 | conn_freq[str(node1)].append(float(perc)) 219 | 220 | except FileNotFoundError: 221 | raise FileNotFoundError 222 | 223 | for k, v in conn_freq.items(): 224 | conn_freq[k] = 1 - math.prod([(1 - x) for x in v]) 225 | 226 | return conn_freq 227 | 228 | 229 | def get_freq_combined_all_interactions(obj, tmp_dir, interchain=False): 230 | conn_freq = dict() 231 | for inter in intTypeMap.keys(): 232 | with open(tmp_dir + "/md/{}.gfreq_{}".format(obj, inter), 'r') as f: 233 | for line in f: 234 | node1, _, node2, perc = line.split('\t') 235 | node1 = Node(node1) 236 | node2 = Node(node2) 237 | edge = Edge(node1, node2) 238 | if interchain and node1.chain == node2.chain: 239 | pass 240 | conn_freq.setdefault(edge, []) 241 | conn_freq[edge].append(float(perc)) 242 | 243 | all_freq = dict() 244 | for k, v in conn_freq.items(): 245 | all_freq[k] = 1 - math.prod([(1 - x) for x in v]) 246 | 247 | return all_freq 248 | 249 | 250 | def get_node_names_ordered(obj, tmp_dir): 251 | node_list = [] 252 | with open(tmp_dir + "/{}.cif_ringNodes".format(obj), 'r') as f: 253 | f.readline() 254 | for line in f: 255 | node_id, *_, model = line.strip().split("\t") 256 | if model == "1": 257 | node_list.append(Node(node_id)) 258 | else: 259 | return node_list 260 | return node_list 261 | 262 | 263 | def draw_links(interactions, color, object_name, coords, state): 264 | from pymol import cmd 265 | 266 | tup_color = [] 267 | if type(color) is str: 268 | try: 269 | tup_color = list(map(float, color.replace('(', '').replace(')', '').split(','))) 270 | except ValueError: 271 | tup_color = list(cmd.get_color_tuple(color)) 272 | elif type(color) is list or type(color) is tuple: 273 | tup_color = list(color) 274 | 275 | obj = [BEGIN, LINES, COLOR] + tup_color 276 | for interaction in interactions: 277 | valid = True 278 | if "," in interaction[0]: 279 | coord1 = ([float(x) for x in interaction[0].split(',')],) 280 | else: 281 | try: 282 | coord1 = (coords[interaction[0]],) 283 | except KeyError: 284 | valid = False 285 | 286 | if "," in interaction[1]: 287 | coord2 = ([float(x) for x in interaction[1].split(',')],) 288 | else: 289 | try: 290 | coord2 = (coords[interaction[1]],) 291 | except KeyError: 292 | valid = False 293 | 294 | if valid: 295 | for x, y in zip(coord1, coord2): 296 | obj.extend([VERTEX] + x + [VERTEX] + y) 297 | obj.append(END) 298 | cmd.load_cgo(obj, object_name, state=state, zoom=False) 299 | 300 | 301 | def calculate_correlation(obj, frames, tmp_dir, min_presence=0.05, max_presence=0.95, coeff_thresh=0.5, p_thresh=0.3, 302 | int_type="HBOND"): 303 | all_cm = dict() 304 | nodes = [] 305 | if int_type == "ALL": 306 | to_read = intTypeMap.keys() 307 | else: 308 | to_read = [int_type] 309 | 310 | for interaction in to_read: 311 | all_cm[interaction] = pd.read_csv(tmp_dir + '/md/{}.cm_{}'.format(obj, interaction), sep=' ', 312 | header=None) 313 | if len(nodes) == 0: 314 | nodes = all_cm[interaction][all_cm[interaction][0] == 1][1] 315 | nodes = [Node(x) for x in nodes] 316 | 317 | if int_type != "ALL": 318 | conn_freq = get_freq(obj, tmp_dir) 319 | else: 320 | conn_freq = get_freq_combined_all_interactions(obj, tmp_dir) 321 | contacts_sparse = dict() 322 | for frame in range(0, frames): 323 | for interaction in to_read: 324 | df = all_cm[interaction][all_cm[interaction][0] == frame + 1] 325 | df = df.iloc[:, 2:] 326 | matrix = df.values 327 | matrix[np.triu_indices(matrix.shape[0])] = 0 328 | for i, j in np.argwhere(matrix > 0): 329 | node1 = nodes[i] 330 | node2 = nodes[j] 331 | edge = Edge(sorted([node1, node2])) 332 | freq_val = conn_freq[interaction][edge] if int_type != "ALL" else conn_freq[edge] 333 | if min_presence < freq_val < max_presence: 334 | contacts_sparse.setdefault(edge, np.zeros(frames)) 335 | contacts_sparse[edge][frame] += 1 336 | 337 | coeffs_matr = np.ones((len(contacts_sparse), len(contacts_sparse))) * np.nan 338 | p_matr = np.ones((len(contacts_sparse), len(contacts_sparse))) * np.nan 339 | 340 | indexes = np.triu_indices(len(contacts_sparse), k=1) 341 | keys = list(contacts_sparse.keys()) 342 | for i, j in zip(indexes[0], indexes[1]): 343 | corr_coeff, p_val = pearsonr(contacts_sparse[keys[i]], contacts_sparse[keys[j]]) 344 | if p_val < p_thresh and (corr_coeff > coeff_thresh or corr_coeff < -coeff_thresh): 345 | coeffs_matr[i, j] = corr_coeff 346 | p_matr[i, j] = p_val 347 | return list(contacts_sparse.keys()), coeffs_matr, p_matr 348 | 349 | 350 | def get_bg_fg_colors(color): 351 | if color == 1: 352 | bk_color = QColor(232, 231, 252) 353 | fg_color = QColor(0, 0, 0) 354 | color = 2 355 | else: 356 | bk_color = QColor(255, 255, 255) 357 | fg_color = QColor(0, 0, 0) 358 | color = 1 359 | return bk_color, fg_color, color 360 | 361 | 362 | def is_selection(string): 363 | return string[0] == "(" and string[-1] == ")" 364 | 365 | 366 | def generate_colormap(number_of_distinct_colors: int = 80): 367 | if number_of_distinct_colors == 0: 368 | number_of_distinct_colors = 80 369 | 370 | number_of_distinct_colors_min = max(8, number_of_distinct_colors) 371 | 372 | number_of_shades = 7 373 | number_of_distinct_colors_with_multiply_of_shades = int( 374 | math.ceil(number_of_distinct_colors_min / number_of_shades) * number_of_shades) 375 | 376 | # Create an array with uniformly drawn floats taken from <0, 1) partition 377 | linearly_distributed_nums = np.arange( 378 | number_of_distinct_colors_with_multiply_of_shades) / number_of_distinct_colors_with_multiply_of_shades 379 | 380 | # We are going to reorganise monotonically growing numbers in such way that there will be single array with saw-like pattern 381 | # but each saw tooth is slightly higher than the one before 382 | # First divide linearly_distributed_nums into number_of_shades sub-arrays containing linearly distributed numbers 383 | arr_by_shade_rows = linearly_distributed_nums.reshape(number_of_shades, 384 | number_of_distinct_colors_with_multiply_of_shades // number_of_shades) 385 | 386 | # Transpose the above matrix (columns become rows) - as a result each row contains saw tooth with values slightly higher than row above 387 | arr_by_shade_columns = arr_by_shade_rows.T 388 | 389 | # Keep number of saw teeth for later 390 | number_of_partitions = arr_by_shade_columns.shape[0] 391 | 392 | # Flatten the above matrix - join each row into single array 393 | nums_distributed_like_rising_saw = arr_by_shade_columns.reshape(-1) 394 | 395 | # HSV colour map is cyclic (https://matplotlib.org/tutorials/colors/colormaps.html#cyclic), we'll use this property 396 | initial_cm = cm.hsv(nums_distributed_like_rising_saw) 397 | 398 | lower_partitions_half = number_of_partitions // 2 399 | upper_partitions_half = number_of_partitions - lower_partitions_half 400 | 401 | # Modify lower half in such way that colours towards beginning of partition are darker 402 | # First colours are affected more, colours closer to the middle are affected less 403 | lower_half = lower_partitions_half * number_of_shades 404 | for i in range(3): 405 | initial_cm[0:lower_half, i] *= np.arange(0.2, 1, 0.8 / lower_half) 406 | 407 | # Modify second half in such way that colours towards end of partition are less intense and brighter 408 | # Colours closer to the middle are affected less, colours closer to the end are affected more 409 | for i in range(3): 410 | for j in range(upper_partitions_half): 411 | modifier = np.ones(number_of_shades) - initial_cm[lower_half + j * number_of_shades: lower_half + ( 412 | j + 1) * number_of_shades, i] 413 | modifier = j * modifier / upper_partitions_half 414 | initial_cm[lower_half + j * number_of_shades: lower_half + (j + 1) * number_of_shades, i] += modifier 415 | 416 | return ListedColormap(initial_cm, N=number_of_distinct_colors) 417 | 418 | 419 | def remap(value, low1, high1, low2, high2): 420 | return low2 + (value - low1) * (high2 - low2) / (high1 - low1) 421 | 422 | 423 | def discrete_mapping(value): 424 | if value < 0.33: 425 | return 0.7 426 | if value < 0.66: 427 | return 2.0 428 | return 3.2 429 | 430 | 431 | def export_network_graph(model, tmp_dir, log_f, disable_f, enable_f): 432 | disable_f() 433 | G = nx.MultiGraph() 434 | 435 | # Add the nodes to the graph 436 | file_pth = os.path.join(tmp_dir, model + ".cif_ringNodes") 437 | if not os.path.exists(file_pth): 438 | log_f("RING output files not found, run RING on the object first!", error=True) 439 | enable_f() 440 | return 441 | 442 | df = pd.read_csv(file_pth, sep='\t') 443 | if len(df) == 0: 444 | return IndexError 445 | df = df.groupby('NodeId').mean() 446 | 447 | for (nodeId, _, degree, *_) in df.itertuples(index=True): 448 | node = Node(nodeId) 449 | G.add_node(node, degree=round(degree, 3), chain=node.chain, resi=node.resi, resn=node.resn) 450 | 451 | # Add the edges to the graph 452 | file_pth = os.path.join(tmp_dir, model + ".cif_ringEdges") 453 | df = pd.read_csv(file_pth, sep='\t') 454 | 455 | distance_dict = dict() 456 | mean_distance = df.groupby(['NodeId1', 'NodeId2', 'Interaction']).mean() 457 | for (nodeId, distance, *_) in mean_distance.itertuples(index=True, name='Distance'): 458 | nodeId1, nodeId2, interaction = nodeId 459 | intType = interaction.split(":")[0] 460 | node1 = Node(nodeId1) 461 | node2 = Node(nodeId2) 462 | edge = Edge(node1, node2) 463 | distance_dict.setdefault(intType, dict()).setdefault(edge, distance) 464 | 465 | conn_freq = get_freq(model, tmp_dir) 466 | 467 | sawn = set() 468 | df = df.groupby(["NodeId1", "Interaction", "NodeId2"]).sum() 469 | for (ids, *_) in df.itertuples(index=True): 470 | nodeId1, interaction, nodeId2 = ids 471 | intType = interaction.split(":")[0] 472 | node1 = Node(nodeId1) 473 | node2 = Node(nodeId2) 474 | edge = Edge(node1, node2) 475 | key = (edge, intType) 476 | if key not in sawn: 477 | G.add_edge(node1, node2, interaction=intType, frequency=round(conn_freq[intType][edge], 3), 478 | distance=round(distance_dict[intType][edge], 3)) 479 | sawn.add(key) 480 | 481 | with open("{}/{}.json".format(os.getcwd(), model), 'w+') as f: 482 | json.dump(nx.cytoscape_data(G), f) 483 | 484 | enable_f() 485 | 486 | log_f("Cytoscape network format saved as {}/{}.json".format(os.getcwd(), model)) 487 | 488 | 489 | async_threads = [] 490 | 491 | 492 | def async_(func, *args, **kwargs): 493 | ''' 494 | DESCRIPTION 495 | 496 | Run function threaded and show "please wait..." message. 497 | ''' 498 | from pymol.wizard.message import Message 499 | 500 | _self = kwargs.pop('_self', cmd) 501 | 502 | wiz = Message([], dismiss=0, _self=_self) 503 | 504 | try: 505 | _self.set_wizard(wiz) 506 | except: 507 | wiz = None 508 | 509 | if isinstance(func, str): 510 | func = _self.keyword[func][0] 511 | 512 | def wrapper(): 513 | async_threads.append(t) 514 | try: 515 | func(*args, **kwargs) 516 | except (pymol.CmdException, cmd.QuietException) as e: 517 | if e.args: 518 | print(e) 519 | finally: 520 | if wiz is not None: 521 | try: 522 | _self.set_wizard_stack([w 523 | for w in _self.get_wizard_stack() if w != wiz]) 524 | except: 525 | _self.do('_ wizard') 526 | else: 527 | _self.refresh_wizard() 528 | 529 | async_threads.remove(t) 530 | 531 | t = threading.Thread(target=wrapper) 532 | t.setDaemon(1) 533 | t.start() 534 | --------------------------------------------------------------------------------