Spaces:
Paused
Paused
Upload folder using huggingface_hub
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitattributes +19 -0
- LICENSE +201 -0
- README.md +45 -12
- __pycache__/cdkit1.cpython-311.pyc +0 -0
- app.py +216 -0
- readme.txt +46 -0
- requirements.txt +6 -0
- venv/Lib/site-packages/CDK_pywrapper-0.1.0.dist-info/INSTALLER +1 -0
- venv/Lib/site-packages/CDK_pywrapper-0.1.0.dist-info/LICENSE +21 -0
- venv/Lib/site-packages/CDK_pywrapper-0.1.0.dist-info/METADATA +152 -0
- venv/Lib/site-packages/CDK_pywrapper-0.1.0.dist-info/RECORD +15 -0
- venv/Lib/site-packages/CDK_pywrapper-0.1.0.dist-info/REQUESTED +0 -0
- venv/Lib/site-packages/CDK_pywrapper-0.1.0.dist-info/WHEEL +5 -0
- venv/Lib/site-packages/CDK_pywrapper-0.1.0.dist-info/top_level.txt +1 -0
- venv/Lib/site-packages/CDK_pywrapper/CDKdesc.jar +3 -0
- venv/Lib/site-packages/CDK_pywrapper/__init__.py +7 -0
- venv/Lib/site-packages/CDK_pywrapper/__pycache__/__init__.cpython-311.pyc +0 -0
- venv/Lib/site-packages/CDK_pywrapper/__pycache__/cdk_pywrapper.cpython-311.pyc +0 -0
- venv/Lib/site-packages/CDK_pywrapper/__pycache__/utils.cpython-311.pyc +0 -0
- venv/Lib/site-packages/CDK_pywrapper/cdk_pywrapper.py +308 -0
- venv/Lib/site-packages/CDK_pywrapper/descs.json +1730 -0
- venv/Lib/site-packages/CDK_pywrapper/utils.py +105 -0
- venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/INSTALLER +1 -0
- venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/LICENSE.rst +28 -0
- venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/METADATA +93 -0
- venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/RECORD +14 -0
- venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/WHEEL +5 -0
- venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/top_level.txt +1 -0
- venv/Lib/site-packages/PIL/BdfFontFile.py +133 -0
- venv/Lib/site-packages/PIL/BlpImagePlugin.py +476 -0
- venv/Lib/site-packages/PIL/BmpImagePlugin.py +472 -0
- venv/Lib/site-packages/PIL/BufrStubImagePlugin.py +74 -0
- venv/Lib/site-packages/PIL/ContainerIO.py +121 -0
- venv/Lib/site-packages/PIL/CurImagePlugin.py +75 -0
- venv/Lib/site-packages/PIL/DcxImagePlugin.py +80 -0
- venv/Lib/site-packages/PIL/DdsImagePlugin.py +572 -0
- venv/Lib/site-packages/PIL/EpsImagePlugin.py +474 -0
- venv/Lib/site-packages/PIL/ExifTags.py +381 -0
- venv/Lib/site-packages/PIL/FitsImagePlugin.py +148 -0
- venv/Lib/site-packages/PIL/FliImagePlugin.py +174 -0
- venv/Lib/site-packages/PIL/FontFile.py +134 -0
- venv/Lib/site-packages/PIL/FpxImagePlugin.py +255 -0
- venv/Lib/site-packages/PIL/FtexImagePlugin.py +115 -0
- venv/Lib/site-packages/PIL/GbrImagePlugin.py +103 -0
- venv/Lib/site-packages/PIL/GdImageFile.py +102 -0
- venv/Lib/site-packages/PIL/GifImagePlugin.py +1107 -0
- venv/Lib/site-packages/PIL/GimpGradientFile.py +137 -0
- venv/Lib/site-packages/PIL/GimpPaletteFile.py +57 -0
- venv/Lib/site-packages/PIL/GribStubImagePlugin.py +74 -0
- venv/Lib/site-packages/PIL/Hdf5StubImagePlugin.py +74 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,22 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
venv/Lib/site-packages/altair/vegalite/v5/schema/__pycache__/channels.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
venv/Lib/site-packages/altair/vegalite/v5/schema/__pycache__/core.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
venv/Lib/site-packages/CDK_pywrapper/CDKdesc.jar filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
venv/Lib/site-packages/numpy/core/_multiarray_umath.cp311-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
venv/Lib/site-packages/numpy/core/_simd.cp311-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
venv/Lib/site-packages/numpy.libs/libopenblas64__v0.3.23-293-gc2f4bdbb-gcc_10_3_0-2bde3a66a51006b2b53eb373ff767a3f.dll filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
venv/Lib/site-packages/pandas/_libs/algos.cp311-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
venv/Lib/site-packages/pandas/_libs/groupby.cp311-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
| 44 |
+
venv/Lib/site-packages/pandas/_libs/hashtable.cp311-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
| 45 |
+
venv/Lib/site-packages/pandas/_libs/interval.cp311-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
| 46 |
+
venv/Lib/site-packages/pandas/_libs/join.cp311-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
| 47 |
+
venv/Lib/site-packages/PIL/_imaging.cp311-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
| 48 |
+
venv/Lib/site-packages/PIL/_imagingft.cp311-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
| 49 |
+
venv/Lib/site-packages/pydantic_core/_pydantic_core.cp311-win_amd64.pyd filter=lfs diff=lfs merge=lfs -text
|
| 50 |
+
venv/Lib/site-packages/rdkit/Chem/rdchem.pyd filter=lfs diff=lfs merge=lfs -text
|
| 51 |
+
venv/Lib/site-packages/rdkit.libs/RDKitFileParsers-5d3f62f7babfcb6bcade84980d20cb24.dll filter=lfs diff=lfs merge=lfs -text
|
| 52 |
+
venv/Lib/site-packages/rdkit.libs/RDKitGraphMol-a0f7618b9e75a0a84b3f1cb34f92b554.dll filter=lfs diff=lfs merge=lfs -text
|
| 53 |
+
venv/Lib/site-packages/rdkit.libs/RDKitInchi-2adc093eac85469d4a9f373d5583b2d1.dll filter=lfs diff=lfs merge=lfs -text
|
| 54 |
+
venv/Scripts/ruff.exe filter=lfs diff=lfs merge=lfs -text
|
LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Apache License
|
| 2 |
+
Version 2.0, January 2004
|
| 3 |
+
http://www.apache.org/licenses/
|
| 4 |
+
|
| 5 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 6 |
+
|
| 7 |
+
1. Definitions.
|
| 8 |
+
|
| 9 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 10 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 11 |
+
|
| 12 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 13 |
+
the copyright owner that is granting the License.
|
| 14 |
+
|
| 15 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 16 |
+
other entities that control, are controlled by, or are under common
|
| 17 |
+
control with that entity. For the purposes of this definition,
|
| 18 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 19 |
+
direction or management of such entity, whether by contract or
|
| 20 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 21 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 22 |
+
|
| 23 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 24 |
+
exercising permissions granted by this License.
|
| 25 |
+
|
| 26 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 27 |
+
including but not limited to software source code, documentation
|
| 28 |
+
source, and configuration files.
|
| 29 |
+
|
| 30 |
+
"Object" form shall mean any form resulting from mechanical
|
| 31 |
+
transformation or translation of a Source form, including but
|
| 32 |
+
not limited to compiled object code, generated documentation,
|
| 33 |
+
and conversions to other media types.
|
| 34 |
+
|
| 35 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 36 |
+
Object form, made available under the License, as indicated by a
|
| 37 |
+
copyright notice that is included in or attached to the work
|
| 38 |
+
(an example is provided in the Appendix below).
|
| 39 |
+
|
| 40 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 41 |
+
form, that is based on (or derived from) the Work and for which the
|
| 42 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 43 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 44 |
+
of this License, Derivative Works shall not include works that remain
|
| 45 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 46 |
+
the Work and Derivative Works thereof.
|
| 47 |
+
|
| 48 |
+
"Contribution" shall mean any work of authorship, including
|
| 49 |
+
the original version of the Work and any modifications or additions
|
| 50 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 51 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 52 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 53 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 54 |
+
means any form of electronic, verbal, or written communication sent
|
| 55 |
+
to the Licensor or its representatives, including but not limited to
|
| 56 |
+
communication on electronic mailing lists, source code control systems,
|
| 57 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 58 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 59 |
+
excluding communication that is conspicuously marked or otherwise
|
| 60 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 61 |
+
|
| 62 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 63 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 64 |
+
subsequently incorporated within the Work.
|
| 65 |
+
|
| 66 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 67 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 68 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 69 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 70 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 71 |
+
Work and such Derivative Works in Source or Object form.
|
| 72 |
+
|
| 73 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 74 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 75 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 76 |
+
(except as stated in this section) patent license to make, have made,
|
| 77 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 78 |
+
where such license applies only to those patent claims licensable
|
| 79 |
+
by such Contributor that are necessarily infringed by their
|
| 80 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 81 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 82 |
+
institute patent litigation against any entity (including a
|
| 83 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 84 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 85 |
+
or contributory patent infringement, then any patent licenses
|
| 86 |
+
granted to You under this License for that Work shall terminate
|
| 87 |
+
as of the date such litigation is filed.
|
| 88 |
+
|
| 89 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 90 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 91 |
+
modifications, and in Source or Object form, provided that You
|
| 92 |
+
meet the following conditions:
|
| 93 |
+
|
| 94 |
+
(a) You must give any other recipients of the Work or
|
| 95 |
+
Derivative Works a copy of this License; and
|
| 96 |
+
|
| 97 |
+
(b) You must cause any modified files to carry prominent notices
|
| 98 |
+
stating that You changed the files; and
|
| 99 |
+
|
| 100 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 101 |
+
that You distribute, all copyright, patent, trademark, and
|
| 102 |
+
attribution notices from the Source form of the Work,
|
| 103 |
+
excluding those notices that do not pertain to any part of
|
| 104 |
+
the Derivative Works; and
|
| 105 |
+
|
| 106 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 107 |
+
distribution, then any Derivative Works that You distribute must
|
| 108 |
+
include a readable copy of the attribution notices contained
|
| 109 |
+
within such NOTICE file, excluding those notices that do not
|
| 110 |
+
pertain to any part of the Derivative Works, in at least one
|
| 111 |
+
of the following places: within a NOTICE text file distributed
|
| 112 |
+
as part of the Derivative Works; within the Source form or
|
| 113 |
+
documentation, if provided along with the Derivative Works; or,
|
| 114 |
+
within a display generated by the Derivative Works, if and
|
| 115 |
+
wherever such third-party notices normally appear. The contents
|
| 116 |
+
of the NOTICE file are for informational purposes only and
|
| 117 |
+
do not modify the License. You may add Your own attribution
|
| 118 |
+
notices within Derivative Works that You distribute, alongside
|
| 119 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 120 |
+
that such additional attribution notices cannot be construed
|
| 121 |
+
as modifying the License.
|
| 122 |
+
|
| 123 |
+
You may add Your own copyright statement to Your modifications and
|
| 124 |
+
may provide additional or different license terms and conditions
|
| 125 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 126 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 127 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 128 |
+
the conditions stated in this License.
|
| 129 |
+
|
| 130 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 131 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 132 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 133 |
+
this License, without any additional terms or conditions.
|
| 134 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 135 |
+
the terms of any separate license agreement you may have executed
|
| 136 |
+
with Licensor regarding such Contributions.
|
| 137 |
+
|
| 138 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 139 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 140 |
+
except as required for reasonable and customary use in describing the
|
| 141 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 142 |
+
|
| 143 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 144 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 145 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 146 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 147 |
+
implied, including, without limitation, any warranties or conditions
|
| 148 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 149 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 150 |
+
appropriateness of using or redistributing the Work and assume any
|
| 151 |
+
risks associated with Your exercise of permissions under this License.
|
| 152 |
+
|
| 153 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 154 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 155 |
+
unless required by applicable law (such as deliberate and grossly
|
| 156 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 157 |
+
liable to You for damages, including any direct, indirect, special,
|
| 158 |
+
incidental, or consequential damages of any character arising as a
|
| 159 |
+
result of this License or out of the use or inability to use the
|
| 160 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 161 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 162 |
+
other commercial damages or losses), even if such Contributor
|
| 163 |
+
has been advised of the possibility of such damages.
|
| 164 |
+
|
| 165 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 166 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 167 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 168 |
+
or other liability obligations and/or rights consistent with this
|
| 169 |
+
License. However, in accepting such obligations, You may act only
|
| 170 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 171 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 172 |
+
defend, and hold each Contributor harmless for any liability
|
| 173 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 174 |
+
of your accepting any such warranty or additional liability.
|
| 175 |
+
|
| 176 |
+
END OF TERMS AND CONDITIONS
|
| 177 |
+
|
| 178 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 179 |
+
|
| 180 |
+
To apply the Apache License to your work, attach the following
|
| 181 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 182 |
+
replaced with your own identifying information. (Don't include
|
| 183 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 184 |
+
comment syntax for the file format. We also recommend that a
|
| 185 |
+
file or class name and description of purpose be included on the
|
| 186 |
+
same "printed page" as the copyright notice for easier
|
| 187 |
+
identification within third-party archives.
|
| 188 |
+
|
| 189 |
+
Copyright [yyyy] [name of copyright owner]
|
| 190 |
+
|
| 191 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 192 |
+
you may not use this file except in compliance with the License.
|
| 193 |
+
You may obtain a copy of the License at
|
| 194 |
+
|
| 195 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 196 |
+
|
| 197 |
+
Unless required by applicable law or agreed to in writing, software
|
| 198 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 199 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 200 |
+
See the License for the specific language governing permissions and
|
| 201 |
+
limitations under the License.
|
README.md
CHANGED
|
@@ -1,12 +1,45 @@
|
|
| 1 |
-
---
|
| 2 |
-
title:
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: CDK_library
|
| 3 |
+
app_file: app.py
|
| 4 |
+
sdk: gradio
|
| 5 |
+
sdk_version: 4.36.1
|
| 6 |
+
---
|
| 7 |
+
# Molecular Descriptor Analysis and Chemical Reaction Visualization
|
| 8 |
+
|
| 9 |
+
This repository provides a web-based interface using Gradio for performing various molecular descriptor calculations, geometric analyses, substructure checks, similarity calculations, and chemical reaction visualizations. It integrates functionalities from RDKit and CDK libraries, enabling users to interactively analyze chemical data without needing extensive programming knowledge.
|
| 10 |
+
|
| 11 |
+
## Features
|
| 12 |
+
|
| 13 |
+
- **Calculate Descriptors:** Input SMILES strings and optionally include 3D coordinates to generate MolFile outputs and download descriptors as Excel.
|
| 14 |
+
- **Geometric Values:** Compute molecular weight, centroid, mean distance to centroid, and max distance to centroid for a given molecule and display its structure.
|
| 15 |
+
- **Check Substructure:** Check if a specified substructure is present within a molecule and visualize both structures.
|
| 16 |
+
- **Calculate Similarity:** Compute Tanimoto similarity coefficient between two molecules based on their SMILES strings.
|
| 17 |
+
- **Chemical Reaction:** Define chemical reactions using SMARTS notation, input reactant SMILES strings, and visualize the resulting reaction products interactively.
|
| 18 |
+
|
| 19 |
+
## Usage
|
| 20 |
+
|
| 21 |
+
1. **Setup Environment:**
|
| 22 |
+
- Ensure Python 3.7+ is installed.
|
| 23 |
+
- Install required packages using `pip install -r requirements.txt`.
|
| 24 |
+
|
| 25 |
+
2. **Run the Application:**
|
| 26 |
+
- Execute `python app.py` to start the Gradio interface locally.
|
| 27 |
+
- Access the interface at `http://localhost:7860`.
|
| 28 |
+
|
| 29 |
+
3. **Interface Navigation:**
|
| 30 |
+
- Use the tabs provided to input SMILES strings and parameters for the desired chemical analysis or reaction.
|
| 31 |
+
- Click buttons to perform calculations or reactions and view results interactively.
|
| 32 |
+
|
| 33 |
+
4. **Contribute:**
|
| 34 |
+
- Fork the repository, make your changes, and submit a pull request.
|
| 35 |
+
- Report any issues or suggest improvements through GitHub issues.
|
| 36 |
+
|
| 37 |
+
## Dependencies
|
| 38 |
+
|
| 39 |
+
- RDKit: Open-source cheminformatics software.
|
| 40 |
+
- CDK: Chemistry Development Kit for molecular descriptor calculation.
|
| 41 |
+
- Gradio: User-friendly Python library for creating UIs around ML models.
|
| 42 |
+
|
| 43 |
+
## License
|
| 44 |
+
|
| 45 |
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
__pycache__/cdkit1.cpython-311.pyc
ADDED
|
Binary file (3.82 kB). View file
|
|
|
app.py
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
from CDK_pywrapper import CDK
|
| 3 |
+
from rdkit import Chem
|
| 4 |
+
from rdkit.Chem import Descriptors, Draw, AllChem, rdMolDescriptors
|
| 5 |
+
from rdkit.Chem.Fingerprints import FingerprintMols
|
| 6 |
+
from rdkit.DataStructs import TanimotoSimilarity
|
| 7 |
+
import pandas as pd
|
| 8 |
+
import numpy as np
|
| 9 |
+
import tempfile
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
# Function to convert SMILES to MolFile
|
| 13 |
+
def convert_smiles_to_mol(smiles_list, checkbox):
|
| 14 |
+
|
| 15 |
+
if checkbox == True:
|
| 16 |
+
cdk = CDK(ignore_3D=False)
|
| 17 |
+
else:
|
| 18 |
+
cdk = CDK()
|
| 19 |
+
smiles_list = list(smiles_list.split(','))
|
| 20 |
+
try:
|
| 21 |
+
mols = [Chem.AddHs(Chem.MolFromSmiles(smiles)) for smiles in smiles_list]
|
| 22 |
+
|
| 23 |
+
molfile = cdk.calculate(mols)
|
| 24 |
+
|
| 25 |
+
try:
|
| 26 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix='.xlsx') as tmp:
|
| 27 |
+
file_path = tmp.name
|
| 28 |
+
molfile.to_excel(file_path, index=False)
|
| 29 |
+
return molfile,file_path
|
| 30 |
+
except Exception as e:
|
| 31 |
+
return molfile,str(e)
|
| 32 |
+
|
| 33 |
+
except Exception as e:
|
| 34 |
+
return str(e), str(e)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
# Function to calculate Molecular Weight
|
| 38 |
+
def calculate_molecular_weight(smiles):
|
| 39 |
+
if smiles is None:
|
| 40 |
+
return "SMILES string is None"
|
| 41 |
+
try:
|
| 42 |
+
molecule = Chem.MolFromSmiles(smiles)
|
| 43 |
+
if molecule is None:
|
| 44 |
+
return "Invalid SMILES"
|
| 45 |
+
mol_weight = Descriptors.MolWt(molecule)
|
| 46 |
+
img = Draw.MolToImage(molecule)
|
| 47 |
+
return mol_weight, img
|
| 48 |
+
except Exception as e:
|
| 49 |
+
return str(e)
|
| 50 |
+
|
| 51 |
+
def get_geometric_descriptors(smiles):
|
| 52 |
+
try:
|
| 53 |
+
mol = Chem.MolFromSmiles(smiles)
|
| 54 |
+
if mol is None:
|
| 55 |
+
return "Invalid SMILES string"
|
| 56 |
+
|
| 57 |
+
# Add hydrogens and compute 3D coordinates
|
| 58 |
+
mol = Chem.AddHs(mol)
|
| 59 |
+
AllChem.EmbedMolecule(mol, AllChem.ETKDG())
|
| 60 |
+
AllChem.UFFOptimizeMolecule(mol)
|
| 61 |
+
|
| 62 |
+
# Calculate geometric descriptors
|
| 63 |
+
conformer = mol.GetConformer()
|
| 64 |
+
coords = conformer.GetPositions()
|
| 65 |
+
|
| 66 |
+
centroid = np.mean(coords, axis=0)
|
| 67 |
+
centroid = np.round(centroid, 12)
|
| 68 |
+
distances = np.linalg.norm(coords - centroid, axis=1)
|
| 69 |
+
mol_weight = Descriptors.MolWt(mol)
|
| 70 |
+
|
| 71 |
+
geometric_descriptors = {
|
| 72 |
+
'Molecular Weight': mol_weight,
|
| 73 |
+
'Centroid': centroid.tolist(),
|
| 74 |
+
'Mean Distance To Centroid': np.mean(distances),
|
| 75 |
+
'Max Distance To Centroid': np.max(distances)
|
| 76 |
+
}
|
| 77 |
+
img = Draw.MolToImage(mol)
|
| 78 |
+
df = pd.DataFrame([geometric_descriptors])
|
| 79 |
+
return df.T,img
|
| 80 |
+
except Exception as e:
|
| 81 |
+
return str(e), str(e)
|
| 82 |
+
|
| 83 |
+
# Function to check if a substructure is present
|
| 84 |
+
def check_substructure(smiles, substructure_smiles):
|
| 85 |
+
# Convert the SMILES strings to RDKit molecule objects
|
| 86 |
+
molecule = Chem.MolFromSmiles(smiles)
|
| 87 |
+
substructure = Chem.MolFromSmiles(substructure_smiles)
|
| 88 |
+
|
| 89 |
+
# Check if the molecule is None (invalid SMILES)
|
| 90 |
+
if molecule is None or substructure is None:
|
| 91 |
+
return "Error","Error","Invalid SMILES string provided."
|
| 92 |
+
|
| 93 |
+
# Use RDKit's HasSubstructMatch to check for the substructure
|
| 94 |
+
val = molecule.HasSubstructMatch(substructure)
|
| 95 |
+
img1 = Draw.MolToImage(molecule)
|
| 96 |
+
|
| 97 |
+
if val:
|
| 98 |
+
try:
|
| 99 |
+
molecule = Chem.MolFromSmiles(smiles)
|
| 100 |
+
sub_molecule = Chem.MolFromSmiles(substructure_smiles)
|
| 101 |
+
img1 = Draw.MolToImage(molecule)
|
| 102 |
+
img2 = Draw.MolToImage(sub_molecule)
|
| 103 |
+
return img1, img2, "Substructure is present."
|
| 104 |
+
except Exception as e:
|
| 105 |
+
return str(e), str(e), "Substructure is present."
|
| 106 |
+
else:
|
| 107 |
+
return img1,"NO Image","Substructure is not present."
|
| 108 |
+
|
| 109 |
+
def calculate_similarity(smiles1, smiles2):
|
| 110 |
+
try:
|
| 111 |
+
mol1 = Chem.MolFromSmiles(smiles1)
|
| 112 |
+
mol2 = Chem.MolFromSmiles(smiles2)
|
| 113 |
+
|
| 114 |
+
if mol1 is None or mol2 is None:
|
| 115 |
+
return "Invalid SMILES string"
|
| 116 |
+
|
| 117 |
+
fp1 = rdMolDescriptors.GetMorganFingerprintAsBitVect(mol1, radius=2, nBits=2048)
|
| 118 |
+
fp2 = rdMolDescriptors.GetMorganFingerprintAsBitVect(mol2, radius=2, nBits=2048)
|
| 119 |
+
|
| 120 |
+
similarity = TanimotoSimilarity(fp1, fp2)
|
| 121 |
+
return similarity
|
| 122 |
+
except Exception as e:
|
| 123 |
+
return str(e)
|
| 124 |
+
|
| 125 |
+
def perform_reaction(reactant1_smiles, reactant2_smiles, reaction_smarts):
|
| 126 |
+
try:
|
| 127 |
+
# Define the reaction using SMARTS provided by the user
|
| 128 |
+
reaction = AllChem.ReactionFromSmarts(reaction_smarts)
|
| 129 |
+
|
| 130 |
+
# Convert SMILES to RDKit molecules
|
| 131 |
+
reactant1 = Chem.MolFromSmiles(reactant1_smiles)
|
| 132 |
+
reactant2 = Chem.MolFromSmiles(reactant2_smiles)
|
| 133 |
+
|
| 134 |
+
if reactant1 is None or reactant2 is None:
|
| 135 |
+
return "Invalid SMILES string(s)", None
|
| 136 |
+
|
| 137 |
+
# Run the reaction
|
| 138 |
+
products = reaction.RunReactants((reactant1, reactant2))
|
| 139 |
+
|
| 140 |
+
# Create a grid image of reactants and products
|
| 141 |
+
all_mols = [reactant1, reactant2]
|
| 142 |
+
legends = ["Reactant 1", "Reactant 2"]
|
| 143 |
+
for i, product_set in enumerate(products):
|
| 144 |
+
for j, product in enumerate(product_set):
|
| 145 |
+
all_mols.append(product)
|
| 146 |
+
legends.append(f'Product {i+1}.{j+1}')
|
| 147 |
+
|
| 148 |
+
img = Draw.MolsToGridImage(all_mols, molsPerRow=4, subImgSize=(300, 300), legends=legends)
|
| 149 |
+
return "Reaction Successful", img
|
| 150 |
+
|
| 151 |
+
except Exception as e:
|
| 152 |
+
return str(e), None
|
| 153 |
+
|
| 154 |
+
# Gradio Interface
|
| 155 |
+
def generate_reaction_image(reaction_smarts,reactant1_smiles, reactant2_smiles):
|
| 156 |
+
result, img = perform_reaction(reactant1_smiles, reactant2_smiles, reaction_smarts)
|
| 157 |
+
return result, img
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
# Gradio Interface
|
| 163 |
+
with gr.Blocks(theme='earneleh/paris') as demo:
|
| 164 |
+
gr.Markdown("### CDK Functionality with Gradio Interface")
|
| 165 |
+
|
| 166 |
+
with gr.Tab("Calculate Descriptors"):
|
| 167 |
+
smiles_input = gr.Textbox(label="SMILES", info="Enter SMILES separated by comma")
|
| 168 |
+
checkbox = gr.Checkbox(label="Include 3D Coordinates")
|
| 169 |
+
molfile_output = gr.Textbox(label="MolFile", lines=10)
|
| 170 |
+
convert_button = gr.Button("Calculate")
|
| 171 |
+
download_link = gr.File(label="Download Descriptors as Excel")
|
| 172 |
+
convert_button.click(fn=convert_smiles_to_mol, inputs=[smiles_input, checkbox], outputs=[molfile_output,download_link])
|
| 173 |
+
|
| 174 |
+
with gr.Tab("Geometric Values"):
|
| 175 |
+
with gr.Row():
|
| 176 |
+
with gr.Column(min_width=800):
|
| 177 |
+
smiles_input_mw = gr.Textbox(label="SMILE")
|
| 178 |
+
weight_output = gr.TextArea(label="Geometric Values", lines=8, show_copy_button=True)
|
| 179 |
+
calculate_button = gr.Button("Calculate")
|
| 180 |
+
with gr.Column():
|
| 181 |
+
image_output = gr.Image(label="Molecular Structure", height=400, width=500)
|
| 182 |
+
calculate_button.click(fn=get_geometric_descriptors, inputs=smiles_input_mw,outputs=[weight_output, image_output])
|
| 183 |
+
|
| 184 |
+
with gr.Tab("Check Substructure"):
|
| 185 |
+
with gr.Row():
|
| 186 |
+
with gr.Column():
|
| 187 |
+
smiles_input_sub = gr.Textbox(label="SMILES")
|
| 188 |
+
substructure_input = gr.Textbox(label="Substructure SMILES")
|
| 189 |
+
substructure_output = gr.Label(label="Is Substructure Present?")
|
| 190 |
+
check_button = gr.Button("Check")
|
| 191 |
+
with gr.Column():
|
| 192 |
+
image_output1 = gr.Image(label="Molecular Structure", height=350, width=500)
|
| 193 |
+
image_output2 = gr.Image(label="Sub_Molecular Structure", height=350, width=500)
|
| 194 |
+
check_button.click(fn=check_substructure, inputs=[smiles_input_sub, substructure_input], outputs=[image_output1, image_output2, substructure_output])
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
with gr.Tab("Calculate Similarity"):
|
| 198 |
+
smiles_input1 = gr.Textbox(label="SMILES 1")
|
| 199 |
+
smiles_input2 = gr.Textbox(label="SMILES 2")
|
| 200 |
+
similarity_output = gr.Number(label="Similarity (Tanimoto)")
|
| 201 |
+
calculate_button_sim = gr.Button("Calculate Similarity")
|
| 202 |
+
calculate_button_sim.click(fn=calculate_similarity, inputs=[smiles_input1, smiles_input2], outputs=similarity_output)
|
| 203 |
+
|
| 204 |
+
with gr.Tab("Chemical Reaction"):
|
| 205 |
+
reaction_smarts_input = gr.Textbox(label="Reaction SMARTS",value="[O:2]=[C:1][OH].[N:3]>>[O:2]=[C:1][N:3]")
|
| 206 |
+
smiles_input1 = gr.Textbox(label="Reactant 1 SMILES", value="OC=O")
|
| 207 |
+
smiles_input2 = gr.Textbox(label="Reactant 2 SMILES", value= "NCC")
|
| 208 |
+
calculate_button = gr.Button("Perform Reaction")
|
| 209 |
+
|
| 210 |
+
result_output = gr.Label(label="Result")
|
| 211 |
+
image_output = gr.Image(label="Reaction Image", interactive=True)
|
| 212 |
+
calculate_button.click(fn=generate_reaction_image, inputs=[reaction_smarts_input, smiles_input1, smiles_input2], outputs=[result_output, image_output])
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
# Launch Gradio Interface
|
| 216 |
+
demo.launch(share=True)
|
readme.txt
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
CDK Functionality with Gradio Interface
|
| 2 |
+
Calculate Descriptors Tab
|
| 3 |
+
Input:
|
| 4 |
+
|
| 5 |
+
SMILES: Enter SMILES strings separated by commas.
|
| 6 |
+
Include 3D Coordinates Checkbox: Optional checkbox to include 3D coordinates in output.
|
| 7 |
+
Outputs:
|
| 8 |
+
|
| 9 |
+
MolFile: Textbox displaying MolFile data for the input SMILES.
|
| 10 |
+
Download Descriptors as Excel: File download link for the descriptors in Excel format.
|
| 11 |
+
Geometric Values Tab
|
| 12 |
+
Input:
|
| 13 |
+
|
| 14 |
+
SMILES: Enter a single SMILES string.
|
| 15 |
+
Outputs:
|
| 16 |
+
|
| 17 |
+
Geometric Values: Text area displaying molecular weight, centroid, mean distance to centroid, and max distance to centroid.
|
| 18 |
+
Molecular Structure: Image display of the input molecule.
|
| 19 |
+
Check Substructure Tab
|
| 20 |
+
Inputs:
|
| 21 |
+
|
| 22 |
+
SMILES: Input SMILES for the molecule.
|
| 23 |
+
Substructure SMILES: SMILES for the substructure to check.
|
| 24 |
+
Outputs:
|
| 25 |
+
|
| 26 |
+
Molecular Structure: Image of the molecule with the substructure highlighted.
|
| 27 |
+
Substructure Molecular Structure: Image of the substructure.
|
| 28 |
+
Is Substructure Present?: Text label indicating presence or absence of the substructure.
|
| 29 |
+
Calculate Similarity Tab
|
| 30 |
+
Inputs:
|
| 31 |
+
|
| 32 |
+
SMILES 1: First SMILES string.
|
| 33 |
+
SMILES 2: Second SMILES string.
|
| 34 |
+
Output:
|
| 35 |
+
|
| 36 |
+
Similarity (Tanimoto): Numeric value indicating similarity between the two molecules.
|
| 37 |
+
Chemical Reaction Tab
|
| 38 |
+
Inputs:
|
| 39 |
+
|
| 40 |
+
Reaction SMARTS: SMARTS string defining the chemical reaction.
|
| 41 |
+
Reactant 1 SMILES: SMILES string for the first reactant.
|
| 42 |
+
Reactant 2 SMILES: SMILES string for the second reactant.
|
| 43 |
+
Outputs:
|
| 44 |
+
|
| 45 |
+
Result: Text label indicating the success or failure of the reaction.
|
| 46 |
+
Reaction Image: Interactive image showing reactants and products.
|
requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
CDK-pywrapper
|
| 2 |
+
gradio
|
| 3 |
+
rdkit
|
| 4 |
+
pandas
|
| 5 |
+
numpy
|
| 6 |
+
tempfile
|
venv/Lib/site-packages/CDK_pywrapper-0.1.0.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
pip
|
venv/Lib/site-packages/CDK_pywrapper-0.1.0.dist-info/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2023 Olivier J. M. Béquignon
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
venv/Lib/site-packages/CDK_pywrapper-0.1.0.dist-info/METADATA
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.1
|
| 2 |
+
Name: CDK_pywrapper
|
| 3 |
+
Version: 0.1.0
|
| 4 |
+
Summary: Python wrapper for CDK molecular descriptors and fingerprints
|
| 5 |
+
Home-page: https://github.com/OlivierBeq/CDK_pywrapper
|
| 6 |
+
Author: Olivier J. M. Béquignon
|
| 7 |
+
Author-email: "olivier.bequignon.maintainer@gmail.com"
|
| 8 |
+
Maintainer: Olivier J. M. Béquignon
|
| 9 |
+
Maintainer-email: "olivier.bequignon.maintainer@gmail.com"
|
| 10 |
+
Keywords: Chemistry Development Kit,molecular descriptors,molecular fingerprints,cheminformatics,QSAR
|
| 11 |
+
Classifier: Development Status :: 5 - Production/Stable
|
| 12 |
+
Classifier: License :: OSI Approved :: MIT License
|
| 13 |
+
Classifier: Programming Language :: Python :: 3.10
|
| 14 |
+
Classifier: Programming Language :: Python :: 3.9
|
| 15 |
+
Classifier: Programming Language :: Python :: 3.8
|
| 16 |
+
Classifier: Programming Language :: Python :: 3.7
|
| 17 |
+
Classifier: Programming Language :: Python :: 3.6
|
| 18 |
+
Description-Content-Type: text/markdown
|
| 19 |
+
License-File: LICENSE
|
| 20 |
+
Requires-Dist: more-itertools
|
| 21 |
+
Requires-Dist: numpy
|
| 22 |
+
Requires-Dist: pandas
|
| 23 |
+
Requires-Dist: rdkit
|
| 24 |
+
Requires-Dist: install-jdk ==0.3.0
|
| 25 |
+
Requires-Dist: bounded-pool-executor ==0.0.3
|
| 26 |
+
Provides-Extra: docs
|
| 27 |
+
Requires-Dist: sphinx ; extra == 'docs'
|
| 28 |
+
Requires-Dist: sphinx-rtd-theme ; extra == 'docs'
|
| 29 |
+
Requires-Dist: sphinx-autodoc-typehints ; extra == 'docs'
|
| 30 |
+
Provides-Extra: testing
|
| 31 |
+
Requires-Dist: pytest ; extra == 'testing'
|
| 32 |
+
|
| 33 |
+
[](https://opensource.org/licenses/MIT)
|
| 34 |
+
|
| 35 |
+
# CDK Python wrapper
|
| 36 |
+
|
| 37 |
+
Python wrapper to ease the calculation of [CDK](https://cdk.github.io/) molecular descriptors and fingerprints.
|
| 38 |
+
|
| 39 |
+
## Installation
|
| 40 |
+
|
| 41 |
+
From source:
|
| 42 |
+
|
| 43 |
+
git clone https://github.com/OlivierBeq/CDK_pywrapper.git
|
| 44 |
+
pip install ./CDK_pywrapper
|
| 45 |
+
|
| 46 |
+
with pip:
|
| 47 |
+
|
| 48 |
+
```bash
|
| 49 |
+
pip install CDK-pywrapper
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
### Get started
|
| 53 |
+
|
| 54 |
+
```python
|
| 55 |
+
from CDK_pywrapper import CDK
|
| 56 |
+
from rdkit import Chem
|
| 57 |
+
|
| 58 |
+
smiles_list = [
|
| 59 |
+
# erlotinib
|
| 60 |
+
"n1cnc(c2cc(c(cc12)OCCOC)OCCOC)Nc1cc(ccc1)C#C",
|
| 61 |
+
# midecamycin
|
| 62 |
+
"CCC(=O)O[C@@H]1CC(=O)O[C@@H](C/C=C/C=C/[C@@H]([C@@H](C[C@@H]([C@@H]([C@H]1OC)O[C@H]2[C@@H]([C@H]([C@@H]([C@H](O2)C)O[C@H]3C[C@@]([C@H]([C@@H](O3)C)OC(=O)CC)(C)O)N(C)C)O)CC=O)C)O)C",
|
| 63 |
+
# selenofolate
|
| 64 |
+
"C1=CC(=CC=C1C(=O)NC(CCC(=O)OCC[Se]C#N)C(=O)O)NCC2=CN=C3C(=N2)C(=O)NC(=N3)N",
|
| 65 |
+
# cisplatin
|
| 66 |
+
"N.N.Cl[Pt]Cl"
|
| 67 |
+
]
|
| 68 |
+
mols = [Chem.AddHs(Chem.MolFromSmiles(smiles)) for smiles in smiles_list]
|
| 69 |
+
|
| 70 |
+
cdk = CDK()
|
| 71 |
+
print(cdk.calculate(mols))
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
The above calculates 222 molecular descriptors (23 1D and 200 2D).<br/>
|
| 75 |
+
|
| 76 |
+
The additional 65 three-dimensional (3D) descriptors may be obtained with the following:
|
| 77 |
+
:warning: Molecules are required to have conformers for 3D descriptors to be calculated.<br/>
|
| 78 |
+
|
| 79 |
+
```python
|
| 80 |
+
from rdkit.Chem import AllChem
|
| 81 |
+
|
| 82 |
+
for mol in mols:
|
| 83 |
+
_ = AllChem.EmbedMolecule(mol)
|
| 84 |
+
|
| 85 |
+
cdk = CDK(ignore_3D=False)
|
| 86 |
+
print(cdk.calculate(mols))
|
| 87 |
+
```
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
To obtain molecular fingerprint, one can used the following:
|
| 91 |
+
|
| 92 |
+
```python
|
| 93 |
+
from CDK_pywrapper import CDK, FPType
|
| 94 |
+
cdk = CDK(fingerprint=.PubchemFP)
|
| 95 |
+
print(cdk.calculate(mols))
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
The following fingerprints can be calculated:
|
| 99 |
+
|
| 100 |
+
| FPType | Fingerprint name |
|
| 101 |
+
|-----------|------------------------------------------------------------------------------------|
|
| 102 |
+
| FP | CDK fingerprint |
|
| 103 |
+
| ExtFP | Extended CDK fingerprint (includes 25 bits for ring features and isotopic masses) |
|
| 104 |
+
| EStateFP | Electrotopological state fingerprint (79 bits) |
|
| 105 |
+
| GraphFP | CDK fingerprinter ignoring bond orders |
|
| 106 |
+
| MACCSFP | Public MACCS fingerprint |
|
| 107 |
+
| PubchemFP | PubChem substructure fingerprint |
|
| 108 |
+
| SubFP | Fingerprint describing 307 substructures |
|
| 109 |
+
| KRFP | Klekota-Roth fingerprint |
|
| 110 |
+
| AP2DFP | Atom pair 2D fingerprint as implemented in PaDEL |
|
| 111 |
+
| HybridFP | CDK fingerprint ignoring aromaticity |
|
| 112 |
+
| LingoFP | LINGO fingerprint |
|
| 113 |
+
| SPFP | Fingerprint based on the shortest paths between two atoms |
|
| 114 |
+
| SigFP | Signature fingerprint |
|
| 115 |
+
| CircFP | Circular fingerprint |
|
| 116 |
+
|
| 117 |
+
## Documentation
|
| 118 |
+
|
| 119 |
+
```python
|
| 120 |
+
class CDK(ignore_3D=True, fingerprint=None, nbits=1024, depth=6):
|
| 121 |
+
```
|
| 122 |
+
|
| 123 |
+
Constructor of a CDK calculator for molecular descriptors or fingerprints
|
| 124 |
+
|
| 125 |
+
Parameters:
|
| 126 |
+
|
| 127 |
+
- ***ignore_3D : bool***
|
| 128 |
+
Should 3D molecular descriptors be calculated (default: False). Ignored if a fingerprint is set.
|
| 129 |
+
- ***fingerprint : FPType***
|
| 130 |
+
Type of fingerprint to calculate (default: None). If None, calculate descriptors.
|
| 131 |
+
- ***nbits : int***
|
| 132 |
+
Number of bits in the fingerprint.
|
| 133 |
+
- ***depth : int***
|
| 134 |
+
Depth of the fingerprint.
|
| 135 |
+
<br/>
|
| 136 |
+
<br/>
|
| 137 |
+
```python
|
| 138 |
+
def calculate(mols, show_banner=True, njobs=1, chunksize=1000):
|
| 139 |
+
```
|
| 140 |
+
|
| 141 |
+
Default method to calculate CDK molecular descriptors and fingerprints.
|
| 142 |
+
|
| 143 |
+
Parameters:
|
| 144 |
+
|
| 145 |
+
- ***mols : Iterable[Chem.Mol]***
|
| 146 |
+
RDKit molecule objects for which to obtain CDK descriptors.
|
| 147 |
+
- ***show_banner : bool***
|
| 148 |
+
Displays default notice about CDK.
|
| 149 |
+
- ***njobs : int***
|
| 150 |
+
Maximum number of simultaneous processes.
|
| 151 |
+
- ***chunksize : int***
|
| 152 |
+
Maximum number of molecules each process is charged of.
|
venv/Lib/site-packages/CDK_pywrapper-0.1.0.dist-info/RECORD
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
CDK_pywrapper-0.1.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 2 |
+
CDK_pywrapper-0.1.0.dist-info/LICENSE,sha256=8XfEdB_CgNewtZLb9xPkJpIMb_iRCGFbnM2B86ynvgg,1100
|
| 3 |
+
CDK_pywrapper-0.1.0.dist-info/METADATA,sha256=84SFhHtsKNoBEp1cqd-l-f7CF4GuKH3NFttjwQIUi6E,5737
|
| 4 |
+
CDK_pywrapper-0.1.0.dist-info/RECORD,,
|
| 5 |
+
CDK_pywrapper-0.1.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 6 |
+
CDK_pywrapper-0.1.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
| 7 |
+
CDK_pywrapper-0.1.0.dist-info/top_level.txt,sha256=LrqN34bTqsjyx5SUadb3ONALge1zi4QhZAcuanHUiMA,14
|
| 8 |
+
CDK_pywrapper/CDKdesc.jar,sha256=e4JY7QJJB1C8yI5bsuwyGGtZXcvUz750DBuWUX1TF9k,47157078
|
| 9 |
+
CDK_pywrapper/__init__.py,sha256=P5gmu31aXYXpzpOqVW-L-wa43uxWT72QxYoiEDlsZaU,152
|
| 10 |
+
CDK_pywrapper/__pycache__/__init__.cpython-311.pyc,,
|
| 11 |
+
CDK_pywrapper/__pycache__/cdk_pywrapper.cpython-311.pyc,,
|
| 12 |
+
CDK_pywrapper/__pycache__/utils.cpython-311.pyc,,
|
| 13 |
+
CDK_pywrapper/cdk_pywrapper.py,sha256=e8Fi2_A0G50l_QTLy9dFnefkmW2HapmmZVSJQY2rObg,16483
|
| 14 |
+
CDK_pywrapper/descs.json,sha256=azoT6OdJZelocf-qxjLwscMmSOBYTytLRWhVSXeEXUE,68538
|
| 15 |
+
CDK_pywrapper/utils.py,sha256=w1dLTNlK4cu1QFeYuWo57hIuD0r-e2A2cz-pe2n4CKc,3224
|
venv/Lib/site-packages/CDK_pywrapper-0.1.0.dist-info/REQUESTED
ADDED
|
File without changes
|
venv/Lib/site-packages/CDK_pywrapper-0.1.0.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: bdist_wheel (0.42.0)
|
| 3 |
+
Root-Is-Purelib: true
|
| 4 |
+
Tag: py3-none-any
|
| 5 |
+
|
venv/Lib/site-packages/CDK_pywrapper-0.1.0.dist-info/top_level.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
CDK_pywrapper
|
venv/Lib/site-packages/CDK_pywrapper/CDKdesc.jar
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:7b8258ed02490750bcc88e5bb2ec32186b595dcbd4cfbe740c1b96517d5317d9
|
| 3 |
+
size 47157078
|
venv/Lib/site-packages/CDK_pywrapper/__init__.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8
|
| 2 |
+
|
| 3 |
+
"""Wrapper for CDK molecular descriptors and fingerprints"""
|
| 4 |
+
|
| 5 |
+
from .cdk_pywrapper import CDK, FPType
|
| 6 |
+
|
| 7 |
+
__version__ = "0.1.0"
|
venv/Lib/site-packages/CDK_pywrapper/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (362 Bytes). View file
|
|
|
venv/Lib/site-packages/CDK_pywrapper/__pycache__/cdk_pywrapper.cpython-311.pyc
ADDED
|
Binary file (21 kB). View file
|
|
|
venv/Lib/site-packages/CDK_pywrapper/__pycache__/utils.cpython-311.pyc
ADDED
|
Binary file (5.8 kB). View file
|
|
|
venv/Lib/site-packages/CDK_pywrapper/cdk_pywrapper.py
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8
|
| 2 |
+
|
| 3 |
+
"""Python wrapper for CDK descriptors and fingerprints"""
|
| 4 |
+
|
| 5 |
+
from __future__ import annotations
|
| 6 |
+
|
| 7 |
+
import io
|
| 8 |
+
import multiprocessing
|
| 9 |
+
import os
|
| 10 |
+
import subprocess
|
| 11 |
+
import warnings
|
| 12 |
+
from copy import deepcopy
|
| 13 |
+
from enum import Enum, auto
|
| 14 |
+
from subprocess import Popen, PIPE
|
| 15 |
+
from typing import Iterable, List, Optional
|
| 16 |
+
|
| 17 |
+
import more_itertools
|
| 18 |
+
import numpy as np
|
| 19 |
+
import pandas as pd
|
| 20 |
+
from bounded_pool_executor import BoundedProcessPoolExecutor
|
| 21 |
+
from rdkit import Chem
|
| 22 |
+
from rdkit.rdBase import BlockLogs
|
| 23 |
+
|
| 24 |
+
from .utils import install_java, mktempfile, needsHs
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class FPType(Enum):
|
| 28 |
+
FP = auto()
|
| 29 |
+
ExtFP = auto()
|
| 30 |
+
EStateFP = auto()
|
| 31 |
+
GraphFP = auto()
|
| 32 |
+
MACCSFP = auto()
|
| 33 |
+
PubchemFP = auto()
|
| 34 |
+
SubFP = auto()
|
| 35 |
+
KRFP = auto()
|
| 36 |
+
AP2DFP = auto()
|
| 37 |
+
HybridFP = auto()
|
| 38 |
+
LingoFP = auto()
|
| 39 |
+
SPFP = auto()
|
| 40 |
+
SigFP = auto()
|
| 41 |
+
CircFP = auto()
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class CDK:
|
| 45 |
+
"""Wrapper to obtain molecular descriptor from CDK."""
|
| 46 |
+
|
| 47 |
+
lock = multiprocessing.RLock() # Ensure installation of JRE is thread safe
|
| 48 |
+
# Path to the JAR file
|
| 49 |
+
_jarfile = os.path.abspath(os.path.join(__file__, os.pardir, 'CDKdesc.jar'))
|
| 50 |
+
|
| 51 |
+
def __init__(self, ignore_3D: bool = True, fingerprint: FPType = None, nbits: int = 1024, depth: int = 6,
|
| 52 |
+
backend_smiles: bool = False):
|
| 53 |
+
"""Instantiate a wrapper to calculate CDK molecular descriptors or a fingerprint.
|
| 54 |
+
|
| 55 |
+
:param ignore_3D: whether to include 3D molecular descriptors
|
| 56 |
+
:param fingerprint: a fingerprint type to be calculated (default: None, calculates descriptors)
|
| 57 |
+
:param nbits: number of bits (default: 1024 unless the fingerprint has a fixed size)
|
| 58 |
+
:param depth: depth of the fingerprint (default: 6 unless the fingerprint does not depend on depth)
|
| 59 |
+
:param backend_smiles: use SMILES as the interchange format to discuss with the CDKdesc backend;
|
| 60 |
+
the default (i.e. backend_smiles=False) makes use of the V2000 SD format; is ignored if ignore_3D=True.
|
| 61 |
+
"""
|
| 62 |
+
# Ensure the jar file exists
|
| 63 |
+
if not os.path.isfile(self._jarfile):
|
| 64 |
+
raise IOError('The required CDKdesc JAR file is not present. Reinstall CDK-pywrapper.')
|
| 65 |
+
if fingerprint is not None:
|
| 66 |
+
if not isinstance(fingerprint, FPType):
|
| 67 |
+
raise TypeError(f'Fingerprint type not supported: {fingerprint}')
|
| 68 |
+
self.include_3D = not ignore_3D
|
| 69 |
+
self.fingerprint = None if fingerprint is None else fingerprint.name
|
| 70 |
+
self.nbits = nbits
|
| 71 |
+
self.depth = depth
|
| 72 |
+
self.backend_smiles = backend_smiles and ignore_3D # if include_3D, then always False
|
| 73 |
+
|
| 74 |
+
def calculate(self, mols: List[Chem.Mol], show_banner: bool = True, cdk_smiles: bool = False,
|
| 75 |
+
njobs: int = 1, chunksize: Optional[int] = 1000) -> pd.DataFrame:
|
| 76 |
+
"""Calculate molecular fingerprints.
|
| 77 |
+
|
| 78 |
+
:param mols: RDKit molecules for which descriptors/fingerprints should be calculated
|
| 79 |
+
(must have 3D conformers if calculating descriptors)
|
| 80 |
+
:param show_banner: If True, show notice on this package usage
|
| 81 |
+
:param cdk_smiles: If True, generate the canonical SMILES (generated by CDK) of molecules parsed by CDK
|
| 82 |
+
:param njobs: number of concurrent processes
|
| 83 |
+
:param chunksize: number of molecules to be processed by a process; ignored if njobs is 1
|
| 84 |
+
:return: a pandas DataFrame containing all CDK descriptor/fingerprint values
|
| 85 |
+
"""
|
| 86 |
+
if show_banner:
|
| 87 |
+
self._show_banner()
|
| 88 |
+
if njobs < 0:
|
| 89 |
+
njobs = os.cpu_count() - njobs + 1
|
| 90 |
+
# Parallelize should need be
|
| 91 |
+
if njobs > 1:
|
| 92 |
+
with BoundedProcessPoolExecutor(max_workers=njobs) as worker:
|
| 93 |
+
futures = [worker.submit(self._multiproc_calculate, list(chunk), cdk_smiles)
|
| 94 |
+
for chunk in more_itertools.batched(mols, chunksize)
|
| 95 |
+
]
|
| 96 |
+
return (pd.concat([future.result() for future in futures]).
|
| 97 |
+
reset_index(drop=True)
|
| 98 |
+
)
|
| 99 |
+
# Single process
|
| 100 |
+
return self._calculate(list(mols), cdk_smiles)
|
| 101 |
+
|
| 102 |
+
def _show_banner(self):
|
| 103 |
+
"""Print info message for citing."""
|
| 104 |
+
print("""The Chemistry Development Kit (CDK) is a collection of modular Java libraries
|
| 105 |
+
for processing chemical information (Cheminformatics). It can compute 14 different fingerprint
|
| 106 |
+
types and 287 molecular descriptors (it requires 3D molecular structures for the latter).
|
| 107 |
+
|
| 108 |
+
###################################
|
| 109 |
+
|
| 110 |
+
Should you publish results based on the PaDEL descriptors,
|
| 111 |
+
please cite:
|
| 112 |
+
|
| 113 |
+
Willighagen et al., (2017) J. Cheminf. 9(3), doi:10.1186/s13321-017-0220-4,
|
| 114 |
+
May and Steinbeck., (2014) J. Cheminf., doi:10.1186/1758-2946-6-3,
|
| 115 |
+
Steinbeck et al., (2006) Curr. Pharm. Des. 12(17):2111-2120, doi:10.2174/138161206777585274,
|
| 116 |
+
Steinbeck et al., (2003) J. Chem. Inf. Comput. Sci. 43(2):493-500, doi:10.1021/ci025584y.
|
| 117 |
+
|
| 118 |
+
###################################
|
| 119 |
+
|
| 120 |
+
""")
|
| 121 |
+
|
| 122 |
+
def _prepare_command(self, mols: List[Chem.Mol], cdk_smiles: bool = False) -> str:
|
| 123 |
+
"""Create the CDK command to be run to obtain molecular descriptors.
|
| 124 |
+
|
| 125 |
+
:param mols: molecules to obtained molecular descriptors of
|
| 126 |
+
:param cdk_smiles: If True, generate the canonical SMILES (generated by CDK) of molecules parsed by CDK
|
| 127 |
+
:return: The command to run.
|
| 128 |
+
"""
|
| 129 |
+
# 1) Ensure JRE is accessible
|
| 130 |
+
with self.lock:
|
| 131 |
+
self._java_path = install_java(19)
|
| 132 |
+
# 2) Create temp SD v2k or SMILES file
|
| 133 |
+
self._tmp_input = mktempfile('molecules_.smi') if self.backend_smiles else mktempfile('molecules_v2k.sd')
|
| 134 |
+
self._n_mols = 0
|
| 135 |
+
self._skipped = []
|
| 136 |
+
self.n = 0
|
| 137 |
+
try:
|
| 138 |
+
block = BlockLogs()
|
| 139 |
+
if self.backend_smiles:
|
| 140 |
+
writer = Chem.SmilesWriter(self._tmp_input, includeHeader=False, isomericSmiles=True, kekuleSmiles=True)
|
| 141 |
+
else:
|
| 142 |
+
writer = Chem.SDWriter(self._tmp_input)
|
| 143 |
+
# Ensure V2000 as CDK cannot properly process v3000
|
| 144 |
+
writer.SetForceV3000(False)
|
| 145 |
+
for i, mol in enumerate(mols):
|
| 146 |
+
if mol is not None and isinstance(mol, Chem.Mol):
|
| 147 |
+
if not self.backend_smiles and mol.GetNumAtoms() > 999:
|
| 148 |
+
raise ValueError('Cannot calculate descriptors for molecules with more than 999 atoms.')
|
| 149 |
+
# Does molecule lack hydrogen atoms?
|
| 150 |
+
if needsHs(mol):
|
| 151 |
+
warnings.warn('Molecule lacks hydrogen atoms: this might affect the value of calculated descriptors')
|
| 152 |
+
# Are molecules 3D
|
| 153 |
+
if self.include_3D:
|
| 154 |
+
confs = list(mol.GetConformers())
|
| 155 |
+
if self.fingerprint is None and not (len(confs) > 0 and confs[-1].Is3D()):
|
| 156 |
+
raise ValueError('Cannot calculate the 3D descriptors of a conformer-less molecule')
|
| 157 |
+
writer.write(mol)
|
| 158 |
+
self._n_mols += 1
|
| 159 |
+
else:
|
| 160 |
+
self._skipped.append(i)
|
| 161 |
+
self.n += 1
|
| 162 |
+
writer.close()
|
| 163 |
+
del block
|
| 164 |
+
except ValueError as e:
|
| 165 |
+
# Free resources and raise error
|
| 166 |
+
writer.close()
|
| 167 |
+
del block
|
| 168 |
+
os.remove(self._tmp_input)
|
| 169 |
+
raise e from None
|
| 170 |
+
# 3) Create command
|
| 171 |
+
java_path = install_java(19)
|
| 172 |
+
command_parameters = (f"-f {self.fingerprint} -nBits {self.nbits} "
|
| 173 |
+
f"-depth {self.depth}") if self.fingerprint is not None else ""
|
| 174 |
+
command_file = f"-{'s' if self.backend_smiles else 'i'} {self._tmp_input}"
|
| 175 |
+
command_out_smiles = "-S" if cdk_smiles else ""
|
| 176 |
+
command = f"{java_path} -jar {self._jarfile} {command_file} {command_parameters} {command_out_smiles}"
|
| 177 |
+
return command
|
| 178 |
+
|
| 179 |
+
def _cleanup(self) -> None:
|
| 180 |
+
"""Cleanup resources used for calculation."""
|
| 181 |
+
# Remove temporary files
|
| 182 |
+
os.remove(self._tmp_input)
|
| 183 |
+
|
| 184 |
+
def _run_command(self, command: str) -> pd.DataFrame:
|
| 185 |
+
"""Run the CDK command.
|
| 186 |
+
|
| 187 |
+
:param command: The command to be run.
|
| 188 |
+
"""
|
| 189 |
+
with Popen(command.split(), stdout=PIPE, stderr=subprocess.DEVNULL) as process:
|
| 190 |
+
values = process.stdout.read().decode()
|
| 191 |
+
# CDK barf preventing correct parsing
|
| 192 |
+
if 'not found' in values:
|
| 193 |
+
# Omit error
|
| 194 |
+
values = '\n'.join(line for line in values.split('\n') if 'not found' not in line)
|
| 195 |
+
# Empty result file
|
| 196 |
+
if len(values) == 0:
|
| 197 |
+
details = self.get_details()
|
| 198 |
+
values = pd.DataFrame(np.full((self._n_mols, details.shape[0]), np.nan),
|
| 199 |
+
columns=details.Name)
|
| 200 |
+
elif '{' not in values:
|
| 201 |
+
if self.fingerprint is None:
|
| 202 |
+
values = values.split('\n')
|
| 203 |
+
# Ensure all columns are present in the header
|
| 204 |
+
values[0] = (f'{"SMILES " if values[0].startswith("SMILES") else ""}'
|
| 205 |
+
'Fsp3 nSmallRings nAromRings nRingBlocks nAromBlocks nRings3 nRings4 nRings5 nRings6 '
|
| 206 |
+
'nRings7 nRings8 nRings9 tpsaEfficiency Zagreb XLogP WPATH WPOL Wlambda1.unity Wlambda2.unity '
|
| 207 |
+
'Wlambda3.unity Wnu1.unity Wnu2.unity Wgamma1.unity Wgamma2.unity Wgamma3.unity Weta1.unity '
|
| 208 |
+
'Weta2.unity Weta3.unity WT.unity WA.unity WV.unity WK.unity WG.unity WD.unity WTPT-1 WTPT-2 '
|
| 209 |
+
'WTPT-3 WTPT-4 WTPT-5 MW VAdjMat VABC TopoPSA LipinskiFailures nRotB topoShape geomShape '
|
| 210 |
+
'PetitjeanNumber MOMI-X MOMI-Y MOMI-Z MOMI-XY MOMI-XZ MOMI-YZ MOMI-R MDEC-11 MDEC-12 MDEC-13 '
|
| 211 |
+
'MDEC-14 MDEC-22 MDEC-23 MDEC-24 MDEC-33 MDEC-34 MDEC-44 MDEO-11 MDEO-12 MDEO-22 MDEN-11 '
|
| 212 |
+
'MDEN-12 MDEN-13 MDEN-22 MDEN-23 MDEN-33 MLogP nAtomLAC LOBMAX LOBMIN nAtomP nAtomLC khs.sLi '
|
| 213 |
+
'khs.ssBe khs.ssssBe khs.ssBH khs.sssB khs.ssssB khs.sCH3 khs.dCH2 khs.ssCH2 khs.tCH khs.dsCH '
|
| 214 |
+
'khs.aaCH khs.sssCH khs.ddC khs.tsC khs.dssC khs.aasC khs.aaaC khs.ssssC khs.sNH3 khs.sNH2 '
|
| 215 |
+
'khs.ssNH2 khs.dNH khs.ssNH khs.aaNH khs.tN khs.sssNH khs.dsN khs.aaN khs.sssN khs.ddsN '
|
| 216 |
+
'khs.aasN khs.ssssN khs.sOH khs.dO khs.ssO khs.aaO khs.sF khs.sSiH3 khs.ssSiH2 khs.sssSiH '
|
| 217 |
+
'khs.ssssSi khs.sPH2 khs.ssPH khs.sssP khs.dsssP khs.sssssP khs.sSH khs.dS khs.ssS khs.aaS '
|
| 218 |
+
'khs.dssS khs.ddssS khs.sCl khs.sGeH3 khs.ssGeH2 khs.sssGeH khs.ssssGe khs.sAsH2 khs.ssAsH '
|
| 219 |
+
'khs.sssAs khs.sssdAs khs.sssssAs khs.sSeH khs.dSe khs.ssSe khs.aaSe khs.dssSe khs.ddssSe '
|
| 220 |
+
'khs.sBr khs.sSnH3 khs.ssSnH2 khs.sssSnH khs.ssssSn khs.sI khs.sPbH3 khs.ssPbH2 khs.sssPbH '
|
| 221 |
+
'khs.ssssPb Kier1 Kier2 Kier3 HybRatio nHBDon nHBAcc GRAV-1 GRAV-2 GRAV-3 GRAVH-1 GRAVH-2 '
|
| 222 |
+
'GRAVH-3 GRAV-4 GRAV-5 GRAV-6 fragC FMF ECCEN PPSA-1 PPSA-2 PPSA-3 PNSA-1 PNSA-2 PNSA-3 '
|
| 223 |
+
'DPSA-1 DPSA-2 DPSA-3 FPSA-1 FPSA-2 FPSA-3 FNSA-1 FNSA-2 FNSA-3 WPSA-1 WPSA-2 WPSA-3 WNSA-1 '
|
| 224 |
+
'WNSA-2 WNSA-3 RPCG RNCG RPCS RNCS THSA TPSA RHSA RPSA SP-0 SP-1 SP-2 SP-3 SP-4 SP-5 SP-6 '
|
| 225 |
+
'SP-7 VP-0 VP-1 VP-2 VP-3 VP-4 VP-5 VP-6 VP-7 SPC-4 SPC-5 SPC-6 VPC-4 VPC-5 VPC-6 SC-3 SC-4 '
|
| 226 |
+
'SC-5 SC-6 VC-3 VC-4 VC-5 VC-6 SCH-3 SCH-4 SCH-5 SCH-6 SCH-7 VCH-3 VCH-4 VCH-5 VCH-6 VCH-7 '
|
| 227 |
+
'C1SP1 C2SP1 C1SP2 C2SP2 C3SP2 C1SP3 C2SP3 C3SP3 C4SP3 bpol nB BCUTw-1l BCUTw-1h BCUTc-1l '
|
| 228 |
+
'BCUTc-1h BCUTp-1l BCUTp-1h nBase ATSp1 ATSp2 ATSp3 ATSp4 ATSp5 ATSm1 ATSm2 ATSm3 ATSm4 '
|
| 229 |
+
'ATSm5 ATSc1 ATSc2 ATSc3 ATSc4 ATSc5 nAtom nAromBond naAromAtom apol ALogP ALogp2 AMR nAcid '
|
| 230 |
+
'JPLogP')
|
| 231 |
+
# CDK uses uppercase exponents but pandas needs a lowercase
|
| 232 |
+
values = '\n'.join([values[0]] + [line.replace('E', 'e') for line in values[1:]])
|
| 233 |
+
# Parse with pandas
|
| 234 |
+
values = pd.read_csv(io.StringIO(values), sep=' ')
|
| 235 |
+
else:
|
| 236 |
+
try:
|
| 237 |
+
values = pd.DataFrame.from_dict(eval('{%s}' % values), orient='index').fillna(0)
|
| 238 |
+
# Separate SMILES when calculated
|
| 239 |
+
if isinstance(values.index[0], str) and values.index.str.contains('|').any():
|
| 240 |
+
smiles = pd.Series(values.index.str.split('|').str[1], name='SMILES').reset_index(drop=True)
|
| 241 |
+
values = (pd.concat([smiles, values.reset_index(drop=True)], axis=1)
|
| 242 |
+
.reset_index(drop=True))
|
| 243 |
+
except pd.errors.EmptyDataError:
|
| 244 |
+
raise RuntimeError('CDK could not obtain molecular descriptors, maybe due to a faulty molecule')
|
| 245 |
+
# If only 2D, remove 3D descriptors
|
| 246 |
+
if not self.include_3D and self.fingerprint is None:
|
| 247 |
+
# Get 3D descriptor names to remove
|
| 248 |
+
descs_3D = self.get_details()
|
| 249 |
+
descs_3D = descs_3D[descs_3D.Dimensions == '3D']
|
| 250 |
+
values = values[[col for col in values.columns if col not in descs_3D.Name.tolist()]]
|
| 251 |
+
return values
|
| 252 |
+
|
| 253 |
+
def _calculate(self, mols: List[Chem.Mol], cdk_smiles: bool = False) -> pd.DataFrame:
|
| 254 |
+
"""Calculate CDK molecular descriptors on one process.
|
| 255 |
+
|
| 256 |
+
:param mols: RDKit molecules for which CDK descriptors and fingerprints should be calculated.
|
| 257 |
+
:param cdk_smiles: If True, generate the canonical SMILES (generated by CDK) of molecules parsed by CDK
|
| 258 |
+
:return: a pandas DataFrame containing CDK descriptor values
|
| 259 |
+
"""
|
| 260 |
+
# Prepare inputs
|
| 261 |
+
command = self._prepare_command(mols, cdk_smiles)
|
| 262 |
+
# Run command and obtain results
|
| 263 |
+
results = self._run_command(command)
|
| 264 |
+
# Cleanup
|
| 265 |
+
self._cleanup()
|
| 266 |
+
# Insert lines of skipped molecules
|
| 267 |
+
if len(self._skipped):
|
| 268 |
+
results = pd.DataFrame(np.insert(results.values, self._skipped,
|
| 269 |
+
values=[np.NaN] * len(results.columns),
|
| 270 |
+
axis=0),
|
| 271 |
+
columns=results.columns)
|
| 272 |
+
# Omit SMILES column from casting if in values
|
| 273 |
+
if cdk_smiles:
|
| 274 |
+
smiles_col = results['SMILES']
|
| 275 |
+
results = results.drop(columns=['SMILES'])
|
| 276 |
+
results = (results.apply(pd.to_numeric, errors='coerce', axis=1)
|
| 277 |
+
)
|
| 278 |
+
# Insert SMILES column back
|
| 279 |
+
if cdk_smiles:
|
| 280 |
+
results = pd.concat([smiles_col, results], axis=1)
|
| 281 |
+
return results
|
| 282 |
+
|
| 283 |
+
def _multiproc_calculate(self, mols: List[Chem.Mol], cdk_smiles: bool = False) -> pd.DataFrame:
|
| 284 |
+
"""Calculate CDK descriptors and fingerprints in thread-safe manner.
|
| 285 |
+
|
| 286 |
+
:param mols: RDKit molecules for which CDK descriptors and fingerprints should be calculated
|
| 287 |
+
:param cdk_smiles: If True, generate the canonical SMILES (generated by CDK) of molecules parsed by CDK
|
| 288 |
+
:return: a pandas DataFrame containing all CDK descriptor values
|
| 289 |
+
"""
|
| 290 |
+
# Copy self instance to make thread safe
|
| 291 |
+
cdk = deepcopy(self)
|
| 292 |
+
# Run copy
|
| 293 |
+
result = cdk.calculate(mols, show_banner=False, njobs=1, cdk_smiles=cdk_smiles)
|
| 294 |
+
return result
|
| 295 |
+
|
| 296 |
+
@staticmethod
|
| 297 |
+
def get_details(desc_name: Optional[str] = None):
|
| 298 |
+
"""Obtain details about either one or all descriptors.
|
| 299 |
+
|
| 300 |
+
:param desc_name: the name of the descriptor to obtain details about (default: None).
|
| 301 |
+
If None, returns details about all descriptors.
|
| 302 |
+
"""
|
| 303 |
+
details = pd.read_json(os.path.abspath(os.path.join(__file__, os.pardir, 'descs.json')), orient='index')
|
| 304 |
+
if desc_name is not None:
|
| 305 |
+
if desc_name not in details.Name.tolist():
|
| 306 |
+
raise ValueError(f'descriptor name {desc_name} is not available')
|
| 307 |
+
details = details[details.Name == desc_name]
|
| 308 |
+
return details
|
venv/Lib/site-packages/CDK_pywrapper/descs.json
ADDED
|
@@ -0,0 +1,1730 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"0":{
|
| 3 |
+
"Name":"nAcid",
|
| 4 |
+
"Description":"The number of acidic groups.",
|
| 5 |
+
"Type":"Constitutional descriptors",
|
| 6 |
+
"Dimensions":"1D"
|
| 7 |
+
},
|
| 8 |
+
"1":{
|
| 9 |
+
"Name":"naAromAtom",
|
| 10 |
+
"Description":"The number of aromatic atoms in an atom container.",
|
| 11 |
+
"Type":"Constitutional descriptors",
|
| 12 |
+
"Dimensions":"1D"
|
| 13 |
+
},
|
| 14 |
+
"2":{
|
| 15 |
+
"Name":"nAromBond",
|
| 16 |
+
"Description":"The number of aromatic atoms in an AtomContainer.",
|
| 17 |
+
"Type":"Constitutional descriptors",
|
| 18 |
+
"Dimensions":"1D"
|
| 19 |
+
},
|
| 20 |
+
"3":{
|
| 21 |
+
"Name":"nAtom",
|
| 22 |
+
"Description":"The number of atoms of a certain element type.",
|
| 23 |
+
"Type":"Constitutional descriptors",
|
| 24 |
+
"Dimensions":"1D"
|
| 25 |
+
},
|
| 26 |
+
"4":{
|
| 27 |
+
"Name":"nBase",
|
| 28 |
+
"Description":"The number of basic groups.",
|
| 29 |
+
"Type":"Constitutional descriptors",
|
| 30 |
+
"Dimensions":"1D"
|
| 31 |
+
},
|
| 32 |
+
"5":{
|
| 33 |
+
"Name":"nB",
|
| 34 |
+
"Description":"The number of bonds of a certain bond order.",
|
| 35 |
+
"Type":"Constitutional descriptors",
|
| 36 |
+
"Dimensions":"1D"
|
| 37 |
+
},
|
| 38 |
+
"6":{
|
| 39 |
+
"Name":"HybRatio",
|
| 40 |
+
"Description":"The fraction of sp3 carbons to sp2 carbons.",
|
| 41 |
+
"Type":"Constitutional descriptors",
|
| 42 |
+
"Dimensions":"1D"
|
| 43 |
+
},
|
| 44 |
+
"7":{
|
| 45 |
+
"Name":"Fsp3",
|
| 46 |
+
"Description":"The fraction of sp3 carbons to all carbons.",
|
| 47 |
+
"Type":"Constitutional descriptors",
|
| 48 |
+
"Dimensions":"1D"
|
| 49 |
+
},
|
| 50 |
+
"8":{
|
| 51 |
+
"Name":"nAtomLC",
|
| 52 |
+
"Description":"The number of atoms in the largest chain.",
|
| 53 |
+
"Type":"Constitutional descriptors",
|
| 54 |
+
"Dimensions":"1D"
|
| 55 |
+
},
|
| 56 |
+
"9":{
|
| 57 |
+
"Name":"nAtomP",
|
| 58 |
+
"Description":"?The number of atoms in the largest pi system.",
|
| 59 |
+
"Type":"Constitutional descriptors",
|
| 60 |
+
"Dimensions":"1D"
|
| 61 |
+
},
|
| 62 |
+
"10":{
|
| 63 |
+
"Name":"nAtomLAC",
|
| 64 |
+
"Description":"The number of atoms in the longest aliphatic chain.",
|
| 65 |
+
"Type":"Constitutional descriptors",
|
| 66 |
+
"Dimensions":"1D"
|
| 67 |
+
},
|
| 68 |
+
"11":{
|
| 69 |
+
"Name":"nRotB",
|
| 70 |
+
"Description":"The number of rotatable bonds is given by the SMARTS specified by Daylight on?SMARTS tutorial",
|
| 71 |
+
"Type":"Constitutional descriptors",
|
| 72 |
+
"Dimensions":"1D"
|
| 73 |
+
},
|
| 74 |
+
"12":{
|
| 75 |
+
"Name":"nSmallRings",
|
| 76 |
+
"Description":"total number of small rings (of size 3 through 9)",
|
| 77 |
+
"Type":"Constitutional descriptors",
|
| 78 |
+
"Dimensions":"1D"
|
| 79 |
+
},
|
| 80 |
+
"13":{
|
| 81 |
+
"Name":"nAromRings",
|
| 82 |
+
"Description":"total number of small aromatic rings",
|
| 83 |
+
"Type":"Constitutional descriptors",
|
| 84 |
+
"Dimensions":"1D"
|
| 85 |
+
},
|
| 86 |
+
"14":{
|
| 87 |
+
"Name":"nRingBlocks",
|
| 88 |
+
"Description":"total number of distinct ring blocks",
|
| 89 |
+
"Type":"Constitutional descriptors",
|
| 90 |
+
"Dimensions":"1D"
|
| 91 |
+
},
|
| 92 |
+
"15":{
|
| 93 |
+
"Name":"nAromBlocks",
|
| 94 |
+
"Description":"total number of aromatically connected components",
|
| 95 |
+
"Type":"Constitutional descriptors",
|
| 96 |
+
"Dimensions":"1D"
|
| 97 |
+
},
|
| 98 |
+
"16":{
|
| 99 |
+
"Name":"nRings3",
|
| 100 |
+
"Description":"total number of 3-membered rings",
|
| 101 |
+
"Type":"Constitutional descriptors",
|
| 102 |
+
"Dimensions":"1D"
|
| 103 |
+
},
|
| 104 |
+
"17":{
|
| 105 |
+
"Name":"nRings4",
|
| 106 |
+
"Description":"total number of 4-membered rings",
|
| 107 |
+
"Type":"Constitutional descriptors",
|
| 108 |
+
"Dimensions":"1D"
|
| 109 |
+
},
|
| 110 |
+
"18":{
|
| 111 |
+
"Name":"nRings5",
|
| 112 |
+
"Description":"total number of 5-membered rings",
|
| 113 |
+
"Type":"Constitutional descriptors",
|
| 114 |
+
"Dimensions":"1D"
|
| 115 |
+
},
|
| 116 |
+
"19":{
|
| 117 |
+
"Name":"nRings6",
|
| 118 |
+
"Description":"total number of 6-membered rings",
|
| 119 |
+
"Type":"Constitutional descriptors",
|
| 120 |
+
"Dimensions":"1D"
|
| 121 |
+
},
|
| 122 |
+
"20":{
|
| 123 |
+
"Name":"nRings7",
|
| 124 |
+
"Description":"total number of 7-membered rings",
|
| 125 |
+
"Type":"Constitutional descriptors",
|
| 126 |
+
"Dimensions":"1D"
|
| 127 |
+
},
|
| 128 |
+
"21":{
|
| 129 |
+
"Name":"nRings8",
|
| 130 |
+
"Description":"total number of 8-membered rings",
|
| 131 |
+
"Type":"Constitutional descriptors",
|
| 132 |
+
"Dimensions":"1D"
|
| 133 |
+
},
|
| 134 |
+
"22":{
|
| 135 |
+
"Name":"nRings9",
|
| 136 |
+
"Description":"total number of 9-membered rings",
|
| 137 |
+
"Type":"Constitutional descriptors",
|
| 138 |
+
"Dimensions":"1D"
|
| 139 |
+
},
|
| 140 |
+
"23":{
|
| 141 |
+
"Name":"ALogP",
|
| 142 |
+
"Description":"This class calculates ALOGP (Ghose-Crippen LogKow) and the Ghose-Crippen molar refractivity [Ghose, A.K. and Crippen, G.M. ,?Atomic physicochemical parameters for three-dimensional structure-directed quantitative structure-activity relationships. I. Partition coefficients as a measure of hydrophobicity, Journal of Computational Chemistry,?1986, 7:565-577, Ghose, A.K. and Crippen, G.M. ,?Atomic physicochemical parameters for three-dimensional-structure-directed quantitative structure-activity relationships. 2. Modeling dispersive and hydrophobic interactions, Journal of Chemical Information and Computer Science,?1987, 27:21-35].",
|
| 143 |
+
"Type":"Molecular property descriptors",
|
| 144 |
+
"Dimensions":"2D"
|
| 145 |
+
},
|
| 146 |
+
"24":{
|
| 147 |
+
"Name":"ALogp2",
|
| 148 |
+
"Description":"This class calculates ALOGP (Ghose-Crippen LogKow) and the Ghose-Crippen molar refractivity [Ghose, A.K. and Crippen, G.M. ,?Atomic physicochemical parameters for three-dimensional structure-directed quantitative structure-activity relationships. I. Partition coefficients as a measure of hydrophobicity, Journal of Computational Chemistry,?1986, 7:565-577, Ghose, A.K. and Crippen, G.M. ,?Atomic physicochemical parameters for three-dimensional-structure-directed quantitative structure-activity relationships. 2. Modeling dispersive and hydrophobic interactions, Journal of Chemical Information and Computer Science,?1987, 27:21-35].",
|
| 149 |
+
"Type":"Molecular property descriptors",
|
| 150 |
+
"Dimensions":"2D"
|
| 151 |
+
},
|
| 152 |
+
"25":{
|
| 153 |
+
"Name":"AMR",
|
| 154 |
+
"Description":"This class calculates ALOGP (Ghose-Crippen LogKow) and the Ghose-Crippen molar refractivity [Ghose, A.K. and Crippen, G.M. ,?Atomic physicochemical parameters for three-dimensional structure-directed quantitative structure-activity relationships. I. Partition coefficients as a measure of hydrophobicity, Journal of Computational Chemistry,?1986, 7:565-577, Ghose, A.K. and Crippen, G.M. ,?Atomic physicochemical parameters for three-dimensional-structure-directed quantitative structure-activity relationships. 2. Modeling dispersive and hydrophobic interactions, Journal of Chemical Information and Computer Science,?1987, 27:21-35].",
|
| 155 |
+
"Type":"Molecular property descriptors",
|
| 156 |
+
"Dimensions":"2D"
|
| 157 |
+
},
|
| 158 |
+
"26":{
|
| 159 |
+
"Name":"BCUTw-1l",
|
| 160 |
+
"Description":"Eigenvalue based descriptor noted for its utility in chemical diversity.",
|
| 161 |
+
"Type":"BCUT descriptors",
|
| 162 |
+
"Dimensions":"2D"
|
| 163 |
+
},
|
| 164 |
+
"27":{
|
| 165 |
+
"Name":"BCUTw-1h",
|
| 166 |
+
"Description":"Eigenvalue based descriptor noted for its utility in chemical diversity.",
|
| 167 |
+
"Type":"BCUT descriptors",
|
| 168 |
+
"Dimensions":"2D"
|
| 169 |
+
},
|
| 170 |
+
"28":{
|
| 171 |
+
"Name":"BCUTc-1l",
|
| 172 |
+
"Description":"Eigenvalue based descriptor noted for its utility in chemical diversity.",
|
| 173 |
+
"Type":"BCUT descriptors",
|
| 174 |
+
"Dimensions":"2D"
|
| 175 |
+
},
|
| 176 |
+
"29":{
|
| 177 |
+
"Name":"BCUTc-1h",
|
| 178 |
+
"Description":"Eigenvalue based descriptor noted for its utility in chemical diversity.",
|
| 179 |
+
"Type":"BCUT descriptors",
|
| 180 |
+
"Dimensions":"2D"
|
| 181 |
+
},
|
| 182 |
+
"30":{
|
| 183 |
+
"Name":"BCUTp-1l",
|
| 184 |
+
"Description":"Eigenvalue based descriptor noted for its utility in chemical diversity.",
|
| 185 |
+
"Type":"BCUT descriptors",
|
| 186 |
+
"Dimensions":"2D"
|
| 187 |
+
},
|
| 188 |
+
"31":{
|
| 189 |
+
"Name":"BCUTp-1h",
|
| 190 |
+
"Description":"Eigenvalue based descriptor noted for its utility in chemical diversity.",
|
| 191 |
+
"Type":"BCUT descriptors",
|
| 192 |
+
"Dimensions":"2D"
|
| 193 |
+
},
|
| 194 |
+
"32":{
|
| 195 |
+
"Name":"fragC",
|
| 196 |
+
"Description":"the complexity of a system. The complexity is defined as [Nilakantan, R. and Nunn, D.S. and Greenblatt, L. and Walker, G. and Haraki, K. and Mobilio, D., A family of ring system-based structural fragments for use in structure-activity studies: database mining and recursive partitioning., Journal of chemical information and modeling, 2006, 46:1069-1077]:",
|
| 197 |
+
"Type":"Topological descriptors",
|
| 198 |
+
"Dimensions":"2D"
|
| 199 |
+
},
|
| 200 |
+
"33":{
|
| 201 |
+
"Name":"apol",
|
| 202 |
+
"Description":"Sum of the atomic polarizabilities (including implicit hydrogens).",
|
| 203 |
+
"Type":"Molecular property descriptors",
|
| 204 |
+
"Dimensions":"2D"
|
| 205 |
+
},
|
| 206 |
+
"34":{
|
| 207 |
+
"Name":"ATSc1",
|
| 208 |
+
"Description":"?ATS autocorrelation descriptor, where the weight equal to the charges.",
|
| 209 |
+
"Type":"Autocorrelation descriptors",
|
| 210 |
+
"Dimensions":"2D"
|
| 211 |
+
},
|
| 212 |
+
"35":{
|
| 213 |
+
"Name":"ATSc2",
|
| 214 |
+
"Description":"?ATS autocorrelation descriptor, where the weight equal to the charges.",
|
| 215 |
+
"Type":"Autocorrelation descriptors",
|
| 216 |
+
"Dimensions":"2D"
|
| 217 |
+
},
|
| 218 |
+
"36":{
|
| 219 |
+
"Name":"ATSc3",
|
| 220 |
+
"Description":"?ATS autocorrelation descriptor, where the weight equal to the charges.",
|
| 221 |
+
"Type":"Autocorrelation descriptors",
|
| 222 |
+
"Dimensions":"2D"
|
| 223 |
+
},
|
| 224 |
+
"37":{
|
| 225 |
+
"Name":"ATSc4",
|
| 226 |
+
"Description":"?ATS autocorrelation descriptor, where the weight equal to the charges.",
|
| 227 |
+
"Type":"Autocorrelation descriptors",
|
| 228 |
+
"Dimensions":"2D"
|
| 229 |
+
},
|
| 230 |
+
"38":{
|
| 231 |
+
"Name":"ATSc5",
|
| 232 |
+
"Description":"?ATS autocorrelation descriptor, where the weight equal to the charges.",
|
| 233 |
+
"Type":"Autocorrelation descriptors",
|
| 234 |
+
"Dimensions":"2D"
|
| 235 |
+
},
|
| 236 |
+
"39":{
|
| 237 |
+
"Name":"ATSm1",
|
| 238 |
+
"Description":"ATS autocorrelation descriptor, where the weight equal to the scaled atomic mass [Moreau G. and Broto P.,?The autocorrelation of a topological structure: A new molecular descriptor, Nouveau Journal de Chimie,?1980, ?:359-360].",
|
| 239 |
+
"Type":"Autocorrelation descriptors",
|
| 240 |
+
"Dimensions":"2D"
|
| 241 |
+
},
|
| 242 |
+
"40":{
|
| 243 |
+
"Name":"ATSm2",
|
| 244 |
+
"Description":"ATS autocorrelation descriptor, where the weight equal to the scaled atomic mass [Moreau G. and Broto P.,?The autocorrelation of a topological structure: A new molecular descriptor, Nouveau Journal de Chimie,?1980, ?:359-361].",
|
| 245 |
+
"Type":"Autocorrelation descriptors",
|
| 246 |
+
"Dimensions":"2D"
|
| 247 |
+
},
|
| 248 |
+
"41":{
|
| 249 |
+
"Name":"ATSm3",
|
| 250 |
+
"Description":"ATS autocorrelation descriptor, where the weight equal to the scaled atomic mass [Moreau G. and Broto P.,?The autocorrelation of a topological structure: A new molecular descriptor, Nouveau Journal de Chimie,?1980, ?:359-362].",
|
| 251 |
+
"Type":"Autocorrelation descriptors",
|
| 252 |
+
"Dimensions":"2D"
|
| 253 |
+
},
|
| 254 |
+
"42":{
|
| 255 |
+
"Name":"ATSm4",
|
| 256 |
+
"Description":"ATS autocorrelation descriptor, where the weight equal to the scaled atomic mass [Moreau G. and Broto P.,?The autocorrelation of a topological structure: A new molecular descriptor, Nouveau Journal de Chimie,?1980, ?:359-363].",
|
| 257 |
+
"Type":"Autocorrelation descriptors",
|
| 258 |
+
"Dimensions":"2D"
|
| 259 |
+
},
|
| 260 |
+
"43":{
|
| 261 |
+
"Name":"ATSm5",
|
| 262 |
+
"Description":"ATS autocorrelation descriptor, where the weight equal to the scaled atomic mass [Moreau G. and Broto P.,?The autocorrelation of a topological structure: A new molecular descriptor, Nouveau Journal de Chimie,?1980, ?:359-364].",
|
| 263 |
+
"Type":"Autocorrelation descriptors",
|
| 264 |
+
"Dimensions":"2D"
|
| 265 |
+
},
|
| 266 |
+
"44":{
|
| 267 |
+
"Name":"ATSp1",
|
| 268 |
+
"Description":"ATS autocorrelation descriptor, where the weight equal to the charges.",
|
| 269 |
+
"Type":"Autocorrelation descriptors",
|
| 270 |
+
"Dimensions":"2D"
|
| 271 |
+
},
|
| 272 |
+
"45":{
|
| 273 |
+
"Name":"ATSp2",
|
| 274 |
+
"Description":"ATS autocorrelation descriptor, where the weight equal to the charges.",
|
| 275 |
+
"Type":"Autocorrelation descriptors",
|
| 276 |
+
"Dimensions":"2D"
|
| 277 |
+
},
|
| 278 |
+
"46":{
|
| 279 |
+
"Name":"ATSp3",
|
| 280 |
+
"Description":"ATS autocorrelation descriptor, where the weight equal to the charges.",
|
| 281 |
+
"Type":"Autocorrelation descriptors",
|
| 282 |
+
"Dimensions":"2D"
|
| 283 |
+
},
|
| 284 |
+
"47":{
|
| 285 |
+
"Name":"ATSp4",
|
| 286 |
+
"Description":"ATS autocorrelation descriptor, where the weight equal to the charges.",
|
| 287 |
+
"Type":"Autocorrelation descriptors",
|
| 288 |
+
"Dimensions":"2D"
|
| 289 |
+
},
|
| 290 |
+
"48":{
|
| 291 |
+
"Name":"ATSp5",
|
| 292 |
+
"Description":"ATS autocorrelation descriptor, where the weight equal to the charges.",
|
| 293 |
+
"Type":"Autocorrelation descriptors",
|
| 294 |
+
"Dimensions":"2D"
|
| 295 |
+
},
|
| 296 |
+
"49":{
|
| 297 |
+
"Name":"bpol",
|
| 298 |
+
"Description":"Sum of the absolute value of the difference between atomic polarizabilities of all bonded atoms in the molecule (including implicit hydrogens) with polarizabilities taken from http:\/\/www.sunysccc.edu\/academic\/mst\/ptable\/p-table2.htm This descriptor assumes 2-centered bonds.",
|
| 299 |
+
"Type":"Molecular property descriptors",
|
| 300 |
+
"Dimensions":"2D"
|
| 301 |
+
},
|
| 302 |
+
"50":{
|
| 303 |
+
"Name":"C1SP1",
|
| 304 |
+
"Description":"Topological descriptor characterizing the carbon connectivity.",
|
| 305 |
+
"Type":"Topological descriptors",
|
| 306 |
+
"Dimensions":"2D"
|
| 307 |
+
},
|
| 308 |
+
"51":{
|
| 309 |
+
"Name":"C2SP1",
|
| 310 |
+
"Description":"Topological descriptor characterizing the carbon connectivity.",
|
| 311 |
+
"Type":"Topological descriptors",
|
| 312 |
+
"Dimensions":"2D"
|
| 313 |
+
},
|
| 314 |
+
"52":{
|
| 315 |
+
"Name":"C1SP2",
|
| 316 |
+
"Description":"Topological descriptor characterizing the carbon connectivity.",
|
| 317 |
+
"Type":"Topological descriptors",
|
| 318 |
+
"Dimensions":"2D"
|
| 319 |
+
},
|
| 320 |
+
"53":{
|
| 321 |
+
"Name":"C2SP2",
|
| 322 |
+
"Description":"Topological descriptor characterizing the carbon connectivity.",
|
| 323 |
+
"Type":"Topological descriptors",
|
| 324 |
+
"Dimensions":"2D"
|
| 325 |
+
},
|
| 326 |
+
"54":{
|
| 327 |
+
"Name":"C3SP2",
|
| 328 |
+
"Description":"Topological descriptor characterizing the carbon connectivity.",
|
| 329 |
+
"Type":"Topological descriptors",
|
| 330 |
+
"Dimensions":"2D"
|
| 331 |
+
},
|
| 332 |
+
"55":{
|
| 333 |
+
"Name":"C1SP3",
|
| 334 |
+
"Description":"Topological descriptor characterizing the carbon connectivity.",
|
| 335 |
+
"Type":"Topological descriptors",
|
| 336 |
+
"Dimensions":"2D"
|
| 337 |
+
},
|
| 338 |
+
"56":{
|
| 339 |
+
"Name":"C2SP3",
|
| 340 |
+
"Description":"Topological descriptor characterizing the carbon connectivity.",
|
| 341 |
+
"Type":"Topological descriptors",
|
| 342 |
+
"Dimensions":"2D"
|
| 343 |
+
},
|
| 344 |
+
"57":{
|
| 345 |
+
"Name":"C3SP3",
|
| 346 |
+
"Description":"Topological descriptor characterizing the carbon connectivity.",
|
| 347 |
+
"Type":"Topological descriptors",
|
| 348 |
+
"Dimensions":"2D"
|
| 349 |
+
},
|
| 350 |
+
"58":{
|
| 351 |
+
"Name":"C4SP3",
|
| 352 |
+
"Description":"Topological descriptor characterizing the carbon connectivity.",
|
| 353 |
+
"Type":"Topological descriptors",
|
| 354 |
+
"Dimensions":"2D"
|
| 355 |
+
},
|
| 356 |
+
"59":{
|
| 357 |
+
"Name":"SCH-3",
|
| 358 |
+
"Description":"Evaluates chi chain descriptors.",
|
| 359 |
+
"Type":"Connectivity descriptors",
|
| 360 |
+
"Dimensions":"2D"
|
| 361 |
+
},
|
| 362 |
+
"60":{
|
| 363 |
+
"Name":"SCH-4",
|
| 364 |
+
"Description":"Evaluates chi chain descriptors.",
|
| 365 |
+
"Type":"Connectivity descriptors",
|
| 366 |
+
"Dimensions":"2D"
|
| 367 |
+
},
|
| 368 |
+
"61":{
|
| 369 |
+
"Name":"SCH-5",
|
| 370 |
+
"Description":"Evaluates chi chain descriptors.",
|
| 371 |
+
"Type":"Connectivity descriptors",
|
| 372 |
+
"Dimensions":"2D"
|
| 373 |
+
},
|
| 374 |
+
"62":{
|
| 375 |
+
"Name":"SCH-6",
|
| 376 |
+
"Description":"Evaluates chi chain descriptors.",
|
| 377 |
+
"Type":"Connectivity descriptors",
|
| 378 |
+
"Dimensions":"2D"
|
| 379 |
+
},
|
| 380 |
+
"63":{
|
| 381 |
+
"Name":"SCH-7",
|
| 382 |
+
"Description":"Evaluates chi chain descriptors.",
|
| 383 |
+
"Type":"Connectivity descriptors",
|
| 384 |
+
"Dimensions":"2D"
|
| 385 |
+
},
|
| 386 |
+
"64":{
|
| 387 |
+
"Name":"VCH-3",
|
| 388 |
+
"Description":"Evaluates chi chain descriptors.",
|
| 389 |
+
"Type":"Connectivity descriptors",
|
| 390 |
+
"Dimensions":"2D"
|
| 391 |
+
},
|
| 392 |
+
"65":{
|
| 393 |
+
"Name":"VCH-4",
|
| 394 |
+
"Description":"Evaluates chi chain descriptors.",
|
| 395 |
+
"Type":"Connectivity descriptors",
|
| 396 |
+
"Dimensions":"2D"
|
| 397 |
+
},
|
| 398 |
+
"66":{
|
| 399 |
+
"Name":"VCH-5",
|
| 400 |
+
"Description":"Evaluates chi chain descriptors.",
|
| 401 |
+
"Type":"Connectivity descriptors",
|
| 402 |
+
"Dimensions":"2D"
|
| 403 |
+
},
|
| 404 |
+
"67":{
|
| 405 |
+
"Name":"VCH-6",
|
| 406 |
+
"Description":"Evaluates chi chain descriptors.",
|
| 407 |
+
"Type":"Connectivity descriptors",
|
| 408 |
+
"Dimensions":"2D"
|
| 409 |
+
},
|
| 410 |
+
"68":{
|
| 411 |
+
"Name":"VCH-7",
|
| 412 |
+
"Description":"Evaluates chi chain descriptors.",
|
| 413 |
+
"Type":"Connectivity descriptors",
|
| 414 |
+
"Dimensions":"2D"
|
| 415 |
+
},
|
| 416 |
+
"69":{
|
| 417 |
+
"Name":"SC-3",
|
| 418 |
+
"Description":"Evaluates chi cluster descriptors.",
|
| 419 |
+
"Type":"Connectivity descriptors",
|
| 420 |
+
"Dimensions":"2D"
|
| 421 |
+
},
|
| 422 |
+
"70":{
|
| 423 |
+
"Name":"SC-4",
|
| 424 |
+
"Description":"Evaluates chi cluster descriptors.",
|
| 425 |
+
"Type":"Connectivity descriptors",
|
| 426 |
+
"Dimensions":"2D"
|
| 427 |
+
},
|
| 428 |
+
"71":{
|
| 429 |
+
"Name":"SC-5",
|
| 430 |
+
"Description":"Evaluates chi cluster descriptors.",
|
| 431 |
+
"Type":"Connectivity descriptors",
|
| 432 |
+
"Dimensions":"2D"
|
| 433 |
+
},
|
| 434 |
+
"72":{
|
| 435 |
+
"Name":"SC-6",
|
| 436 |
+
"Description":"Evaluates chi cluster descriptors.",
|
| 437 |
+
"Type":"Connectivity descriptors",
|
| 438 |
+
"Dimensions":"2D"
|
| 439 |
+
},
|
| 440 |
+
"73":{
|
| 441 |
+
"Name":"VC-3",
|
| 442 |
+
"Description":"Evaluates chi cluster descriptors.",
|
| 443 |
+
"Type":"Connectivity descriptors",
|
| 444 |
+
"Dimensions":"2D"
|
| 445 |
+
},
|
| 446 |
+
"74":{
|
| 447 |
+
"Name":"VC-4",
|
| 448 |
+
"Description":"Evaluates chi cluster descriptors.",
|
| 449 |
+
"Type":"Connectivity descriptors",
|
| 450 |
+
"Dimensions":"2D"
|
| 451 |
+
},
|
| 452 |
+
"75":{
|
| 453 |
+
"Name":"VC-5",
|
| 454 |
+
"Description":"Evaluates chi cluster descriptors.",
|
| 455 |
+
"Type":"Connectivity descriptors",
|
| 456 |
+
"Dimensions":"2D"
|
| 457 |
+
},
|
| 458 |
+
"76":{
|
| 459 |
+
"Name":"VC-6",
|
| 460 |
+
"Description":"Evaluates chi cluster descriptors.",
|
| 461 |
+
"Type":"Connectivity descriptors",
|
| 462 |
+
"Dimensions":"2D"
|
| 463 |
+
},
|
| 464 |
+
"77":{
|
| 465 |
+
"Name":"SP-0",
|
| 466 |
+
"Description":"Evaluates chi path descriptors.",
|
| 467 |
+
"Type":"Connectivity descriptors",
|
| 468 |
+
"Dimensions":"2D"
|
| 469 |
+
},
|
| 470 |
+
"78":{
|
| 471 |
+
"Name":"SP-1",
|
| 472 |
+
"Description":"Evaluates chi path descriptors.",
|
| 473 |
+
"Type":"Connectivity descriptors",
|
| 474 |
+
"Dimensions":"2D"
|
| 475 |
+
},
|
| 476 |
+
"79":{
|
| 477 |
+
"Name":"SP-2",
|
| 478 |
+
"Description":"Evaluates chi path descriptors.",
|
| 479 |
+
"Type":"Connectivity descriptors",
|
| 480 |
+
"Dimensions":"2D"
|
| 481 |
+
},
|
| 482 |
+
"80":{
|
| 483 |
+
"Name":"SP-3",
|
| 484 |
+
"Description":"Evaluates chi path descriptors.",
|
| 485 |
+
"Type":"Connectivity descriptors",
|
| 486 |
+
"Dimensions":"2D"
|
| 487 |
+
},
|
| 488 |
+
"81":{
|
| 489 |
+
"Name":"SP-4",
|
| 490 |
+
"Description":"Evaluates chi path descriptors.",
|
| 491 |
+
"Type":"Connectivity descriptors",
|
| 492 |
+
"Dimensions":"2D"
|
| 493 |
+
},
|
| 494 |
+
"82":{
|
| 495 |
+
"Name":"SP-5",
|
| 496 |
+
"Description":"Evaluates chi path descriptors.",
|
| 497 |
+
"Type":"Connectivity descriptors",
|
| 498 |
+
"Dimensions":"2D"
|
| 499 |
+
},
|
| 500 |
+
"83":{
|
| 501 |
+
"Name":"SP-6",
|
| 502 |
+
"Description":"Evaluates chi path descriptors.",
|
| 503 |
+
"Type":"Connectivity descriptors",
|
| 504 |
+
"Dimensions":"2D"
|
| 505 |
+
},
|
| 506 |
+
"84":{
|
| 507 |
+
"Name":"SP-7",
|
| 508 |
+
"Description":"Evaluates chi path descriptors.",
|
| 509 |
+
"Type":"Connectivity descriptors",
|
| 510 |
+
"Dimensions":"2D"
|
| 511 |
+
},
|
| 512 |
+
"85":{
|
| 513 |
+
"Name":"VP-0",
|
| 514 |
+
"Description":"Evaluates chi path descriptors.",
|
| 515 |
+
"Type":"Connectivity descriptors",
|
| 516 |
+
"Dimensions":"2D"
|
| 517 |
+
},
|
| 518 |
+
"86":{
|
| 519 |
+
"Name":"VP-1",
|
| 520 |
+
"Description":"Evaluates chi path descriptors.",
|
| 521 |
+
"Type":"Connectivity descriptors",
|
| 522 |
+
"Dimensions":"2D"
|
| 523 |
+
},
|
| 524 |
+
"87":{
|
| 525 |
+
"Name":"VP-2",
|
| 526 |
+
"Description":"Evaluates chi path descriptors.",
|
| 527 |
+
"Type":"Connectivity descriptors",
|
| 528 |
+
"Dimensions":"2D"
|
| 529 |
+
},
|
| 530 |
+
"88":{
|
| 531 |
+
"Name":"VP-3",
|
| 532 |
+
"Description":"Evaluates chi path descriptors.",
|
| 533 |
+
"Type":"Connectivity descriptors",
|
| 534 |
+
"Dimensions":"2D"
|
| 535 |
+
},
|
| 536 |
+
"89":{
|
| 537 |
+
"Name":"VP-4",
|
| 538 |
+
"Description":"Evaluates chi path descriptors.",
|
| 539 |
+
"Type":"Connectivity descriptors",
|
| 540 |
+
"Dimensions":"2D"
|
| 541 |
+
},
|
| 542 |
+
"90":{
|
| 543 |
+
"Name":"VP-5",
|
| 544 |
+
"Description":"Evaluates chi path descriptors.",
|
| 545 |
+
"Type":"Connectivity descriptors",
|
| 546 |
+
"Dimensions":"2D"
|
| 547 |
+
},
|
| 548 |
+
"91":{
|
| 549 |
+
"Name":"VP-6",
|
| 550 |
+
"Description":"Evaluates chi path descriptors.",
|
| 551 |
+
"Type":"Connectivity descriptors",
|
| 552 |
+
"Dimensions":"2D"
|
| 553 |
+
},
|
| 554 |
+
"92":{
|
| 555 |
+
"Name":"VP-7",
|
| 556 |
+
"Description":"Evaluates chi path descriptors.",
|
| 557 |
+
"Type":"Connectivity descriptors",
|
| 558 |
+
"Dimensions":"2D"
|
| 559 |
+
},
|
| 560 |
+
"93":{
|
| 561 |
+
"Name":"SPC-4",
|
| 562 |
+
"Description":"Evaluates chi path cluster descriptors.",
|
| 563 |
+
"Type":"Connectivity descriptors",
|
| 564 |
+
"Dimensions":"2D"
|
| 565 |
+
},
|
| 566 |
+
"94":{
|
| 567 |
+
"Name":"SPC-5",
|
| 568 |
+
"Description":"Evaluates chi path cluster descriptors.",
|
| 569 |
+
"Type":"Connectivity descriptors",
|
| 570 |
+
"Dimensions":"2D"
|
| 571 |
+
},
|
| 572 |
+
"95":{
|
| 573 |
+
"Name":"SPC-6",
|
| 574 |
+
"Description":"Evaluates chi path cluster descriptors.",
|
| 575 |
+
"Type":"Connectivity descriptors",
|
| 576 |
+
"Dimensions":"2D"
|
| 577 |
+
},
|
| 578 |
+
"96":{
|
| 579 |
+
"Name":"VPC-4",
|
| 580 |
+
"Description":"Evaluates chi path cluster descriptors.",
|
| 581 |
+
"Type":"Connectivity descriptors",
|
| 582 |
+
"Dimensions":"2D"
|
| 583 |
+
},
|
| 584 |
+
"97":{
|
| 585 |
+
"Name":"VPC-5",
|
| 586 |
+
"Description":"Evaluates chi path cluster descriptors.",
|
| 587 |
+
"Type":"Connectivity descriptors",
|
| 588 |
+
"Dimensions":"2D"
|
| 589 |
+
},
|
| 590 |
+
"98":{
|
| 591 |
+
"Name":"VPC-6",
|
| 592 |
+
"Description":"Evaluates chi path cluster descriptors.",
|
| 593 |
+
"Type":"Connectivity descriptors",
|
| 594 |
+
"Dimensions":"2D"
|
| 595 |
+
},
|
| 596 |
+
"99":{
|
| 597 |
+
"Name":"ECCEN",
|
| 598 |
+
"Description":"A topological descriptor combining distance and adjacency information.",
|
| 599 |
+
"Type":"Topological descriptors",
|
| 600 |
+
"Dimensions":"2D"
|
| 601 |
+
},
|
| 602 |
+
"100":{
|
| 603 |
+
"Name":"FMF",
|
| 604 |
+
"Description":"An implementation of the FMF descriptor characterizing complexity of a molecule.",
|
| 605 |
+
"Type":"Topological descriptors",
|
| 606 |
+
"Dimensions":"2D"
|
| 607 |
+
},
|
| 608 |
+
"101":{
|
| 609 |
+
"Name":"nHBAcc",
|
| 610 |
+
"Description":"The number of hydrogen bond donors using a slightly simplified version of the?PHACIR atom types.",
|
| 611 |
+
"Type":"Molecular property descriptors",
|
| 612 |
+
"Dimensions":"2D"
|
| 613 |
+
},
|
| 614 |
+
"102":{
|
| 615 |
+
"Name":"nHBDon",
|
| 616 |
+
"Description":"?The number of hydrogen bond acceptors using a slightly simplified version of the?PHACIR atom types.",
|
| 617 |
+
"Type":"Molecular property descriptors",
|
| 618 |
+
"Dimensions":"2D"
|
| 619 |
+
},
|
| 620 |
+
"103":{
|
| 621 |
+
"Name":"khs.sLi",
|
| 622 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 623 |
+
"Type":"Kappa descriptors",
|
| 624 |
+
"Dimensions":"2D"
|
| 625 |
+
},
|
| 626 |
+
"104":{
|
| 627 |
+
"Name":"khs.ssBe",
|
| 628 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 629 |
+
"Type":"Kappa descriptors",
|
| 630 |
+
"Dimensions":"2D"
|
| 631 |
+
},
|
| 632 |
+
"105":{
|
| 633 |
+
"Name":"khs.ssssBe",
|
| 634 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 635 |
+
"Type":"Kappa descriptors",
|
| 636 |
+
"Dimensions":"2D"
|
| 637 |
+
},
|
| 638 |
+
"106":{
|
| 639 |
+
"Name":"khs.ssBH",
|
| 640 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 641 |
+
"Type":"Kappa descriptors",
|
| 642 |
+
"Dimensions":"2D"
|
| 643 |
+
},
|
| 644 |
+
"107":{
|
| 645 |
+
"Name":"khs.sssB",
|
| 646 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 647 |
+
"Type":"Kappa descriptors",
|
| 648 |
+
"Dimensions":"2D"
|
| 649 |
+
},
|
| 650 |
+
"108":{
|
| 651 |
+
"Name":"khs.ssssB",
|
| 652 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 653 |
+
"Type":"Kappa descriptors",
|
| 654 |
+
"Dimensions":"2D"
|
| 655 |
+
},
|
| 656 |
+
"109":{
|
| 657 |
+
"Name":"khs.sCH3",
|
| 658 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 659 |
+
"Type":"Kappa descriptors",
|
| 660 |
+
"Dimensions":"2D"
|
| 661 |
+
},
|
| 662 |
+
"110":{
|
| 663 |
+
"Name":"khs.dCH2",
|
| 664 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 665 |
+
"Type":"Kappa descriptors",
|
| 666 |
+
"Dimensions":"2D"
|
| 667 |
+
},
|
| 668 |
+
"111":{
|
| 669 |
+
"Name":"khs.ssCH2",
|
| 670 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 671 |
+
"Type":"Kappa descriptors",
|
| 672 |
+
"Dimensions":"2D"
|
| 673 |
+
},
|
| 674 |
+
"112":{
|
| 675 |
+
"Name":"khs.tCH",
|
| 676 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 677 |
+
"Type":"Kappa descriptors",
|
| 678 |
+
"Dimensions":"2D"
|
| 679 |
+
},
|
| 680 |
+
"113":{
|
| 681 |
+
"Name":"khs.dsCH",
|
| 682 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 683 |
+
"Type":"Kappa descriptors",
|
| 684 |
+
"Dimensions":"2D"
|
| 685 |
+
},
|
| 686 |
+
"114":{
|
| 687 |
+
"Name":"khs.aaCH",
|
| 688 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 689 |
+
"Type":"Kappa descriptors",
|
| 690 |
+
"Dimensions":"2D"
|
| 691 |
+
},
|
| 692 |
+
"115":{
|
| 693 |
+
"Name":"khs.sssCH",
|
| 694 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 695 |
+
"Type":"Kappa descriptors",
|
| 696 |
+
"Dimensions":"2D"
|
| 697 |
+
},
|
| 698 |
+
"116":{
|
| 699 |
+
"Name":"khs.ddC",
|
| 700 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 701 |
+
"Type":"Kappa descriptors",
|
| 702 |
+
"Dimensions":"2D"
|
| 703 |
+
},
|
| 704 |
+
"117":{
|
| 705 |
+
"Name":"khs.tsC",
|
| 706 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 707 |
+
"Type":"Kappa descriptors",
|
| 708 |
+
"Dimensions":"2D"
|
| 709 |
+
},
|
| 710 |
+
"118":{
|
| 711 |
+
"Name":"khs.dssC",
|
| 712 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 713 |
+
"Type":"Kappa descriptors",
|
| 714 |
+
"Dimensions":"2D"
|
| 715 |
+
},
|
| 716 |
+
"119":{
|
| 717 |
+
"Name":"khs.aasC",
|
| 718 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 719 |
+
"Type":"Kappa descriptors",
|
| 720 |
+
"Dimensions":"2D"
|
| 721 |
+
},
|
| 722 |
+
"120":{
|
| 723 |
+
"Name":"khs.aaaC",
|
| 724 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 725 |
+
"Type":"Kappa descriptors",
|
| 726 |
+
"Dimensions":"2D"
|
| 727 |
+
},
|
| 728 |
+
"121":{
|
| 729 |
+
"Name":"khs.ssssC",
|
| 730 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 731 |
+
"Type":"Kappa descriptors",
|
| 732 |
+
"Dimensions":"2D"
|
| 733 |
+
},
|
| 734 |
+
"122":{
|
| 735 |
+
"Name":"khs.sNH3",
|
| 736 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 737 |
+
"Type":"Kappa descriptors",
|
| 738 |
+
"Dimensions":"2D"
|
| 739 |
+
},
|
| 740 |
+
"123":{
|
| 741 |
+
"Name":"khs.sNH2",
|
| 742 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 743 |
+
"Type":"Kappa descriptors",
|
| 744 |
+
"Dimensions":"2D"
|
| 745 |
+
},
|
| 746 |
+
"124":{
|
| 747 |
+
"Name":"khs.ssNH2",
|
| 748 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 749 |
+
"Type":"Kappa descriptors",
|
| 750 |
+
"Dimensions":"2D"
|
| 751 |
+
},
|
| 752 |
+
"125":{
|
| 753 |
+
"Name":"khs.dNH",
|
| 754 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 755 |
+
"Type":"Kappa descriptors",
|
| 756 |
+
"Dimensions":"2D"
|
| 757 |
+
},
|
| 758 |
+
"126":{
|
| 759 |
+
"Name":"khs.ssNH",
|
| 760 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 761 |
+
"Type":"Kappa descriptors",
|
| 762 |
+
"Dimensions":"2D"
|
| 763 |
+
},
|
| 764 |
+
"127":{
|
| 765 |
+
"Name":"khs.aaNH",
|
| 766 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 767 |
+
"Type":"Kappa descriptors",
|
| 768 |
+
"Dimensions":"2D"
|
| 769 |
+
},
|
| 770 |
+
"128":{
|
| 771 |
+
"Name":"khs.tN",
|
| 772 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 773 |
+
"Type":"Kappa descriptors",
|
| 774 |
+
"Dimensions":"2D"
|
| 775 |
+
},
|
| 776 |
+
"129":{
|
| 777 |
+
"Name":"khs.sssNH",
|
| 778 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 779 |
+
"Type":"Kappa descriptors",
|
| 780 |
+
"Dimensions":"2D"
|
| 781 |
+
},
|
| 782 |
+
"130":{
|
| 783 |
+
"Name":"khs.dsN",
|
| 784 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 785 |
+
"Type":"Kappa descriptors",
|
| 786 |
+
"Dimensions":"2D"
|
| 787 |
+
},
|
| 788 |
+
"131":{
|
| 789 |
+
"Name":"khs.aaN",
|
| 790 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 791 |
+
"Type":"Kappa descriptors",
|
| 792 |
+
"Dimensions":"2D"
|
| 793 |
+
},
|
| 794 |
+
"132":{
|
| 795 |
+
"Name":"khs.sssN",
|
| 796 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 797 |
+
"Type":"Kappa descriptors",
|
| 798 |
+
"Dimensions":"2D"
|
| 799 |
+
},
|
| 800 |
+
"133":{
|
| 801 |
+
"Name":"khs.ddsN",
|
| 802 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 803 |
+
"Type":"Kappa descriptors",
|
| 804 |
+
"Dimensions":"2D"
|
| 805 |
+
},
|
| 806 |
+
"134":{
|
| 807 |
+
"Name":"khs.aasN",
|
| 808 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 809 |
+
"Type":"Kappa descriptors",
|
| 810 |
+
"Dimensions":"2D"
|
| 811 |
+
},
|
| 812 |
+
"135":{
|
| 813 |
+
"Name":"khs.ssssN",
|
| 814 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 815 |
+
"Type":"Kappa descriptors",
|
| 816 |
+
"Dimensions":"2D"
|
| 817 |
+
},
|
| 818 |
+
"136":{
|
| 819 |
+
"Name":"khs.sOH",
|
| 820 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 821 |
+
"Type":"Kappa descriptors",
|
| 822 |
+
"Dimensions":"2D"
|
| 823 |
+
},
|
| 824 |
+
"137":{
|
| 825 |
+
"Name":"khs.dO",
|
| 826 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 827 |
+
"Type":"Kappa descriptors",
|
| 828 |
+
"Dimensions":"2D"
|
| 829 |
+
},
|
| 830 |
+
"138":{
|
| 831 |
+
"Name":"khs.ssO",
|
| 832 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 833 |
+
"Type":"Kappa descriptors",
|
| 834 |
+
"Dimensions":"2D"
|
| 835 |
+
},
|
| 836 |
+
"139":{
|
| 837 |
+
"Name":"khs.aaO",
|
| 838 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 839 |
+
"Type":"Kappa descriptors",
|
| 840 |
+
"Dimensions":"2D"
|
| 841 |
+
},
|
| 842 |
+
"140":{
|
| 843 |
+
"Name":"khs.sF",
|
| 844 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 845 |
+
"Type":"Kappa descriptors",
|
| 846 |
+
"Dimensions":"2D"
|
| 847 |
+
},
|
| 848 |
+
"141":{
|
| 849 |
+
"Name":"khs.sSiH3",
|
| 850 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 851 |
+
"Type":"Kappa descriptors",
|
| 852 |
+
"Dimensions":"2D"
|
| 853 |
+
},
|
| 854 |
+
"142":{
|
| 855 |
+
"Name":"khs.ssSiH2",
|
| 856 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 857 |
+
"Type":"Kappa descriptors",
|
| 858 |
+
"Dimensions":"2D"
|
| 859 |
+
},
|
| 860 |
+
"143":{
|
| 861 |
+
"Name":"khs.sssSiH",
|
| 862 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 863 |
+
"Type":"Kappa descriptors",
|
| 864 |
+
"Dimensions":"2D"
|
| 865 |
+
},
|
| 866 |
+
"144":{
|
| 867 |
+
"Name":"khs.ssssSi",
|
| 868 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 869 |
+
"Type":"Kappa descriptors",
|
| 870 |
+
"Dimensions":"2D"
|
| 871 |
+
},
|
| 872 |
+
"145":{
|
| 873 |
+
"Name":"khs.sPH2",
|
| 874 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 875 |
+
"Type":"Kappa descriptors",
|
| 876 |
+
"Dimensions":"2D"
|
| 877 |
+
},
|
| 878 |
+
"146":{
|
| 879 |
+
"Name":"khs.ssPH",
|
| 880 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 881 |
+
"Type":"Kappa descriptors",
|
| 882 |
+
"Dimensions":"2D"
|
| 883 |
+
},
|
| 884 |
+
"147":{
|
| 885 |
+
"Name":"khs.sssP",
|
| 886 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 887 |
+
"Type":"Kappa descriptors",
|
| 888 |
+
"Dimensions":"2D"
|
| 889 |
+
},
|
| 890 |
+
"148":{
|
| 891 |
+
"Name":"khs.dsssP",
|
| 892 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 893 |
+
"Type":"Kappa descriptors",
|
| 894 |
+
"Dimensions":"2D"
|
| 895 |
+
},
|
| 896 |
+
"149":{
|
| 897 |
+
"Name":"khs.sssssP",
|
| 898 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 899 |
+
"Type":"Kappa descriptors",
|
| 900 |
+
"Dimensions":"2D"
|
| 901 |
+
},
|
| 902 |
+
"150":{
|
| 903 |
+
"Name":"khs.sSH",
|
| 904 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 905 |
+
"Type":"Kappa descriptors",
|
| 906 |
+
"Dimensions":"2D"
|
| 907 |
+
},
|
| 908 |
+
"151":{
|
| 909 |
+
"Name":"khs.dS",
|
| 910 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 911 |
+
"Type":"Kappa descriptors",
|
| 912 |
+
"Dimensions":"2D"
|
| 913 |
+
},
|
| 914 |
+
"152":{
|
| 915 |
+
"Name":"khs.ssS",
|
| 916 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 917 |
+
"Type":"Kappa descriptors",
|
| 918 |
+
"Dimensions":"2D"
|
| 919 |
+
},
|
| 920 |
+
"153":{
|
| 921 |
+
"Name":"khs.aaS",
|
| 922 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 923 |
+
"Type":"Kappa descriptors",
|
| 924 |
+
"Dimensions":"2D"
|
| 925 |
+
},
|
| 926 |
+
"154":{
|
| 927 |
+
"Name":"khs.dssS",
|
| 928 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 929 |
+
"Type":"Kappa descriptors",
|
| 930 |
+
"Dimensions":"2D"
|
| 931 |
+
},
|
| 932 |
+
"155":{
|
| 933 |
+
"Name":"khs.ddssS",
|
| 934 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 935 |
+
"Type":"Kappa descriptors",
|
| 936 |
+
"Dimensions":"2D"
|
| 937 |
+
},
|
| 938 |
+
"156":{
|
| 939 |
+
"Name":"khs.sCl",
|
| 940 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 941 |
+
"Type":"Kappa descriptors",
|
| 942 |
+
"Dimensions":"2D"
|
| 943 |
+
},
|
| 944 |
+
"157":{
|
| 945 |
+
"Name":"khs.sGeH3",
|
| 946 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 947 |
+
"Type":"Kappa descriptors",
|
| 948 |
+
"Dimensions":"2D"
|
| 949 |
+
},
|
| 950 |
+
"158":{
|
| 951 |
+
"Name":"khs.ssGeH2",
|
| 952 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 953 |
+
"Type":"Kappa descriptors",
|
| 954 |
+
"Dimensions":"2D"
|
| 955 |
+
},
|
| 956 |
+
"159":{
|
| 957 |
+
"Name":"khs.sssGeH",
|
| 958 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 959 |
+
"Type":"Kappa descriptors",
|
| 960 |
+
"Dimensions":"2D"
|
| 961 |
+
},
|
| 962 |
+
"160":{
|
| 963 |
+
"Name":"khs.ssssGe",
|
| 964 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 965 |
+
"Type":"Kappa descriptors",
|
| 966 |
+
"Dimensions":"2D"
|
| 967 |
+
},
|
| 968 |
+
"161":{
|
| 969 |
+
"Name":"khs.sAsH2",
|
| 970 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 971 |
+
"Type":"Kappa descriptors",
|
| 972 |
+
"Dimensions":"2D"
|
| 973 |
+
},
|
| 974 |
+
"162":{
|
| 975 |
+
"Name":"khs.ssAsH",
|
| 976 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 977 |
+
"Type":"Kappa descriptors",
|
| 978 |
+
"Dimensions":"2D"
|
| 979 |
+
},
|
| 980 |
+
"163":{
|
| 981 |
+
"Name":"khs.sssAs",
|
| 982 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 983 |
+
"Type":"Kappa descriptors",
|
| 984 |
+
"Dimensions":"2D"
|
| 985 |
+
},
|
| 986 |
+
"164":{
|
| 987 |
+
"Name":"khs.sssdAs",
|
| 988 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 989 |
+
"Type":"Kappa descriptors",
|
| 990 |
+
"Dimensions":"2D"
|
| 991 |
+
},
|
| 992 |
+
"165":{
|
| 993 |
+
"Name":"khs.sssssAs",
|
| 994 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 995 |
+
"Type":"Kappa descriptors",
|
| 996 |
+
"Dimensions":"2D"
|
| 997 |
+
},
|
| 998 |
+
"166":{
|
| 999 |
+
"Name":"khs.sSeH",
|
| 1000 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 1001 |
+
"Type":"Kappa descriptors",
|
| 1002 |
+
"Dimensions":"2D"
|
| 1003 |
+
},
|
| 1004 |
+
"167":{
|
| 1005 |
+
"Name":"khs.dSe",
|
| 1006 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 1007 |
+
"Type":"Kappa descriptors",
|
| 1008 |
+
"Dimensions":"2D"
|
| 1009 |
+
},
|
| 1010 |
+
"168":{
|
| 1011 |
+
"Name":"khs.ssSe",
|
| 1012 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 1013 |
+
"Type":"Kappa descriptors",
|
| 1014 |
+
"Dimensions":"2D"
|
| 1015 |
+
},
|
| 1016 |
+
"169":{
|
| 1017 |
+
"Name":"khs.aaSe",
|
| 1018 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 1019 |
+
"Type":"Kappa descriptors",
|
| 1020 |
+
"Dimensions":"2D"
|
| 1021 |
+
},
|
| 1022 |
+
"170":{
|
| 1023 |
+
"Name":"khs.dssSe",
|
| 1024 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 1025 |
+
"Type":"Kappa descriptors",
|
| 1026 |
+
"Dimensions":"2D"
|
| 1027 |
+
},
|
| 1028 |
+
"171":{
|
| 1029 |
+
"Name":"khs.ddssSe",
|
| 1030 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 1031 |
+
"Type":"Kappa descriptors",
|
| 1032 |
+
"Dimensions":"2D"
|
| 1033 |
+
},
|
| 1034 |
+
"172":{
|
| 1035 |
+
"Name":"khs.sBr",
|
| 1036 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 1037 |
+
"Type":"Kappa descriptors",
|
| 1038 |
+
"Dimensions":"2D"
|
| 1039 |
+
},
|
| 1040 |
+
"173":{
|
| 1041 |
+
"Name":"khs.sSnH3",
|
| 1042 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 1043 |
+
"Type":"Kappa descriptors",
|
| 1044 |
+
"Dimensions":"2D"
|
| 1045 |
+
},
|
| 1046 |
+
"174":{
|
| 1047 |
+
"Name":"khs.ssSnH2",
|
| 1048 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 1049 |
+
"Type":"Kappa descriptors",
|
| 1050 |
+
"Dimensions":"2D"
|
| 1051 |
+
},
|
| 1052 |
+
"175":{
|
| 1053 |
+
"Name":"khs.sssSnH",
|
| 1054 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 1055 |
+
"Type":"Kappa descriptors",
|
| 1056 |
+
"Dimensions":"2D"
|
| 1057 |
+
},
|
| 1058 |
+
"176":{
|
| 1059 |
+
"Name":"khs.ssssSn",
|
| 1060 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 1061 |
+
"Type":"Kappa descriptors",
|
| 1062 |
+
"Dimensions":"2D"
|
| 1063 |
+
},
|
| 1064 |
+
"177":{
|
| 1065 |
+
"Name":"khs.sI",
|
| 1066 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 1067 |
+
"Type":"Kappa descriptors",
|
| 1068 |
+
"Dimensions":"2D"
|
| 1069 |
+
},
|
| 1070 |
+
"178":{
|
| 1071 |
+
"Name":"khs.sPbH3",
|
| 1072 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 1073 |
+
"Type":"Kappa descriptors",
|
| 1074 |
+
"Dimensions":"2D"
|
| 1075 |
+
},
|
| 1076 |
+
"179":{
|
| 1077 |
+
"Name":"khs.ssPbH2",
|
| 1078 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 1079 |
+
"Type":"Kappa descriptors",
|
| 1080 |
+
"Dimensions":"2D"
|
| 1081 |
+
},
|
| 1082 |
+
"180":{
|
| 1083 |
+
"Name":"khs.sssPbH",
|
| 1084 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 1085 |
+
"Type":"Kappa descriptors",
|
| 1086 |
+
"Dimensions":"2D"
|
| 1087 |
+
},
|
| 1088 |
+
"181":{
|
| 1089 |
+
"Name":"khs.ssssPb",
|
| 1090 |
+
"Description":"A fragment count descriptor that uses e-state fragments.",
|
| 1091 |
+
"Type":"Kappa descriptors",
|
| 1092 |
+
"Dimensions":"2D"
|
| 1093 |
+
},
|
| 1094 |
+
"182":{
|
| 1095 |
+
"Name":"Kier1",
|
| 1096 |
+
"Description":"Kier and Hall kappa molecular shape indices compare the molecular graph with minimal and maximal molecular graphs; a description is given at: http:\/\/www.chemcomp.com\/Journal_of_CCG\/Features\/descr.htm#KH : \"they are intended to capture different aspects of molecular shape.",
|
| 1097 |
+
"Type":"Kappa descriptors",
|
| 1098 |
+
"Dimensions":"2D"
|
| 1099 |
+
},
|
| 1100 |
+
"183":{
|
| 1101 |
+
"Name":"Kier2",
|
| 1102 |
+
"Description":"Kier and Hall kappa molecular shape indices compare the molecular graph with minimal and maximal molecular graphs; a description is given at: http:\/\/www.chemcomp.com\/Journal_of_CCG\/Features\/descr.htm#KH : \"they are intended to capture different aspects of molecular shape.",
|
| 1103 |
+
"Type":"Kappa descriptors",
|
| 1104 |
+
"Dimensions":"2D"
|
| 1105 |
+
},
|
| 1106 |
+
"184":{
|
| 1107 |
+
"Name":"Kier3",
|
| 1108 |
+
"Description":"Kier and Hall kappa molecular shape indices compare the molecular graph with minimal and maximal molecular graphs; a description is given at: http:\/\/www.chemcomp.com\/Journal_of_CCG\/Features\/descr.htm#KH : \"they are intended to capture different aspects of molecular shape.",
|
| 1109 |
+
"Type":"Kappa descriptors",
|
| 1110 |
+
"Dimensions":"2D"
|
| 1111 |
+
},
|
| 1112 |
+
"185":{
|
| 1113 |
+
"Name":"LipinskiFailures",
|
| 1114 |
+
"Description":"The number failures of the Lipinski's Rule Of 5.",
|
| 1115 |
+
"Type":"Molecular property descriptors",
|
| 1116 |
+
"Dimensions":"2D"
|
| 1117 |
+
},
|
| 1118 |
+
"186":{
|
| 1119 |
+
"Name":"MLogP",
|
| 1120 |
+
"Description":"Prediction of logP based on the number of carbon and hetero atoms.",
|
| 1121 |
+
"Type":"Molecular property descriptors",
|
| 1122 |
+
"Dimensions":"2D"
|
| 1123 |
+
},
|
| 1124 |
+
"187":{
|
| 1125 |
+
"Name":"MDEC-11",
|
| 1126 |
+
"Description":"?The Molecular Distance Edge descriptor described in [Liu, S. and Cao, C. and Li, Z. ,?Approach to Estimation and Prediction for Normal Boiling Point (NBP) of Alkanes Based on a Novel Molecular Distance Edge (MDE) Vector, lambda, Journal of Chemical Information and Computer Sciences,?1998, 38:387-394].",
|
| 1127 |
+
"Type":"Topological descriptors",
|
| 1128 |
+
"Dimensions":"2D"
|
| 1129 |
+
},
|
| 1130 |
+
"188":{
|
| 1131 |
+
"Name":"MDEC-12",
|
| 1132 |
+
"Description":"?The Molecular Distance Edge descriptor described in [Liu, S. and Cao, C. and Li, Z. ,?Approach to Estimation and Prediction for Normal Boiling Point (NBP) of Alkanes Based on a Novel Molecular Distance Edge (MDE) Vector, lambda, Journal of Chemical Information and Computer Sciences,?1998, 38:387-394].",
|
| 1133 |
+
"Type":"Topological descriptors",
|
| 1134 |
+
"Dimensions":"2D"
|
| 1135 |
+
},
|
| 1136 |
+
"189":{
|
| 1137 |
+
"Name":"MDEC-13",
|
| 1138 |
+
"Description":"?The Molecular Distance Edge descriptor described in [Liu, S. and Cao, C. and Li, Z. ,?Approach to Estimation and Prediction for Normal Boiling Point (NBP) of Alkanes Based on a Novel Molecular Distance Edge (MDE) Vector, lambda, Journal of Chemical Information and Computer Sciences,?1998, 38:387-394].",
|
| 1139 |
+
"Type":"Topological descriptors",
|
| 1140 |
+
"Dimensions":"2D"
|
| 1141 |
+
},
|
| 1142 |
+
"190":{
|
| 1143 |
+
"Name":"MDEC-14",
|
| 1144 |
+
"Description":"?The Molecular Distance Edge descriptor described in [Liu, S. and Cao, C. and Li, Z. ,?Approach to Estimation and Prediction for Normal Boiling Point (NBP) of Alkanes Based on a Novel Molecular Distance Edge (MDE) Vector, lambda, Journal of Chemical Information and Computer Sciences,?1998, 38:387-394].",
|
| 1145 |
+
"Type":"Topological descriptors",
|
| 1146 |
+
"Dimensions":"2D"
|
| 1147 |
+
},
|
| 1148 |
+
"191":{
|
| 1149 |
+
"Name":"MDEC-22",
|
| 1150 |
+
"Description":"?The Molecular Distance Edge descriptor described in [Liu, S. and Cao, C. and Li, Z. ,?Approach to Estimation and Prediction for Normal Boiling Point (NBP) of Alkanes Based on a Novel Molecular Distance Edge (MDE) Vector, lambda, Journal of Chemical Information and Computer Sciences,?1998, 38:387-394].",
|
| 1151 |
+
"Type":"Topological descriptors",
|
| 1152 |
+
"Dimensions":"2D"
|
| 1153 |
+
},
|
| 1154 |
+
"192":{
|
| 1155 |
+
"Name":"MDEC-23",
|
| 1156 |
+
"Description":"?The Molecular Distance Edge descriptor described in [Liu, S. and Cao, C. and Li, Z. ,?Approach to Estimation and Prediction for Normal Boiling Point (NBP) of Alkanes Based on a Novel Molecular Distance Edge (MDE) Vector, lambda, Journal of Chemical Information and Computer Sciences,?1998, 38:387-394].",
|
| 1157 |
+
"Type":"Topological descriptors",
|
| 1158 |
+
"Dimensions":"2D"
|
| 1159 |
+
},
|
| 1160 |
+
"193":{
|
| 1161 |
+
"Name":"MDEC-24",
|
| 1162 |
+
"Description":"?The Molecular Distance Edge descriptor described in [Liu, S. and Cao, C. and Li, Z. ,?Approach to Estimation and Prediction for Normal Boiling Point (NBP) of Alkanes Based on a Novel Molecular Distance Edge (MDE) Vector, lambda, Journal of Chemical Information and Computer Sciences,?1998, 38:387-394].",
|
| 1163 |
+
"Type":"Topological descriptors",
|
| 1164 |
+
"Dimensions":"2D"
|
| 1165 |
+
},
|
| 1166 |
+
"194":{
|
| 1167 |
+
"Name":"MDEC-33",
|
| 1168 |
+
"Description":"?The Molecular Distance Edge descriptor described in [Liu, S. and Cao, C. and Li, Z. ,?Approach to Estimation and Prediction for Normal Boiling Point (NBP) of Alkanes Based on a Novel Molecular Distance Edge (MDE) Vector, lambda, Journal of Chemical Information and Computer Sciences,?1998, 38:387-394].",
|
| 1169 |
+
"Type":"Topological descriptors",
|
| 1170 |
+
"Dimensions":"2D"
|
| 1171 |
+
},
|
| 1172 |
+
"195":{
|
| 1173 |
+
"Name":"MDEC-34",
|
| 1174 |
+
"Description":"?The Molecular Distance Edge descriptor described in [Liu, S. and Cao, C. and Li, Z. ,?Approach to Estimation and Prediction for Normal Boiling Point (NBP) of Alkanes Based on a Novel Molecular Distance Edge (MDE) Vector, lambda, Journal of Chemical Information and Computer Sciences,?1998, 38:387-394].",
|
| 1175 |
+
"Type":"Topological descriptors",
|
| 1176 |
+
"Dimensions":"2D"
|
| 1177 |
+
},
|
| 1178 |
+
"196":{
|
| 1179 |
+
"Name":"MDEC-44",
|
| 1180 |
+
"Description":"?The Molecular Distance Edge descriptor described in [Liu, S. and Cao, C. and Li, Z. ,?Approach to Estimation and Prediction for Normal Boiling Point (NBP) of Alkanes Based on a Novel Molecular Distance Edge (MDE) Vector, lambda, Journal of Chemical Information and Computer Sciences,?1998, 38:387-394].",
|
| 1181 |
+
"Type":"Topological descriptors",
|
| 1182 |
+
"Dimensions":"2D"
|
| 1183 |
+
},
|
| 1184 |
+
"197":{
|
| 1185 |
+
"Name":"MDEO-11",
|
| 1186 |
+
"Description":"?The Molecular Distance Edge descriptor described in [Liu, S. and Cao, C. and Li, Z. ,?Approach to Estimation and Prediction for Normal Boiling Point (NBP) of Alkanes Based on a Novel Molecular Distance Edge (MDE) Vector, lambda, Journal of Chemical Information and Computer Sciences,?1998, 38:387-394].",
|
| 1187 |
+
"Type":"Topological descriptors",
|
| 1188 |
+
"Dimensions":"2D"
|
| 1189 |
+
},
|
| 1190 |
+
"198":{
|
| 1191 |
+
"Name":"MDEO-12",
|
| 1192 |
+
"Description":"?The Molecular Distance Edge descriptor described in [Liu, S. and Cao, C. and Li, Z. ,?Approach to Estimation and Prediction for Normal Boiling Point (NBP) of Alkanes Based on a Novel Molecular Distance Edge (MDE) Vector, lambda, Journal of Chemical Information and Computer Sciences,?1998, 38:387-394].",
|
| 1193 |
+
"Type":"Topological descriptors",
|
| 1194 |
+
"Dimensions":"2D"
|
| 1195 |
+
},
|
| 1196 |
+
"199":{
|
| 1197 |
+
"Name":"MDEO-22",
|
| 1198 |
+
"Description":"?The Molecular Distance Edge descriptor described in [Liu, S. and Cao, C. and Li, Z. ,?Approach to Estimation and Prediction for Normal Boiling Point (NBP) of Alkanes Based on a Novel Molecular Distance Edge (MDE) Vector, lambda, Journal of Chemical Information and Computer Sciences,?1998, 38:387-394].",
|
| 1199 |
+
"Type":"Topological descriptors",
|
| 1200 |
+
"Dimensions":"2D"
|
| 1201 |
+
},
|
| 1202 |
+
"200":{
|
| 1203 |
+
"Name":"MDEN-11",
|
| 1204 |
+
"Description":"?The Molecular Distance Edge descriptor described in [Liu, S. and Cao, C. and Li, Z. ,?Approach to Estimation and Prediction for Normal Boiling Point (NBP) of Alkanes Based on a Novel Molecular Distance Edge (MDE) Vector, lambda, Journal of Chemical Information and Computer Sciences,?1998, 38:387-394].",
|
| 1205 |
+
"Type":"Topological descriptors",
|
| 1206 |
+
"Dimensions":"2D"
|
| 1207 |
+
},
|
| 1208 |
+
"201":{
|
| 1209 |
+
"Name":"MDEN-12",
|
| 1210 |
+
"Description":"?The Molecular Distance Edge descriptor described in [Liu, S. and Cao, C. and Li, Z. ,?Approach to Estimation and Prediction for Normal Boiling Point (NBP) of Alkanes Based on a Novel Molecular Distance Edge (MDE) Vector, lambda, Journal of Chemical Information and Computer Sciences,?1998, 38:387-394].",
|
| 1211 |
+
"Type":"Topological descriptors",
|
| 1212 |
+
"Dimensions":"2D"
|
| 1213 |
+
},
|
| 1214 |
+
"202":{
|
| 1215 |
+
"Name":"MDEN-13",
|
| 1216 |
+
"Description":"?The Molecular Distance Edge descriptor described in [Liu, S. and Cao, C. and Li, Z. ,?Approach to Estimation and Prediction for Normal Boiling Point (NBP) of Alkanes Based on a Novel Molecular Distance Edge (MDE) Vector, lambda, Journal of Chemical Information and Computer Sciences,?1998, 38:387-394].",
|
| 1217 |
+
"Type":"Topological descriptors",
|
| 1218 |
+
"Dimensions":"2D"
|
| 1219 |
+
},
|
| 1220 |
+
"203":{
|
| 1221 |
+
"Name":"MDEN-22",
|
| 1222 |
+
"Description":"?The Molecular Distance Edge descriptor described in [Liu, S. and Cao, C. and Li, Z. ,?Approach to Estimation and Prediction for Normal Boiling Point (NBP) of Alkanes Based on a Novel Molecular Distance Edge (MDE) Vector, lambda, Journal of Chemical Information and Computer Sciences,?1998, 38:387-394].",
|
| 1223 |
+
"Type":"Topological descriptors",
|
| 1224 |
+
"Dimensions":"2D"
|
| 1225 |
+
},
|
| 1226 |
+
"204":{
|
| 1227 |
+
"Name":"MDEN-23",
|
| 1228 |
+
"Description":"?The Molecular Distance Edge descriptor described in [Liu, S. and Cao, C. and Li, Z. ,?Approach to Estimation and Prediction for Normal Boiling Point (NBP) of Alkanes Based on a Novel Molecular Distance Edge (MDE) Vector, lambda, Journal of Chemical Information and Computer Sciences,?1998, 38:387-394].",
|
| 1229 |
+
"Type":"Topological descriptors",
|
| 1230 |
+
"Dimensions":"2D"
|
| 1231 |
+
},
|
| 1232 |
+
"205":{
|
| 1233 |
+
"Name":"MDEN-33",
|
| 1234 |
+
"Description":"?The Molecular Distance Edge descriptor described in [Liu, S. and Cao, C. and Li, Z. ,?Approach to Estimation and Prediction for Normal Boiling Point (NBP) of Alkanes Based on a Novel Molecular Distance Edge (MDE) Vector, lambda, Journal of Chemical Information and Computer Sciences,?1998, 38:387-394].",
|
| 1235 |
+
"Type":"Topological descriptors",
|
| 1236 |
+
"Dimensions":"2D"
|
| 1237 |
+
},
|
| 1238 |
+
"206":{
|
| 1239 |
+
"Name":"PetitjeanNumber",
|
| 1240 |
+
"Description":"According to the Petitjean definition, the eccentricity of a vertex corresponds to the distance from that vertex to the most remote vertex in the graph.",
|
| 1241 |
+
"Type":"Topological descriptors",
|
| 1242 |
+
"Dimensions":"2D"
|
| 1243 |
+
},
|
| 1244 |
+
"207":{
|
| 1245 |
+
"Name":"topoShape",
|
| 1246 |
+
"Description":"Evaluates the Petitjean shape indices,These original Petitjean number was described by Petitjean ([?Petitjean, M. ,?Applications of the radius-diameter diagram to the classification of topological and geometrical shapes of chemical compounds, Journal of Chemical Information and Computer Science,?1992, 32:331-337]) and considered the molecular graph.",
|
| 1247 |
+
"Type":"Topological descriptors",
|
| 1248 |
+
"Dimensions":"2D"
|
| 1249 |
+
},
|
| 1250 |
+
"208":{
|
| 1251 |
+
"Name":"geomShape",
|
| 1252 |
+
"Description":"Evaluates the Petitjean shape indices,These original Petitjean number was described by Petitjean ([?Petitjean, M. ,?Applications of the radius-diameter diagram to the classification of topological and geometrical shapes of chemical compounds, Journal of Chemical Information and Computer Science,?1992, 32:331-337]) and considered the molecular graph.",
|
| 1253 |
+
"Type":"Topological descriptors",
|
| 1254 |
+
"Dimensions":"3D"
|
| 1255 |
+
},
|
| 1256 |
+
"209":{
|
| 1257 |
+
"Name":"TopoPSA",
|
| 1258 |
+
"Description":"Calculation of topological polar surface area based on fragment contributions (TPSA) [Ertl, P. and Rohde, B. and Selzer, P.,?Fast Calculation of Molecular Polar Surface Area as a Sum of Fragment-Based Contributions and Its Application to the Prediction of Drug Transport Properties, J. Med. Chem.,?2000, 43:3714-3717].",
|
| 1259 |
+
"Type":"Molecular property descriptors",
|
| 1260 |
+
"Dimensions":"2D"
|
| 1261 |
+
},
|
| 1262 |
+
"210":{
|
| 1263 |
+
"Name":"tpsaEfficiency",
|
| 1264 |
+
"Description":"Polar surface area expressed as a ratio to molecular size.",
|
| 1265 |
+
"Type":"Molecular property descriptors",
|
| 1266 |
+
"Dimensions":"2D"
|
| 1267 |
+
},
|
| 1268 |
+
"211":{
|
| 1269 |
+
"Name":"VAdjMat",
|
| 1270 |
+
"Description":"Vertex adjacency information (magnitude): 1 + log2 m where m is the number of heavy-heavy bonds.",
|
| 1271 |
+
"Type":"Topological descriptors",
|
| 1272 |
+
"Dimensions":"2D"
|
| 1273 |
+
},
|
| 1274 |
+
"212":{
|
| 1275 |
+
"Name":"VABC",
|
| 1276 |
+
"Description":"Volume descriptor using the method implemented in the?VABCVolume?class.",
|
| 1277 |
+
"Type":"Topological descriptors",
|
| 1278 |
+
"Dimensions":"2D"
|
| 1279 |
+
},
|
| 1280 |
+
"213":{
|
| 1281 |
+
"Name":"MW",
|
| 1282 |
+
"Description":"The weight of atoms of a certain element type.",
|
| 1283 |
+
"Type":"Molecular property descriptors",
|
| 1284 |
+
"Dimensions":"2D"
|
| 1285 |
+
},
|
| 1286 |
+
"214":{
|
| 1287 |
+
"Name":"WTPT-1",
|
| 1288 |
+
"Description":"Evaluates the weighted path descriptors.",
|
| 1289 |
+
"Type":"Topological descriptors",
|
| 1290 |
+
"Dimensions":"2D"
|
| 1291 |
+
},
|
| 1292 |
+
"215":{
|
| 1293 |
+
"Name":"WTPT-2",
|
| 1294 |
+
"Description":"Evaluates the weighted path descriptors.",
|
| 1295 |
+
"Type":"Topological descriptors",
|
| 1296 |
+
"Dimensions":"2D"
|
| 1297 |
+
},
|
| 1298 |
+
"216":{
|
| 1299 |
+
"Name":"WTPT-3",
|
| 1300 |
+
"Description":"Evaluates the weighted path descriptors.",
|
| 1301 |
+
"Type":"Topological descriptors",
|
| 1302 |
+
"Dimensions":"2D"
|
| 1303 |
+
},
|
| 1304 |
+
"217":{
|
| 1305 |
+
"Name":"WTPT-4",
|
| 1306 |
+
"Description":"Evaluates the weighted path descriptors.",
|
| 1307 |
+
"Type":"Topological descriptors",
|
| 1308 |
+
"Dimensions":"2D"
|
| 1309 |
+
},
|
| 1310 |
+
"218":{
|
| 1311 |
+
"Name":"WTPT-5",
|
| 1312 |
+
"Description":"Evaluates the weighted path descriptors.",
|
| 1313 |
+
"Type":"Topological descriptors",
|
| 1314 |
+
"Dimensions":"2D"
|
| 1315 |
+
},
|
| 1316 |
+
"219":{
|
| 1317 |
+
"Name":"WPATH",
|
| 1318 |
+
"Description":"This descriptor calculates the Wiener numbers.",
|
| 1319 |
+
"Type":"Topological descriptors",
|
| 1320 |
+
"Dimensions":"2D"
|
| 1321 |
+
},
|
| 1322 |
+
"220":{
|
| 1323 |
+
"Name":"WPOL",
|
| 1324 |
+
"Description":"This descriptor calculates the Wiener numbers.",
|
| 1325 |
+
"Type":"Topological descriptors",
|
| 1326 |
+
"Dimensions":"2D"
|
| 1327 |
+
},
|
| 1328 |
+
"221":{
|
| 1329 |
+
"Name":"XLogP",
|
| 1330 |
+
"Description":"Prediction of logP based on the atom-type method called XLogP.",
|
| 1331 |
+
"Type":"Molecular property descriptors",
|
| 1332 |
+
"Dimensions":"2D"
|
| 1333 |
+
},
|
| 1334 |
+
"222":{
|
| 1335 |
+
"Name":"Zagreb",
|
| 1336 |
+
"Description":"Zagreb index: the sum of the squares of atom degree over all heavy atoms i.",
|
| 1337 |
+
"Type":"Topological descriptors",
|
| 1338 |
+
"Dimensions":"2D"
|
| 1339 |
+
},
|
| 1340 |
+
"223":{
|
| 1341 |
+
"Name":"PPSA-1",
|
| 1342 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1343 |
+
"Type":"CPSA descriptors",
|
| 1344 |
+
"Dimensions":"3D"
|
| 1345 |
+
},
|
| 1346 |
+
"224":{
|
| 1347 |
+
"Name":"PPSA-2",
|
| 1348 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1349 |
+
"Type":"CPSA descriptors",
|
| 1350 |
+
"Dimensions":"3D"
|
| 1351 |
+
},
|
| 1352 |
+
"225":{
|
| 1353 |
+
"Name":"PPSA-3",
|
| 1354 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1355 |
+
"Type":"CPSA descriptors",
|
| 1356 |
+
"Dimensions":"3D"
|
| 1357 |
+
},
|
| 1358 |
+
"226":{
|
| 1359 |
+
"Name":"PNSA-1",
|
| 1360 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1361 |
+
"Type":"CPSA descriptors",
|
| 1362 |
+
"Dimensions":"3D"
|
| 1363 |
+
},
|
| 1364 |
+
"227":{
|
| 1365 |
+
"Name":"PNSA-2",
|
| 1366 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1367 |
+
"Type":"CPSA descriptors",
|
| 1368 |
+
"Dimensions":"3D"
|
| 1369 |
+
},
|
| 1370 |
+
"228":{
|
| 1371 |
+
"Name":"PNSA-3",
|
| 1372 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1373 |
+
"Type":"CPSA descriptors",
|
| 1374 |
+
"Dimensions":"3D"
|
| 1375 |
+
},
|
| 1376 |
+
"229":{
|
| 1377 |
+
"Name":"DPSA-1",
|
| 1378 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1379 |
+
"Type":"CPSA descriptors",
|
| 1380 |
+
"Dimensions":"3D"
|
| 1381 |
+
},
|
| 1382 |
+
"230":{
|
| 1383 |
+
"Name":"DPSA-2",
|
| 1384 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1385 |
+
"Type":"CPSA descriptors",
|
| 1386 |
+
"Dimensions":"3D"
|
| 1387 |
+
},
|
| 1388 |
+
"231":{
|
| 1389 |
+
"Name":"DPSA-3",
|
| 1390 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1391 |
+
"Type":"CPSA descriptors",
|
| 1392 |
+
"Dimensions":"3D"
|
| 1393 |
+
},
|
| 1394 |
+
"232":{
|
| 1395 |
+
"Name":"FPSA-1",
|
| 1396 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1397 |
+
"Type":"CPSA descriptors",
|
| 1398 |
+
"Dimensions":"3D"
|
| 1399 |
+
},
|
| 1400 |
+
"233":{
|
| 1401 |
+
"Name":"FPSA-2",
|
| 1402 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1403 |
+
"Type":"CPSA descriptors",
|
| 1404 |
+
"Dimensions":"3D"
|
| 1405 |
+
},
|
| 1406 |
+
"234":{
|
| 1407 |
+
"Name":"FPSA-3",
|
| 1408 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1409 |
+
"Type":"CPSA descriptors",
|
| 1410 |
+
"Dimensions":"3D"
|
| 1411 |
+
},
|
| 1412 |
+
"235":{
|
| 1413 |
+
"Name":"FNSA-1",
|
| 1414 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1415 |
+
"Type":"CPSA descriptors",
|
| 1416 |
+
"Dimensions":"3D"
|
| 1417 |
+
},
|
| 1418 |
+
"236":{
|
| 1419 |
+
"Name":"FNSA-2",
|
| 1420 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1421 |
+
"Type":"CPSA descriptors",
|
| 1422 |
+
"Dimensions":"3D"
|
| 1423 |
+
},
|
| 1424 |
+
"237":{
|
| 1425 |
+
"Name":"FNSA-3",
|
| 1426 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1427 |
+
"Type":"CPSA descriptors",
|
| 1428 |
+
"Dimensions":"3D"
|
| 1429 |
+
},
|
| 1430 |
+
"238":{
|
| 1431 |
+
"Name":"WPSA-1",
|
| 1432 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1433 |
+
"Type":"CPSA descriptors",
|
| 1434 |
+
"Dimensions":"3D"
|
| 1435 |
+
},
|
| 1436 |
+
"239":{
|
| 1437 |
+
"Name":"WPSA-2",
|
| 1438 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1439 |
+
"Type":"CPSA descriptors",
|
| 1440 |
+
"Dimensions":"3D"
|
| 1441 |
+
},
|
| 1442 |
+
"240":{
|
| 1443 |
+
"Name":"WPSA-3",
|
| 1444 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1445 |
+
"Type":"CPSA descriptors",
|
| 1446 |
+
"Dimensions":"3D"
|
| 1447 |
+
},
|
| 1448 |
+
"241":{
|
| 1449 |
+
"Name":"WNSA-1",
|
| 1450 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1451 |
+
"Type":"CPSA descriptors",
|
| 1452 |
+
"Dimensions":"3D"
|
| 1453 |
+
},
|
| 1454 |
+
"242":{
|
| 1455 |
+
"Name":"WNSA-2",
|
| 1456 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1457 |
+
"Type":"CPSA descriptors",
|
| 1458 |
+
"Dimensions":"3D"
|
| 1459 |
+
},
|
| 1460 |
+
"243":{
|
| 1461 |
+
"Name":"WNSA-3",
|
| 1462 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1463 |
+
"Type":"CPSA descriptors",
|
| 1464 |
+
"Dimensions":"3D"
|
| 1465 |
+
},
|
| 1466 |
+
"244":{
|
| 1467 |
+
"Name":"RPCG",
|
| 1468 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1469 |
+
"Type":"CPSA descriptors",
|
| 1470 |
+
"Dimensions":"3D"
|
| 1471 |
+
},
|
| 1472 |
+
"245":{
|
| 1473 |
+
"Name":"RNCG",
|
| 1474 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1475 |
+
"Type":"CPSA descriptors",
|
| 1476 |
+
"Dimensions":"3D"
|
| 1477 |
+
},
|
| 1478 |
+
"246":{
|
| 1479 |
+
"Name":"RPCS",
|
| 1480 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1481 |
+
"Type":"CPSA descriptors",
|
| 1482 |
+
"Dimensions":"3D"
|
| 1483 |
+
},
|
| 1484 |
+
"247":{
|
| 1485 |
+
"Name":"RNCS",
|
| 1486 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1487 |
+
"Type":"CPSA descriptors",
|
| 1488 |
+
"Dimensions":"3D"
|
| 1489 |
+
},
|
| 1490 |
+
"248":{
|
| 1491 |
+
"Name":"THSA",
|
| 1492 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1493 |
+
"Type":"CPSA descriptors",
|
| 1494 |
+
"Dimensions":"3D"
|
| 1495 |
+
},
|
| 1496 |
+
"249":{
|
| 1497 |
+
"Name":"TPSA",
|
| 1498 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1499 |
+
"Type":"CPSA descriptors",
|
| 1500 |
+
"Dimensions":"3D"
|
| 1501 |
+
},
|
| 1502 |
+
"250":{
|
| 1503 |
+
"Name":"RHSA",
|
| 1504 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1505 |
+
"Type":"CPSA descriptors",
|
| 1506 |
+
"Dimensions":"3D"
|
| 1507 |
+
},
|
| 1508 |
+
"251":{
|
| 1509 |
+
"Name":"RPSA",
|
| 1510 |
+
"Description":"29 Charged Partial Surface Area (CPSA) descriptors.",
|
| 1511 |
+
"Type":"CPSA descriptors",
|
| 1512 |
+
"Dimensions":"3D"
|
| 1513 |
+
},
|
| 1514 |
+
"252":{
|
| 1515 |
+
"Name":"Wlambda1.unity",
|
| 1516 |
+
"Description":"Holistic descriptors described by Todeschini et al [Todeschini, R. and Gramatica, P.,?New 3D Molecular Descriptors: The WHIM theory and QAR Applications, Persepectives in Drug Discovery and Design,?1998, ?:355-380].",
|
| 1517 |
+
"Type":"WHIM descriptors",
|
| 1518 |
+
"Dimensions":"3D"
|
| 1519 |
+
},
|
| 1520 |
+
"253":{
|
| 1521 |
+
"Name":"Wlambda2.unity",
|
| 1522 |
+
"Description":"Holistic descriptors described by Todeschini et al [Todeschini, R. and Gramatica, P.,?New 3D Molecular Descriptors: The WHIM theory and QAR Applications, Persepectives in Drug Discovery and Design,?1998, ?:355-381].",
|
| 1523 |
+
"Type":"WHIM descriptors",
|
| 1524 |
+
"Dimensions":"3D"
|
| 1525 |
+
},
|
| 1526 |
+
"254":{
|
| 1527 |
+
"Name":"Wlambda3.unity",
|
| 1528 |
+
"Description":"Holistic descriptors described by Todeschini et al [Todeschini, R. and Gramatica, P.,?New 3D Molecular Descriptors: The WHIM theory and QAR Applications, Persepectives in Drug Discovery and Design,?1998, ?:355-382].",
|
| 1529 |
+
"Type":"WHIM descriptors",
|
| 1530 |
+
"Dimensions":"3D"
|
| 1531 |
+
},
|
| 1532 |
+
"255":{
|
| 1533 |
+
"Name":"Wnu1.unity",
|
| 1534 |
+
"Description":"Holistic descriptors described by Todeschini et al [Todeschini, R. and Gramatica, P.,?New 3D Molecular Descriptors: The WHIM theory and QAR Applications, Persepectives in Drug Discovery and Design,?1998, ?:355-383].",
|
| 1535 |
+
"Type":"WHIM descriptors",
|
| 1536 |
+
"Dimensions":"3D"
|
| 1537 |
+
},
|
| 1538 |
+
"256":{
|
| 1539 |
+
"Name":"Wnu2.unity",
|
| 1540 |
+
"Description":"Holistic descriptors described by Todeschini et al [Todeschini, R. and Gramatica, P.,?New 3D Molecular Descriptors: The WHIM theory and QAR Applications, Persepectives in Drug Discovery and Design,?1998, ?:355-384].",
|
| 1541 |
+
"Type":"WHIM descriptors",
|
| 1542 |
+
"Dimensions":"3D"
|
| 1543 |
+
},
|
| 1544 |
+
"257":{
|
| 1545 |
+
"Name":"Wgamma1.unity",
|
| 1546 |
+
"Description":"Holistic descriptors described by Todeschini et al [Todeschini, R. and Gramatica, P.,?New 3D Molecular Descriptors: The WHIM theory and QAR Applications, Persepectives in Drug Discovery and Design,?1998, ?:355-385].",
|
| 1547 |
+
"Type":"WHIM descriptors",
|
| 1548 |
+
"Dimensions":"3D"
|
| 1549 |
+
},
|
| 1550 |
+
"258":{
|
| 1551 |
+
"Name":"Wgamma2.unity",
|
| 1552 |
+
"Description":"Holistic descriptors described by Todeschini et al [Todeschini, R. and Gramatica, P.,?New 3D Molecular Descriptors: The WHIM theory and QAR Applications, Persepectives in Drug Discovery and Design,?1998, ?:355-386].",
|
| 1553 |
+
"Type":"WHIM descriptors",
|
| 1554 |
+
"Dimensions":"3D"
|
| 1555 |
+
},
|
| 1556 |
+
"259":{
|
| 1557 |
+
"Name":"Wgamma3.unity",
|
| 1558 |
+
"Description":"Holistic descriptors described by Todeschini et al [Todeschini, R. and Gramatica, P.,?New 3D Molecular Descriptors: The WHIM theory and QAR Applications, Persepectives in Drug Discovery and Design,?1998, ?:355-387].",
|
| 1559 |
+
"Type":"WHIM descriptors",
|
| 1560 |
+
"Dimensions":"3D"
|
| 1561 |
+
},
|
| 1562 |
+
"260":{
|
| 1563 |
+
"Name":"Weta1.unity",
|
| 1564 |
+
"Description":"Holistic descriptors described by Todeschini et al [Todeschini, R. and Gramatica, P.,?New 3D Molecular Descriptors: The WHIM theory and QAR Applications, Persepectives in Drug Discovery and Design,?1998, ?:355-388].",
|
| 1565 |
+
"Type":"WHIM descriptors",
|
| 1566 |
+
"Dimensions":"3D"
|
| 1567 |
+
},
|
| 1568 |
+
"261":{
|
| 1569 |
+
"Name":"Weta2.unity",
|
| 1570 |
+
"Description":"Holistic descriptors described by Todeschini et al [Todeschini, R. and Gramatica, P.,?New 3D Molecular Descriptors: The WHIM theory and QAR Applications, Persepectives in Drug Discovery and Design,?1998, ?:355-389].",
|
| 1571 |
+
"Type":"WHIM descriptors",
|
| 1572 |
+
"Dimensions":"3D"
|
| 1573 |
+
},
|
| 1574 |
+
"262":{
|
| 1575 |
+
"Name":"Weta3.unity",
|
| 1576 |
+
"Description":"Holistic descriptors described by Todeschini et al [Todeschini, R. and Gramatica, P.,?New 3D Molecular Descriptors: The WHIM theory and QAR Applications, Persepectives in Drug Discovery and Design,?1998, ?:355-390].",
|
| 1577 |
+
"Type":"WHIM descriptors",
|
| 1578 |
+
"Dimensions":"3D"
|
| 1579 |
+
},
|
| 1580 |
+
"263":{
|
| 1581 |
+
"Name":"WT.unity",
|
| 1582 |
+
"Description":"Holistic descriptors described by Todeschini et al [Todeschini, R. and Gramatica, P.,?New 3D Molecular Descriptors: The WHIM theory and QAR Applications, Persepectives in Drug Discovery and Design,?1998, ?:355-391].",
|
| 1583 |
+
"Type":"WHIM descriptors",
|
| 1584 |
+
"Dimensions":"3D"
|
| 1585 |
+
},
|
| 1586 |
+
"264":{
|
| 1587 |
+
"Name":"WA.unity",
|
| 1588 |
+
"Description":"Holistic descriptors described by Todeschini et al [Todeschini, R. and Gramatica, P.,?New 3D Molecular Descriptors: The WHIM theory and QAR Applications, Persepectives in Drug Discovery and Design,?1998, ?:355-392].",
|
| 1589 |
+
"Type":"WHIM descriptors",
|
| 1590 |
+
"Dimensions":"3D"
|
| 1591 |
+
},
|
| 1592 |
+
"265":{
|
| 1593 |
+
"Name":"WV.unity",
|
| 1594 |
+
"Description":"Holistic descriptors described by Todeschini et al [Todeschini, R. and Gramatica, P.,?New 3D Molecular Descriptors: The WHIM theory and QAR Applications, Persepectives in Drug Discovery and Design,?1998, ?:355-393].",
|
| 1595 |
+
"Type":"WHIM descriptors",
|
| 1596 |
+
"Dimensions":"3D"
|
| 1597 |
+
},
|
| 1598 |
+
"266":{
|
| 1599 |
+
"Name":"WK.unity",
|
| 1600 |
+
"Description":"Holistic descriptors described by Todeschini et al [Todeschini, R. and Gramatica, P.,?New 3D Molecular Descriptors: The WHIM theory and QAR Applications, Persepectives in Drug Discovery and Design,?1998, ?:355-394].",
|
| 1601 |
+
"Type":"WHIM descriptors",
|
| 1602 |
+
"Dimensions":"3D"
|
| 1603 |
+
},
|
| 1604 |
+
"267":{
|
| 1605 |
+
"Name":"WG.unity",
|
| 1606 |
+
"Description":"Holistic descriptors described by Todeschini et al [Todeschini, R. and Gramatica, P.,?New 3D Molecular Descriptors: The WHIM theory and QAR Applications, Persepectives in Drug Discovery and Design,?1998, ?:355-395].",
|
| 1607 |
+
"Type":"WHIM descriptors",
|
| 1608 |
+
"Dimensions":"3D"
|
| 1609 |
+
},
|
| 1610 |
+
"268":{
|
| 1611 |
+
"Name":"WD.unity",
|
| 1612 |
+
"Description":"Holistic descriptors described by Todeschini et al [Todeschini, R. and Gramatica, P.,?New 3D Molecular Descriptors: The WHIM theory and QAR Applications, Persepectives in Drug Discovery and Design,?1998, ?:355-396].",
|
| 1613 |
+
"Type":"WHIM descriptors",
|
| 1614 |
+
"Dimensions":"3D"
|
| 1615 |
+
},
|
| 1616 |
+
"269":{
|
| 1617 |
+
"Name":"GRAV-1",
|
| 1618 |
+
"Description":"IDescriptor characterizing the mass distribution of the molecule.",
|
| 1619 |
+
"Type":"Geometrical descriptors",
|
| 1620 |
+
"Dimensions":"3D"
|
| 1621 |
+
},
|
| 1622 |
+
"270":{
|
| 1623 |
+
"Name":"GRAV-2",
|
| 1624 |
+
"Description":"IDescriptor characterizing the mass distribution of the molecule.",
|
| 1625 |
+
"Type":"Geometrical descriptors",
|
| 1626 |
+
"Dimensions":"3D"
|
| 1627 |
+
},
|
| 1628 |
+
"271":{
|
| 1629 |
+
"Name":"GRAV-3",
|
| 1630 |
+
"Description":"IDescriptor characterizing the mass distribution of the molecule.",
|
| 1631 |
+
"Type":"Geometrical descriptors",
|
| 1632 |
+
"Dimensions":"3D"
|
| 1633 |
+
},
|
| 1634 |
+
"272":{
|
| 1635 |
+
"Name":"GRAVH-1",
|
| 1636 |
+
"Description":"IDescriptor characterizing the mass distribution of the molecule.",
|
| 1637 |
+
"Type":"Geometrical descriptors",
|
| 1638 |
+
"Dimensions":"3D"
|
| 1639 |
+
},
|
| 1640 |
+
"273":{
|
| 1641 |
+
"Name":"GRAVH-2",
|
| 1642 |
+
"Description":"IDescriptor characterizing the mass distribution of the molecule.",
|
| 1643 |
+
"Type":"Geometrical descriptors",
|
| 1644 |
+
"Dimensions":"3D"
|
| 1645 |
+
},
|
| 1646 |
+
"274":{
|
| 1647 |
+
"Name":"GRAVH-3",
|
| 1648 |
+
"Description":"IDescriptor characterizing the mass distribution of the molecule.",
|
| 1649 |
+
"Type":"Geometrical descriptors",
|
| 1650 |
+
"Dimensions":"3D"
|
| 1651 |
+
},
|
| 1652 |
+
"275":{
|
| 1653 |
+
"Name":"GRAV-4",
|
| 1654 |
+
"Description":"IDescriptor characterizing the mass distribution of the molecule.",
|
| 1655 |
+
"Type":"Geometrical descriptors",
|
| 1656 |
+
"Dimensions":"3D"
|
| 1657 |
+
},
|
| 1658 |
+
"276":{
|
| 1659 |
+
"Name":"GRAV-5",
|
| 1660 |
+
"Description":"IDescriptor characterizing the mass distribution of the molecule.",
|
| 1661 |
+
"Type":"Geometrical descriptors",
|
| 1662 |
+
"Dimensions":"3D"
|
| 1663 |
+
},
|
| 1664 |
+
"277":{
|
| 1665 |
+
"Name":"GRAV-6",
|
| 1666 |
+
"Description":"IDescriptor characterizing the mass distribution of the molecule.",
|
| 1667 |
+
"Type":"Geometrical descriptors",
|
| 1668 |
+
"Dimensions":"3D"
|
| 1669 |
+
},
|
| 1670 |
+
"278":{
|
| 1671 |
+
"Name":"LOBMIN",
|
| 1672 |
+
"Description":"Evaluates length over breadth descriptors.",
|
| 1673 |
+
"Type":"Geometrical descriptors",
|
| 1674 |
+
"Dimensions":"3D"
|
| 1675 |
+
},
|
| 1676 |
+
"279":{
|
| 1677 |
+
"Name":"LOBMAX",
|
| 1678 |
+
"Description":"Evaluates length over breadth descriptors.",
|
| 1679 |
+
"Type":"Geometrical descriptors",
|
| 1680 |
+
"Dimensions":"3D"
|
| 1681 |
+
},
|
| 1682 |
+
"280":{
|
| 1683 |
+
"Name":"MOMI-X",
|
| 1684 |
+
"Description":"A descriptor that calculates the moment of inertia and radius of gyration.",
|
| 1685 |
+
"Type":"Geometrical descriptors",
|
| 1686 |
+
"Dimensions":"3D"
|
| 1687 |
+
},
|
| 1688 |
+
"281":{
|
| 1689 |
+
"Name":"MOMI-Y",
|
| 1690 |
+
"Description":"A descriptor that calculates the moment of inertia and radius of gyration.",
|
| 1691 |
+
"Type":"Geometrical descriptors",
|
| 1692 |
+
"Dimensions":"3D"
|
| 1693 |
+
},
|
| 1694 |
+
"282":{
|
| 1695 |
+
"Name":"MOMI-Z",
|
| 1696 |
+
"Description":"A descriptor that calculates the moment of inertia and radius of gyration.",
|
| 1697 |
+
"Type":"Geometrical descriptors",
|
| 1698 |
+
"Dimensions":"3D"
|
| 1699 |
+
},
|
| 1700 |
+
"283":{
|
| 1701 |
+
"Name":"MOMI-XY",
|
| 1702 |
+
"Description":"A descriptor that calculates the moment of inertia and radius of gyration.",
|
| 1703 |
+
"Type":"Geometrical descriptors",
|
| 1704 |
+
"Dimensions":"3D"
|
| 1705 |
+
},
|
| 1706 |
+
"284":{
|
| 1707 |
+
"Name":"MOMI-XZ",
|
| 1708 |
+
"Description":"A descriptor that calculates the moment of inertia and radius of gyration.",
|
| 1709 |
+
"Type":"Geometrical descriptors",
|
| 1710 |
+
"Dimensions":"3D"
|
| 1711 |
+
},
|
| 1712 |
+
"285":{
|
| 1713 |
+
"Name":"MOMI-YZ",
|
| 1714 |
+
"Description":"A descriptor that calculates the moment of inertia and radius of gyration.",
|
| 1715 |
+
"Type":"Geometrical descriptors",
|
| 1716 |
+
"Dimensions":"3D"
|
| 1717 |
+
},
|
| 1718 |
+
"286":{
|
| 1719 |
+
"Name":"MOMI-R",
|
| 1720 |
+
"Description":"A descriptor that calculates the moment of inertia and radius of gyration.",
|
| 1721 |
+
"Type":"Geometrical descriptors",
|
| 1722 |
+
"Dimensions":"3D"
|
| 1723 |
+
},
|
| 1724 |
+
"287":{
|
| 1725 |
+
"Name":"JPLogP",
|
| 1726 |
+
"Description":"This class calculates JPlogP [Plante, J. and Werner, S. ,JPlogP: an improved logP predictor trained using predicted data, Journal of Cheminformatics, 2018, 10].",
|
| 1727 |
+
"Type":"Geometrical descriptors",
|
| 1728 |
+
"Dimensions":"2D"
|
| 1729 |
+
}
|
| 1730 |
+
}
|
venv/Lib/site-packages/CDK_pywrapper/utils.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8
|
| 2 |
+
|
| 3 |
+
"""Utility functions."""
|
| 4 |
+
|
| 5 |
+
import sys
|
| 6 |
+
import os
|
| 7 |
+
import glob
|
| 8 |
+
import tempfile
|
| 9 |
+
import shutil
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
from typing import Tuple
|
| 12 |
+
|
| 13 |
+
from rdkit import Chem
|
| 14 |
+
from jdk import install as _jre_install, _JRE_DIR
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def install_jre(version: int = 19):
|
| 18 |
+
"""Install a Java Runtime Environment."""
|
| 19 |
+
path = get_jre_in_dir(_JRE_DIR, version)
|
| 20 |
+
if len(path) == 0:
|
| 21 |
+
# Could not find JRE, install it
|
| 22 |
+
_ = _jre_install(version, jre=True)
|
| 23 |
+
path = get_jre_in_dir(_JRE_DIR, version)
|
| 24 |
+
return path
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def make_temp_jre() -> Tuple[str, str]:
|
| 28 |
+
"""Copy the installed JRE to a temporary file.
|
| 29 |
+
|
| 30 |
+
Allows multiprocessing capacities.
|
| 31 |
+
|
| 32 |
+
:return: a tuple of (path to temp dir, path to the JRE)
|
| 33 |
+
"""
|
| 34 |
+
# Path of JRE installation
|
| 35 |
+
install_path = [path for path in Path(install_jre()).parents
|
| 36 |
+
if path.as_posix().endswith('jre') and not path.as_posix().endswith('.jre')][0]
|
| 37 |
+
# Path of temp dir
|
| 38 |
+
outdir = mktempdir()
|
| 39 |
+
# Make copy into dir
|
| 40 |
+
shutil.copytree(install_path, os.path.join(outdir, 'jre'))
|
| 41 |
+
# Find JRE in temp dir
|
| 42 |
+
path = get_jre_in_dir(outdir)
|
| 43 |
+
return outdir, path
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def get_jre_in_dir(dir: str):
|
| 47 |
+
"""Recursively search the directory to find a JRE."""
|
| 48 |
+
paths = glob.glob(os.path.join(dir, '**', 'server',
|
| 49 |
+
'jvm.dll' if sys.platform == "win32" else 'libjvm.so'
|
| 50 |
+
), recursive=True)
|
| 51 |
+
path = [path for path in paths if f'jre-{version}' in path]
|
| 52 |
+
if len(path):
|
| 53 |
+
return path[0]
|
| 54 |
+
return None
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def install_java(version: int = 19):
|
| 58 |
+
"""Install a Java Runtime Environment."""
|
| 59 |
+
path = get_java_in_dir(_JRE_DIR, version)
|
| 60 |
+
if path is None:
|
| 61 |
+
# Could not find JRE, install it
|
| 62 |
+
_ = _jre_install(version, jre=True)
|
| 63 |
+
path = get_java_in_dir(_JRE_DIR, version)
|
| 64 |
+
return path
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def get_java_in_dir(dir: str, version: int):
|
| 68 |
+
"""Recursively search the directory to find a JRE."""
|
| 69 |
+
paths = glob.glob(os.path.join(dir, '**', 'bin',
|
| 70 |
+
'java.exe' if sys.platform == "win32" else 'java'
|
| 71 |
+
), recursive=True)
|
| 72 |
+
path = [path for path in paths if f'jdk-{version}' in path]
|
| 73 |
+
if len(path):
|
| 74 |
+
return os.path.abspath(path[0])
|
| 75 |
+
return None
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def mktempdir(suffix: str = None) -> str:
|
| 79 |
+
"""Return the path to a writeable temporary directory."""
|
| 80 |
+
dir = tempfile.mkdtemp(suffix=suffix)
|
| 81 |
+
return dir
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def mktempfile(suffix: str = None) -> str:
|
| 85 |
+
"""Return the path to a writeable temporary file."""
|
| 86 |
+
file = tempfile.mkstemp(suffix=suffix)
|
| 87 |
+
os.close(file[0])
|
| 88 |
+
return file[1]
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def needsHs(mol: Chem.Mol) -> bool:
|
| 92 |
+
"""Return if the molecule lacks hydrogen atoms or not.
|
| 93 |
+
|
| 94 |
+
:param mol: RDKit Molecule
|
| 95 |
+
:return: True if the molecule lacks hydrogens.
|
| 96 |
+
"""
|
| 97 |
+
for atom in mol.GetAtoms():
|
| 98 |
+
nHNbrs = 0
|
| 99 |
+
for nbr in atom.GetNeighbors():
|
| 100 |
+
if nbr.GetAtomicNum() == 1:
|
| 101 |
+
nHNbrs += 1
|
| 102 |
+
noNeighbors = False
|
| 103 |
+
if atom.GetTotalNumHs(noNeighbors) > nHNbrs:
|
| 104 |
+
return True
|
| 105 |
+
return False
|
venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
pip
|
venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/LICENSE.rst
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Copyright 2010 Pallets
|
| 2 |
+
|
| 3 |
+
Redistribution and use in source and binary forms, with or without
|
| 4 |
+
modification, are permitted provided that the following conditions are
|
| 5 |
+
met:
|
| 6 |
+
|
| 7 |
+
1. Redistributions of source code must retain the above copyright
|
| 8 |
+
notice, this list of conditions and the following disclaimer.
|
| 9 |
+
|
| 10 |
+
2. Redistributions in binary form must reproduce the above copyright
|
| 11 |
+
notice, this list of conditions and the following disclaimer in the
|
| 12 |
+
documentation and/or other materials provided with the distribution.
|
| 13 |
+
|
| 14 |
+
3. Neither the name of the copyright holder nor the names of its
|
| 15 |
+
contributors may be used to endorse or promote products derived from
|
| 16 |
+
this software without specific prior written permission.
|
| 17 |
+
|
| 18 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
| 19 |
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
| 20 |
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
| 21 |
+
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
| 22 |
+
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
| 23 |
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
| 24 |
+
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
| 25 |
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
| 26 |
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
| 27 |
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
| 28 |
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/METADATA
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.1
|
| 2 |
+
Name: MarkupSafe
|
| 3 |
+
Version: 2.1.5
|
| 4 |
+
Summary: Safely add untrusted strings to HTML/XML markup.
|
| 5 |
+
Home-page: https://palletsprojects.com/p/markupsafe/
|
| 6 |
+
Maintainer: Pallets
|
| 7 |
+
Maintainer-email: contact@palletsprojects.com
|
| 8 |
+
License: BSD-3-Clause
|
| 9 |
+
Project-URL: Donate, https://palletsprojects.com/donate
|
| 10 |
+
Project-URL: Documentation, https://markupsafe.palletsprojects.com/
|
| 11 |
+
Project-URL: Changes, https://markupsafe.palletsprojects.com/changes/
|
| 12 |
+
Project-URL: Source Code, https://github.com/pallets/markupsafe/
|
| 13 |
+
Project-URL: Issue Tracker, https://github.com/pallets/markupsafe/issues/
|
| 14 |
+
Project-URL: Chat, https://discord.gg/pallets
|
| 15 |
+
Classifier: Development Status :: 5 - Production/Stable
|
| 16 |
+
Classifier: Environment :: Web Environment
|
| 17 |
+
Classifier: Intended Audience :: Developers
|
| 18 |
+
Classifier: License :: OSI Approved :: BSD License
|
| 19 |
+
Classifier: Operating System :: OS Independent
|
| 20 |
+
Classifier: Programming Language :: Python
|
| 21 |
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
| 22 |
+
Classifier: Topic :: Text Processing :: Markup :: HTML
|
| 23 |
+
Requires-Python: >=3.7
|
| 24 |
+
Description-Content-Type: text/x-rst
|
| 25 |
+
License-File: LICENSE.rst
|
| 26 |
+
|
| 27 |
+
MarkupSafe
|
| 28 |
+
==========
|
| 29 |
+
|
| 30 |
+
MarkupSafe implements a text object that escapes characters so it is
|
| 31 |
+
safe to use in HTML and XML. Characters that have special meanings are
|
| 32 |
+
replaced so that they display as the actual characters. This mitigates
|
| 33 |
+
injection attacks, meaning untrusted user input can safely be displayed
|
| 34 |
+
on a page.
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
Installing
|
| 38 |
+
----------
|
| 39 |
+
|
| 40 |
+
Install and update using `pip`_:
|
| 41 |
+
|
| 42 |
+
.. code-block:: text
|
| 43 |
+
|
| 44 |
+
pip install -U MarkupSafe
|
| 45 |
+
|
| 46 |
+
.. _pip: https://pip.pypa.io/en/stable/getting-started/
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
Examples
|
| 50 |
+
--------
|
| 51 |
+
|
| 52 |
+
.. code-block:: pycon
|
| 53 |
+
|
| 54 |
+
>>> from markupsafe import Markup, escape
|
| 55 |
+
|
| 56 |
+
>>> # escape replaces special characters and wraps in Markup
|
| 57 |
+
>>> escape("<script>alert(document.cookie);</script>")
|
| 58 |
+
Markup('<script>alert(document.cookie);</script>')
|
| 59 |
+
|
| 60 |
+
>>> # wrap in Markup to mark text "safe" and prevent escaping
|
| 61 |
+
>>> Markup("<strong>Hello</strong>")
|
| 62 |
+
Markup('<strong>hello</strong>')
|
| 63 |
+
|
| 64 |
+
>>> escape(Markup("<strong>Hello</strong>"))
|
| 65 |
+
Markup('<strong>hello</strong>')
|
| 66 |
+
|
| 67 |
+
>>> # Markup is a str subclass
|
| 68 |
+
>>> # methods and operators escape their arguments
|
| 69 |
+
>>> template = Markup("Hello <em>{name}</em>")
|
| 70 |
+
>>> template.format(name='"World"')
|
| 71 |
+
Markup('Hello <em>"World"</em>')
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
Donate
|
| 75 |
+
------
|
| 76 |
+
|
| 77 |
+
The Pallets organization develops and supports MarkupSafe and other
|
| 78 |
+
popular packages. In order to grow the community of contributors and
|
| 79 |
+
users, and allow the maintainers to devote more time to the projects,
|
| 80 |
+
`please donate today`_.
|
| 81 |
+
|
| 82 |
+
.. _please donate today: https://palletsprojects.com/donate
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
Links
|
| 86 |
+
-----
|
| 87 |
+
|
| 88 |
+
- Documentation: https://markupsafe.palletsprojects.com/
|
| 89 |
+
- Changes: https://markupsafe.palletsprojects.com/changes/
|
| 90 |
+
- PyPI Releases: https://pypi.org/project/MarkupSafe/
|
| 91 |
+
- Source Code: https://github.com/pallets/markupsafe/
|
| 92 |
+
- Issue Tracker: https://github.com/pallets/markupsafe/issues/
|
| 93 |
+
- Chat: https://discord.gg/pallets
|
venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/RECORD
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MarkupSafe-2.1.5.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 2 |
+
MarkupSafe-2.1.5.dist-info/LICENSE.rst,sha256=RjHsDbX9kKVH4zaBcmTGeYIUM4FG-KyUtKV_lu6MnsQ,1503
|
| 3 |
+
MarkupSafe-2.1.5.dist-info/METADATA,sha256=icNlaniV7YIQZ1BScCVqNaRtm7MAgfw8d3OBmoSVyAY,3096
|
| 4 |
+
MarkupSafe-2.1.5.dist-info/RECORD,,
|
| 5 |
+
MarkupSafe-2.1.5.dist-info/WHEEL,sha256=ircjsfhzblqgSzO8ow7-0pXK-RVqDqNRGQ8F650AUNM,102
|
| 6 |
+
MarkupSafe-2.1.5.dist-info/top_level.txt,sha256=qy0Plje5IJuvsCBjejJyhDCjEAdcDLK_2agVcex8Z6U,11
|
| 7 |
+
markupsafe/__init__.py,sha256=m1ysNeqf55zbEoJtaovca40ivrkEFolPlw5bGoC5Gi4,11290
|
| 8 |
+
markupsafe/__pycache__/__init__.cpython-311.pyc,,
|
| 9 |
+
markupsafe/__pycache__/_native.cpython-311.pyc,,
|
| 10 |
+
markupsafe/_native.py,sha256=_Q7UsXCOvgdonCgqG3l5asANI6eo50EKnDM-mlwEC5M,1776
|
| 11 |
+
markupsafe/_speedups.c,sha256=n3jzzaJwXcoN8nTFyA53f3vSqsWK2vujI-v6QYifjhQ,7403
|
| 12 |
+
markupsafe/_speedups.cp311-win_amd64.pyd,sha256=MEqnkyBOHmstwQr50hKitovHjrHhMJ0gYmya4Fu1DK0,15872
|
| 13 |
+
markupsafe/_speedups.pyi,sha256=f5QtwIOP0eLrxh2v5p6SmaYmlcHIGIfmz0DovaqL0OU,238
|
| 14 |
+
markupsafe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: bdist_wheel (0.42.0)
|
| 3 |
+
Root-Is-Purelib: false
|
| 4 |
+
Tag: cp311-cp311-win_amd64
|
| 5 |
+
|
venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/top_level.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
markupsafe
|
venv/Lib/site-packages/PIL/BdfFontFile.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# bitmap distribution font (bdf) file parser
|
| 6 |
+
#
|
| 7 |
+
# history:
|
| 8 |
+
# 1996-05-16 fl created (as bdf2pil)
|
| 9 |
+
# 1997-08-25 fl converted to FontFile driver
|
| 10 |
+
# 2001-05-25 fl removed bogus __init__ call
|
| 11 |
+
# 2002-11-20 fl robustification (from Kevin Cazabon, Dmitry Vasiliev)
|
| 12 |
+
# 2003-04-22 fl more robustification (from Graham Dumpleton)
|
| 13 |
+
#
|
| 14 |
+
# Copyright (c) 1997-2003 by Secret Labs AB.
|
| 15 |
+
# Copyright (c) 1997-2003 by Fredrik Lundh.
|
| 16 |
+
#
|
| 17 |
+
# See the README file for information on usage and redistribution.
|
| 18 |
+
#
|
| 19 |
+
|
| 20 |
+
"""
|
| 21 |
+
Parse X Bitmap Distribution Format (BDF)
|
| 22 |
+
"""
|
| 23 |
+
from __future__ import annotations
|
| 24 |
+
|
| 25 |
+
from typing import BinaryIO
|
| 26 |
+
|
| 27 |
+
from . import FontFile, Image
|
| 28 |
+
|
| 29 |
+
bdf_slant = {
|
| 30 |
+
"R": "Roman",
|
| 31 |
+
"I": "Italic",
|
| 32 |
+
"O": "Oblique",
|
| 33 |
+
"RI": "Reverse Italic",
|
| 34 |
+
"RO": "Reverse Oblique",
|
| 35 |
+
"OT": "Other",
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
bdf_spacing = {"P": "Proportional", "M": "Monospaced", "C": "Cell"}
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def bdf_char(
|
| 42 |
+
f: BinaryIO,
|
| 43 |
+
) -> (
|
| 44 |
+
tuple[
|
| 45 |
+
str,
|
| 46 |
+
int,
|
| 47 |
+
tuple[tuple[int, int], tuple[int, int, int, int], tuple[int, int, int, int]],
|
| 48 |
+
Image.Image,
|
| 49 |
+
]
|
| 50 |
+
| None
|
| 51 |
+
):
|
| 52 |
+
# skip to STARTCHAR
|
| 53 |
+
while True:
|
| 54 |
+
s = f.readline()
|
| 55 |
+
if not s:
|
| 56 |
+
return None
|
| 57 |
+
if s[:9] == b"STARTCHAR":
|
| 58 |
+
break
|
| 59 |
+
id = s[9:].strip().decode("ascii")
|
| 60 |
+
|
| 61 |
+
# load symbol properties
|
| 62 |
+
props = {}
|
| 63 |
+
while True:
|
| 64 |
+
s = f.readline()
|
| 65 |
+
if not s or s[:6] == b"BITMAP":
|
| 66 |
+
break
|
| 67 |
+
i = s.find(b" ")
|
| 68 |
+
props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii")
|
| 69 |
+
|
| 70 |
+
# load bitmap
|
| 71 |
+
bitmap = bytearray()
|
| 72 |
+
while True:
|
| 73 |
+
s = f.readline()
|
| 74 |
+
if not s or s[:7] == b"ENDCHAR":
|
| 75 |
+
break
|
| 76 |
+
bitmap += s[:-1]
|
| 77 |
+
|
| 78 |
+
# The word BBX
|
| 79 |
+
# followed by the width in x (BBw), height in y (BBh),
|
| 80 |
+
# and x and y displacement (BBxoff0, BByoff0)
|
| 81 |
+
# of the lower left corner from the origin of the character.
|
| 82 |
+
width, height, x_disp, y_disp = (int(p) for p in props["BBX"].split())
|
| 83 |
+
|
| 84 |
+
# The word DWIDTH
|
| 85 |
+
# followed by the width in x and y of the character in device pixels.
|
| 86 |
+
dwx, dwy = (int(p) for p in props["DWIDTH"].split())
|
| 87 |
+
|
| 88 |
+
bbox = (
|
| 89 |
+
(dwx, dwy),
|
| 90 |
+
(x_disp, -y_disp - height, width + x_disp, -y_disp),
|
| 91 |
+
(0, 0, width, height),
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
try:
|
| 95 |
+
im = Image.frombytes("1", (width, height), bitmap, "hex", "1")
|
| 96 |
+
except ValueError:
|
| 97 |
+
# deal with zero-width characters
|
| 98 |
+
im = Image.new("1", (width, height))
|
| 99 |
+
|
| 100 |
+
return id, int(props["ENCODING"]), bbox, im
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
class BdfFontFile(FontFile.FontFile):
|
| 104 |
+
"""Font file plugin for the X11 BDF format."""
|
| 105 |
+
|
| 106 |
+
def __init__(self, fp: BinaryIO):
|
| 107 |
+
super().__init__()
|
| 108 |
+
|
| 109 |
+
s = fp.readline()
|
| 110 |
+
if s[:13] != b"STARTFONT 2.1":
|
| 111 |
+
msg = "not a valid BDF file"
|
| 112 |
+
raise SyntaxError(msg)
|
| 113 |
+
|
| 114 |
+
props = {}
|
| 115 |
+
comments = []
|
| 116 |
+
|
| 117 |
+
while True:
|
| 118 |
+
s = fp.readline()
|
| 119 |
+
if not s or s[:13] == b"ENDPROPERTIES":
|
| 120 |
+
break
|
| 121 |
+
i = s.find(b" ")
|
| 122 |
+
props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii")
|
| 123 |
+
if s[:i] in [b"COMMENT", b"COPYRIGHT"]:
|
| 124 |
+
if s.find(b"LogicalFontDescription") < 0:
|
| 125 |
+
comments.append(s[i + 1 : -1].decode("ascii"))
|
| 126 |
+
|
| 127 |
+
while True:
|
| 128 |
+
c = bdf_char(fp)
|
| 129 |
+
if not c:
|
| 130 |
+
break
|
| 131 |
+
id, ch, (xy, dst, src), im = c
|
| 132 |
+
if 0 <= ch < len(self.glyph):
|
| 133 |
+
self.glyph[ch] = xy, dst, src, im
|
venv/Lib/site-packages/PIL/BlpImagePlugin.py
ADDED
|
@@ -0,0 +1,476 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Blizzard Mipmap Format (.blp)
|
| 3 |
+
Jerome Leclanche <jerome@leclan.ch>
|
| 4 |
+
|
| 5 |
+
The contents of this file are hereby released in the public domain (CC0)
|
| 6 |
+
Full text of the CC0 license:
|
| 7 |
+
https://creativecommons.org/publicdomain/zero/1.0/
|
| 8 |
+
|
| 9 |
+
BLP1 files, used mostly in Warcraft III, are not fully supported.
|
| 10 |
+
All types of BLP2 files used in World of Warcraft are supported.
|
| 11 |
+
|
| 12 |
+
The BLP file structure consists of a header, up to 16 mipmaps of the
|
| 13 |
+
texture
|
| 14 |
+
|
| 15 |
+
Texture sizes must be powers of two, though the two dimensions do
|
| 16 |
+
not have to be equal; 512x256 is valid, but 512x200 is not.
|
| 17 |
+
The first mipmap (mipmap #0) is the full size image; each subsequent
|
| 18 |
+
mipmap halves both dimensions. The final mipmap should be 1x1.
|
| 19 |
+
|
| 20 |
+
BLP files come in many different flavours:
|
| 21 |
+
* JPEG-compressed (type == 0) - only supported for BLP1.
|
| 22 |
+
* RAW images (type == 1, encoding == 1). Each mipmap is stored as an
|
| 23 |
+
array of 8-bit values, one per pixel, left to right, top to bottom.
|
| 24 |
+
Each value is an index to the palette.
|
| 25 |
+
* DXT-compressed (type == 1, encoding == 2):
|
| 26 |
+
- DXT1 compression is used if alpha_encoding == 0.
|
| 27 |
+
- An additional alpha bit is used if alpha_depth == 1.
|
| 28 |
+
- DXT3 compression is used if alpha_encoding == 1.
|
| 29 |
+
- DXT5 compression is used if alpha_encoding == 7.
|
| 30 |
+
"""
|
| 31 |
+
|
| 32 |
+
from __future__ import annotations
|
| 33 |
+
|
| 34 |
+
import os
|
| 35 |
+
import struct
|
| 36 |
+
from enum import IntEnum
|
| 37 |
+
from io import BytesIO
|
| 38 |
+
|
| 39 |
+
from . import Image, ImageFile
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
class Format(IntEnum):
|
| 43 |
+
JPEG = 0
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class Encoding(IntEnum):
|
| 47 |
+
UNCOMPRESSED = 1
|
| 48 |
+
DXT = 2
|
| 49 |
+
UNCOMPRESSED_RAW_BGRA = 3
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
class AlphaEncoding(IntEnum):
|
| 53 |
+
DXT1 = 0
|
| 54 |
+
DXT3 = 1
|
| 55 |
+
DXT5 = 7
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def unpack_565(i):
|
| 59 |
+
return ((i >> 11) & 0x1F) << 3, ((i >> 5) & 0x3F) << 2, (i & 0x1F) << 3
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def decode_dxt1(data, alpha=False):
|
| 63 |
+
"""
|
| 64 |
+
input: one "row" of data (i.e. will produce 4*width pixels)
|
| 65 |
+
"""
|
| 66 |
+
|
| 67 |
+
blocks = len(data) // 8 # number of blocks in row
|
| 68 |
+
ret = (bytearray(), bytearray(), bytearray(), bytearray())
|
| 69 |
+
|
| 70 |
+
for block in range(blocks):
|
| 71 |
+
# Decode next 8-byte block.
|
| 72 |
+
idx = block * 8
|
| 73 |
+
color0, color1, bits = struct.unpack_from("<HHI", data, idx)
|
| 74 |
+
|
| 75 |
+
r0, g0, b0 = unpack_565(color0)
|
| 76 |
+
r1, g1, b1 = unpack_565(color1)
|
| 77 |
+
|
| 78 |
+
# Decode this block into 4x4 pixels
|
| 79 |
+
# Accumulate the results onto our 4 row accumulators
|
| 80 |
+
for j in range(4):
|
| 81 |
+
for i in range(4):
|
| 82 |
+
# get next control op and generate a pixel
|
| 83 |
+
|
| 84 |
+
control = bits & 3
|
| 85 |
+
bits = bits >> 2
|
| 86 |
+
|
| 87 |
+
a = 0xFF
|
| 88 |
+
if control == 0:
|
| 89 |
+
r, g, b = r0, g0, b0
|
| 90 |
+
elif control == 1:
|
| 91 |
+
r, g, b = r1, g1, b1
|
| 92 |
+
elif control == 2:
|
| 93 |
+
if color0 > color1:
|
| 94 |
+
r = (2 * r0 + r1) // 3
|
| 95 |
+
g = (2 * g0 + g1) // 3
|
| 96 |
+
b = (2 * b0 + b1) // 3
|
| 97 |
+
else:
|
| 98 |
+
r = (r0 + r1) // 2
|
| 99 |
+
g = (g0 + g1) // 2
|
| 100 |
+
b = (b0 + b1) // 2
|
| 101 |
+
elif control == 3:
|
| 102 |
+
if color0 > color1:
|
| 103 |
+
r = (2 * r1 + r0) // 3
|
| 104 |
+
g = (2 * g1 + g0) // 3
|
| 105 |
+
b = (2 * b1 + b0) // 3
|
| 106 |
+
else:
|
| 107 |
+
r, g, b, a = 0, 0, 0, 0
|
| 108 |
+
|
| 109 |
+
if alpha:
|
| 110 |
+
ret[j].extend([r, g, b, a])
|
| 111 |
+
else:
|
| 112 |
+
ret[j].extend([r, g, b])
|
| 113 |
+
|
| 114 |
+
return ret
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def decode_dxt3(data):
|
| 118 |
+
"""
|
| 119 |
+
input: one "row" of data (i.e. will produce 4*width pixels)
|
| 120 |
+
"""
|
| 121 |
+
|
| 122 |
+
blocks = len(data) // 16 # number of blocks in row
|
| 123 |
+
ret = (bytearray(), bytearray(), bytearray(), bytearray())
|
| 124 |
+
|
| 125 |
+
for block in range(blocks):
|
| 126 |
+
idx = block * 16
|
| 127 |
+
block = data[idx : idx + 16]
|
| 128 |
+
# Decode next 16-byte block.
|
| 129 |
+
bits = struct.unpack_from("<8B", block)
|
| 130 |
+
color0, color1 = struct.unpack_from("<HH", block, 8)
|
| 131 |
+
|
| 132 |
+
(code,) = struct.unpack_from("<I", block, 12)
|
| 133 |
+
|
| 134 |
+
r0, g0, b0 = unpack_565(color0)
|
| 135 |
+
r1, g1, b1 = unpack_565(color1)
|
| 136 |
+
|
| 137 |
+
for j in range(4):
|
| 138 |
+
high = False # Do we want the higher bits?
|
| 139 |
+
for i in range(4):
|
| 140 |
+
alphacode_index = (4 * j + i) // 2
|
| 141 |
+
a = bits[alphacode_index]
|
| 142 |
+
if high:
|
| 143 |
+
high = False
|
| 144 |
+
a >>= 4
|
| 145 |
+
else:
|
| 146 |
+
high = True
|
| 147 |
+
a &= 0xF
|
| 148 |
+
a *= 17 # We get a value between 0 and 15
|
| 149 |
+
|
| 150 |
+
color_code = (code >> 2 * (4 * j + i)) & 0x03
|
| 151 |
+
|
| 152 |
+
if color_code == 0:
|
| 153 |
+
r, g, b = r0, g0, b0
|
| 154 |
+
elif color_code == 1:
|
| 155 |
+
r, g, b = r1, g1, b1
|
| 156 |
+
elif color_code == 2:
|
| 157 |
+
r = (2 * r0 + r1) // 3
|
| 158 |
+
g = (2 * g0 + g1) // 3
|
| 159 |
+
b = (2 * b0 + b1) // 3
|
| 160 |
+
elif color_code == 3:
|
| 161 |
+
r = (2 * r1 + r0) // 3
|
| 162 |
+
g = (2 * g1 + g0) // 3
|
| 163 |
+
b = (2 * b1 + b0) // 3
|
| 164 |
+
|
| 165 |
+
ret[j].extend([r, g, b, a])
|
| 166 |
+
|
| 167 |
+
return ret
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
def decode_dxt5(data):
|
| 171 |
+
"""
|
| 172 |
+
input: one "row" of data (i.e. will produce 4 * width pixels)
|
| 173 |
+
"""
|
| 174 |
+
|
| 175 |
+
blocks = len(data) // 16 # number of blocks in row
|
| 176 |
+
ret = (bytearray(), bytearray(), bytearray(), bytearray())
|
| 177 |
+
|
| 178 |
+
for block in range(blocks):
|
| 179 |
+
idx = block * 16
|
| 180 |
+
block = data[idx : idx + 16]
|
| 181 |
+
# Decode next 16-byte block.
|
| 182 |
+
a0, a1 = struct.unpack_from("<BB", block)
|
| 183 |
+
|
| 184 |
+
bits = struct.unpack_from("<6B", block, 2)
|
| 185 |
+
alphacode1 = bits[2] | (bits[3] << 8) | (bits[4] << 16) | (bits[5] << 24)
|
| 186 |
+
alphacode2 = bits[0] | (bits[1] << 8)
|
| 187 |
+
|
| 188 |
+
color0, color1 = struct.unpack_from("<HH", block, 8)
|
| 189 |
+
|
| 190 |
+
(code,) = struct.unpack_from("<I", block, 12)
|
| 191 |
+
|
| 192 |
+
r0, g0, b0 = unpack_565(color0)
|
| 193 |
+
r1, g1, b1 = unpack_565(color1)
|
| 194 |
+
|
| 195 |
+
for j in range(4):
|
| 196 |
+
for i in range(4):
|
| 197 |
+
# get next control op and generate a pixel
|
| 198 |
+
alphacode_index = 3 * (4 * j + i)
|
| 199 |
+
|
| 200 |
+
if alphacode_index <= 12:
|
| 201 |
+
alphacode = (alphacode2 >> alphacode_index) & 0x07
|
| 202 |
+
elif alphacode_index == 15:
|
| 203 |
+
alphacode = (alphacode2 >> 15) | ((alphacode1 << 1) & 0x06)
|
| 204 |
+
else: # alphacode_index >= 18 and alphacode_index <= 45
|
| 205 |
+
alphacode = (alphacode1 >> (alphacode_index - 16)) & 0x07
|
| 206 |
+
|
| 207 |
+
if alphacode == 0:
|
| 208 |
+
a = a0
|
| 209 |
+
elif alphacode == 1:
|
| 210 |
+
a = a1
|
| 211 |
+
elif a0 > a1:
|
| 212 |
+
a = ((8 - alphacode) * a0 + (alphacode - 1) * a1) // 7
|
| 213 |
+
elif alphacode == 6:
|
| 214 |
+
a = 0
|
| 215 |
+
elif alphacode == 7:
|
| 216 |
+
a = 255
|
| 217 |
+
else:
|
| 218 |
+
a = ((6 - alphacode) * a0 + (alphacode - 1) * a1) // 5
|
| 219 |
+
|
| 220 |
+
color_code = (code >> 2 * (4 * j + i)) & 0x03
|
| 221 |
+
|
| 222 |
+
if color_code == 0:
|
| 223 |
+
r, g, b = r0, g0, b0
|
| 224 |
+
elif color_code == 1:
|
| 225 |
+
r, g, b = r1, g1, b1
|
| 226 |
+
elif color_code == 2:
|
| 227 |
+
r = (2 * r0 + r1) // 3
|
| 228 |
+
g = (2 * g0 + g1) // 3
|
| 229 |
+
b = (2 * b0 + b1) // 3
|
| 230 |
+
elif color_code == 3:
|
| 231 |
+
r = (2 * r1 + r0) // 3
|
| 232 |
+
g = (2 * g1 + g0) // 3
|
| 233 |
+
b = (2 * b1 + b0) // 3
|
| 234 |
+
|
| 235 |
+
ret[j].extend([r, g, b, a])
|
| 236 |
+
|
| 237 |
+
return ret
|
| 238 |
+
|
| 239 |
+
|
| 240 |
+
class BLPFormatError(NotImplementedError):
|
| 241 |
+
pass
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
def _accept(prefix):
|
| 245 |
+
return prefix[:4] in (b"BLP1", b"BLP2")
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
class BlpImageFile(ImageFile.ImageFile):
|
| 249 |
+
"""
|
| 250 |
+
Blizzard Mipmap Format
|
| 251 |
+
"""
|
| 252 |
+
|
| 253 |
+
format = "BLP"
|
| 254 |
+
format_description = "Blizzard Mipmap Format"
|
| 255 |
+
|
| 256 |
+
def _open(self):
|
| 257 |
+
self.magic = self.fp.read(4)
|
| 258 |
+
|
| 259 |
+
self.fp.seek(5, os.SEEK_CUR)
|
| 260 |
+
(self._blp_alpha_depth,) = struct.unpack("<b", self.fp.read(1))
|
| 261 |
+
|
| 262 |
+
self.fp.seek(2, os.SEEK_CUR)
|
| 263 |
+
self._size = struct.unpack("<II", self.fp.read(8))
|
| 264 |
+
|
| 265 |
+
if self.magic in (b"BLP1", b"BLP2"):
|
| 266 |
+
decoder = self.magic.decode()
|
| 267 |
+
else:
|
| 268 |
+
msg = f"Bad BLP magic {repr(self.magic)}"
|
| 269 |
+
raise BLPFormatError(msg)
|
| 270 |
+
|
| 271 |
+
self._mode = "RGBA" if self._blp_alpha_depth else "RGB"
|
| 272 |
+
self.tile = [(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]
|
| 273 |
+
|
| 274 |
+
|
| 275 |
+
class _BLPBaseDecoder(ImageFile.PyDecoder):
|
| 276 |
+
_pulls_fd = True
|
| 277 |
+
|
| 278 |
+
def decode(self, buffer):
|
| 279 |
+
try:
|
| 280 |
+
self._read_blp_header()
|
| 281 |
+
self._load()
|
| 282 |
+
except struct.error as e:
|
| 283 |
+
msg = "Truncated BLP file"
|
| 284 |
+
raise OSError(msg) from e
|
| 285 |
+
return -1, 0
|
| 286 |
+
|
| 287 |
+
def _read_blp_header(self):
|
| 288 |
+
self.fd.seek(4)
|
| 289 |
+
(self._blp_compression,) = struct.unpack("<i", self._safe_read(4))
|
| 290 |
+
|
| 291 |
+
(self._blp_encoding,) = struct.unpack("<b", self._safe_read(1))
|
| 292 |
+
(self._blp_alpha_depth,) = struct.unpack("<b", self._safe_read(1))
|
| 293 |
+
(self._blp_alpha_encoding,) = struct.unpack("<b", self._safe_read(1))
|
| 294 |
+
self.fd.seek(1, os.SEEK_CUR) # mips
|
| 295 |
+
|
| 296 |
+
self.size = struct.unpack("<II", self._safe_read(8))
|
| 297 |
+
|
| 298 |
+
if isinstance(self, BLP1Decoder):
|
| 299 |
+
# Only present for BLP1
|
| 300 |
+
(self._blp_encoding,) = struct.unpack("<i", self._safe_read(4))
|
| 301 |
+
self.fd.seek(4, os.SEEK_CUR) # subtype
|
| 302 |
+
|
| 303 |
+
self._blp_offsets = struct.unpack("<16I", self._safe_read(16 * 4))
|
| 304 |
+
self._blp_lengths = struct.unpack("<16I", self._safe_read(16 * 4))
|
| 305 |
+
|
| 306 |
+
def _safe_read(self, length):
|
| 307 |
+
return ImageFile._safe_read(self.fd, length)
|
| 308 |
+
|
| 309 |
+
def _read_palette(self):
|
| 310 |
+
ret = []
|
| 311 |
+
for i in range(256):
|
| 312 |
+
try:
|
| 313 |
+
b, g, r, a = struct.unpack("<4B", self._safe_read(4))
|
| 314 |
+
except struct.error:
|
| 315 |
+
break
|
| 316 |
+
ret.append((b, g, r, a))
|
| 317 |
+
return ret
|
| 318 |
+
|
| 319 |
+
def _read_bgra(self, palette):
|
| 320 |
+
data = bytearray()
|
| 321 |
+
_data = BytesIO(self._safe_read(self._blp_lengths[0]))
|
| 322 |
+
while True:
|
| 323 |
+
try:
|
| 324 |
+
(offset,) = struct.unpack("<B", _data.read(1))
|
| 325 |
+
except struct.error:
|
| 326 |
+
break
|
| 327 |
+
b, g, r, a = palette[offset]
|
| 328 |
+
d = (r, g, b)
|
| 329 |
+
if self._blp_alpha_depth:
|
| 330 |
+
d += (a,)
|
| 331 |
+
data.extend(d)
|
| 332 |
+
return data
|
| 333 |
+
|
| 334 |
+
|
| 335 |
+
class BLP1Decoder(_BLPBaseDecoder):
|
| 336 |
+
def _load(self):
|
| 337 |
+
if self._blp_compression == Format.JPEG:
|
| 338 |
+
self._decode_jpeg_stream()
|
| 339 |
+
|
| 340 |
+
elif self._blp_compression == 1:
|
| 341 |
+
if self._blp_encoding in (4, 5):
|
| 342 |
+
palette = self._read_palette()
|
| 343 |
+
data = self._read_bgra(palette)
|
| 344 |
+
self.set_as_raw(data)
|
| 345 |
+
else:
|
| 346 |
+
msg = f"Unsupported BLP encoding {repr(self._blp_encoding)}"
|
| 347 |
+
raise BLPFormatError(msg)
|
| 348 |
+
else:
|
| 349 |
+
msg = f"Unsupported BLP compression {repr(self._blp_encoding)}"
|
| 350 |
+
raise BLPFormatError(msg)
|
| 351 |
+
|
| 352 |
+
def _decode_jpeg_stream(self):
|
| 353 |
+
from .JpegImagePlugin import JpegImageFile
|
| 354 |
+
|
| 355 |
+
(jpeg_header_size,) = struct.unpack("<I", self._safe_read(4))
|
| 356 |
+
jpeg_header = self._safe_read(jpeg_header_size)
|
| 357 |
+
self._safe_read(self._blp_offsets[0] - self.fd.tell()) # What IS this?
|
| 358 |
+
data = self._safe_read(self._blp_lengths[0])
|
| 359 |
+
data = jpeg_header + data
|
| 360 |
+
data = BytesIO(data)
|
| 361 |
+
image = JpegImageFile(data)
|
| 362 |
+
Image._decompression_bomb_check(image.size)
|
| 363 |
+
if image.mode == "CMYK":
|
| 364 |
+
decoder_name, extents, offset, args = image.tile[0]
|
| 365 |
+
image.tile = [(decoder_name, extents, offset, (args[0], "CMYK"))]
|
| 366 |
+
r, g, b = image.convert("RGB").split()
|
| 367 |
+
image = Image.merge("RGB", (b, g, r))
|
| 368 |
+
self.set_as_raw(image.tobytes())
|
| 369 |
+
|
| 370 |
+
|
| 371 |
+
class BLP2Decoder(_BLPBaseDecoder):
|
| 372 |
+
def _load(self):
|
| 373 |
+
palette = self._read_palette()
|
| 374 |
+
|
| 375 |
+
self.fd.seek(self._blp_offsets[0])
|
| 376 |
+
|
| 377 |
+
if self._blp_compression == 1:
|
| 378 |
+
# Uncompressed or DirectX compression
|
| 379 |
+
|
| 380 |
+
if self._blp_encoding == Encoding.UNCOMPRESSED:
|
| 381 |
+
data = self._read_bgra(palette)
|
| 382 |
+
|
| 383 |
+
elif self._blp_encoding == Encoding.DXT:
|
| 384 |
+
data = bytearray()
|
| 385 |
+
if self._blp_alpha_encoding == AlphaEncoding.DXT1:
|
| 386 |
+
linesize = (self.size[0] + 3) // 4 * 8
|
| 387 |
+
for yb in range((self.size[1] + 3) // 4):
|
| 388 |
+
for d in decode_dxt1(
|
| 389 |
+
self._safe_read(linesize), alpha=bool(self._blp_alpha_depth)
|
| 390 |
+
):
|
| 391 |
+
data += d
|
| 392 |
+
|
| 393 |
+
elif self._blp_alpha_encoding == AlphaEncoding.DXT3:
|
| 394 |
+
linesize = (self.size[0] + 3) // 4 * 16
|
| 395 |
+
for yb in range((self.size[1] + 3) // 4):
|
| 396 |
+
for d in decode_dxt3(self._safe_read(linesize)):
|
| 397 |
+
data += d
|
| 398 |
+
|
| 399 |
+
elif self._blp_alpha_encoding == AlphaEncoding.DXT5:
|
| 400 |
+
linesize = (self.size[0] + 3) // 4 * 16
|
| 401 |
+
for yb in range((self.size[1] + 3) // 4):
|
| 402 |
+
for d in decode_dxt5(self._safe_read(linesize)):
|
| 403 |
+
data += d
|
| 404 |
+
else:
|
| 405 |
+
msg = f"Unsupported alpha encoding {repr(self._blp_alpha_encoding)}"
|
| 406 |
+
raise BLPFormatError(msg)
|
| 407 |
+
else:
|
| 408 |
+
msg = f"Unknown BLP encoding {repr(self._blp_encoding)}"
|
| 409 |
+
raise BLPFormatError(msg)
|
| 410 |
+
|
| 411 |
+
else:
|
| 412 |
+
msg = f"Unknown BLP compression {repr(self._blp_compression)}"
|
| 413 |
+
raise BLPFormatError(msg)
|
| 414 |
+
|
| 415 |
+
self.set_as_raw(data)
|
| 416 |
+
|
| 417 |
+
|
| 418 |
+
class BLPEncoder(ImageFile.PyEncoder):
|
| 419 |
+
_pushes_fd = True
|
| 420 |
+
|
| 421 |
+
def _write_palette(self):
|
| 422 |
+
data = b""
|
| 423 |
+
palette = self.im.getpalette("RGBA", "RGBA")
|
| 424 |
+
for i in range(len(palette) // 4):
|
| 425 |
+
r, g, b, a = palette[i * 4 : (i + 1) * 4]
|
| 426 |
+
data += struct.pack("<4B", b, g, r, a)
|
| 427 |
+
while len(data) < 256 * 4:
|
| 428 |
+
data += b"\x00" * 4
|
| 429 |
+
return data
|
| 430 |
+
|
| 431 |
+
def encode(self, bufsize):
|
| 432 |
+
palette_data = self._write_palette()
|
| 433 |
+
|
| 434 |
+
offset = 20 + 16 * 4 * 2 + len(palette_data)
|
| 435 |
+
data = struct.pack("<16I", offset, *((0,) * 15))
|
| 436 |
+
|
| 437 |
+
w, h = self.im.size
|
| 438 |
+
data += struct.pack("<16I", w * h, *((0,) * 15))
|
| 439 |
+
|
| 440 |
+
data += palette_data
|
| 441 |
+
|
| 442 |
+
for y in range(h):
|
| 443 |
+
for x in range(w):
|
| 444 |
+
data += struct.pack("<B", self.im.getpixel((x, y)))
|
| 445 |
+
|
| 446 |
+
return len(data), 0, data
|
| 447 |
+
|
| 448 |
+
|
| 449 |
+
def _save(im, fp, filename):
|
| 450 |
+
if im.mode != "P":
|
| 451 |
+
msg = "Unsupported BLP image mode"
|
| 452 |
+
raise ValueError(msg)
|
| 453 |
+
|
| 454 |
+
magic = b"BLP1" if im.encoderinfo.get("blp_version") == "BLP1" else b"BLP2"
|
| 455 |
+
fp.write(magic)
|
| 456 |
+
|
| 457 |
+
fp.write(struct.pack("<i", 1)) # Uncompressed or DirectX compression
|
| 458 |
+
fp.write(struct.pack("<b", Encoding.UNCOMPRESSED))
|
| 459 |
+
fp.write(struct.pack("<b", 1 if im.palette.mode == "RGBA" else 0))
|
| 460 |
+
fp.write(struct.pack("<b", 0)) # alpha encoding
|
| 461 |
+
fp.write(struct.pack("<b", 0)) # mips
|
| 462 |
+
fp.write(struct.pack("<II", *im.size))
|
| 463 |
+
if magic == b"BLP1":
|
| 464 |
+
fp.write(struct.pack("<i", 5))
|
| 465 |
+
fp.write(struct.pack("<i", 0))
|
| 466 |
+
|
| 467 |
+
ImageFile._save(im, fp, [("BLP", (0, 0) + im.size, 0, im.mode)])
|
| 468 |
+
|
| 469 |
+
|
| 470 |
+
Image.register_open(BlpImageFile.format, BlpImageFile, _accept)
|
| 471 |
+
Image.register_extension(BlpImageFile.format, ".blp")
|
| 472 |
+
Image.register_decoder("BLP1", BLP1Decoder)
|
| 473 |
+
Image.register_decoder("BLP2", BLP2Decoder)
|
| 474 |
+
|
| 475 |
+
Image.register_save(BlpImageFile.format, _save)
|
| 476 |
+
Image.register_encoder("BLP", BLPEncoder)
|
venv/Lib/site-packages/PIL/BmpImagePlugin.py
ADDED
|
@@ -0,0 +1,472 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# BMP file handler
|
| 6 |
+
#
|
| 7 |
+
# Windows (and OS/2) native bitmap storage format.
|
| 8 |
+
#
|
| 9 |
+
# history:
|
| 10 |
+
# 1995-09-01 fl Created
|
| 11 |
+
# 1996-04-30 fl Added save
|
| 12 |
+
# 1997-08-27 fl Fixed save of 1-bit images
|
| 13 |
+
# 1998-03-06 fl Load P images as L where possible
|
| 14 |
+
# 1998-07-03 fl Load P images as 1 where possible
|
| 15 |
+
# 1998-12-29 fl Handle small palettes
|
| 16 |
+
# 2002-12-30 fl Fixed load of 1-bit palette images
|
| 17 |
+
# 2003-04-21 fl Fixed load of 1-bit monochrome images
|
| 18 |
+
# 2003-04-23 fl Added limited support for BI_BITFIELDS compression
|
| 19 |
+
#
|
| 20 |
+
# Copyright (c) 1997-2003 by Secret Labs AB
|
| 21 |
+
# Copyright (c) 1995-2003 by Fredrik Lundh
|
| 22 |
+
#
|
| 23 |
+
# See the README file for information on usage and redistribution.
|
| 24 |
+
#
|
| 25 |
+
from __future__ import annotations
|
| 26 |
+
|
| 27 |
+
import os
|
| 28 |
+
|
| 29 |
+
from . import Image, ImageFile, ImagePalette
|
| 30 |
+
from ._binary import i16le as i16
|
| 31 |
+
from ._binary import i32le as i32
|
| 32 |
+
from ._binary import o8
|
| 33 |
+
from ._binary import o16le as o16
|
| 34 |
+
from ._binary import o32le as o32
|
| 35 |
+
|
| 36 |
+
#
|
| 37 |
+
# --------------------------------------------------------------------
|
| 38 |
+
# Read BMP file
|
| 39 |
+
|
| 40 |
+
BIT2MODE = {
|
| 41 |
+
# bits => mode, rawmode
|
| 42 |
+
1: ("P", "P;1"),
|
| 43 |
+
4: ("P", "P;4"),
|
| 44 |
+
8: ("P", "P"),
|
| 45 |
+
16: ("RGB", "BGR;15"),
|
| 46 |
+
24: ("RGB", "BGR"),
|
| 47 |
+
32: ("RGB", "BGRX"),
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def _accept(prefix):
|
| 52 |
+
return prefix[:2] == b"BM"
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def _dib_accept(prefix):
|
| 56 |
+
return i32(prefix) in [12, 40, 64, 108, 124]
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
# =============================================================================
|
| 60 |
+
# Image plugin for the Windows BMP format.
|
| 61 |
+
# =============================================================================
|
| 62 |
+
class BmpImageFile(ImageFile.ImageFile):
|
| 63 |
+
"""Image plugin for the Windows Bitmap format (BMP)"""
|
| 64 |
+
|
| 65 |
+
# ------------------------------------------------------------- Description
|
| 66 |
+
format_description = "Windows Bitmap"
|
| 67 |
+
format = "BMP"
|
| 68 |
+
|
| 69 |
+
# -------------------------------------------------- BMP Compression values
|
| 70 |
+
COMPRESSIONS = {"RAW": 0, "RLE8": 1, "RLE4": 2, "BITFIELDS": 3, "JPEG": 4, "PNG": 5}
|
| 71 |
+
for k, v in COMPRESSIONS.items():
|
| 72 |
+
vars()[k] = v
|
| 73 |
+
|
| 74 |
+
def _bitmap(self, header=0, offset=0):
|
| 75 |
+
"""Read relevant info about the BMP"""
|
| 76 |
+
read, seek = self.fp.read, self.fp.seek
|
| 77 |
+
if header:
|
| 78 |
+
seek(header)
|
| 79 |
+
# read bmp header size @offset 14 (this is part of the header size)
|
| 80 |
+
file_info = {"header_size": i32(read(4)), "direction": -1}
|
| 81 |
+
|
| 82 |
+
# -------------------- If requested, read header at a specific position
|
| 83 |
+
# read the rest of the bmp header, without its size
|
| 84 |
+
header_data = ImageFile._safe_read(self.fp, file_info["header_size"] - 4)
|
| 85 |
+
|
| 86 |
+
# -------------------------------------------------- IBM OS/2 Bitmap v1
|
| 87 |
+
# ----- This format has different offsets because of width/height types
|
| 88 |
+
if file_info["header_size"] == 12:
|
| 89 |
+
file_info["width"] = i16(header_data, 0)
|
| 90 |
+
file_info["height"] = i16(header_data, 2)
|
| 91 |
+
file_info["planes"] = i16(header_data, 4)
|
| 92 |
+
file_info["bits"] = i16(header_data, 6)
|
| 93 |
+
file_info["compression"] = self.RAW
|
| 94 |
+
file_info["palette_padding"] = 3
|
| 95 |
+
|
| 96 |
+
# --------------------------------------------- Windows Bitmap v2 to v5
|
| 97 |
+
# v3, OS/2 v2, v4, v5
|
| 98 |
+
elif file_info["header_size"] in (40, 64, 108, 124):
|
| 99 |
+
file_info["y_flip"] = header_data[7] == 0xFF
|
| 100 |
+
file_info["direction"] = 1 if file_info["y_flip"] else -1
|
| 101 |
+
file_info["width"] = i32(header_data, 0)
|
| 102 |
+
file_info["height"] = (
|
| 103 |
+
i32(header_data, 4)
|
| 104 |
+
if not file_info["y_flip"]
|
| 105 |
+
else 2**32 - i32(header_data, 4)
|
| 106 |
+
)
|
| 107 |
+
file_info["planes"] = i16(header_data, 8)
|
| 108 |
+
file_info["bits"] = i16(header_data, 10)
|
| 109 |
+
file_info["compression"] = i32(header_data, 12)
|
| 110 |
+
# byte size of pixel data
|
| 111 |
+
file_info["data_size"] = i32(header_data, 16)
|
| 112 |
+
file_info["pixels_per_meter"] = (
|
| 113 |
+
i32(header_data, 20),
|
| 114 |
+
i32(header_data, 24),
|
| 115 |
+
)
|
| 116 |
+
file_info["colors"] = i32(header_data, 28)
|
| 117 |
+
file_info["palette_padding"] = 4
|
| 118 |
+
self.info["dpi"] = tuple(x / 39.3701 for x in file_info["pixels_per_meter"])
|
| 119 |
+
if file_info["compression"] == self.BITFIELDS:
|
| 120 |
+
if len(header_data) >= 52:
|
| 121 |
+
for idx, mask in enumerate(
|
| 122 |
+
["r_mask", "g_mask", "b_mask", "a_mask"]
|
| 123 |
+
):
|
| 124 |
+
file_info[mask] = i32(header_data, 36 + idx * 4)
|
| 125 |
+
else:
|
| 126 |
+
# 40 byte headers only have the three components in the
|
| 127 |
+
# bitfields masks, ref:
|
| 128 |
+
# https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx
|
| 129 |
+
# See also
|
| 130 |
+
# https://github.com/python-pillow/Pillow/issues/1293
|
| 131 |
+
# There is a 4th component in the RGBQuad, in the alpha
|
| 132 |
+
# location, but it is listed as a reserved component,
|
| 133 |
+
# and it is not generally an alpha channel
|
| 134 |
+
file_info["a_mask"] = 0x0
|
| 135 |
+
for mask in ["r_mask", "g_mask", "b_mask"]:
|
| 136 |
+
file_info[mask] = i32(read(4))
|
| 137 |
+
file_info["rgb_mask"] = (
|
| 138 |
+
file_info["r_mask"],
|
| 139 |
+
file_info["g_mask"],
|
| 140 |
+
file_info["b_mask"],
|
| 141 |
+
)
|
| 142 |
+
file_info["rgba_mask"] = (
|
| 143 |
+
file_info["r_mask"],
|
| 144 |
+
file_info["g_mask"],
|
| 145 |
+
file_info["b_mask"],
|
| 146 |
+
file_info["a_mask"],
|
| 147 |
+
)
|
| 148 |
+
else:
|
| 149 |
+
msg = f"Unsupported BMP header type ({file_info['header_size']})"
|
| 150 |
+
raise OSError(msg)
|
| 151 |
+
|
| 152 |
+
# ------------------ Special case : header is reported 40, which
|
| 153 |
+
# ---------------------- is shorter than real size for bpp >= 16
|
| 154 |
+
self._size = file_info["width"], file_info["height"]
|
| 155 |
+
|
| 156 |
+
# ------- If color count was not found in the header, compute from bits
|
| 157 |
+
file_info["colors"] = (
|
| 158 |
+
file_info["colors"]
|
| 159 |
+
if file_info.get("colors", 0)
|
| 160 |
+
else (1 << file_info["bits"])
|
| 161 |
+
)
|
| 162 |
+
if offset == 14 + file_info["header_size"] and file_info["bits"] <= 8:
|
| 163 |
+
offset += 4 * file_info["colors"]
|
| 164 |
+
|
| 165 |
+
# ---------------------- Check bit depth for unusual unsupported values
|
| 166 |
+
self._mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None))
|
| 167 |
+
if self.mode is None:
|
| 168 |
+
msg = f"Unsupported BMP pixel depth ({file_info['bits']})"
|
| 169 |
+
raise OSError(msg)
|
| 170 |
+
|
| 171 |
+
# ---------------- Process BMP with Bitfields compression (not palette)
|
| 172 |
+
decoder_name = "raw"
|
| 173 |
+
if file_info["compression"] == self.BITFIELDS:
|
| 174 |
+
SUPPORTED = {
|
| 175 |
+
32: [
|
| 176 |
+
(0xFF0000, 0xFF00, 0xFF, 0x0),
|
| 177 |
+
(0xFF000000, 0xFF0000, 0xFF00, 0x0),
|
| 178 |
+
(0xFF000000, 0xFF0000, 0xFF00, 0xFF),
|
| 179 |
+
(0xFF, 0xFF00, 0xFF0000, 0xFF000000),
|
| 180 |
+
(0xFF0000, 0xFF00, 0xFF, 0xFF000000),
|
| 181 |
+
(0x0, 0x0, 0x0, 0x0),
|
| 182 |
+
],
|
| 183 |
+
24: [(0xFF0000, 0xFF00, 0xFF)],
|
| 184 |
+
16: [(0xF800, 0x7E0, 0x1F), (0x7C00, 0x3E0, 0x1F)],
|
| 185 |
+
}
|
| 186 |
+
MASK_MODES = {
|
| 187 |
+
(32, (0xFF0000, 0xFF00, 0xFF, 0x0)): "BGRX",
|
| 188 |
+
(32, (0xFF000000, 0xFF0000, 0xFF00, 0x0)): "XBGR",
|
| 189 |
+
(32, (0xFF000000, 0xFF0000, 0xFF00, 0xFF)): "ABGR",
|
| 190 |
+
(32, (0xFF, 0xFF00, 0xFF0000, 0xFF000000)): "RGBA",
|
| 191 |
+
(32, (0xFF0000, 0xFF00, 0xFF, 0xFF000000)): "BGRA",
|
| 192 |
+
(32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
|
| 193 |
+
(24, (0xFF0000, 0xFF00, 0xFF)): "BGR",
|
| 194 |
+
(16, (0xF800, 0x7E0, 0x1F)): "BGR;16",
|
| 195 |
+
(16, (0x7C00, 0x3E0, 0x1F)): "BGR;15",
|
| 196 |
+
}
|
| 197 |
+
if file_info["bits"] in SUPPORTED:
|
| 198 |
+
if (
|
| 199 |
+
file_info["bits"] == 32
|
| 200 |
+
and file_info["rgba_mask"] in SUPPORTED[file_info["bits"]]
|
| 201 |
+
):
|
| 202 |
+
raw_mode = MASK_MODES[(file_info["bits"], file_info["rgba_mask"])]
|
| 203 |
+
self._mode = "RGBA" if "A" in raw_mode else self.mode
|
| 204 |
+
elif (
|
| 205 |
+
file_info["bits"] in (24, 16)
|
| 206 |
+
and file_info["rgb_mask"] in SUPPORTED[file_info["bits"]]
|
| 207 |
+
):
|
| 208 |
+
raw_mode = MASK_MODES[(file_info["bits"], file_info["rgb_mask"])]
|
| 209 |
+
else:
|
| 210 |
+
msg = "Unsupported BMP bitfields layout"
|
| 211 |
+
raise OSError(msg)
|
| 212 |
+
else:
|
| 213 |
+
msg = "Unsupported BMP bitfields layout"
|
| 214 |
+
raise OSError(msg)
|
| 215 |
+
elif file_info["compression"] == self.RAW:
|
| 216 |
+
if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset
|
| 217 |
+
raw_mode, self._mode = "BGRA", "RGBA"
|
| 218 |
+
elif file_info["compression"] in (self.RLE8, self.RLE4):
|
| 219 |
+
decoder_name = "bmp_rle"
|
| 220 |
+
else:
|
| 221 |
+
msg = f"Unsupported BMP compression ({file_info['compression']})"
|
| 222 |
+
raise OSError(msg)
|
| 223 |
+
|
| 224 |
+
# --------------- Once the header is processed, process the palette/LUT
|
| 225 |
+
if self.mode == "P": # Paletted for 1, 4 and 8 bit images
|
| 226 |
+
# ---------------------------------------------------- 1-bit images
|
| 227 |
+
if not (0 < file_info["colors"] <= 65536):
|
| 228 |
+
msg = f"Unsupported BMP Palette size ({file_info['colors']})"
|
| 229 |
+
raise OSError(msg)
|
| 230 |
+
else:
|
| 231 |
+
padding = file_info["palette_padding"]
|
| 232 |
+
palette = read(padding * file_info["colors"])
|
| 233 |
+
grayscale = True
|
| 234 |
+
indices = (
|
| 235 |
+
(0, 255)
|
| 236 |
+
if file_info["colors"] == 2
|
| 237 |
+
else list(range(file_info["colors"]))
|
| 238 |
+
)
|
| 239 |
+
|
| 240 |
+
# ----------------- Check if grayscale and ignore palette if so
|
| 241 |
+
for ind, val in enumerate(indices):
|
| 242 |
+
rgb = palette[ind * padding : ind * padding + 3]
|
| 243 |
+
if rgb != o8(val) * 3:
|
| 244 |
+
grayscale = False
|
| 245 |
+
|
| 246 |
+
# ------- If all colors are gray, white or black, ditch palette
|
| 247 |
+
if grayscale:
|
| 248 |
+
self._mode = "1" if file_info["colors"] == 2 else "L"
|
| 249 |
+
raw_mode = self.mode
|
| 250 |
+
else:
|
| 251 |
+
self._mode = "P"
|
| 252 |
+
self.palette = ImagePalette.raw(
|
| 253 |
+
"BGRX" if padding == 4 else "BGR", palette
|
| 254 |
+
)
|
| 255 |
+
|
| 256 |
+
# ---------------------------- Finally set the tile data for the plugin
|
| 257 |
+
self.info["compression"] = file_info["compression"]
|
| 258 |
+
args = [raw_mode]
|
| 259 |
+
if decoder_name == "bmp_rle":
|
| 260 |
+
args.append(file_info["compression"] == self.RLE4)
|
| 261 |
+
else:
|
| 262 |
+
args.append(((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3))
|
| 263 |
+
args.append(file_info["direction"])
|
| 264 |
+
self.tile = [
|
| 265 |
+
(
|
| 266 |
+
decoder_name,
|
| 267 |
+
(0, 0, file_info["width"], file_info["height"]),
|
| 268 |
+
offset or self.fp.tell(),
|
| 269 |
+
tuple(args),
|
| 270 |
+
)
|
| 271 |
+
]
|
| 272 |
+
|
| 273 |
+
def _open(self):
|
| 274 |
+
"""Open file, check magic number and read header"""
|
| 275 |
+
# read 14 bytes: magic number, filesize, reserved, header final offset
|
| 276 |
+
head_data = self.fp.read(14)
|
| 277 |
+
# choke if the file does not have the required magic bytes
|
| 278 |
+
if not _accept(head_data):
|
| 279 |
+
msg = "Not a BMP file"
|
| 280 |
+
raise SyntaxError(msg)
|
| 281 |
+
# read the start position of the BMP image data (u32)
|
| 282 |
+
offset = i32(head_data, 10)
|
| 283 |
+
# load bitmap information (offset=raster info)
|
| 284 |
+
self._bitmap(offset=offset)
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
class BmpRleDecoder(ImageFile.PyDecoder):
|
| 288 |
+
_pulls_fd = True
|
| 289 |
+
|
| 290 |
+
def decode(self, buffer):
|
| 291 |
+
rle4 = self.args[1]
|
| 292 |
+
data = bytearray()
|
| 293 |
+
x = 0
|
| 294 |
+
dest_length = self.state.xsize * self.state.ysize
|
| 295 |
+
while len(data) < dest_length:
|
| 296 |
+
pixels = self.fd.read(1)
|
| 297 |
+
byte = self.fd.read(1)
|
| 298 |
+
if not pixels or not byte:
|
| 299 |
+
break
|
| 300 |
+
num_pixels = pixels[0]
|
| 301 |
+
if num_pixels:
|
| 302 |
+
# encoded mode
|
| 303 |
+
if x + num_pixels > self.state.xsize:
|
| 304 |
+
# Too much data for row
|
| 305 |
+
num_pixels = max(0, self.state.xsize - x)
|
| 306 |
+
if rle4:
|
| 307 |
+
first_pixel = o8(byte[0] >> 4)
|
| 308 |
+
second_pixel = o8(byte[0] & 0x0F)
|
| 309 |
+
for index in range(num_pixels):
|
| 310 |
+
if index % 2 == 0:
|
| 311 |
+
data += first_pixel
|
| 312 |
+
else:
|
| 313 |
+
data += second_pixel
|
| 314 |
+
else:
|
| 315 |
+
data += byte * num_pixels
|
| 316 |
+
x += num_pixels
|
| 317 |
+
else:
|
| 318 |
+
if byte[0] == 0:
|
| 319 |
+
# end of line
|
| 320 |
+
while len(data) % self.state.xsize != 0:
|
| 321 |
+
data += b"\x00"
|
| 322 |
+
x = 0
|
| 323 |
+
elif byte[0] == 1:
|
| 324 |
+
# end of bitmap
|
| 325 |
+
break
|
| 326 |
+
elif byte[0] == 2:
|
| 327 |
+
# delta
|
| 328 |
+
bytes_read = self.fd.read(2)
|
| 329 |
+
if len(bytes_read) < 2:
|
| 330 |
+
break
|
| 331 |
+
right, up = self.fd.read(2)
|
| 332 |
+
data += b"\x00" * (right + up * self.state.xsize)
|
| 333 |
+
x = len(data) % self.state.xsize
|
| 334 |
+
else:
|
| 335 |
+
# absolute mode
|
| 336 |
+
if rle4:
|
| 337 |
+
# 2 pixels per byte
|
| 338 |
+
byte_count = byte[0] // 2
|
| 339 |
+
bytes_read = self.fd.read(byte_count)
|
| 340 |
+
for byte_read in bytes_read:
|
| 341 |
+
data += o8(byte_read >> 4)
|
| 342 |
+
data += o8(byte_read & 0x0F)
|
| 343 |
+
else:
|
| 344 |
+
byte_count = byte[0]
|
| 345 |
+
bytes_read = self.fd.read(byte_count)
|
| 346 |
+
data += bytes_read
|
| 347 |
+
if len(bytes_read) < byte_count:
|
| 348 |
+
break
|
| 349 |
+
x += byte[0]
|
| 350 |
+
|
| 351 |
+
# align to 16-bit word boundary
|
| 352 |
+
if self.fd.tell() % 2 != 0:
|
| 353 |
+
self.fd.seek(1, os.SEEK_CUR)
|
| 354 |
+
rawmode = "L" if self.mode == "L" else "P"
|
| 355 |
+
self.set_as_raw(bytes(data), (rawmode, 0, self.args[-1]))
|
| 356 |
+
return -1, 0
|
| 357 |
+
|
| 358 |
+
|
| 359 |
+
# =============================================================================
|
| 360 |
+
# Image plugin for the DIB format (BMP alias)
|
| 361 |
+
# =============================================================================
|
| 362 |
+
class DibImageFile(BmpImageFile):
|
| 363 |
+
format = "DIB"
|
| 364 |
+
format_description = "Windows Bitmap"
|
| 365 |
+
|
| 366 |
+
def _open(self):
|
| 367 |
+
self._bitmap()
|
| 368 |
+
|
| 369 |
+
|
| 370 |
+
#
|
| 371 |
+
# --------------------------------------------------------------------
|
| 372 |
+
# Write BMP file
|
| 373 |
+
|
| 374 |
+
|
| 375 |
+
SAVE = {
|
| 376 |
+
"1": ("1", 1, 2),
|
| 377 |
+
"L": ("L", 8, 256),
|
| 378 |
+
"P": ("P", 8, 256),
|
| 379 |
+
"RGB": ("BGR", 24, 0),
|
| 380 |
+
"RGBA": ("BGRA", 32, 0),
|
| 381 |
+
}
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
def _dib_save(im, fp, filename):
|
| 385 |
+
_save(im, fp, filename, False)
|
| 386 |
+
|
| 387 |
+
|
| 388 |
+
def _save(im, fp, filename, bitmap_header=True):
|
| 389 |
+
try:
|
| 390 |
+
rawmode, bits, colors = SAVE[im.mode]
|
| 391 |
+
except KeyError as e:
|
| 392 |
+
msg = f"cannot write mode {im.mode} as BMP"
|
| 393 |
+
raise OSError(msg) from e
|
| 394 |
+
|
| 395 |
+
info = im.encoderinfo
|
| 396 |
+
|
| 397 |
+
dpi = info.get("dpi", (96, 96))
|
| 398 |
+
|
| 399 |
+
# 1 meter == 39.3701 inches
|
| 400 |
+
ppm = tuple(int(x * 39.3701 + 0.5) for x in dpi)
|
| 401 |
+
|
| 402 |
+
stride = ((im.size[0] * bits + 7) // 8 + 3) & (~3)
|
| 403 |
+
header = 40 # or 64 for OS/2 version 2
|
| 404 |
+
image = stride * im.size[1]
|
| 405 |
+
|
| 406 |
+
if im.mode == "1":
|
| 407 |
+
palette = b"".join(o8(i) * 4 for i in (0, 255))
|
| 408 |
+
elif im.mode == "L":
|
| 409 |
+
palette = b"".join(o8(i) * 4 for i in range(256))
|
| 410 |
+
elif im.mode == "P":
|
| 411 |
+
palette = im.im.getpalette("RGB", "BGRX")
|
| 412 |
+
colors = len(palette) // 4
|
| 413 |
+
else:
|
| 414 |
+
palette = None
|
| 415 |
+
|
| 416 |
+
# bitmap header
|
| 417 |
+
if bitmap_header:
|
| 418 |
+
offset = 14 + header + colors * 4
|
| 419 |
+
file_size = offset + image
|
| 420 |
+
if file_size > 2**32 - 1:
|
| 421 |
+
msg = "File size is too large for the BMP format"
|
| 422 |
+
raise ValueError(msg)
|
| 423 |
+
fp.write(
|
| 424 |
+
b"BM" # file type (magic)
|
| 425 |
+
+ o32(file_size) # file size
|
| 426 |
+
+ o32(0) # reserved
|
| 427 |
+
+ o32(offset) # image data offset
|
| 428 |
+
)
|
| 429 |
+
|
| 430 |
+
# bitmap info header
|
| 431 |
+
fp.write(
|
| 432 |
+
o32(header) # info header size
|
| 433 |
+
+ o32(im.size[0]) # width
|
| 434 |
+
+ o32(im.size[1]) # height
|
| 435 |
+
+ o16(1) # planes
|
| 436 |
+
+ o16(bits) # depth
|
| 437 |
+
+ o32(0) # compression (0=uncompressed)
|
| 438 |
+
+ o32(image) # size of bitmap
|
| 439 |
+
+ o32(ppm[0]) # resolution
|
| 440 |
+
+ o32(ppm[1]) # resolution
|
| 441 |
+
+ o32(colors) # colors used
|
| 442 |
+
+ o32(colors) # colors important
|
| 443 |
+
)
|
| 444 |
+
|
| 445 |
+
fp.write(b"\0" * (header - 40)) # padding (for OS/2 format)
|
| 446 |
+
|
| 447 |
+
if palette:
|
| 448 |
+
fp.write(palette)
|
| 449 |
+
|
| 450 |
+
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, stride, -1))])
|
| 451 |
+
|
| 452 |
+
|
| 453 |
+
#
|
| 454 |
+
# --------------------------------------------------------------------
|
| 455 |
+
# Registry
|
| 456 |
+
|
| 457 |
+
|
| 458 |
+
Image.register_open(BmpImageFile.format, BmpImageFile, _accept)
|
| 459 |
+
Image.register_save(BmpImageFile.format, _save)
|
| 460 |
+
|
| 461 |
+
Image.register_extension(BmpImageFile.format, ".bmp")
|
| 462 |
+
|
| 463 |
+
Image.register_mime(BmpImageFile.format, "image/bmp")
|
| 464 |
+
|
| 465 |
+
Image.register_decoder("bmp_rle", BmpRleDecoder)
|
| 466 |
+
|
| 467 |
+
Image.register_open(DibImageFile.format, DibImageFile, _dib_accept)
|
| 468 |
+
Image.register_save(DibImageFile.format, _dib_save)
|
| 469 |
+
|
| 470 |
+
Image.register_extension(DibImageFile.format, ".dib")
|
| 471 |
+
|
| 472 |
+
Image.register_mime(DibImageFile.format, "image/bmp")
|
venv/Lib/site-packages/PIL/BufrStubImagePlugin.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# BUFR stub adapter
|
| 6 |
+
#
|
| 7 |
+
# Copyright (c) 1996-2003 by Fredrik Lundh
|
| 8 |
+
#
|
| 9 |
+
# See the README file for information on usage and redistribution.
|
| 10 |
+
#
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
from . import Image, ImageFile
|
| 14 |
+
|
| 15 |
+
_handler = None
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def register_handler(handler):
|
| 19 |
+
"""
|
| 20 |
+
Install application-specific BUFR image handler.
|
| 21 |
+
|
| 22 |
+
:param handler: Handler object.
|
| 23 |
+
"""
|
| 24 |
+
global _handler
|
| 25 |
+
_handler = handler
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
# --------------------------------------------------------------------
|
| 29 |
+
# Image adapter
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _accept(prefix):
|
| 33 |
+
return prefix[:4] == b"BUFR" or prefix[:4] == b"ZCZC"
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class BufrStubImageFile(ImageFile.StubImageFile):
|
| 37 |
+
format = "BUFR"
|
| 38 |
+
format_description = "BUFR"
|
| 39 |
+
|
| 40 |
+
def _open(self):
|
| 41 |
+
offset = self.fp.tell()
|
| 42 |
+
|
| 43 |
+
if not _accept(self.fp.read(4)):
|
| 44 |
+
msg = "Not a BUFR file"
|
| 45 |
+
raise SyntaxError(msg)
|
| 46 |
+
|
| 47 |
+
self.fp.seek(offset)
|
| 48 |
+
|
| 49 |
+
# make something up
|
| 50 |
+
self._mode = "F"
|
| 51 |
+
self._size = 1, 1
|
| 52 |
+
|
| 53 |
+
loader = self._load()
|
| 54 |
+
if loader:
|
| 55 |
+
loader.open(self)
|
| 56 |
+
|
| 57 |
+
def _load(self):
|
| 58 |
+
return _handler
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def _save(im, fp, filename):
|
| 62 |
+
if _handler is None or not hasattr(_handler, "save"):
|
| 63 |
+
msg = "BUFR save handler not installed"
|
| 64 |
+
raise OSError(msg)
|
| 65 |
+
_handler.save(im, fp, filename)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
# --------------------------------------------------------------------
|
| 69 |
+
# Registry
|
| 70 |
+
|
| 71 |
+
Image.register_open(BufrStubImageFile.format, BufrStubImageFile, _accept)
|
| 72 |
+
Image.register_save(BufrStubImageFile.format, _save)
|
| 73 |
+
|
| 74 |
+
Image.register_extension(BufrStubImageFile.format, ".bufr")
|
venv/Lib/site-packages/PIL/ContainerIO.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# a class to read from a container file
|
| 6 |
+
#
|
| 7 |
+
# History:
|
| 8 |
+
# 1995-06-18 fl Created
|
| 9 |
+
# 1995-09-07 fl Added readline(), readlines()
|
| 10 |
+
#
|
| 11 |
+
# Copyright (c) 1997-2001 by Secret Labs AB
|
| 12 |
+
# Copyright (c) 1995 by Fredrik Lundh
|
| 13 |
+
#
|
| 14 |
+
# See the README file for information on usage and redistribution.
|
| 15 |
+
#
|
| 16 |
+
from __future__ import annotations
|
| 17 |
+
|
| 18 |
+
import io
|
| 19 |
+
from typing import IO, AnyStr, Generic, Literal
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class ContainerIO(Generic[AnyStr]):
|
| 23 |
+
"""
|
| 24 |
+
A file object that provides read access to a part of an existing
|
| 25 |
+
file (for example a TAR file).
|
| 26 |
+
"""
|
| 27 |
+
|
| 28 |
+
def __init__(self, file: IO[AnyStr], offset: int, length: int) -> None:
|
| 29 |
+
"""
|
| 30 |
+
Create file object.
|
| 31 |
+
|
| 32 |
+
:param file: Existing file.
|
| 33 |
+
:param offset: Start of region, in bytes.
|
| 34 |
+
:param length: Size of region, in bytes.
|
| 35 |
+
"""
|
| 36 |
+
self.fh: IO[AnyStr] = file
|
| 37 |
+
self.pos = 0
|
| 38 |
+
self.offset = offset
|
| 39 |
+
self.length = length
|
| 40 |
+
self.fh.seek(offset)
|
| 41 |
+
|
| 42 |
+
##
|
| 43 |
+
# Always false.
|
| 44 |
+
|
| 45 |
+
def isatty(self) -> bool:
|
| 46 |
+
return False
|
| 47 |
+
|
| 48 |
+
def seek(self, offset: int, mode: Literal[0, 1, 2] = io.SEEK_SET) -> None:
|
| 49 |
+
"""
|
| 50 |
+
Move file pointer.
|
| 51 |
+
|
| 52 |
+
:param offset: Offset in bytes.
|
| 53 |
+
:param mode: Starting position. Use 0 for beginning of region, 1
|
| 54 |
+
for current offset, and 2 for end of region. You cannot move
|
| 55 |
+
the pointer outside the defined region.
|
| 56 |
+
"""
|
| 57 |
+
if mode == 1:
|
| 58 |
+
self.pos = self.pos + offset
|
| 59 |
+
elif mode == 2:
|
| 60 |
+
self.pos = self.length + offset
|
| 61 |
+
else:
|
| 62 |
+
self.pos = offset
|
| 63 |
+
# clamp
|
| 64 |
+
self.pos = max(0, min(self.pos, self.length))
|
| 65 |
+
self.fh.seek(self.offset + self.pos)
|
| 66 |
+
|
| 67 |
+
def tell(self) -> int:
|
| 68 |
+
"""
|
| 69 |
+
Get current file pointer.
|
| 70 |
+
|
| 71 |
+
:returns: Offset from start of region, in bytes.
|
| 72 |
+
"""
|
| 73 |
+
return self.pos
|
| 74 |
+
|
| 75 |
+
def read(self, n: int = 0) -> AnyStr:
|
| 76 |
+
"""
|
| 77 |
+
Read data.
|
| 78 |
+
|
| 79 |
+
:param n: Number of bytes to read. If omitted or zero,
|
| 80 |
+
read until end of region.
|
| 81 |
+
:returns: An 8-bit string.
|
| 82 |
+
"""
|
| 83 |
+
if n:
|
| 84 |
+
n = min(n, self.length - self.pos)
|
| 85 |
+
else:
|
| 86 |
+
n = self.length - self.pos
|
| 87 |
+
if not n: # EOF
|
| 88 |
+
return b"" if "b" in self.fh.mode else "" # type: ignore[return-value]
|
| 89 |
+
self.pos = self.pos + n
|
| 90 |
+
return self.fh.read(n)
|
| 91 |
+
|
| 92 |
+
def readline(self) -> AnyStr:
|
| 93 |
+
"""
|
| 94 |
+
Read a line of text.
|
| 95 |
+
|
| 96 |
+
:returns: An 8-bit string.
|
| 97 |
+
"""
|
| 98 |
+
s: AnyStr = b"" if "b" in self.fh.mode else "" # type: ignore[assignment]
|
| 99 |
+
newline_character = b"\n" if "b" in self.fh.mode else "\n"
|
| 100 |
+
while True:
|
| 101 |
+
c = self.read(1)
|
| 102 |
+
if not c:
|
| 103 |
+
break
|
| 104 |
+
s = s + c
|
| 105 |
+
if c == newline_character:
|
| 106 |
+
break
|
| 107 |
+
return s
|
| 108 |
+
|
| 109 |
+
def readlines(self) -> list[AnyStr]:
|
| 110 |
+
"""
|
| 111 |
+
Read multiple lines of text.
|
| 112 |
+
|
| 113 |
+
:returns: A list of 8-bit strings.
|
| 114 |
+
"""
|
| 115 |
+
lines = []
|
| 116 |
+
while True:
|
| 117 |
+
s = self.readline()
|
| 118 |
+
if not s:
|
| 119 |
+
break
|
| 120 |
+
lines.append(s)
|
| 121 |
+
return lines
|
venv/Lib/site-packages/PIL/CurImagePlugin.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# Windows Cursor support for PIL
|
| 6 |
+
#
|
| 7 |
+
# notes:
|
| 8 |
+
# uses BmpImagePlugin.py to read the bitmap data.
|
| 9 |
+
#
|
| 10 |
+
# history:
|
| 11 |
+
# 96-05-27 fl Created
|
| 12 |
+
#
|
| 13 |
+
# Copyright (c) Secret Labs AB 1997.
|
| 14 |
+
# Copyright (c) Fredrik Lundh 1996.
|
| 15 |
+
#
|
| 16 |
+
# See the README file for information on usage and redistribution.
|
| 17 |
+
#
|
| 18 |
+
from __future__ import annotations
|
| 19 |
+
|
| 20 |
+
from . import BmpImagePlugin, Image
|
| 21 |
+
from ._binary import i16le as i16
|
| 22 |
+
from ._binary import i32le as i32
|
| 23 |
+
|
| 24 |
+
#
|
| 25 |
+
# --------------------------------------------------------------------
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def _accept(prefix):
|
| 29 |
+
return prefix[:4] == b"\0\0\2\0"
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
##
|
| 33 |
+
# Image plugin for Windows Cursor files.
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class CurImageFile(BmpImagePlugin.BmpImageFile):
|
| 37 |
+
format = "CUR"
|
| 38 |
+
format_description = "Windows Cursor"
|
| 39 |
+
|
| 40 |
+
def _open(self):
|
| 41 |
+
offset = self.fp.tell()
|
| 42 |
+
|
| 43 |
+
# check magic
|
| 44 |
+
s = self.fp.read(6)
|
| 45 |
+
if not _accept(s):
|
| 46 |
+
msg = "not a CUR file"
|
| 47 |
+
raise SyntaxError(msg)
|
| 48 |
+
|
| 49 |
+
# pick the largest cursor in the file
|
| 50 |
+
m = b""
|
| 51 |
+
for i in range(i16(s, 4)):
|
| 52 |
+
s = self.fp.read(16)
|
| 53 |
+
if not m:
|
| 54 |
+
m = s
|
| 55 |
+
elif s[0] > m[0] and s[1] > m[1]:
|
| 56 |
+
m = s
|
| 57 |
+
if not m:
|
| 58 |
+
msg = "No cursors were found"
|
| 59 |
+
raise TypeError(msg)
|
| 60 |
+
|
| 61 |
+
# load as bitmap
|
| 62 |
+
self._bitmap(i32(m, 12) + offset)
|
| 63 |
+
|
| 64 |
+
# patch up the bitmap height
|
| 65 |
+
self._size = self.size[0], self.size[1] // 2
|
| 66 |
+
d, e, o, a = self.tile[0]
|
| 67 |
+
self.tile[0] = d, (0, 0) + self.size, o, a
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
#
|
| 71 |
+
# --------------------------------------------------------------------
|
| 72 |
+
|
| 73 |
+
Image.register_open(CurImageFile.format, CurImageFile, _accept)
|
| 74 |
+
|
| 75 |
+
Image.register_extension(CurImageFile.format, ".cur")
|
venv/Lib/site-packages/PIL/DcxImagePlugin.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# DCX file handling
|
| 6 |
+
#
|
| 7 |
+
# DCX is a container file format defined by Intel, commonly used
|
| 8 |
+
# for fax applications. Each DCX file consists of a directory
|
| 9 |
+
# (a list of file offsets) followed by a set of (usually 1-bit)
|
| 10 |
+
# PCX files.
|
| 11 |
+
#
|
| 12 |
+
# History:
|
| 13 |
+
# 1995-09-09 fl Created
|
| 14 |
+
# 1996-03-20 fl Properly derived from PcxImageFile.
|
| 15 |
+
# 1998-07-15 fl Renamed offset attribute to avoid name clash
|
| 16 |
+
# 2002-07-30 fl Fixed file handling
|
| 17 |
+
#
|
| 18 |
+
# Copyright (c) 1997-98 by Secret Labs AB.
|
| 19 |
+
# Copyright (c) 1995-96 by Fredrik Lundh.
|
| 20 |
+
#
|
| 21 |
+
# See the README file for information on usage and redistribution.
|
| 22 |
+
#
|
| 23 |
+
from __future__ import annotations
|
| 24 |
+
|
| 25 |
+
from . import Image
|
| 26 |
+
from ._binary import i32le as i32
|
| 27 |
+
from .PcxImagePlugin import PcxImageFile
|
| 28 |
+
|
| 29 |
+
MAGIC = 0x3ADE68B1 # QUIZ: what's this value, then?
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _accept(prefix):
|
| 33 |
+
return len(prefix) >= 4 and i32(prefix) == MAGIC
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
##
|
| 37 |
+
# Image plugin for the Intel DCX format.
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class DcxImageFile(PcxImageFile):
|
| 41 |
+
format = "DCX"
|
| 42 |
+
format_description = "Intel DCX"
|
| 43 |
+
_close_exclusive_fp_after_loading = False
|
| 44 |
+
|
| 45 |
+
def _open(self):
|
| 46 |
+
# Header
|
| 47 |
+
s = self.fp.read(4)
|
| 48 |
+
if not _accept(s):
|
| 49 |
+
msg = "not a DCX file"
|
| 50 |
+
raise SyntaxError(msg)
|
| 51 |
+
|
| 52 |
+
# Component directory
|
| 53 |
+
self._offset = []
|
| 54 |
+
for i in range(1024):
|
| 55 |
+
offset = i32(self.fp.read(4))
|
| 56 |
+
if not offset:
|
| 57 |
+
break
|
| 58 |
+
self._offset.append(offset)
|
| 59 |
+
|
| 60 |
+
self._fp = self.fp
|
| 61 |
+
self.frame = None
|
| 62 |
+
self.n_frames = len(self._offset)
|
| 63 |
+
self.is_animated = self.n_frames > 1
|
| 64 |
+
self.seek(0)
|
| 65 |
+
|
| 66 |
+
def seek(self, frame):
|
| 67 |
+
if not self._seek_check(frame):
|
| 68 |
+
return
|
| 69 |
+
self.frame = frame
|
| 70 |
+
self.fp = self._fp
|
| 71 |
+
self.fp.seek(self._offset[frame])
|
| 72 |
+
PcxImageFile._open(self)
|
| 73 |
+
|
| 74 |
+
def tell(self):
|
| 75 |
+
return self.frame
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
Image.register_open(DcxImageFile.format, DcxImageFile, _accept)
|
| 79 |
+
|
| 80 |
+
Image.register_extension(DcxImageFile.format, ".dcx")
|
venv/Lib/site-packages/PIL/DdsImagePlugin.py
ADDED
|
@@ -0,0 +1,572 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
A Pillow loader for .dds files (S3TC-compressed aka DXTC)
|
| 3 |
+
Jerome Leclanche <jerome@leclan.ch>
|
| 4 |
+
|
| 5 |
+
Documentation:
|
| 6 |
+
https://web.archive.org/web/20170802060935/http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt
|
| 7 |
+
|
| 8 |
+
The contents of this file are hereby released in the public domain (CC0)
|
| 9 |
+
Full text of the CC0 license:
|
| 10 |
+
https://creativecommons.org/publicdomain/zero/1.0/
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
from __future__ import annotations
|
| 14 |
+
|
| 15 |
+
import io
|
| 16 |
+
import struct
|
| 17 |
+
import sys
|
| 18 |
+
from enum import IntEnum, IntFlag
|
| 19 |
+
|
| 20 |
+
from . import Image, ImageFile, ImagePalette
|
| 21 |
+
from ._binary import i32le as i32
|
| 22 |
+
from ._binary import o8
|
| 23 |
+
from ._binary import o32le as o32
|
| 24 |
+
|
| 25 |
+
# Magic ("DDS ")
|
| 26 |
+
DDS_MAGIC = 0x20534444
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
# DDS flags
|
| 30 |
+
class DDSD(IntFlag):
|
| 31 |
+
CAPS = 0x1
|
| 32 |
+
HEIGHT = 0x2
|
| 33 |
+
WIDTH = 0x4
|
| 34 |
+
PITCH = 0x8
|
| 35 |
+
PIXELFORMAT = 0x1000
|
| 36 |
+
MIPMAPCOUNT = 0x20000
|
| 37 |
+
LINEARSIZE = 0x80000
|
| 38 |
+
DEPTH = 0x800000
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
# DDS caps
|
| 42 |
+
class DDSCAPS(IntFlag):
|
| 43 |
+
COMPLEX = 0x8
|
| 44 |
+
TEXTURE = 0x1000
|
| 45 |
+
MIPMAP = 0x400000
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
class DDSCAPS2(IntFlag):
|
| 49 |
+
CUBEMAP = 0x200
|
| 50 |
+
CUBEMAP_POSITIVEX = 0x400
|
| 51 |
+
CUBEMAP_NEGATIVEX = 0x800
|
| 52 |
+
CUBEMAP_POSITIVEY = 0x1000
|
| 53 |
+
CUBEMAP_NEGATIVEY = 0x2000
|
| 54 |
+
CUBEMAP_POSITIVEZ = 0x4000
|
| 55 |
+
CUBEMAP_NEGATIVEZ = 0x8000
|
| 56 |
+
VOLUME = 0x200000
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
# Pixel Format
|
| 60 |
+
class DDPF(IntFlag):
|
| 61 |
+
ALPHAPIXELS = 0x1
|
| 62 |
+
ALPHA = 0x2
|
| 63 |
+
FOURCC = 0x4
|
| 64 |
+
PALETTEINDEXED8 = 0x20
|
| 65 |
+
RGB = 0x40
|
| 66 |
+
LUMINANCE = 0x20000
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
# dxgiformat.h
|
| 70 |
+
class DXGI_FORMAT(IntEnum):
|
| 71 |
+
UNKNOWN = 0
|
| 72 |
+
R32G32B32A32_TYPELESS = 1
|
| 73 |
+
R32G32B32A32_FLOAT = 2
|
| 74 |
+
R32G32B32A32_UINT = 3
|
| 75 |
+
R32G32B32A32_SINT = 4
|
| 76 |
+
R32G32B32_TYPELESS = 5
|
| 77 |
+
R32G32B32_FLOAT = 6
|
| 78 |
+
R32G32B32_UINT = 7
|
| 79 |
+
R32G32B32_SINT = 8
|
| 80 |
+
R16G16B16A16_TYPELESS = 9
|
| 81 |
+
R16G16B16A16_FLOAT = 10
|
| 82 |
+
R16G16B16A16_UNORM = 11
|
| 83 |
+
R16G16B16A16_UINT = 12
|
| 84 |
+
R16G16B16A16_SNORM = 13
|
| 85 |
+
R16G16B16A16_SINT = 14
|
| 86 |
+
R32G32_TYPELESS = 15
|
| 87 |
+
R32G32_FLOAT = 16
|
| 88 |
+
R32G32_UINT = 17
|
| 89 |
+
R32G32_SINT = 18
|
| 90 |
+
R32G8X24_TYPELESS = 19
|
| 91 |
+
D32_FLOAT_S8X24_UINT = 20
|
| 92 |
+
R32_FLOAT_X8X24_TYPELESS = 21
|
| 93 |
+
X32_TYPELESS_G8X24_UINT = 22
|
| 94 |
+
R10G10B10A2_TYPELESS = 23
|
| 95 |
+
R10G10B10A2_UNORM = 24
|
| 96 |
+
R10G10B10A2_UINT = 25
|
| 97 |
+
R11G11B10_FLOAT = 26
|
| 98 |
+
R8G8B8A8_TYPELESS = 27
|
| 99 |
+
R8G8B8A8_UNORM = 28
|
| 100 |
+
R8G8B8A8_UNORM_SRGB = 29
|
| 101 |
+
R8G8B8A8_UINT = 30
|
| 102 |
+
R8G8B8A8_SNORM = 31
|
| 103 |
+
R8G8B8A8_SINT = 32
|
| 104 |
+
R16G16_TYPELESS = 33
|
| 105 |
+
R16G16_FLOAT = 34
|
| 106 |
+
R16G16_UNORM = 35
|
| 107 |
+
R16G16_UINT = 36
|
| 108 |
+
R16G16_SNORM = 37
|
| 109 |
+
R16G16_SINT = 38
|
| 110 |
+
R32_TYPELESS = 39
|
| 111 |
+
D32_FLOAT = 40
|
| 112 |
+
R32_FLOAT = 41
|
| 113 |
+
R32_UINT = 42
|
| 114 |
+
R32_SINT = 43
|
| 115 |
+
R24G8_TYPELESS = 44
|
| 116 |
+
D24_UNORM_S8_UINT = 45
|
| 117 |
+
R24_UNORM_X8_TYPELESS = 46
|
| 118 |
+
X24_TYPELESS_G8_UINT = 47
|
| 119 |
+
R8G8_TYPELESS = 48
|
| 120 |
+
R8G8_UNORM = 49
|
| 121 |
+
R8G8_UINT = 50
|
| 122 |
+
R8G8_SNORM = 51
|
| 123 |
+
R8G8_SINT = 52
|
| 124 |
+
R16_TYPELESS = 53
|
| 125 |
+
R16_FLOAT = 54
|
| 126 |
+
D16_UNORM = 55
|
| 127 |
+
R16_UNORM = 56
|
| 128 |
+
R16_UINT = 57
|
| 129 |
+
R16_SNORM = 58
|
| 130 |
+
R16_SINT = 59
|
| 131 |
+
R8_TYPELESS = 60
|
| 132 |
+
R8_UNORM = 61
|
| 133 |
+
R8_UINT = 62
|
| 134 |
+
R8_SNORM = 63
|
| 135 |
+
R8_SINT = 64
|
| 136 |
+
A8_UNORM = 65
|
| 137 |
+
R1_UNORM = 66
|
| 138 |
+
R9G9B9E5_SHAREDEXP = 67
|
| 139 |
+
R8G8_B8G8_UNORM = 68
|
| 140 |
+
G8R8_G8B8_UNORM = 69
|
| 141 |
+
BC1_TYPELESS = 70
|
| 142 |
+
BC1_UNORM = 71
|
| 143 |
+
BC1_UNORM_SRGB = 72
|
| 144 |
+
BC2_TYPELESS = 73
|
| 145 |
+
BC2_UNORM = 74
|
| 146 |
+
BC2_UNORM_SRGB = 75
|
| 147 |
+
BC3_TYPELESS = 76
|
| 148 |
+
BC3_UNORM = 77
|
| 149 |
+
BC3_UNORM_SRGB = 78
|
| 150 |
+
BC4_TYPELESS = 79
|
| 151 |
+
BC4_UNORM = 80
|
| 152 |
+
BC4_SNORM = 81
|
| 153 |
+
BC5_TYPELESS = 82
|
| 154 |
+
BC5_UNORM = 83
|
| 155 |
+
BC5_SNORM = 84
|
| 156 |
+
B5G6R5_UNORM = 85
|
| 157 |
+
B5G5R5A1_UNORM = 86
|
| 158 |
+
B8G8R8A8_UNORM = 87
|
| 159 |
+
B8G8R8X8_UNORM = 88
|
| 160 |
+
R10G10B10_XR_BIAS_A2_UNORM = 89
|
| 161 |
+
B8G8R8A8_TYPELESS = 90
|
| 162 |
+
B8G8R8A8_UNORM_SRGB = 91
|
| 163 |
+
B8G8R8X8_TYPELESS = 92
|
| 164 |
+
B8G8R8X8_UNORM_SRGB = 93
|
| 165 |
+
BC6H_TYPELESS = 94
|
| 166 |
+
BC6H_UF16 = 95
|
| 167 |
+
BC6H_SF16 = 96
|
| 168 |
+
BC7_TYPELESS = 97
|
| 169 |
+
BC7_UNORM = 98
|
| 170 |
+
BC7_UNORM_SRGB = 99
|
| 171 |
+
AYUV = 100
|
| 172 |
+
Y410 = 101
|
| 173 |
+
Y416 = 102
|
| 174 |
+
NV12 = 103
|
| 175 |
+
P010 = 104
|
| 176 |
+
P016 = 105
|
| 177 |
+
OPAQUE_420 = 106
|
| 178 |
+
YUY2 = 107
|
| 179 |
+
Y210 = 108
|
| 180 |
+
Y216 = 109
|
| 181 |
+
NV11 = 110
|
| 182 |
+
AI44 = 111
|
| 183 |
+
IA44 = 112
|
| 184 |
+
P8 = 113
|
| 185 |
+
A8P8 = 114
|
| 186 |
+
B4G4R4A4_UNORM = 115
|
| 187 |
+
P208 = 130
|
| 188 |
+
V208 = 131
|
| 189 |
+
V408 = 132
|
| 190 |
+
SAMPLER_FEEDBACK_MIN_MIP_OPAQUE = 189
|
| 191 |
+
SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE = 190
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
class D3DFMT(IntEnum):
|
| 195 |
+
UNKNOWN = 0
|
| 196 |
+
R8G8B8 = 20
|
| 197 |
+
A8R8G8B8 = 21
|
| 198 |
+
X8R8G8B8 = 22
|
| 199 |
+
R5G6B5 = 23
|
| 200 |
+
X1R5G5B5 = 24
|
| 201 |
+
A1R5G5B5 = 25
|
| 202 |
+
A4R4G4B4 = 26
|
| 203 |
+
R3G3B2 = 27
|
| 204 |
+
A8 = 28
|
| 205 |
+
A8R3G3B2 = 29
|
| 206 |
+
X4R4G4B4 = 30
|
| 207 |
+
A2B10G10R10 = 31
|
| 208 |
+
A8B8G8R8 = 32
|
| 209 |
+
X8B8G8R8 = 33
|
| 210 |
+
G16R16 = 34
|
| 211 |
+
A2R10G10B10 = 35
|
| 212 |
+
A16B16G16R16 = 36
|
| 213 |
+
A8P8 = 40
|
| 214 |
+
P8 = 41
|
| 215 |
+
L8 = 50
|
| 216 |
+
A8L8 = 51
|
| 217 |
+
A4L4 = 52
|
| 218 |
+
V8U8 = 60
|
| 219 |
+
L6V5U5 = 61
|
| 220 |
+
X8L8V8U8 = 62
|
| 221 |
+
Q8W8V8U8 = 63
|
| 222 |
+
V16U16 = 64
|
| 223 |
+
A2W10V10U10 = 67
|
| 224 |
+
D16_LOCKABLE = 70
|
| 225 |
+
D32 = 71
|
| 226 |
+
D15S1 = 73
|
| 227 |
+
D24S8 = 75
|
| 228 |
+
D24X8 = 77
|
| 229 |
+
D24X4S4 = 79
|
| 230 |
+
D16 = 80
|
| 231 |
+
D32F_LOCKABLE = 82
|
| 232 |
+
D24FS8 = 83
|
| 233 |
+
D32_LOCKABLE = 84
|
| 234 |
+
S8_LOCKABLE = 85
|
| 235 |
+
L16 = 81
|
| 236 |
+
VERTEXDATA = 100
|
| 237 |
+
INDEX16 = 101
|
| 238 |
+
INDEX32 = 102
|
| 239 |
+
Q16W16V16U16 = 110
|
| 240 |
+
R16F = 111
|
| 241 |
+
G16R16F = 112
|
| 242 |
+
A16B16G16R16F = 113
|
| 243 |
+
R32F = 114
|
| 244 |
+
G32R32F = 115
|
| 245 |
+
A32B32G32R32F = 116
|
| 246 |
+
CxV8U8 = 117
|
| 247 |
+
A1 = 118
|
| 248 |
+
A2B10G10R10_XR_BIAS = 119
|
| 249 |
+
BINARYBUFFER = 199
|
| 250 |
+
|
| 251 |
+
UYVY = i32(b"UYVY")
|
| 252 |
+
R8G8_B8G8 = i32(b"RGBG")
|
| 253 |
+
YUY2 = i32(b"YUY2")
|
| 254 |
+
G8R8_G8B8 = i32(b"GRGB")
|
| 255 |
+
DXT1 = i32(b"DXT1")
|
| 256 |
+
DXT2 = i32(b"DXT2")
|
| 257 |
+
DXT3 = i32(b"DXT3")
|
| 258 |
+
DXT4 = i32(b"DXT4")
|
| 259 |
+
DXT5 = i32(b"DXT5")
|
| 260 |
+
DX10 = i32(b"DX10")
|
| 261 |
+
BC4S = i32(b"BC4S")
|
| 262 |
+
BC4U = i32(b"BC4U")
|
| 263 |
+
BC5S = i32(b"BC5S")
|
| 264 |
+
BC5U = i32(b"BC5U")
|
| 265 |
+
ATI1 = i32(b"ATI1")
|
| 266 |
+
ATI2 = i32(b"ATI2")
|
| 267 |
+
MULTI2_ARGB8 = i32(b"MET1")
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
# Backward compatibility layer
|
| 271 |
+
module = sys.modules[__name__]
|
| 272 |
+
for item in DDSD:
|
| 273 |
+
assert item.name is not None
|
| 274 |
+
setattr(module, "DDSD_" + item.name, item.value)
|
| 275 |
+
for item1 in DDSCAPS:
|
| 276 |
+
assert item1.name is not None
|
| 277 |
+
setattr(module, "DDSCAPS_" + item1.name, item1.value)
|
| 278 |
+
for item2 in DDSCAPS2:
|
| 279 |
+
assert item2.name is not None
|
| 280 |
+
setattr(module, "DDSCAPS2_" + item2.name, item2.value)
|
| 281 |
+
for item3 in DDPF:
|
| 282 |
+
assert item3.name is not None
|
| 283 |
+
setattr(module, "DDPF_" + item3.name, item3.value)
|
| 284 |
+
|
| 285 |
+
DDS_FOURCC = DDPF.FOURCC
|
| 286 |
+
DDS_RGB = DDPF.RGB
|
| 287 |
+
DDS_RGBA = DDPF.RGB | DDPF.ALPHAPIXELS
|
| 288 |
+
DDS_LUMINANCE = DDPF.LUMINANCE
|
| 289 |
+
DDS_LUMINANCEA = DDPF.LUMINANCE | DDPF.ALPHAPIXELS
|
| 290 |
+
DDS_ALPHA = DDPF.ALPHA
|
| 291 |
+
DDS_PAL8 = DDPF.PALETTEINDEXED8
|
| 292 |
+
|
| 293 |
+
DDS_HEADER_FLAGS_TEXTURE = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PIXELFORMAT
|
| 294 |
+
DDS_HEADER_FLAGS_MIPMAP = DDSD.MIPMAPCOUNT
|
| 295 |
+
DDS_HEADER_FLAGS_VOLUME = DDSD.DEPTH
|
| 296 |
+
DDS_HEADER_FLAGS_PITCH = DDSD.PITCH
|
| 297 |
+
DDS_HEADER_FLAGS_LINEARSIZE = DDSD.LINEARSIZE
|
| 298 |
+
|
| 299 |
+
DDS_HEIGHT = DDSD.HEIGHT
|
| 300 |
+
DDS_WIDTH = DDSD.WIDTH
|
| 301 |
+
|
| 302 |
+
DDS_SURFACE_FLAGS_TEXTURE = DDSCAPS.TEXTURE
|
| 303 |
+
DDS_SURFACE_FLAGS_MIPMAP = DDSCAPS.COMPLEX | DDSCAPS.MIPMAP
|
| 304 |
+
DDS_SURFACE_FLAGS_CUBEMAP = DDSCAPS.COMPLEX
|
| 305 |
+
|
| 306 |
+
DDS_CUBEMAP_POSITIVEX = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEX
|
| 307 |
+
DDS_CUBEMAP_NEGATIVEX = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEX
|
| 308 |
+
DDS_CUBEMAP_POSITIVEY = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEY
|
| 309 |
+
DDS_CUBEMAP_NEGATIVEY = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEY
|
| 310 |
+
DDS_CUBEMAP_POSITIVEZ = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEZ
|
| 311 |
+
DDS_CUBEMAP_NEGATIVEZ = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEZ
|
| 312 |
+
|
| 313 |
+
DXT1_FOURCC = D3DFMT.DXT1
|
| 314 |
+
DXT3_FOURCC = D3DFMT.DXT3
|
| 315 |
+
DXT5_FOURCC = D3DFMT.DXT5
|
| 316 |
+
|
| 317 |
+
DXGI_FORMAT_R8G8B8A8_TYPELESS = DXGI_FORMAT.R8G8B8A8_TYPELESS
|
| 318 |
+
DXGI_FORMAT_R8G8B8A8_UNORM = DXGI_FORMAT.R8G8B8A8_UNORM
|
| 319 |
+
DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = DXGI_FORMAT.R8G8B8A8_UNORM_SRGB
|
| 320 |
+
DXGI_FORMAT_BC5_TYPELESS = DXGI_FORMAT.BC5_TYPELESS
|
| 321 |
+
DXGI_FORMAT_BC5_UNORM = DXGI_FORMAT.BC5_UNORM
|
| 322 |
+
DXGI_FORMAT_BC5_SNORM = DXGI_FORMAT.BC5_SNORM
|
| 323 |
+
DXGI_FORMAT_BC6H_UF16 = DXGI_FORMAT.BC6H_UF16
|
| 324 |
+
DXGI_FORMAT_BC6H_SF16 = DXGI_FORMAT.BC6H_SF16
|
| 325 |
+
DXGI_FORMAT_BC7_TYPELESS = DXGI_FORMAT.BC7_TYPELESS
|
| 326 |
+
DXGI_FORMAT_BC7_UNORM = DXGI_FORMAT.BC7_UNORM
|
| 327 |
+
DXGI_FORMAT_BC7_UNORM_SRGB = DXGI_FORMAT.BC7_UNORM_SRGB
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
class DdsImageFile(ImageFile.ImageFile):
|
| 331 |
+
format = "DDS"
|
| 332 |
+
format_description = "DirectDraw Surface"
|
| 333 |
+
|
| 334 |
+
def _open(self):
|
| 335 |
+
if not _accept(self.fp.read(4)):
|
| 336 |
+
msg = "not a DDS file"
|
| 337 |
+
raise SyntaxError(msg)
|
| 338 |
+
(header_size,) = struct.unpack("<I", self.fp.read(4))
|
| 339 |
+
if header_size != 124:
|
| 340 |
+
msg = f"Unsupported header size {repr(header_size)}"
|
| 341 |
+
raise OSError(msg)
|
| 342 |
+
header_bytes = self.fp.read(header_size - 4)
|
| 343 |
+
if len(header_bytes) != 120:
|
| 344 |
+
msg = f"Incomplete header: {len(header_bytes)} bytes"
|
| 345 |
+
raise OSError(msg)
|
| 346 |
+
header = io.BytesIO(header_bytes)
|
| 347 |
+
|
| 348 |
+
flags, height, width = struct.unpack("<3I", header.read(12))
|
| 349 |
+
self._size = (width, height)
|
| 350 |
+
extents = (0, 0) + self.size
|
| 351 |
+
|
| 352 |
+
pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
|
| 353 |
+
struct.unpack("<11I", header.read(44)) # reserved
|
| 354 |
+
|
| 355 |
+
# pixel format
|
| 356 |
+
pfsize, pfflags, fourcc, bitcount = struct.unpack("<4I", header.read(16))
|
| 357 |
+
n = 0
|
| 358 |
+
rawmode = None
|
| 359 |
+
if pfflags & DDPF.RGB:
|
| 360 |
+
# Texture contains uncompressed RGB data
|
| 361 |
+
if pfflags & DDPF.ALPHAPIXELS:
|
| 362 |
+
self._mode = "RGBA"
|
| 363 |
+
mask_count = 4
|
| 364 |
+
else:
|
| 365 |
+
self._mode = "RGB"
|
| 366 |
+
mask_count = 3
|
| 367 |
+
|
| 368 |
+
masks = struct.unpack(f"<{mask_count}I", header.read(mask_count * 4))
|
| 369 |
+
self.tile = [("dds_rgb", extents, 0, (bitcount, masks))]
|
| 370 |
+
return
|
| 371 |
+
elif pfflags & DDPF.LUMINANCE:
|
| 372 |
+
if bitcount == 8:
|
| 373 |
+
self._mode = "L"
|
| 374 |
+
elif bitcount == 16 and pfflags & DDPF.ALPHAPIXELS:
|
| 375 |
+
self._mode = "LA"
|
| 376 |
+
else:
|
| 377 |
+
msg = f"Unsupported bitcount {bitcount} for {pfflags}"
|
| 378 |
+
raise OSError(msg)
|
| 379 |
+
elif pfflags & DDPF.PALETTEINDEXED8:
|
| 380 |
+
self._mode = "P"
|
| 381 |
+
self.palette = ImagePalette.raw("RGBA", self.fp.read(1024))
|
| 382 |
+
elif pfflags & DDPF.FOURCC:
|
| 383 |
+
offset = header_size + 4
|
| 384 |
+
if fourcc == D3DFMT.DXT1:
|
| 385 |
+
self._mode = "RGBA"
|
| 386 |
+
self.pixel_format = "DXT1"
|
| 387 |
+
n = 1
|
| 388 |
+
elif fourcc == D3DFMT.DXT3:
|
| 389 |
+
self._mode = "RGBA"
|
| 390 |
+
self.pixel_format = "DXT3"
|
| 391 |
+
n = 2
|
| 392 |
+
elif fourcc == D3DFMT.DXT5:
|
| 393 |
+
self._mode = "RGBA"
|
| 394 |
+
self.pixel_format = "DXT5"
|
| 395 |
+
n = 3
|
| 396 |
+
elif fourcc in (D3DFMT.BC4U, D3DFMT.ATI1):
|
| 397 |
+
self._mode = "L"
|
| 398 |
+
self.pixel_format = "BC4"
|
| 399 |
+
n = 4
|
| 400 |
+
elif fourcc == D3DFMT.BC5S:
|
| 401 |
+
self._mode = "RGB"
|
| 402 |
+
self.pixel_format = "BC5S"
|
| 403 |
+
n = 5
|
| 404 |
+
elif fourcc in (D3DFMT.BC5U, D3DFMT.ATI2):
|
| 405 |
+
self._mode = "RGB"
|
| 406 |
+
self.pixel_format = "BC5"
|
| 407 |
+
n = 5
|
| 408 |
+
elif fourcc == D3DFMT.DX10:
|
| 409 |
+
offset += 20
|
| 410 |
+
# ignoring flags which pertain to volume textures and cubemaps
|
| 411 |
+
(dxgi_format,) = struct.unpack("<I", self.fp.read(4))
|
| 412 |
+
self.fp.read(16)
|
| 413 |
+
if dxgi_format in (
|
| 414 |
+
DXGI_FORMAT.BC1_UNORM,
|
| 415 |
+
DXGI_FORMAT.BC1_TYPELESS,
|
| 416 |
+
):
|
| 417 |
+
self._mode = "RGBA"
|
| 418 |
+
self.pixel_format = "BC1"
|
| 419 |
+
n = 1
|
| 420 |
+
elif dxgi_format in (DXGI_FORMAT.BC4_TYPELESS, DXGI_FORMAT.BC4_UNORM):
|
| 421 |
+
self._mode = "L"
|
| 422 |
+
self.pixel_format = "BC4"
|
| 423 |
+
n = 4
|
| 424 |
+
elif dxgi_format in (DXGI_FORMAT.BC5_TYPELESS, DXGI_FORMAT.BC5_UNORM):
|
| 425 |
+
self._mode = "RGB"
|
| 426 |
+
self.pixel_format = "BC5"
|
| 427 |
+
n = 5
|
| 428 |
+
elif dxgi_format == DXGI_FORMAT.BC5_SNORM:
|
| 429 |
+
self._mode = "RGB"
|
| 430 |
+
self.pixel_format = "BC5S"
|
| 431 |
+
n = 5
|
| 432 |
+
elif dxgi_format == DXGI_FORMAT.BC6H_UF16:
|
| 433 |
+
self._mode = "RGB"
|
| 434 |
+
self.pixel_format = "BC6H"
|
| 435 |
+
n = 6
|
| 436 |
+
elif dxgi_format == DXGI_FORMAT.BC6H_SF16:
|
| 437 |
+
self._mode = "RGB"
|
| 438 |
+
self.pixel_format = "BC6HS"
|
| 439 |
+
n = 6
|
| 440 |
+
elif dxgi_format in (
|
| 441 |
+
DXGI_FORMAT.BC7_TYPELESS,
|
| 442 |
+
DXGI_FORMAT.BC7_UNORM,
|
| 443 |
+
DXGI_FORMAT.BC7_UNORM_SRGB,
|
| 444 |
+
):
|
| 445 |
+
self._mode = "RGBA"
|
| 446 |
+
self.pixel_format = "BC7"
|
| 447 |
+
n = 7
|
| 448 |
+
if dxgi_format == DXGI_FORMAT.BC7_UNORM_SRGB:
|
| 449 |
+
self.info["gamma"] = 1 / 2.2
|
| 450 |
+
elif dxgi_format in (
|
| 451 |
+
DXGI_FORMAT.R8G8B8A8_TYPELESS,
|
| 452 |
+
DXGI_FORMAT.R8G8B8A8_UNORM,
|
| 453 |
+
DXGI_FORMAT.R8G8B8A8_UNORM_SRGB,
|
| 454 |
+
):
|
| 455 |
+
self._mode = "RGBA"
|
| 456 |
+
if dxgi_format == DXGI_FORMAT.R8G8B8A8_UNORM_SRGB:
|
| 457 |
+
self.info["gamma"] = 1 / 2.2
|
| 458 |
+
else:
|
| 459 |
+
msg = f"Unimplemented DXGI format {dxgi_format}"
|
| 460 |
+
raise NotImplementedError(msg)
|
| 461 |
+
else:
|
| 462 |
+
msg = f"Unimplemented pixel format {repr(fourcc)}"
|
| 463 |
+
raise NotImplementedError(msg)
|
| 464 |
+
else:
|
| 465 |
+
msg = f"Unknown pixel format flags {pfflags}"
|
| 466 |
+
raise NotImplementedError(msg)
|
| 467 |
+
|
| 468 |
+
if n:
|
| 469 |
+
self.tile = [
|
| 470 |
+
ImageFile._Tile("bcn", extents, offset, (n, self.pixel_format))
|
| 471 |
+
]
|
| 472 |
+
else:
|
| 473 |
+
self.tile = [ImageFile._Tile("raw", extents, 0, rawmode or self.mode)]
|
| 474 |
+
|
| 475 |
+
def load_seek(self, pos):
|
| 476 |
+
pass
|
| 477 |
+
|
| 478 |
+
|
| 479 |
+
class DdsRgbDecoder(ImageFile.PyDecoder):
|
| 480 |
+
_pulls_fd = True
|
| 481 |
+
|
| 482 |
+
def decode(self, buffer):
|
| 483 |
+
bitcount, masks = self.args
|
| 484 |
+
|
| 485 |
+
# Some masks will be padded with zeros, e.g. R 0b11 G 0b1100
|
| 486 |
+
# Calculate how many zeros each mask is padded with
|
| 487 |
+
mask_offsets = []
|
| 488 |
+
# And the maximum value of each channel without the padding
|
| 489 |
+
mask_totals = []
|
| 490 |
+
for mask in masks:
|
| 491 |
+
offset = 0
|
| 492 |
+
if mask != 0:
|
| 493 |
+
while mask >> (offset + 1) << (offset + 1) == mask:
|
| 494 |
+
offset += 1
|
| 495 |
+
mask_offsets.append(offset)
|
| 496 |
+
mask_totals.append(mask >> offset)
|
| 497 |
+
|
| 498 |
+
data = bytearray()
|
| 499 |
+
bytecount = bitcount // 8
|
| 500 |
+
dest_length = self.state.xsize * self.state.ysize * len(masks)
|
| 501 |
+
while len(data) < dest_length:
|
| 502 |
+
value = int.from_bytes(self.fd.read(bytecount), "little")
|
| 503 |
+
for i, mask in enumerate(masks):
|
| 504 |
+
masked_value = value & mask
|
| 505 |
+
# Remove the zero padding, and scale it to 8 bits
|
| 506 |
+
data += o8(
|
| 507 |
+
int(((masked_value >> mask_offsets[i]) / mask_totals[i]) * 255)
|
| 508 |
+
)
|
| 509 |
+
self.set_as_raw(data)
|
| 510 |
+
return -1, 0
|
| 511 |
+
|
| 512 |
+
|
| 513 |
+
def _save(im, fp, filename):
|
| 514 |
+
if im.mode not in ("RGB", "RGBA", "L", "LA"):
|
| 515 |
+
msg = f"cannot write mode {im.mode} as DDS"
|
| 516 |
+
raise OSError(msg)
|
| 517 |
+
|
| 518 |
+
alpha = im.mode[-1] == "A"
|
| 519 |
+
if im.mode[0] == "L":
|
| 520 |
+
pixel_flags = DDPF.LUMINANCE
|
| 521 |
+
rawmode = im.mode
|
| 522 |
+
if alpha:
|
| 523 |
+
rgba_mask = [0x000000FF, 0x000000FF, 0x000000FF]
|
| 524 |
+
else:
|
| 525 |
+
rgba_mask = [0xFF000000, 0xFF000000, 0xFF000000]
|
| 526 |
+
else:
|
| 527 |
+
pixel_flags = DDPF.RGB
|
| 528 |
+
rawmode = im.mode[::-1]
|
| 529 |
+
rgba_mask = [0x00FF0000, 0x0000FF00, 0x000000FF]
|
| 530 |
+
|
| 531 |
+
if alpha:
|
| 532 |
+
r, g, b, a = im.split()
|
| 533 |
+
im = Image.merge("RGBA", (a, r, g, b))
|
| 534 |
+
if alpha:
|
| 535 |
+
pixel_flags |= DDPF.ALPHAPIXELS
|
| 536 |
+
rgba_mask.append(0xFF000000 if alpha else 0)
|
| 537 |
+
|
| 538 |
+
flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PITCH | DDSD.PIXELFORMAT
|
| 539 |
+
bitcount = len(im.getbands()) * 8
|
| 540 |
+
pitch = (im.width * bitcount + 7) // 8
|
| 541 |
+
|
| 542 |
+
fp.write(
|
| 543 |
+
o32(DDS_MAGIC)
|
| 544 |
+
+ struct.pack(
|
| 545 |
+
"<7I",
|
| 546 |
+
124, # header size
|
| 547 |
+
flags, # flags
|
| 548 |
+
im.height,
|
| 549 |
+
im.width,
|
| 550 |
+
pitch,
|
| 551 |
+
0, # depth
|
| 552 |
+
0, # mipmaps
|
| 553 |
+
)
|
| 554 |
+
+ struct.pack("11I", *((0,) * 11)) # reserved
|
| 555 |
+
# pfsize, pfflags, fourcc, bitcount
|
| 556 |
+
+ struct.pack("<4I", 32, pixel_flags, 0, bitcount)
|
| 557 |
+
+ struct.pack("<4I", *rgba_mask) # dwRGBABitMask
|
| 558 |
+
+ struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0)
|
| 559 |
+
)
|
| 560 |
+
ImageFile._save(
|
| 561 |
+
im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]
|
| 562 |
+
)
|
| 563 |
+
|
| 564 |
+
|
| 565 |
+
def _accept(prefix):
|
| 566 |
+
return prefix[:4] == b"DDS "
|
| 567 |
+
|
| 568 |
+
|
| 569 |
+
Image.register_open(DdsImageFile.format, DdsImageFile, _accept)
|
| 570 |
+
Image.register_decoder("dds_rgb", DdsRgbDecoder)
|
| 571 |
+
Image.register_save(DdsImageFile.format, _save)
|
| 572 |
+
Image.register_extension(DdsImageFile.format, ".dds")
|
venv/Lib/site-packages/PIL/EpsImagePlugin.py
ADDED
|
@@ -0,0 +1,474 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# EPS file handling
|
| 6 |
+
#
|
| 7 |
+
# History:
|
| 8 |
+
# 1995-09-01 fl Created (0.1)
|
| 9 |
+
# 1996-05-18 fl Don't choke on "atend" fields, Ghostscript interface (0.2)
|
| 10 |
+
# 1996-08-22 fl Don't choke on floating point BoundingBox values
|
| 11 |
+
# 1996-08-23 fl Handle files from Macintosh (0.3)
|
| 12 |
+
# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.4)
|
| 13 |
+
# 2003-09-07 fl Check gs.close status (from Federico Di Gregorio) (0.5)
|
| 14 |
+
# 2014-05-07 e Handling of EPS with binary preview and fixed resolution
|
| 15 |
+
# resizing
|
| 16 |
+
#
|
| 17 |
+
# Copyright (c) 1997-2003 by Secret Labs AB.
|
| 18 |
+
# Copyright (c) 1995-2003 by Fredrik Lundh
|
| 19 |
+
#
|
| 20 |
+
# See the README file for information on usage and redistribution.
|
| 21 |
+
#
|
| 22 |
+
from __future__ import annotations
|
| 23 |
+
|
| 24 |
+
import io
|
| 25 |
+
import os
|
| 26 |
+
import re
|
| 27 |
+
import subprocess
|
| 28 |
+
import sys
|
| 29 |
+
import tempfile
|
| 30 |
+
|
| 31 |
+
from . import Image, ImageFile
|
| 32 |
+
from ._binary import i32le as i32
|
| 33 |
+
from ._deprecate import deprecate
|
| 34 |
+
|
| 35 |
+
# --------------------------------------------------------------------
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
|
| 39 |
+
field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
|
| 40 |
+
|
| 41 |
+
gs_binary: str | bool | None = None
|
| 42 |
+
gs_windows_binary = None
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def has_ghostscript():
|
| 46 |
+
global gs_binary, gs_windows_binary
|
| 47 |
+
if gs_binary is None:
|
| 48 |
+
if sys.platform.startswith("win"):
|
| 49 |
+
if gs_windows_binary is None:
|
| 50 |
+
import shutil
|
| 51 |
+
|
| 52 |
+
for binary in ("gswin32c", "gswin64c", "gs"):
|
| 53 |
+
if shutil.which(binary) is not None:
|
| 54 |
+
gs_windows_binary = binary
|
| 55 |
+
break
|
| 56 |
+
else:
|
| 57 |
+
gs_windows_binary = False
|
| 58 |
+
gs_binary = gs_windows_binary
|
| 59 |
+
else:
|
| 60 |
+
try:
|
| 61 |
+
subprocess.check_call(["gs", "--version"], stdout=subprocess.DEVNULL)
|
| 62 |
+
gs_binary = "gs"
|
| 63 |
+
except OSError:
|
| 64 |
+
gs_binary = False
|
| 65 |
+
return gs_binary is not False
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def Ghostscript(tile, size, fp, scale=1, transparency=False):
|
| 69 |
+
"""Render an image using Ghostscript"""
|
| 70 |
+
global gs_binary
|
| 71 |
+
if not has_ghostscript():
|
| 72 |
+
msg = "Unable to locate Ghostscript on paths"
|
| 73 |
+
raise OSError(msg)
|
| 74 |
+
|
| 75 |
+
# Unpack decoder tile
|
| 76 |
+
decoder, tile, offset, data = tile[0]
|
| 77 |
+
length, bbox = data
|
| 78 |
+
|
| 79 |
+
# Hack to support hi-res rendering
|
| 80 |
+
scale = int(scale) or 1
|
| 81 |
+
width = size[0] * scale
|
| 82 |
+
height = size[1] * scale
|
| 83 |
+
# resolution is dependent on bbox and size
|
| 84 |
+
res_x = 72.0 * width / (bbox[2] - bbox[0])
|
| 85 |
+
res_y = 72.0 * height / (bbox[3] - bbox[1])
|
| 86 |
+
|
| 87 |
+
out_fd, outfile = tempfile.mkstemp()
|
| 88 |
+
os.close(out_fd)
|
| 89 |
+
|
| 90 |
+
infile_temp = None
|
| 91 |
+
if hasattr(fp, "name") and os.path.exists(fp.name):
|
| 92 |
+
infile = fp.name
|
| 93 |
+
else:
|
| 94 |
+
in_fd, infile_temp = tempfile.mkstemp()
|
| 95 |
+
os.close(in_fd)
|
| 96 |
+
infile = infile_temp
|
| 97 |
+
|
| 98 |
+
# Ignore length and offset!
|
| 99 |
+
# Ghostscript can read it
|
| 100 |
+
# Copy whole file to read in Ghostscript
|
| 101 |
+
with open(infile_temp, "wb") as f:
|
| 102 |
+
# fetch length of fp
|
| 103 |
+
fp.seek(0, io.SEEK_END)
|
| 104 |
+
fsize = fp.tell()
|
| 105 |
+
# ensure start position
|
| 106 |
+
# go back
|
| 107 |
+
fp.seek(0)
|
| 108 |
+
lengthfile = fsize
|
| 109 |
+
while lengthfile > 0:
|
| 110 |
+
s = fp.read(min(lengthfile, 100 * 1024))
|
| 111 |
+
if not s:
|
| 112 |
+
break
|
| 113 |
+
lengthfile -= len(s)
|
| 114 |
+
f.write(s)
|
| 115 |
+
|
| 116 |
+
device = "pngalpha" if transparency else "ppmraw"
|
| 117 |
+
|
| 118 |
+
# Build Ghostscript command
|
| 119 |
+
command = [
|
| 120 |
+
gs_binary,
|
| 121 |
+
"-q", # quiet mode
|
| 122 |
+
f"-g{width:d}x{height:d}", # set output geometry (pixels)
|
| 123 |
+
f"-r{res_x:f}x{res_y:f}", # set input DPI (dots per inch)
|
| 124 |
+
"-dBATCH", # exit after processing
|
| 125 |
+
"-dNOPAUSE", # don't pause between pages
|
| 126 |
+
"-dSAFER", # safe mode
|
| 127 |
+
f"-sDEVICE={device}",
|
| 128 |
+
f"-sOutputFile={outfile}", # output file
|
| 129 |
+
# adjust for image origin
|
| 130 |
+
"-c",
|
| 131 |
+
f"{-bbox[0]} {-bbox[1]} translate",
|
| 132 |
+
"-f",
|
| 133 |
+
infile, # input file
|
| 134 |
+
# showpage (see https://bugs.ghostscript.com/show_bug.cgi?id=698272)
|
| 135 |
+
"-c",
|
| 136 |
+
"showpage",
|
| 137 |
+
]
|
| 138 |
+
|
| 139 |
+
# push data through Ghostscript
|
| 140 |
+
try:
|
| 141 |
+
startupinfo = None
|
| 142 |
+
if sys.platform.startswith("win"):
|
| 143 |
+
startupinfo = subprocess.STARTUPINFO()
|
| 144 |
+
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
| 145 |
+
subprocess.check_call(command, startupinfo=startupinfo)
|
| 146 |
+
out_im = Image.open(outfile)
|
| 147 |
+
out_im.load()
|
| 148 |
+
finally:
|
| 149 |
+
try:
|
| 150 |
+
os.unlink(outfile)
|
| 151 |
+
if infile_temp:
|
| 152 |
+
os.unlink(infile_temp)
|
| 153 |
+
except OSError:
|
| 154 |
+
pass
|
| 155 |
+
|
| 156 |
+
im = out_im.im.copy()
|
| 157 |
+
out_im.close()
|
| 158 |
+
return im
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
class PSFile:
|
| 162 |
+
"""
|
| 163 |
+
Wrapper for bytesio object that treats either CR or LF as end of line.
|
| 164 |
+
This class is no longer used internally, but kept for backwards compatibility.
|
| 165 |
+
"""
|
| 166 |
+
|
| 167 |
+
def __init__(self, fp):
|
| 168 |
+
deprecate(
|
| 169 |
+
"PSFile",
|
| 170 |
+
11,
|
| 171 |
+
action="If you need the functionality of this class "
|
| 172 |
+
"you will need to implement it yourself.",
|
| 173 |
+
)
|
| 174 |
+
self.fp = fp
|
| 175 |
+
self.char = None
|
| 176 |
+
|
| 177 |
+
def seek(self, offset, whence=io.SEEK_SET):
|
| 178 |
+
self.char = None
|
| 179 |
+
self.fp.seek(offset, whence)
|
| 180 |
+
|
| 181 |
+
def readline(self):
|
| 182 |
+
s = [self.char or b""]
|
| 183 |
+
self.char = None
|
| 184 |
+
|
| 185 |
+
c = self.fp.read(1)
|
| 186 |
+
while (c not in b"\r\n") and len(c):
|
| 187 |
+
s.append(c)
|
| 188 |
+
c = self.fp.read(1)
|
| 189 |
+
|
| 190 |
+
self.char = self.fp.read(1)
|
| 191 |
+
# line endings can be 1 or 2 of \r \n, in either order
|
| 192 |
+
if self.char in b"\r\n":
|
| 193 |
+
self.char = None
|
| 194 |
+
|
| 195 |
+
return b"".join(s).decode("latin-1")
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
def _accept(prefix):
|
| 199 |
+
return prefix[:4] == b"%!PS" or (len(prefix) >= 4 and i32(prefix) == 0xC6D3D0C5)
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
##
|
| 203 |
+
# Image plugin for Encapsulated PostScript. This plugin supports only
|
| 204 |
+
# a few variants of this format.
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
class EpsImageFile(ImageFile.ImageFile):
|
| 208 |
+
"""EPS File Parser for the Python Imaging Library"""
|
| 209 |
+
|
| 210 |
+
format = "EPS"
|
| 211 |
+
format_description = "Encapsulated Postscript"
|
| 212 |
+
|
| 213 |
+
mode_map = {1: "L", 2: "LAB", 3: "RGB", 4: "CMYK"}
|
| 214 |
+
|
| 215 |
+
def _open(self):
|
| 216 |
+
(length, offset) = self._find_offset(self.fp)
|
| 217 |
+
|
| 218 |
+
# go to offset - start of "%!PS"
|
| 219 |
+
self.fp.seek(offset)
|
| 220 |
+
|
| 221 |
+
self._mode = "RGB"
|
| 222 |
+
self._size = None
|
| 223 |
+
|
| 224 |
+
byte_arr = bytearray(255)
|
| 225 |
+
bytes_mv = memoryview(byte_arr)
|
| 226 |
+
bytes_read = 0
|
| 227 |
+
reading_header_comments = True
|
| 228 |
+
reading_trailer_comments = False
|
| 229 |
+
trailer_reached = False
|
| 230 |
+
|
| 231 |
+
def check_required_header_comments():
|
| 232 |
+
if "PS-Adobe" not in self.info:
|
| 233 |
+
msg = 'EPS header missing "%!PS-Adobe" comment'
|
| 234 |
+
raise SyntaxError(msg)
|
| 235 |
+
if "BoundingBox" not in self.info:
|
| 236 |
+
msg = 'EPS header missing "%%BoundingBox" comment'
|
| 237 |
+
raise SyntaxError(msg)
|
| 238 |
+
|
| 239 |
+
def _read_comment(s):
|
| 240 |
+
nonlocal reading_trailer_comments
|
| 241 |
+
try:
|
| 242 |
+
m = split.match(s)
|
| 243 |
+
except re.error as e:
|
| 244 |
+
msg = "not an EPS file"
|
| 245 |
+
raise SyntaxError(msg) from e
|
| 246 |
+
|
| 247 |
+
if m:
|
| 248 |
+
k, v = m.group(1, 2)
|
| 249 |
+
self.info[k] = v
|
| 250 |
+
if k == "BoundingBox":
|
| 251 |
+
if v == "(atend)":
|
| 252 |
+
reading_trailer_comments = True
|
| 253 |
+
elif not self._size or (
|
| 254 |
+
trailer_reached and reading_trailer_comments
|
| 255 |
+
):
|
| 256 |
+
try:
|
| 257 |
+
# Note: The DSC spec says that BoundingBox
|
| 258 |
+
# fields should be integers, but some drivers
|
| 259 |
+
# put floating point values there anyway.
|
| 260 |
+
box = [int(float(i)) for i in v.split()]
|
| 261 |
+
self._size = box[2] - box[0], box[3] - box[1]
|
| 262 |
+
self.tile = [
|
| 263 |
+
("eps", (0, 0) + self.size, offset, (length, box))
|
| 264 |
+
]
|
| 265 |
+
except Exception:
|
| 266 |
+
pass
|
| 267 |
+
return True
|
| 268 |
+
|
| 269 |
+
while True:
|
| 270 |
+
byte = self.fp.read(1)
|
| 271 |
+
if byte == b"":
|
| 272 |
+
# if we didn't read a byte we must be at the end of the file
|
| 273 |
+
if bytes_read == 0:
|
| 274 |
+
break
|
| 275 |
+
elif byte in b"\r\n":
|
| 276 |
+
# if we read a line ending character, ignore it and parse what
|
| 277 |
+
# we have already read. if we haven't read any other characters,
|
| 278 |
+
# continue reading
|
| 279 |
+
if bytes_read == 0:
|
| 280 |
+
continue
|
| 281 |
+
else:
|
| 282 |
+
# ASCII/hexadecimal lines in an EPS file must not exceed
|
| 283 |
+
# 255 characters, not including line ending characters
|
| 284 |
+
if bytes_read >= 255:
|
| 285 |
+
# only enforce this for lines starting with a "%",
|
| 286 |
+
# otherwise assume it's binary data
|
| 287 |
+
if byte_arr[0] == ord("%"):
|
| 288 |
+
msg = "not an EPS file"
|
| 289 |
+
raise SyntaxError(msg)
|
| 290 |
+
else:
|
| 291 |
+
if reading_header_comments:
|
| 292 |
+
check_required_header_comments()
|
| 293 |
+
reading_header_comments = False
|
| 294 |
+
# reset bytes_read so we can keep reading
|
| 295 |
+
# data until the end of the line
|
| 296 |
+
bytes_read = 0
|
| 297 |
+
byte_arr[bytes_read] = byte[0]
|
| 298 |
+
bytes_read += 1
|
| 299 |
+
continue
|
| 300 |
+
|
| 301 |
+
if reading_header_comments:
|
| 302 |
+
# Load EPS header
|
| 303 |
+
|
| 304 |
+
# if this line doesn't start with a "%",
|
| 305 |
+
# or does start with "%%EndComments",
|
| 306 |
+
# then we've reached the end of the header/comments
|
| 307 |
+
if byte_arr[0] != ord("%") or bytes_mv[:13] == b"%%EndComments":
|
| 308 |
+
check_required_header_comments()
|
| 309 |
+
reading_header_comments = False
|
| 310 |
+
continue
|
| 311 |
+
|
| 312 |
+
s = str(bytes_mv[:bytes_read], "latin-1")
|
| 313 |
+
if not _read_comment(s):
|
| 314 |
+
m = field.match(s)
|
| 315 |
+
if m:
|
| 316 |
+
k = m.group(1)
|
| 317 |
+
if k[:8] == "PS-Adobe":
|
| 318 |
+
self.info["PS-Adobe"] = k[9:]
|
| 319 |
+
else:
|
| 320 |
+
self.info[k] = ""
|
| 321 |
+
elif s[0] == "%":
|
| 322 |
+
# handle non-DSC PostScript comments that some
|
| 323 |
+
# tools mistakenly put in the Comments section
|
| 324 |
+
pass
|
| 325 |
+
else:
|
| 326 |
+
msg = "bad EPS header"
|
| 327 |
+
raise OSError(msg)
|
| 328 |
+
elif bytes_mv[:11] == b"%ImageData:":
|
| 329 |
+
# Check for an "ImageData" descriptor
|
| 330 |
+
# https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577413_pgfId-1035096
|
| 331 |
+
|
| 332 |
+
# Values:
|
| 333 |
+
# columns
|
| 334 |
+
# rows
|
| 335 |
+
# bit depth (1 or 8)
|
| 336 |
+
# mode (1: L, 2: LAB, 3: RGB, 4: CMYK)
|
| 337 |
+
# number of padding channels
|
| 338 |
+
# block size (number of bytes per row per channel)
|
| 339 |
+
# binary/ascii (1: binary, 2: ascii)
|
| 340 |
+
# data start identifier (the image data follows after a single line
|
| 341 |
+
# consisting only of this quoted value)
|
| 342 |
+
image_data_values = byte_arr[11:bytes_read].split(None, 7)
|
| 343 |
+
columns, rows, bit_depth, mode_id = (
|
| 344 |
+
int(value) for value in image_data_values[:4]
|
| 345 |
+
)
|
| 346 |
+
|
| 347 |
+
if bit_depth == 1:
|
| 348 |
+
self._mode = "1"
|
| 349 |
+
elif bit_depth == 8:
|
| 350 |
+
try:
|
| 351 |
+
self._mode = self.mode_map[mode_id]
|
| 352 |
+
except ValueError:
|
| 353 |
+
break
|
| 354 |
+
else:
|
| 355 |
+
break
|
| 356 |
+
|
| 357 |
+
self._size = columns, rows
|
| 358 |
+
return
|
| 359 |
+
elif bytes_mv[:5] == b"%%EOF":
|
| 360 |
+
break
|
| 361 |
+
elif trailer_reached and reading_trailer_comments:
|
| 362 |
+
# Load EPS trailer
|
| 363 |
+
s = str(bytes_mv[:bytes_read], "latin-1")
|
| 364 |
+
_read_comment(s)
|
| 365 |
+
elif bytes_mv[:9] == b"%%Trailer":
|
| 366 |
+
trailer_reached = True
|
| 367 |
+
bytes_read = 0
|
| 368 |
+
|
| 369 |
+
check_required_header_comments()
|
| 370 |
+
|
| 371 |
+
if not self._size:
|
| 372 |
+
msg = "cannot determine EPS bounding box"
|
| 373 |
+
raise OSError(msg)
|
| 374 |
+
|
| 375 |
+
def _find_offset(self, fp):
|
| 376 |
+
s = fp.read(4)
|
| 377 |
+
|
| 378 |
+
if s == b"%!PS":
|
| 379 |
+
# for HEAD without binary preview
|
| 380 |
+
fp.seek(0, io.SEEK_END)
|
| 381 |
+
length = fp.tell()
|
| 382 |
+
offset = 0
|
| 383 |
+
elif i32(s) == 0xC6D3D0C5:
|
| 384 |
+
# FIX for: Some EPS file not handled correctly / issue #302
|
| 385 |
+
# EPS can contain binary data
|
| 386 |
+
# or start directly with latin coding
|
| 387 |
+
# more info see:
|
| 388 |
+
# https://web.archive.org/web/20160528181353/http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
|
| 389 |
+
s = fp.read(8)
|
| 390 |
+
offset = i32(s)
|
| 391 |
+
length = i32(s, 4)
|
| 392 |
+
else:
|
| 393 |
+
msg = "not an EPS file"
|
| 394 |
+
raise SyntaxError(msg)
|
| 395 |
+
|
| 396 |
+
return length, offset
|
| 397 |
+
|
| 398 |
+
def load(self, scale=1, transparency=False):
|
| 399 |
+
# Load EPS via Ghostscript
|
| 400 |
+
if self.tile:
|
| 401 |
+
self.im = Ghostscript(self.tile, self.size, self.fp, scale, transparency)
|
| 402 |
+
self._mode = self.im.mode
|
| 403 |
+
self._size = self.im.size
|
| 404 |
+
self.tile = []
|
| 405 |
+
return Image.Image.load(self)
|
| 406 |
+
|
| 407 |
+
def load_seek(self, pos):
|
| 408 |
+
# we can't incrementally load, so force ImageFile.parser to
|
| 409 |
+
# use our custom load method by defining this method.
|
| 410 |
+
pass
|
| 411 |
+
|
| 412 |
+
|
| 413 |
+
# --------------------------------------------------------------------
|
| 414 |
+
|
| 415 |
+
|
| 416 |
+
def _save(im, fp, filename, eps=1):
|
| 417 |
+
"""EPS Writer for the Python Imaging Library."""
|
| 418 |
+
|
| 419 |
+
# make sure image data is available
|
| 420 |
+
im.load()
|
| 421 |
+
|
| 422 |
+
# determine PostScript image mode
|
| 423 |
+
if im.mode == "L":
|
| 424 |
+
operator = (8, 1, b"image")
|
| 425 |
+
elif im.mode == "RGB":
|
| 426 |
+
operator = (8, 3, b"false 3 colorimage")
|
| 427 |
+
elif im.mode == "CMYK":
|
| 428 |
+
operator = (8, 4, b"false 4 colorimage")
|
| 429 |
+
else:
|
| 430 |
+
msg = "image mode is not supported"
|
| 431 |
+
raise ValueError(msg)
|
| 432 |
+
|
| 433 |
+
if eps:
|
| 434 |
+
# write EPS header
|
| 435 |
+
fp.write(b"%!PS-Adobe-3.0 EPSF-3.0\n")
|
| 436 |
+
fp.write(b"%%Creator: PIL 0.1 EpsEncode\n")
|
| 437 |
+
# fp.write("%%CreationDate: %s"...)
|
| 438 |
+
fp.write(b"%%%%BoundingBox: 0 0 %d %d\n" % im.size)
|
| 439 |
+
fp.write(b"%%Pages: 1\n")
|
| 440 |
+
fp.write(b"%%EndComments\n")
|
| 441 |
+
fp.write(b"%%Page: 1 1\n")
|
| 442 |
+
fp.write(b"%%ImageData: %d %d " % im.size)
|
| 443 |
+
fp.write(b'%d %d 0 1 1 "%s"\n' % operator)
|
| 444 |
+
|
| 445 |
+
# image header
|
| 446 |
+
fp.write(b"gsave\n")
|
| 447 |
+
fp.write(b"10 dict begin\n")
|
| 448 |
+
fp.write(b"/buf %d string def\n" % (im.size[0] * operator[1]))
|
| 449 |
+
fp.write(b"%d %d scale\n" % im.size)
|
| 450 |
+
fp.write(b"%d %d 8\n" % im.size) # <= bits
|
| 451 |
+
fp.write(b"[%d 0 0 -%d 0 %d]\n" % (im.size[0], im.size[1], im.size[1]))
|
| 452 |
+
fp.write(b"{ currentfile buf readhexstring pop } bind\n")
|
| 453 |
+
fp.write(operator[2] + b"\n")
|
| 454 |
+
if hasattr(fp, "flush"):
|
| 455 |
+
fp.flush()
|
| 456 |
+
|
| 457 |
+
ImageFile._save(im, fp, [("eps", (0, 0) + im.size, 0, None)])
|
| 458 |
+
|
| 459 |
+
fp.write(b"\n%%%%EndBinary\n")
|
| 460 |
+
fp.write(b"grestore end\n")
|
| 461 |
+
if hasattr(fp, "flush"):
|
| 462 |
+
fp.flush()
|
| 463 |
+
|
| 464 |
+
|
| 465 |
+
# --------------------------------------------------------------------
|
| 466 |
+
|
| 467 |
+
|
| 468 |
+
Image.register_open(EpsImageFile.format, EpsImageFile, _accept)
|
| 469 |
+
|
| 470 |
+
Image.register_save(EpsImageFile.format, _save)
|
| 471 |
+
|
| 472 |
+
Image.register_extensions(EpsImageFile.format, [".ps", ".eps"])
|
| 473 |
+
|
| 474 |
+
Image.register_mime(EpsImageFile.format, "application/postscript")
|
venv/Lib/site-packages/PIL/ExifTags.py
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# EXIF tags
|
| 6 |
+
#
|
| 7 |
+
# Copyright (c) 2003 by Secret Labs AB
|
| 8 |
+
#
|
| 9 |
+
# See the README file for information on usage and redistribution.
|
| 10 |
+
#
|
| 11 |
+
|
| 12 |
+
"""
|
| 13 |
+
This module provides constants and clear-text names for various
|
| 14 |
+
well-known EXIF tags.
|
| 15 |
+
"""
|
| 16 |
+
from __future__ import annotations
|
| 17 |
+
|
| 18 |
+
from enum import IntEnum
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class Base(IntEnum):
|
| 22 |
+
# possibly incomplete
|
| 23 |
+
InteropIndex = 0x0001
|
| 24 |
+
ProcessingSoftware = 0x000B
|
| 25 |
+
NewSubfileType = 0x00FE
|
| 26 |
+
SubfileType = 0x00FF
|
| 27 |
+
ImageWidth = 0x0100
|
| 28 |
+
ImageLength = 0x0101
|
| 29 |
+
BitsPerSample = 0x0102
|
| 30 |
+
Compression = 0x0103
|
| 31 |
+
PhotometricInterpretation = 0x0106
|
| 32 |
+
Thresholding = 0x0107
|
| 33 |
+
CellWidth = 0x0108
|
| 34 |
+
CellLength = 0x0109
|
| 35 |
+
FillOrder = 0x010A
|
| 36 |
+
DocumentName = 0x010D
|
| 37 |
+
ImageDescription = 0x010E
|
| 38 |
+
Make = 0x010F
|
| 39 |
+
Model = 0x0110
|
| 40 |
+
StripOffsets = 0x0111
|
| 41 |
+
Orientation = 0x0112
|
| 42 |
+
SamplesPerPixel = 0x0115
|
| 43 |
+
RowsPerStrip = 0x0116
|
| 44 |
+
StripByteCounts = 0x0117
|
| 45 |
+
MinSampleValue = 0x0118
|
| 46 |
+
MaxSampleValue = 0x0119
|
| 47 |
+
XResolution = 0x011A
|
| 48 |
+
YResolution = 0x011B
|
| 49 |
+
PlanarConfiguration = 0x011C
|
| 50 |
+
PageName = 0x011D
|
| 51 |
+
FreeOffsets = 0x0120
|
| 52 |
+
FreeByteCounts = 0x0121
|
| 53 |
+
GrayResponseUnit = 0x0122
|
| 54 |
+
GrayResponseCurve = 0x0123
|
| 55 |
+
T4Options = 0x0124
|
| 56 |
+
T6Options = 0x0125
|
| 57 |
+
ResolutionUnit = 0x0128
|
| 58 |
+
PageNumber = 0x0129
|
| 59 |
+
TransferFunction = 0x012D
|
| 60 |
+
Software = 0x0131
|
| 61 |
+
DateTime = 0x0132
|
| 62 |
+
Artist = 0x013B
|
| 63 |
+
HostComputer = 0x013C
|
| 64 |
+
Predictor = 0x013D
|
| 65 |
+
WhitePoint = 0x013E
|
| 66 |
+
PrimaryChromaticities = 0x013F
|
| 67 |
+
ColorMap = 0x0140
|
| 68 |
+
HalftoneHints = 0x0141
|
| 69 |
+
TileWidth = 0x0142
|
| 70 |
+
TileLength = 0x0143
|
| 71 |
+
TileOffsets = 0x0144
|
| 72 |
+
TileByteCounts = 0x0145
|
| 73 |
+
SubIFDs = 0x014A
|
| 74 |
+
InkSet = 0x014C
|
| 75 |
+
InkNames = 0x014D
|
| 76 |
+
NumberOfInks = 0x014E
|
| 77 |
+
DotRange = 0x0150
|
| 78 |
+
TargetPrinter = 0x0151
|
| 79 |
+
ExtraSamples = 0x0152
|
| 80 |
+
SampleFormat = 0x0153
|
| 81 |
+
SMinSampleValue = 0x0154
|
| 82 |
+
SMaxSampleValue = 0x0155
|
| 83 |
+
TransferRange = 0x0156
|
| 84 |
+
ClipPath = 0x0157
|
| 85 |
+
XClipPathUnits = 0x0158
|
| 86 |
+
YClipPathUnits = 0x0159
|
| 87 |
+
Indexed = 0x015A
|
| 88 |
+
JPEGTables = 0x015B
|
| 89 |
+
OPIProxy = 0x015F
|
| 90 |
+
JPEGProc = 0x0200
|
| 91 |
+
JpegIFOffset = 0x0201
|
| 92 |
+
JpegIFByteCount = 0x0202
|
| 93 |
+
JpegRestartInterval = 0x0203
|
| 94 |
+
JpegLosslessPredictors = 0x0205
|
| 95 |
+
JpegPointTransforms = 0x0206
|
| 96 |
+
JpegQTables = 0x0207
|
| 97 |
+
JpegDCTables = 0x0208
|
| 98 |
+
JpegACTables = 0x0209
|
| 99 |
+
YCbCrCoefficients = 0x0211
|
| 100 |
+
YCbCrSubSampling = 0x0212
|
| 101 |
+
YCbCrPositioning = 0x0213
|
| 102 |
+
ReferenceBlackWhite = 0x0214
|
| 103 |
+
XMLPacket = 0x02BC
|
| 104 |
+
RelatedImageFileFormat = 0x1000
|
| 105 |
+
RelatedImageWidth = 0x1001
|
| 106 |
+
RelatedImageLength = 0x1002
|
| 107 |
+
Rating = 0x4746
|
| 108 |
+
RatingPercent = 0x4749
|
| 109 |
+
ImageID = 0x800D
|
| 110 |
+
CFARepeatPatternDim = 0x828D
|
| 111 |
+
BatteryLevel = 0x828F
|
| 112 |
+
Copyright = 0x8298
|
| 113 |
+
ExposureTime = 0x829A
|
| 114 |
+
FNumber = 0x829D
|
| 115 |
+
IPTCNAA = 0x83BB
|
| 116 |
+
ImageResources = 0x8649
|
| 117 |
+
ExifOffset = 0x8769
|
| 118 |
+
InterColorProfile = 0x8773
|
| 119 |
+
ExposureProgram = 0x8822
|
| 120 |
+
SpectralSensitivity = 0x8824
|
| 121 |
+
GPSInfo = 0x8825
|
| 122 |
+
ISOSpeedRatings = 0x8827
|
| 123 |
+
OECF = 0x8828
|
| 124 |
+
Interlace = 0x8829
|
| 125 |
+
TimeZoneOffset = 0x882A
|
| 126 |
+
SelfTimerMode = 0x882B
|
| 127 |
+
SensitivityType = 0x8830
|
| 128 |
+
StandardOutputSensitivity = 0x8831
|
| 129 |
+
RecommendedExposureIndex = 0x8832
|
| 130 |
+
ISOSpeed = 0x8833
|
| 131 |
+
ISOSpeedLatitudeyyy = 0x8834
|
| 132 |
+
ISOSpeedLatitudezzz = 0x8835
|
| 133 |
+
ExifVersion = 0x9000
|
| 134 |
+
DateTimeOriginal = 0x9003
|
| 135 |
+
DateTimeDigitized = 0x9004
|
| 136 |
+
OffsetTime = 0x9010
|
| 137 |
+
OffsetTimeOriginal = 0x9011
|
| 138 |
+
OffsetTimeDigitized = 0x9012
|
| 139 |
+
ComponentsConfiguration = 0x9101
|
| 140 |
+
CompressedBitsPerPixel = 0x9102
|
| 141 |
+
ShutterSpeedValue = 0x9201
|
| 142 |
+
ApertureValue = 0x9202
|
| 143 |
+
BrightnessValue = 0x9203
|
| 144 |
+
ExposureBiasValue = 0x9204
|
| 145 |
+
MaxApertureValue = 0x9205
|
| 146 |
+
SubjectDistance = 0x9206
|
| 147 |
+
MeteringMode = 0x9207
|
| 148 |
+
LightSource = 0x9208
|
| 149 |
+
Flash = 0x9209
|
| 150 |
+
FocalLength = 0x920A
|
| 151 |
+
Noise = 0x920D
|
| 152 |
+
ImageNumber = 0x9211
|
| 153 |
+
SecurityClassification = 0x9212
|
| 154 |
+
ImageHistory = 0x9213
|
| 155 |
+
TIFFEPStandardID = 0x9216
|
| 156 |
+
MakerNote = 0x927C
|
| 157 |
+
UserComment = 0x9286
|
| 158 |
+
SubsecTime = 0x9290
|
| 159 |
+
SubsecTimeOriginal = 0x9291
|
| 160 |
+
SubsecTimeDigitized = 0x9292
|
| 161 |
+
AmbientTemperature = 0x9400
|
| 162 |
+
Humidity = 0x9401
|
| 163 |
+
Pressure = 0x9402
|
| 164 |
+
WaterDepth = 0x9403
|
| 165 |
+
Acceleration = 0x9404
|
| 166 |
+
CameraElevationAngle = 0x9405
|
| 167 |
+
XPTitle = 0x9C9B
|
| 168 |
+
XPComment = 0x9C9C
|
| 169 |
+
XPAuthor = 0x9C9D
|
| 170 |
+
XPKeywords = 0x9C9E
|
| 171 |
+
XPSubject = 0x9C9F
|
| 172 |
+
FlashPixVersion = 0xA000
|
| 173 |
+
ColorSpace = 0xA001
|
| 174 |
+
ExifImageWidth = 0xA002
|
| 175 |
+
ExifImageHeight = 0xA003
|
| 176 |
+
RelatedSoundFile = 0xA004
|
| 177 |
+
ExifInteroperabilityOffset = 0xA005
|
| 178 |
+
FlashEnergy = 0xA20B
|
| 179 |
+
SpatialFrequencyResponse = 0xA20C
|
| 180 |
+
FocalPlaneXResolution = 0xA20E
|
| 181 |
+
FocalPlaneYResolution = 0xA20F
|
| 182 |
+
FocalPlaneResolutionUnit = 0xA210
|
| 183 |
+
SubjectLocation = 0xA214
|
| 184 |
+
ExposureIndex = 0xA215
|
| 185 |
+
SensingMethod = 0xA217
|
| 186 |
+
FileSource = 0xA300
|
| 187 |
+
SceneType = 0xA301
|
| 188 |
+
CFAPattern = 0xA302
|
| 189 |
+
CustomRendered = 0xA401
|
| 190 |
+
ExposureMode = 0xA402
|
| 191 |
+
WhiteBalance = 0xA403
|
| 192 |
+
DigitalZoomRatio = 0xA404
|
| 193 |
+
FocalLengthIn35mmFilm = 0xA405
|
| 194 |
+
SceneCaptureType = 0xA406
|
| 195 |
+
GainControl = 0xA407
|
| 196 |
+
Contrast = 0xA408
|
| 197 |
+
Saturation = 0xA409
|
| 198 |
+
Sharpness = 0xA40A
|
| 199 |
+
DeviceSettingDescription = 0xA40B
|
| 200 |
+
SubjectDistanceRange = 0xA40C
|
| 201 |
+
ImageUniqueID = 0xA420
|
| 202 |
+
CameraOwnerName = 0xA430
|
| 203 |
+
BodySerialNumber = 0xA431
|
| 204 |
+
LensSpecification = 0xA432
|
| 205 |
+
LensMake = 0xA433
|
| 206 |
+
LensModel = 0xA434
|
| 207 |
+
LensSerialNumber = 0xA435
|
| 208 |
+
CompositeImage = 0xA460
|
| 209 |
+
CompositeImageCount = 0xA461
|
| 210 |
+
CompositeImageExposureTimes = 0xA462
|
| 211 |
+
Gamma = 0xA500
|
| 212 |
+
PrintImageMatching = 0xC4A5
|
| 213 |
+
DNGVersion = 0xC612
|
| 214 |
+
DNGBackwardVersion = 0xC613
|
| 215 |
+
UniqueCameraModel = 0xC614
|
| 216 |
+
LocalizedCameraModel = 0xC615
|
| 217 |
+
CFAPlaneColor = 0xC616
|
| 218 |
+
CFALayout = 0xC617
|
| 219 |
+
LinearizationTable = 0xC618
|
| 220 |
+
BlackLevelRepeatDim = 0xC619
|
| 221 |
+
BlackLevel = 0xC61A
|
| 222 |
+
BlackLevelDeltaH = 0xC61B
|
| 223 |
+
BlackLevelDeltaV = 0xC61C
|
| 224 |
+
WhiteLevel = 0xC61D
|
| 225 |
+
DefaultScale = 0xC61E
|
| 226 |
+
DefaultCropOrigin = 0xC61F
|
| 227 |
+
DefaultCropSize = 0xC620
|
| 228 |
+
ColorMatrix1 = 0xC621
|
| 229 |
+
ColorMatrix2 = 0xC622
|
| 230 |
+
CameraCalibration1 = 0xC623
|
| 231 |
+
CameraCalibration2 = 0xC624
|
| 232 |
+
ReductionMatrix1 = 0xC625
|
| 233 |
+
ReductionMatrix2 = 0xC626
|
| 234 |
+
AnalogBalance = 0xC627
|
| 235 |
+
AsShotNeutral = 0xC628
|
| 236 |
+
AsShotWhiteXY = 0xC629
|
| 237 |
+
BaselineExposure = 0xC62A
|
| 238 |
+
BaselineNoise = 0xC62B
|
| 239 |
+
BaselineSharpness = 0xC62C
|
| 240 |
+
BayerGreenSplit = 0xC62D
|
| 241 |
+
LinearResponseLimit = 0xC62E
|
| 242 |
+
CameraSerialNumber = 0xC62F
|
| 243 |
+
LensInfo = 0xC630
|
| 244 |
+
ChromaBlurRadius = 0xC631
|
| 245 |
+
AntiAliasStrength = 0xC632
|
| 246 |
+
ShadowScale = 0xC633
|
| 247 |
+
DNGPrivateData = 0xC634
|
| 248 |
+
MakerNoteSafety = 0xC635
|
| 249 |
+
CalibrationIlluminant1 = 0xC65A
|
| 250 |
+
CalibrationIlluminant2 = 0xC65B
|
| 251 |
+
BestQualityScale = 0xC65C
|
| 252 |
+
RawDataUniqueID = 0xC65D
|
| 253 |
+
OriginalRawFileName = 0xC68B
|
| 254 |
+
OriginalRawFileData = 0xC68C
|
| 255 |
+
ActiveArea = 0xC68D
|
| 256 |
+
MaskedAreas = 0xC68E
|
| 257 |
+
AsShotICCProfile = 0xC68F
|
| 258 |
+
AsShotPreProfileMatrix = 0xC690
|
| 259 |
+
CurrentICCProfile = 0xC691
|
| 260 |
+
CurrentPreProfileMatrix = 0xC692
|
| 261 |
+
ColorimetricReference = 0xC6BF
|
| 262 |
+
CameraCalibrationSignature = 0xC6F3
|
| 263 |
+
ProfileCalibrationSignature = 0xC6F4
|
| 264 |
+
AsShotProfileName = 0xC6F6
|
| 265 |
+
NoiseReductionApplied = 0xC6F7
|
| 266 |
+
ProfileName = 0xC6F8
|
| 267 |
+
ProfileHueSatMapDims = 0xC6F9
|
| 268 |
+
ProfileHueSatMapData1 = 0xC6FA
|
| 269 |
+
ProfileHueSatMapData2 = 0xC6FB
|
| 270 |
+
ProfileToneCurve = 0xC6FC
|
| 271 |
+
ProfileEmbedPolicy = 0xC6FD
|
| 272 |
+
ProfileCopyright = 0xC6FE
|
| 273 |
+
ForwardMatrix1 = 0xC714
|
| 274 |
+
ForwardMatrix2 = 0xC715
|
| 275 |
+
PreviewApplicationName = 0xC716
|
| 276 |
+
PreviewApplicationVersion = 0xC717
|
| 277 |
+
PreviewSettingsName = 0xC718
|
| 278 |
+
PreviewSettingsDigest = 0xC719
|
| 279 |
+
PreviewColorSpace = 0xC71A
|
| 280 |
+
PreviewDateTime = 0xC71B
|
| 281 |
+
RawImageDigest = 0xC71C
|
| 282 |
+
OriginalRawFileDigest = 0xC71D
|
| 283 |
+
SubTileBlockSize = 0xC71E
|
| 284 |
+
RowInterleaveFactor = 0xC71F
|
| 285 |
+
ProfileLookTableDims = 0xC725
|
| 286 |
+
ProfileLookTableData = 0xC726
|
| 287 |
+
OpcodeList1 = 0xC740
|
| 288 |
+
OpcodeList2 = 0xC741
|
| 289 |
+
OpcodeList3 = 0xC74E
|
| 290 |
+
NoiseProfile = 0xC761
|
| 291 |
+
|
| 292 |
+
|
| 293 |
+
"""Maps EXIF tags to tag names."""
|
| 294 |
+
TAGS = {
|
| 295 |
+
**{i.value: i.name for i in Base},
|
| 296 |
+
0x920C: "SpatialFrequencyResponse",
|
| 297 |
+
0x9214: "SubjectLocation",
|
| 298 |
+
0x9215: "ExposureIndex",
|
| 299 |
+
0x828E: "CFAPattern",
|
| 300 |
+
0x920B: "FlashEnergy",
|
| 301 |
+
0x9216: "TIFF/EPStandardID",
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
class GPS(IntEnum):
|
| 306 |
+
GPSVersionID = 0
|
| 307 |
+
GPSLatitudeRef = 1
|
| 308 |
+
GPSLatitude = 2
|
| 309 |
+
GPSLongitudeRef = 3
|
| 310 |
+
GPSLongitude = 4
|
| 311 |
+
GPSAltitudeRef = 5
|
| 312 |
+
GPSAltitude = 6
|
| 313 |
+
GPSTimeStamp = 7
|
| 314 |
+
GPSSatellites = 8
|
| 315 |
+
GPSStatus = 9
|
| 316 |
+
GPSMeasureMode = 10
|
| 317 |
+
GPSDOP = 11
|
| 318 |
+
GPSSpeedRef = 12
|
| 319 |
+
GPSSpeed = 13
|
| 320 |
+
GPSTrackRef = 14
|
| 321 |
+
GPSTrack = 15
|
| 322 |
+
GPSImgDirectionRef = 16
|
| 323 |
+
GPSImgDirection = 17
|
| 324 |
+
GPSMapDatum = 18
|
| 325 |
+
GPSDestLatitudeRef = 19
|
| 326 |
+
GPSDestLatitude = 20
|
| 327 |
+
GPSDestLongitudeRef = 21
|
| 328 |
+
GPSDestLongitude = 22
|
| 329 |
+
GPSDestBearingRef = 23
|
| 330 |
+
GPSDestBearing = 24
|
| 331 |
+
GPSDestDistanceRef = 25
|
| 332 |
+
GPSDestDistance = 26
|
| 333 |
+
GPSProcessingMethod = 27
|
| 334 |
+
GPSAreaInformation = 28
|
| 335 |
+
GPSDateStamp = 29
|
| 336 |
+
GPSDifferential = 30
|
| 337 |
+
GPSHPositioningError = 31
|
| 338 |
+
|
| 339 |
+
|
| 340 |
+
"""Maps EXIF GPS tags to tag names."""
|
| 341 |
+
GPSTAGS = {i.value: i.name for i in GPS}
|
| 342 |
+
|
| 343 |
+
|
| 344 |
+
class Interop(IntEnum):
|
| 345 |
+
InteropIndex = 1
|
| 346 |
+
InteropVersion = 2
|
| 347 |
+
RelatedImageFileFormat = 4096
|
| 348 |
+
RelatedImageWidth = 4097
|
| 349 |
+
RleatedImageHeight = 4098
|
| 350 |
+
|
| 351 |
+
|
| 352 |
+
class IFD(IntEnum):
|
| 353 |
+
Exif = 34665
|
| 354 |
+
GPSInfo = 34853
|
| 355 |
+
Makernote = 37500
|
| 356 |
+
Interop = 40965
|
| 357 |
+
IFD1 = -1
|
| 358 |
+
|
| 359 |
+
|
| 360 |
+
class LightSource(IntEnum):
|
| 361 |
+
Unknown = 0
|
| 362 |
+
Daylight = 1
|
| 363 |
+
Fluorescent = 2
|
| 364 |
+
Tungsten = 3
|
| 365 |
+
Flash = 4
|
| 366 |
+
Fine = 9
|
| 367 |
+
Cloudy = 10
|
| 368 |
+
Shade = 11
|
| 369 |
+
DaylightFluorescent = 12
|
| 370 |
+
DayWhiteFluorescent = 13
|
| 371 |
+
CoolWhiteFluorescent = 14
|
| 372 |
+
WhiteFluorescent = 15
|
| 373 |
+
StandardLightA = 17
|
| 374 |
+
StandardLightB = 18
|
| 375 |
+
StandardLightC = 19
|
| 376 |
+
D55 = 20
|
| 377 |
+
D65 = 21
|
| 378 |
+
D75 = 22
|
| 379 |
+
D50 = 23
|
| 380 |
+
ISO = 24
|
| 381 |
+
Other = 255
|
venv/Lib/site-packages/PIL/FitsImagePlugin.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# FITS file handling
|
| 6 |
+
#
|
| 7 |
+
# Copyright (c) 1998-2003 by Fredrik Lundh
|
| 8 |
+
#
|
| 9 |
+
# See the README file for information on usage and redistribution.
|
| 10 |
+
#
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
import gzip
|
| 14 |
+
import math
|
| 15 |
+
|
| 16 |
+
from . import Image, ImageFile
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def _accept(prefix: bytes) -> bool:
|
| 20 |
+
return prefix[:6] == b"SIMPLE"
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class FitsImageFile(ImageFile.ImageFile):
|
| 24 |
+
format = "FITS"
|
| 25 |
+
format_description = "FITS"
|
| 26 |
+
|
| 27 |
+
def _open(self) -> None:
|
| 28 |
+
assert self.fp is not None
|
| 29 |
+
|
| 30 |
+
headers: dict[bytes, bytes] = {}
|
| 31 |
+
header_in_progress = False
|
| 32 |
+
decoder_name = ""
|
| 33 |
+
while True:
|
| 34 |
+
header = self.fp.read(80)
|
| 35 |
+
if not header:
|
| 36 |
+
msg = "Truncated FITS file"
|
| 37 |
+
raise OSError(msg)
|
| 38 |
+
keyword = header[:8].strip()
|
| 39 |
+
if keyword in (b"SIMPLE", b"XTENSION"):
|
| 40 |
+
header_in_progress = True
|
| 41 |
+
elif headers and not header_in_progress:
|
| 42 |
+
# This is now a data unit
|
| 43 |
+
break
|
| 44 |
+
elif keyword == b"END":
|
| 45 |
+
# Seek to the end of the header unit
|
| 46 |
+
self.fp.seek(math.ceil(self.fp.tell() / 2880) * 2880)
|
| 47 |
+
if not decoder_name:
|
| 48 |
+
decoder_name, offset, args = self._parse_headers(headers)
|
| 49 |
+
|
| 50 |
+
header_in_progress = False
|
| 51 |
+
continue
|
| 52 |
+
|
| 53 |
+
if decoder_name:
|
| 54 |
+
# Keep going to read past the headers
|
| 55 |
+
continue
|
| 56 |
+
|
| 57 |
+
value = header[8:].split(b"/")[0].strip()
|
| 58 |
+
if value.startswith(b"="):
|
| 59 |
+
value = value[1:].strip()
|
| 60 |
+
if not headers and (not _accept(keyword) or value != b"T"):
|
| 61 |
+
msg = "Not a FITS file"
|
| 62 |
+
raise SyntaxError(msg)
|
| 63 |
+
headers[keyword] = value
|
| 64 |
+
|
| 65 |
+
if not decoder_name:
|
| 66 |
+
msg = "No image data"
|
| 67 |
+
raise ValueError(msg)
|
| 68 |
+
|
| 69 |
+
offset += self.fp.tell() - 80
|
| 70 |
+
self.tile = [(decoder_name, (0, 0) + self.size, offset, args)]
|
| 71 |
+
|
| 72 |
+
def _get_size(
|
| 73 |
+
self, headers: dict[bytes, bytes], prefix: bytes
|
| 74 |
+
) -> tuple[int, int] | None:
|
| 75 |
+
naxis = int(headers[prefix + b"NAXIS"])
|
| 76 |
+
if naxis == 0:
|
| 77 |
+
return None
|
| 78 |
+
|
| 79 |
+
if naxis == 1:
|
| 80 |
+
return 1, int(headers[prefix + b"NAXIS1"])
|
| 81 |
+
else:
|
| 82 |
+
return int(headers[prefix + b"NAXIS1"]), int(headers[prefix + b"NAXIS2"])
|
| 83 |
+
|
| 84 |
+
def _parse_headers(
|
| 85 |
+
self, headers: dict[bytes, bytes]
|
| 86 |
+
) -> tuple[str, int, tuple[str | int, ...]]:
|
| 87 |
+
prefix = b""
|
| 88 |
+
decoder_name = "raw"
|
| 89 |
+
offset = 0
|
| 90 |
+
if (
|
| 91 |
+
headers.get(b"XTENSION") == b"'BINTABLE'"
|
| 92 |
+
and headers.get(b"ZIMAGE") == b"T"
|
| 93 |
+
and headers[b"ZCMPTYPE"] == b"'GZIP_1 '"
|
| 94 |
+
):
|
| 95 |
+
no_prefix_size = self._get_size(headers, prefix) or (0, 0)
|
| 96 |
+
number_of_bits = int(headers[b"BITPIX"])
|
| 97 |
+
offset = no_prefix_size[0] * no_prefix_size[1] * (number_of_bits // 8)
|
| 98 |
+
|
| 99 |
+
prefix = b"Z"
|
| 100 |
+
decoder_name = "fits_gzip"
|
| 101 |
+
|
| 102 |
+
size = self._get_size(headers, prefix)
|
| 103 |
+
if not size:
|
| 104 |
+
return "", 0, ()
|
| 105 |
+
|
| 106 |
+
self._size = size
|
| 107 |
+
|
| 108 |
+
number_of_bits = int(headers[prefix + b"BITPIX"])
|
| 109 |
+
if number_of_bits == 8:
|
| 110 |
+
self._mode = "L"
|
| 111 |
+
elif number_of_bits == 16:
|
| 112 |
+
self._mode = "I;16"
|
| 113 |
+
elif number_of_bits == 32:
|
| 114 |
+
self._mode = "I"
|
| 115 |
+
elif number_of_bits in (-32, -64):
|
| 116 |
+
self._mode = "F"
|
| 117 |
+
|
| 118 |
+
args = (self.mode, 0, -1) if decoder_name == "raw" else (number_of_bits,)
|
| 119 |
+
return decoder_name, offset, args
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
class FitsGzipDecoder(ImageFile.PyDecoder):
|
| 123 |
+
_pulls_fd = True
|
| 124 |
+
|
| 125 |
+
def decode(self, buffer):
|
| 126 |
+
assert self.fd is not None
|
| 127 |
+
value = gzip.decompress(self.fd.read())
|
| 128 |
+
|
| 129 |
+
rows = []
|
| 130 |
+
offset = 0
|
| 131 |
+
number_of_bits = min(self.args[0] // 8, 4)
|
| 132 |
+
for y in range(self.state.ysize):
|
| 133 |
+
row = bytearray()
|
| 134 |
+
for x in range(self.state.xsize):
|
| 135 |
+
row += value[offset + (4 - number_of_bits) : offset + 4]
|
| 136 |
+
offset += 4
|
| 137 |
+
rows.append(row)
|
| 138 |
+
self.set_as_raw(bytes([pixel for row in rows[::-1] for pixel in row]))
|
| 139 |
+
return -1, 0
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
# --------------------------------------------------------------------
|
| 143 |
+
# Registry
|
| 144 |
+
|
| 145 |
+
Image.register_open(FitsImageFile.format, FitsImageFile, _accept)
|
| 146 |
+
Image.register_decoder("fits_gzip", FitsGzipDecoder)
|
| 147 |
+
|
| 148 |
+
Image.register_extensions(FitsImageFile.format, [".fit", ".fits"])
|
venv/Lib/site-packages/PIL/FliImagePlugin.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# FLI/FLC file handling.
|
| 6 |
+
#
|
| 7 |
+
# History:
|
| 8 |
+
# 95-09-01 fl Created
|
| 9 |
+
# 97-01-03 fl Fixed parser, setup decoder tile
|
| 10 |
+
# 98-07-15 fl Renamed offset attribute to avoid name clash
|
| 11 |
+
#
|
| 12 |
+
# Copyright (c) Secret Labs AB 1997-98.
|
| 13 |
+
# Copyright (c) Fredrik Lundh 1995-97.
|
| 14 |
+
#
|
| 15 |
+
# See the README file for information on usage and redistribution.
|
| 16 |
+
#
|
| 17 |
+
from __future__ import annotations
|
| 18 |
+
|
| 19 |
+
import os
|
| 20 |
+
|
| 21 |
+
from . import Image, ImageFile, ImagePalette
|
| 22 |
+
from ._binary import i16le as i16
|
| 23 |
+
from ._binary import i32le as i32
|
| 24 |
+
from ._binary import o8
|
| 25 |
+
|
| 26 |
+
#
|
| 27 |
+
# decoder
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def _accept(prefix):
|
| 31 |
+
return (
|
| 32 |
+
len(prefix) >= 6
|
| 33 |
+
and i16(prefix, 4) in [0xAF11, 0xAF12]
|
| 34 |
+
and i16(prefix, 14) in [0, 3] # flags
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
##
|
| 39 |
+
# Image plugin for the FLI/FLC animation format. Use the <b>seek</b>
|
| 40 |
+
# method to load individual frames.
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class FliImageFile(ImageFile.ImageFile):
|
| 44 |
+
format = "FLI"
|
| 45 |
+
format_description = "Autodesk FLI/FLC Animation"
|
| 46 |
+
_close_exclusive_fp_after_loading = False
|
| 47 |
+
|
| 48 |
+
def _open(self):
|
| 49 |
+
# HEAD
|
| 50 |
+
s = self.fp.read(128)
|
| 51 |
+
if not (_accept(s) and s[20:22] == b"\x00\x00"):
|
| 52 |
+
msg = "not an FLI/FLC file"
|
| 53 |
+
raise SyntaxError(msg)
|
| 54 |
+
|
| 55 |
+
# frames
|
| 56 |
+
self.n_frames = i16(s, 6)
|
| 57 |
+
self.is_animated = self.n_frames > 1
|
| 58 |
+
|
| 59 |
+
# image characteristics
|
| 60 |
+
self._mode = "P"
|
| 61 |
+
self._size = i16(s, 8), i16(s, 10)
|
| 62 |
+
|
| 63 |
+
# animation speed
|
| 64 |
+
duration = i32(s, 16)
|
| 65 |
+
magic = i16(s, 4)
|
| 66 |
+
if magic == 0xAF11:
|
| 67 |
+
duration = (duration * 1000) // 70
|
| 68 |
+
self.info["duration"] = duration
|
| 69 |
+
|
| 70 |
+
# look for palette
|
| 71 |
+
palette = [(a, a, a) for a in range(256)]
|
| 72 |
+
|
| 73 |
+
s = self.fp.read(16)
|
| 74 |
+
|
| 75 |
+
self.__offset = 128
|
| 76 |
+
|
| 77 |
+
if i16(s, 4) == 0xF100:
|
| 78 |
+
# prefix chunk; ignore it
|
| 79 |
+
self.__offset = self.__offset + i32(s)
|
| 80 |
+
self.fp.seek(self.__offset)
|
| 81 |
+
s = self.fp.read(16)
|
| 82 |
+
|
| 83 |
+
if i16(s, 4) == 0xF1FA:
|
| 84 |
+
# look for palette chunk
|
| 85 |
+
number_of_subchunks = i16(s, 6)
|
| 86 |
+
chunk_size = None
|
| 87 |
+
for _ in range(number_of_subchunks):
|
| 88 |
+
if chunk_size is not None:
|
| 89 |
+
self.fp.seek(chunk_size - 6, os.SEEK_CUR)
|
| 90 |
+
s = self.fp.read(6)
|
| 91 |
+
chunk_type = i16(s, 4)
|
| 92 |
+
if chunk_type in (4, 11):
|
| 93 |
+
self._palette(palette, 2 if chunk_type == 11 else 0)
|
| 94 |
+
break
|
| 95 |
+
chunk_size = i32(s)
|
| 96 |
+
if not chunk_size:
|
| 97 |
+
break
|
| 98 |
+
|
| 99 |
+
palette = [o8(r) + o8(g) + o8(b) for (r, g, b) in palette]
|
| 100 |
+
self.palette = ImagePalette.raw("RGB", b"".join(palette))
|
| 101 |
+
|
| 102 |
+
# set things up to decode first frame
|
| 103 |
+
self.__frame = -1
|
| 104 |
+
self._fp = self.fp
|
| 105 |
+
self.__rewind = self.fp.tell()
|
| 106 |
+
self.seek(0)
|
| 107 |
+
|
| 108 |
+
def _palette(self, palette, shift):
|
| 109 |
+
# load palette
|
| 110 |
+
|
| 111 |
+
i = 0
|
| 112 |
+
for e in range(i16(self.fp.read(2))):
|
| 113 |
+
s = self.fp.read(2)
|
| 114 |
+
i = i + s[0]
|
| 115 |
+
n = s[1]
|
| 116 |
+
if n == 0:
|
| 117 |
+
n = 256
|
| 118 |
+
s = self.fp.read(n * 3)
|
| 119 |
+
for n in range(0, len(s), 3):
|
| 120 |
+
r = s[n] << shift
|
| 121 |
+
g = s[n + 1] << shift
|
| 122 |
+
b = s[n + 2] << shift
|
| 123 |
+
palette[i] = (r, g, b)
|
| 124 |
+
i += 1
|
| 125 |
+
|
| 126 |
+
def seek(self, frame):
|
| 127 |
+
if not self._seek_check(frame):
|
| 128 |
+
return
|
| 129 |
+
if frame < self.__frame:
|
| 130 |
+
self._seek(0)
|
| 131 |
+
|
| 132 |
+
for f in range(self.__frame + 1, frame + 1):
|
| 133 |
+
self._seek(f)
|
| 134 |
+
|
| 135 |
+
def _seek(self, frame):
|
| 136 |
+
if frame == 0:
|
| 137 |
+
self.__frame = -1
|
| 138 |
+
self._fp.seek(self.__rewind)
|
| 139 |
+
self.__offset = 128
|
| 140 |
+
else:
|
| 141 |
+
# ensure that the previous frame was loaded
|
| 142 |
+
self.load()
|
| 143 |
+
|
| 144 |
+
if frame != self.__frame + 1:
|
| 145 |
+
msg = f"cannot seek to frame {frame}"
|
| 146 |
+
raise ValueError(msg)
|
| 147 |
+
self.__frame = frame
|
| 148 |
+
|
| 149 |
+
# move to next frame
|
| 150 |
+
self.fp = self._fp
|
| 151 |
+
self.fp.seek(self.__offset)
|
| 152 |
+
|
| 153 |
+
s = self.fp.read(4)
|
| 154 |
+
if not s:
|
| 155 |
+
msg = "missing frame size"
|
| 156 |
+
raise EOFError(msg)
|
| 157 |
+
|
| 158 |
+
framesize = i32(s)
|
| 159 |
+
|
| 160 |
+
self.decodermaxblock = framesize
|
| 161 |
+
self.tile = [("fli", (0, 0) + self.size, self.__offset, None)]
|
| 162 |
+
|
| 163 |
+
self.__offset += framesize
|
| 164 |
+
|
| 165 |
+
def tell(self):
|
| 166 |
+
return self.__frame
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
#
|
| 170 |
+
# registry
|
| 171 |
+
|
| 172 |
+
Image.register_open(FliImageFile.format, FliImageFile, _accept)
|
| 173 |
+
|
| 174 |
+
Image.register_extensions(FliImageFile.format, [".fli", ".flc"])
|
venv/Lib/site-packages/PIL/FontFile.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# base class for raster font file parsers
|
| 6 |
+
#
|
| 7 |
+
# history:
|
| 8 |
+
# 1997-06-05 fl created
|
| 9 |
+
# 1997-08-19 fl restrict image width
|
| 10 |
+
#
|
| 11 |
+
# Copyright (c) 1997-1998 by Secret Labs AB
|
| 12 |
+
# Copyright (c) 1997-1998 by Fredrik Lundh
|
| 13 |
+
#
|
| 14 |
+
# See the README file for information on usage and redistribution.
|
| 15 |
+
#
|
| 16 |
+
from __future__ import annotations
|
| 17 |
+
|
| 18 |
+
import os
|
| 19 |
+
from typing import BinaryIO
|
| 20 |
+
|
| 21 |
+
from . import Image, _binary
|
| 22 |
+
|
| 23 |
+
WIDTH = 800
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def puti16(
|
| 27 |
+
fp: BinaryIO, values: tuple[int, int, int, int, int, int, int, int, int, int]
|
| 28 |
+
) -> None:
|
| 29 |
+
"""Write network order (big-endian) 16-bit sequence"""
|
| 30 |
+
for v in values:
|
| 31 |
+
if v < 0:
|
| 32 |
+
v += 65536
|
| 33 |
+
fp.write(_binary.o16be(v))
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class FontFile:
|
| 37 |
+
"""Base class for raster font file handlers."""
|
| 38 |
+
|
| 39 |
+
bitmap: Image.Image | None = None
|
| 40 |
+
|
| 41 |
+
def __init__(self) -> None:
|
| 42 |
+
self.info: dict[bytes, bytes | int] = {}
|
| 43 |
+
self.glyph: list[
|
| 44 |
+
tuple[
|
| 45 |
+
tuple[int, int],
|
| 46 |
+
tuple[int, int, int, int],
|
| 47 |
+
tuple[int, int, int, int],
|
| 48 |
+
Image.Image,
|
| 49 |
+
]
|
| 50 |
+
| None
|
| 51 |
+
] = [None] * 256
|
| 52 |
+
|
| 53 |
+
def __getitem__(self, ix: int) -> (
|
| 54 |
+
tuple[
|
| 55 |
+
tuple[int, int],
|
| 56 |
+
tuple[int, int, int, int],
|
| 57 |
+
tuple[int, int, int, int],
|
| 58 |
+
Image.Image,
|
| 59 |
+
]
|
| 60 |
+
| None
|
| 61 |
+
):
|
| 62 |
+
return self.glyph[ix]
|
| 63 |
+
|
| 64 |
+
def compile(self) -> None:
|
| 65 |
+
"""Create metrics and bitmap"""
|
| 66 |
+
|
| 67 |
+
if self.bitmap:
|
| 68 |
+
return
|
| 69 |
+
|
| 70 |
+
# create bitmap large enough to hold all data
|
| 71 |
+
h = w = maxwidth = 0
|
| 72 |
+
lines = 1
|
| 73 |
+
for glyph in self.glyph:
|
| 74 |
+
if glyph:
|
| 75 |
+
d, dst, src, im = glyph
|
| 76 |
+
h = max(h, src[3] - src[1])
|
| 77 |
+
w = w + (src[2] - src[0])
|
| 78 |
+
if w > WIDTH:
|
| 79 |
+
lines += 1
|
| 80 |
+
w = src[2] - src[0]
|
| 81 |
+
maxwidth = max(maxwidth, w)
|
| 82 |
+
|
| 83 |
+
xsize = maxwidth
|
| 84 |
+
ysize = lines * h
|
| 85 |
+
|
| 86 |
+
if xsize == 0 and ysize == 0:
|
| 87 |
+
return
|
| 88 |
+
|
| 89 |
+
self.ysize = h
|
| 90 |
+
|
| 91 |
+
# paste glyphs into bitmap
|
| 92 |
+
self.bitmap = Image.new("1", (xsize, ysize))
|
| 93 |
+
self.metrics: list[
|
| 94 |
+
tuple[tuple[int, int], tuple[int, int, int, int], tuple[int, int, int, int]]
|
| 95 |
+
| None
|
| 96 |
+
] = [None] * 256
|
| 97 |
+
x = y = 0
|
| 98 |
+
for i in range(256):
|
| 99 |
+
glyph = self[i]
|
| 100 |
+
if glyph:
|
| 101 |
+
d, dst, src, im = glyph
|
| 102 |
+
xx = src[2] - src[0]
|
| 103 |
+
x0, y0 = x, y
|
| 104 |
+
x = x + xx
|
| 105 |
+
if x > WIDTH:
|
| 106 |
+
x, y = 0, y + h
|
| 107 |
+
x0, y0 = x, y
|
| 108 |
+
x = xx
|
| 109 |
+
s = src[0] + x0, src[1] + y0, src[2] + x0, src[3] + y0
|
| 110 |
+
self.bitmap.paste(im.crop(src), s)
|
| 111 |
+
self.metrics[i] = d, dst, s
|
| 112 |
+
|
| 113 |
+
def save(self, filename: str) -> None:
|
| 114 |
+
"""Save font"""
|
| 115 |
+
|
| 116 |
+
self.compile()
|
| 117 |
+
|
| 118 |
+
# font data
|
| 119 |
+
if not self.bitmap:
|
| 120 |
+
msg = "No bitmap created"
|
| 121 |
+
raise ValueError(msg)
|
| 122 |
+
self.bitmap.save(os.path.splitext(filename)[0] + ".pbm", "PNG")
|
| 123 |
+
|
| 124 |
+
# font metrics
|
| 125 |
+
with open(os.path.splitext(filename)[0] + ".pil", "wb") as fp:
|
| 126 |
+
fp.write(b"PILfont\n")
|
| 127 |
+
fp.write(f";;;;;;{self.ysize};\n".encode("ascii")) # HACK!!!
|
| 128 |
+
fp.write(b"DATA\n")
|
| 129 |
+
for id in range(256):
|
| 130 |
+
m = self.metrics[id]
|
| 131 |
+
if not m:
|
| 132 |
+
puti16(fp, (0,) * 10)
|
| 133 |
+
else:
|
| 134 |
+
puti16(fp, m[0] + m[1] + m[2])
|
venv/Lib/site-packages/PIL/FpxImagePlugin.py
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# THIS IS WORK IN PROGRESS
|
| 3 |
+
#
|
| 4 |
+
# The Python Imaging Library.
|
| 5 |
+
# $Id$
|
| 6 |
+
#
|
| 7 |
+
# FlashPix support for PIL
|
| 8 |
+
#
|
| 9 |
+
# History:
|
| 10 |
+
# 97-01-25 fl Created (reads uncompressed RGB images only)
|
| 11 |
+
#
|
| 12 |
+
# Copyright (c) Secret Labs AB 1997.
|
| 13 |
+
# Copyright (c) Fredrik Lundh 1997.
|
| 14 |
+
#
|
| 15 |
+
# See the README file for information on usage and redistribution.
|
| 16 |
+
#
|
| 17 |
+
from __future__ import annotations
|
| 18 |
+
|
| 19 |
+
import olefile
|
| 20 |
+
|
| 21 |
+
from . import Image, ImageFile
|
| 22 |
+
from ._binary import i32le as i32
|
| 23 |
+
|
| 24 |
+
# we map from colour field tuples to (mode, rawmode) descriptors
|
| 25 |
+
MODES = {
|
| 26 |
+
# opacity
|
| 27 |
+
(0x00007FFE,): ("A", "L"),
|
| 28 |
+
# monochrome
|
| 29 |
+
(0x00010000,): ("L", "L"),
|
| 30 |
+
(0x00018000, 0x00017FFE): ("RGBA", "LA"),
|
| 31 |
+
# photo YCC
|
| 32 |
+
(0x00020000, 0x00020001, 0x00020002): ("RGB", "YCC;P"),
|
| 33 |
+
(0x00028000, 0x00028001, 0x00028002, 0x00027FFE): ("RGBA", "YCCA;P"),
|
| 34 |
+
# standard RGB (NIFRGB)
|
| 35 |
+
(0x00030000, 0x00030001, 0x00030002): ("RGB", "RGB"),
|
| 36 |
+
(0x00038000, 0x00038001, 0x00038002, 0x00037FFE): ("RGBA", "RGBA"),
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
#
|
| 41 |
+
# --------------------------------------------------------------------
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def _accept(prefix):
|
| 45 |
+
return prefix[:8] == olefile.MAGIC
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
##
|
| 49 |
+
# Image plugin for the FlashPix images.
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
class FpxImageFile(ImageFile.ImageFile):
|
| 53 |
+
format = "FPX"
|
| 54 |
+
format_description = "FlashPix"
|
| 55 |
+
|
| 56 |
+
def _open(self):
|
| 57 |
+
#
|
| 58 |
+
# read the OLE directory and see if this is a likely
|
| 59 |
+
# to be a FlashPix file
|
| 60 |
+
|
| 61 |
+
try:
|
| 62 |
+
self.ole = olefile.OleFileIO(self.fp)
|
| 63 |
+
except OSError as e:
|
| 64 |
+
msg = "not an FPX file; invalid OLE file"
|
| 65 |
+
raise SyntaxError(msg) from e
|
| 66 |
+
|
| 67 |
+
if self.ole.root.clsid != "56616700-C154-11CE-8553-00AA00A1F95B":
|
| 68 |
+
msg = "not an FPX file; bad root CLSID"
|
| 69 |
+
raise SyntaxError(msg)
|
| 70 |
+
|
| 71 |
+
self._open_index(1)
|
| 72 |
+
|
| 73 |
+
def _open_index(self, index=1):
|
| 74 |
+
#
|
| 75 |
+
# get the Image Contents Property Set
|
| 76 |
+
|
| 77 |
+
prop = self.ole.getproperties(
|
| 78 |
+
[f"Data Object Store {index:06d}", "\005Image Contents"]
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
# size (highest resolution)
|
| 82 |
+
|
| 83 |
+
self._size = prop[0x1000002], prop[0x1000003]
|
| 84 |
+
|
| 85 |
+
size = max(self.size)
|
| 86 |
+
i = 1
|
| 87 |
+
while size > 64:
|
| 88 |
+
size = size / 2
|
| 89 |
+
i += 1
|
| 90 |
+
self.maxid = i - 1
|
| 91 |
+
|
| 92 |
+
# mode. instead of using a single field for this, flashpix
|
| 93 |
+
# requires you to specify the mode for each channel in each
|
| 94 |
+
# resolution subimage, and leaves it to the decoder to make
|
| 95 |
+
# sure that they all match. for now, we'll cheat and assume
|
| 96 |
+
# that this is always the case.
|
| 97 |
+
|
| 98 |
+
id = self.maxid << 16
|
| 99 |
+
|
| 100 |
+
s = prop[0x2000002 | id]
|
| 101 |
+
|
| 102 |
+
bands = i32(s, 4)
|
| 103 |
+
if bands > 4:
|
| 104 |
+
msg = "Invalid number of bands"
|
| 105 |
+
raise OSError(msg)
|
| 106 |
+
|
| 107 |
+
# note: for now, we ignore the "uncalibrated" flag
|
| 108 |
+
colors = tuple(i32(s, 8 + i * 4) & 0x7FFFFFFF for i in range(bands))
|
| 109 |
+
|
| 110 |
+
self._mode, self.rawmode = MODES[colors]
|
| 111 |
+
|
| 112 |
+
# load JPEG tables, if any
|
| 113 |
+
self.jpeg = {}
|
| 114 |
+
for i in range(256):
|
| 115 |
+
id = 0x3000001 | (i << 16)
|
| 116 |
+
if id in prop:
|
| 117 |
+
self.jpeg[i] = prop[id]
|
| 118 |
+
|
| 119 |
+
self._open_subimage(1, self.maxid)
|
| 120 |
+
|
| 121 |
+
def _open_subimage(self, index=1, subimage=0):
|
| 122 |
+
#
|
| 123 |
+
# setup tile descriptors for a given subimage
|
| 124 |
+
|
| 125 |
+
stream = [
|
| 126 |
+
f"Data Object Store {index:06d}",
|
| 127 |
+
f"Resolution {subimage:04d}",
|
| 128 |
+
"Subimage 0000 Header",
|
| 129 |
+
]
|
| 130 |
+
|
| 131 |
+
fp = self.ole.openstream(stream)
|
| 132 |
+
|
| 133 |
+
# skip prefix
|
| 134 |
+
fp.read(28)
|
| 135 |
+
|
| 136 |
+
# header stream
|
| 137 |
+
s = fp.read(36)
|
| 138 |
+
|
| 139 |
+
size = i32(s, 4), i32(s, 8)
|
| 140 |
+
# tilecount = i32(s, 12)
|
| 141 |
+
tilesize = i32(s, 16), i32(s, 20)
|
| 142 |
+
# channels = i32(s, 24)
|
| 143 |
+
offset = i32(s, 28)
|
| 144 |
+
length = i32(s, 32)
|
| 145 |
+
|
| 146 |
+
if size != self.size:
|
| 147 |
+
msg = "subimage mismatch"
|
| 148 |
+
raise OSError(msg)
|
| 149 |
+
|
| 150 |
+
# get tile descriptors
|
| 151 |
+
fp.seek(28 + offset)
|
| 152 |
+
s = fp.read(i32(s, 12) * length)
|
| 153 |
+
|
| 154 |
+
x = y = 0
|
| 155 |
+
xsize, ysize = size
|
| 156 |
+
xtile, ytile = tilesize
|
| 157 |
+
self.tile = []
|
| 158 |
+
|
| 159 |
+
for i in range(0, len(s), length):
|
| 160 |
+
x1 = min(xsize, x + xtile)
|
| 161 |
+
y1 = min(ysize, y + ytile)
|
| 162 |
+
|
| 163 |
+
compression = i32(s, i + 8)
|
| 164 |
+
|
| 165 |
+
if compression == 0:
|
| 166 |
+
self.tile.append(
|
| 167 |
+
(
|
| 168 |
+
"raw",
|
| 169 |
+
(x, y, x1, y1),
|
| 170 |
+
i32(s, i) + 28,
|
| 171 |
+
(self.rawmode,),
|
| 172 |
+
)
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
elif compression == 1:
|
| 176 |
+
# FIXME: the fill decoder is not implemented
|
| 177 |
+
self.tile.append(
|
| 178 |
+
(
|
| 179 |
+
"fill",
|
| 180 |
+
(x, y, x1, y1),
|
| 181 |
+
i32(s, i) + 28,
|
| 182 |
+
(self.rawmode, s[12:16]),
|
| 183 |
+
)
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
elif compression == 2:
|
| 187 |
+
internal_color_conversion = s[14]
|
| 188 |
+
jpeg_tables = s[15]
|
| 189 |
+
rawmode = self.rawmode
|
| 190 |
+
|
| 191 |
+
if internal_color_conversion:
|
| 192 |
+
# The image is stored as usual (usually YCbCr).
|
| 193 |
+
if rawmode == "RGBA":
|
| 194 |
+
# For "RGBA", data is stored as YCbCrA based on
|
| 195 |
+
# negative RGB. The following trick works around
|
| 196 |
+
# this problem :
|
| 197 |
+
jpegmode, rawmode = "YCbCrK", "CMYK"
|
| 198 |
+
else:
|
| 199 |
+
jpegmode = None # let the decoder decide
|
| 200 |
+
|
| 201 |
+
else:
|
| 202 |
+
# The image is stored as defined by rawmode
|
| 203 |
+
jpegmode = rawmode
|
| 204 |
+
|
| 205 |
+
self.tile.append(
|
| 206 |
+
(
|
| 207 |
+
"jpeg",
|
| 208 |
+
(x, y, x1, y1),
|
| 209 |
+
i32(s, i) + 28,
|
| 210 |
+
(rawmode, jpegmode),
|
| 211 |
+
)
|
| 212 |
+
)
|
| 213 |
+
|
| 214 |
+
# FIXME: jpeg tables are tile dependent; the prefix
|
| 215 |
+
# data must be placed in the tile descriptor itself!
|
| 216 |
+
|
| 217 |
+
if jpeg_tables:
|
| 218 |
+
self.tile_prefix = self.jpeg[jpeg_tables]
|
| 219 |
+
|
| 220 |
+
else:
|
| 221 |
+
msg = "unknown/invalid compression"
|
| 222 |
+
raise OSError(msg)
|
| 223 |
+
|
| 224 |
+
x = x + xtile
|
| 225 |
+
if x >= xsize:
|
| 226 |
+
x, y = 0, y + ytile
|
| 227 |
+
if y >= ysize:
|
| 228 |
+
break # isn't really required
|
| 229 |
+
|
| 230 |
+
self.stream = stream
|
| 231 |
+
self._fp = self.fp
|
| 232 |
+
self.fp = None
|
| 233 |
+
|
| 234 |
+
def load(self):
|
| 235 |
+
if not self.fp:
|
| 236 |
+
self.fp = self.ole.openstream(self.stream[:2] + ["Subimage 0000 Data"])
|
| 237 |
+
|
| 238 |
+
return ImageFile.ImageFile.load(self)
|
| 239 |
+
|
| 240 |
+
def close(self):
|
| 241 |
+
self.ole.close()
|
| 242 |
+
super().close()
|
| 243 |
+
|
| 244 |
+
def __exit__(self, *args):
|
| 245 |
+
self.ole.close()
|
| 246 |
+
super().__exit__()
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
#
|
| 250 |
+
# --------------------------------------------------------------------
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
Image.register_open(FpxImageFile.format, FpxImageFile, _accept)
|
| 254 |
+
|
| 255 |
+
Image.register_extension(FpxImageFile.format, ".fpx")
|
venv/Lib/site-packages/PIL/FtexImagePlugin.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
A Pillow loader for .ftc and .ftu files (FTEX)
|
| 3 |
+
Jerome Leclanche <jerome@leclan.ch>
|
| 4 |
+
|
| 5 |
+
The contents of this file are hereby released in the public domain (CC0)
|
| 6 |
+
Full text of the CC0 license:
|
| 7 |
+
https://creativecommons.org/publicdomain/zero/1.0/
|
| 8 |
+
|
| 9 |
+
Independence War 2: Edge Of Chaos - Texture File Format - 16 October 2001
|
| 10 |
+
|
| 11 |
+
The textures used for 3D objects in Independence War 2: Edge Of Chaos are in a
|
| 12 |
+
packed custom format called FTEX. This file format uses file extensions FTC
|
| 13 |
+
and FTU.
|
| 14 |
+
* FTC files are compressed textures (using standard texture compression).
|
| 15 |
+
* FTU files are not compressed.
|
| 16 |
+
Texture File Format
|
| 17 |
+
The FTC and FTU texture files both use the same format. This
|
| 18 |
+
has the following structure:
|
| 19 |
+
{header}
|
| 20 |
+
{format_directory}
|
| 21 |
+
{data}
|
| 22 |
+
Where:
|
| 23 |
+
{header} = {
|
| 24 |
+
u32:magic,
|
| 25 |
+
u32:version,
|
| 26 |
+
u32:width,
|
| 27 |
+
u32:height,
|
| 28 |
+
u32:mipmap_count,
|
| 29 |
+
u32:format_count
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
* The "magic" number is "FTEX".
|
| 33 |
+
* "width" and "height" are the dimensions of the texture.
|
| 34 |
+
* "mipmap_count" is the number of mipmaps in the texture.
|
| 35 |
+
* "format_count" is the number of texture formats (different versions of the
|
| 36 |
+
same texture) in this file.
|
| 37 |
+
|
| 38 |
+
{format_directory} = format_count * { u32:format, u32:where }
|
| 39 |
+
|
| 40 |
+
The format value is 0 for DXT1 compressed textures and 1 for 24-bit RGB
|
| 41 |
+
uncompressed textures.
|
| 42 |
+
The texture data for a format starts at the position "where" in the file.
|
| 43 |
+
|
| 44 |
+
Each set of texture data in the file has the following structure:
|
| 45 |
+
{data} = format_count * { u32:mipmap_size, mipmap_size * { u8 } }
|
| 46 |
+
* "mipmap_size" is the number of bytes in that mip level. For compressed
|
| 47 |
+
textures this is the size of the texture data compressed with DXT1. For 24 bit
|
| 48 |
+
uncompressed textures, this is 3 * width * height. Following this are the image
|
| 49 |
+
bytes for that mipmap level.
|
| 50 |
+
|
| 51 |
+
Note: All data is stored in little-Endian (Intel) byte order.
|
| 52 |
+
"""
|
| 53 |
+
|
| 54 |
+
from __future__ import annotations
|
| 55 |
+
|
| 56 |
+
import struct
|
| 57 |
+
from enum import IntEnum
|
| 58 |
+
from io import BytesIO
|
| 59 |
+
|
| 60 |
+
from . import Image, ImageFile
|
| 61 |
+
|
| 62 |
+
MAGIC = b"FTEX"
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
class Format(IntEnum):
|
| 66 |
+
DXT1 = 0
|
| 67 |
+
UNCOMPRESSED = 1
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
class FtexImageFile(ImageFile.ImageFile):
|
| 71 |
+
format = "FTEX"
|
| 72 |
+
format_description = "Texture File Format (IW2:EOC)"
|
| 73 |
+
|
| 74 |
+
def _open(self):
|
| 75 |
+
if not _accept(self.fp.read(4)):
|
| 76 |
+
msg = "not an FTEX file"
|
| 77 |
+
raise SyntaxError(msg)
|
| 78 |
+
struct.unpack("<i", self.fp.read(4)) # version
|
| 79 |
+
self._size = struct.unpack("<2i", self.fp.read(8))
|
| 80 |
+
mipmap_count, format_count = struct.unpack("<2i", self.fp.read(8))
|
| 81 |
+
|
| 82 |
+
self._mode = "RGB"
|
| 83 |
+
|
| 84 |
+
# Only support single-format files.
|
| 85 |
+
# I don't know of any multi-format file.
|
| 86 |
+
assert format_count == 1
|
| 87 |
+
|
| 88 |
+
format, where = struct.unpack("<2i", self.fp.read(8))
|
| 89 |
+
self.fp.seek(where)
|
| 90 |
+
(mipmap_size,) = struct.unpack("<i", self.fp.read(4))
|
| 91 |
+
|
| 92 |
+
data = self.fp.read(mipmap_size)
|
| 93 |
+
|
| 94 |
+
if format == Format.DXT1:
|
| 95 |
+
self._mode = "RGBA"
|
| 96 |
+
self.tile = [("bcn", (0, 0) + self.size, 0, 1)]
|
| 97 |
+
elif format == Format.UNCOMPRESSED:
|
| 98 |
+
self.tile = [("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))]
|
| 99 |
+
else:
|
| 100 |
+
msg = f"Invalid texture compression format: {repr(format)}"
|
| 101 |
+
raise ValueError(msg)
|
| 102 |
+
|
| 103 |
+
self.fp.close()
|
| 104 |
+
self.fp = BytesIO(data)
|
| 105 |
+
|
| 106 |
+
def load_seek(self, pos):
|
| 107 |
+
pass
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def _accept(prefix):
|
| 111 |
+
return prefix[:4] == MAGIC
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
Image.register_open(FtexImageFile.format, FtexImageFile, _accept)
|
| 115 |
+
Image.register_extensions(FtexImageFile.format, [".ftc", ".ftu"])
|
venv/Lib/site-packages/PIL/GbrImagePlugin.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library
|
| 3 |
+
#
|
| 4 |
+
# load a GIMP brush file
|
| 5 |
+
#
|
| 6 |
+
# History:
|
| 7 |
+
# 96-03-14 fl Created
|
| 8 |
+
# 16-01-08 es Version 2
|
| 9 |
+
#
|
| 10 |
+
# Copyright (c) Secret Labs AB 1997.
|
| 11 |
+
# Copyright (c) Fredrik Lundh 1996.
|
| 12 |
+
# Copyright (c) Eric Soroos 2016.
|
| 13 |
+
#
|
| 14 |
+
# See the README file for information on usage and redistribution.
|
| 15 |
+
#
|
| 16 |
+
#
|
| 17 |
+
# See https://github.com/GNOME/gimp/blob/mainline/devel-docs/gbr.txt for
|
| 18 |
+
# format documentation.
|
| 19 |
+
#
|
| 20 |
+
# This code Interprets version 1 and 2 .gbr files.
|
| 21 |
+
# Version 1 files are obsolete, and should not be used for new
|
| 22 |
+
# brushes.
|
| 23 |
+
# Version 2 files are saved by GIMP v2.8 (at least)
|
| 24 |
+
# Version 3 files have a format specifier of 18 for 16bit floats in
|
| 25 |
+
# the color depth field. This is currently unsupported by Pillow.
|
| 26 |
+
from __future__ import annotations
|
| 27 |
+
|
| 28 |
+
from . import Image, ImageFile
|
| 29 |
+
from ._binary import i32be as i32
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _accept(prefix):
|
| 33 |
+
return len(prefix) >= 8 and i32(prefix, 0) >= 20 and i32(prefix, 4) in (1, 2)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
##
|
| 37 |
+
# Image plugin for the GIMP brush format.
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class GbrImageFile(ImageFile.ImageFile):
|
| 41 |
+
format = "GBR"
|
| 42 |
+
format_description = "GIMP brush file"
|
| 43 |
+
|
| 44 |
+
def _open(self):
|
| 45 |
+
header_size = i32(self.fp.read(4))
|
| 46 |
+
if header_size < 20:
|
| 47 |
+
msg = "not a GIMP brush"
|
| 48 |
+
raise SyntaxError(msg)
|
| 49 |
+
version = i32(self.fp.read(4))
|
| 50 |
+
if version not in (1, 2):
|
| 51 |
+
msg = f"Unsupported GIMP brush version: {version}"
|
| 52 |
+
raise SyntaxError(msg)
|
| 53 |
+
|
| 54 |
+
width = i32(self.fp.read(4))
|
| 55 |
+
height = i32(self.fp.read(4))
|
| 56 |
+
color_depth = i32(self.fp.read(4))
|
| 57 |
+
if width <= 0 or height <= 0:
|
| 58 |
+
msg = "not a GIMP brush"
|
| 59 |
+
raise SyntaxError(msg)
|
| 60 |
+
if color_depth not in (1, 4):
|
| 61 |
+
msg = f"Unsupported GIMP brush color depth: {color_depth}"
|
| 62 |
+
raise SyntaxError(msg)
|
| 63 |
+
|
| 64 |
+
if version == 1:
|
| 65 |
+
comment_length = header_size - 20
|
| 66 |
+
else:
|
| 67 |
+
comment_length = header_size - 28
|
| 68 |
+
magic_number = self.fp.read(4)
|
| 69 |
+
if magic_number != b"GIMP":
|
| 70 |
+
msg = "not a GIMP brush, bad magic number"
|
| 71 |
+
raise SyntaxError(msg)
|
| 72 |
+
self.info["spacing"] = i32(self.fp.read(4))
|
| 73 |
+
|
| 74 |
+
comment = self.fp.read(comment_length)[:-1]
|
| 75 |
+
|
| 76 |
+
if color_depth == 1:
|
| 77 |
+
self._mode = "L"
|
| 78 |
+
else:
|
| 79 |
+
self._mode = "RGBA"
|
| 80 |
+
|
| 81 |
+
self._size = width, height
|
| 82 |
+
|
| 83 |
+
self.info["comment"] = comment
|
| 84 |
+
|
| 85 |
+
# Image might not be small
|
| 86 |
+
Image._decompression_bomb_check(self.size)
|
| 87 |
+
|
| 88 |
+
# Data is an uncompressed block of w * h * bytes/pixel
|
| 89 |
+
self._data_size = width * height * color_depth
|
| 90 |
+
|
| 91 |
+
def load(self):
|
| 92 |
+
if not self.im:
|
| 93 |
+
self.im = Image.core.new(self.mode, self.size)
|
| 94 |
+
self.frombytes(self.fp.read(self._data_size))
|
| 95 |
+
return Image.Image.load(self)
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
#
|
| 99 |
+
# registry
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
Image.register_open(GbrImageFile.format, GbrImageFile, _accept)
|
| 103 |
+
Image.register_extension(GbrImageFile.format, ".gbr")
|
venv/Lib/site-packages/PIL/GdImageFile.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# GD file handling
|
| 6 |
+
#
|
| 7 |
+
# History:
|
| 8 |
+
# 1996-04-12 fl Created
|
| 9 |
+
#
|
| 10 |
+
# Copyright (c) 1997 by Secret Labs AB.
|
| 11 |
+
# Copyright (c) 1996 by Fredrik Lundh.
|
| 12 |
+
#
|
| 13 |
+
# See the README file for information on usage and redistribution.
|
| 14 |
+
#
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
"""
|
| 18 |
+
.. note::
|
| 19 |
+
This format cannot be automatically recognized, so the
|
| 20 |
+
class is not registered for use with :py:func:`PIL.Image.open()`. To open a
|
| 21 |
+
gd file, use the :py:func:`PIL.GdImageFile.open()` function instead.
|
| 22 |
+
|
| 23 |
+
.. warning::
|
| 24 |
+
THE GD FORMAT IS NOT DESIGNED FOR DATA INTERCHANGE. This
|
| 25 |
+
implementation is provided for convenience and demonstrational
|
| 26 |
+
purposes only.
|
| 27 |
+
"""
|
| 28 |
+
from __future__ import annotations
|
| 29 |
+
|
| 30 |
+
from typing import IO
|
| 31 |
+
|
| 32 |
+
from . import ImageFile, ImagePalette, UnidentifiedImageError
|
| 33 |
+
from ._binary import i16be as i16
|
| 34 |
+
from ._binary import i32be as i32
|
| 35 |
+
from ._typing import StrOrBytesPath
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class GdImageFile(ImageFile.ImageFile):
|
| 39 |
+
"""
|
| 40 |
+
Image plugin for the GD uncompressed format. Note that this format
|
| 41 |
+
is not supported by the standard :py:func:`PIL.Image.open()` function. To use
|
| 42 |
+
this plugin, you have to import the :py:mod:`PIL.GdImageFile` module and
|
| 43 |
+
use the :py:func:`PIL.GdImageFile.open()` function.
|
| 44 |
+
"""
|
| 45 |
+
|
| 46 |
+
format = "GD"
|
| 47 |
+
format_description = "GD uncompressed images"
|
| 48 |
+
|
| 49 |
+
def _open(self) -> None:
|
| 50 |
+
# Header
|
| 51 |
+
assert self.fp is not None
|
| 52 |
+
|
| 53 |
+
s = self.fp.read(1037)
|
| 54 |
+
|
| 55 |
+
if i16(s) not in [65534, 65535]:
|
| 56 |
+
msg = "Not a valid GD 2.x .gd file"
|
| 57 |
+
raise SyntaxError(msg)
|
| 58 |
+
|
| 59 |
+
self._mode = "L" # FIXME: "P"
|
| 60 |
+
self._size = i16(s, 2), i16(s, 4)
|
| 61 |
+
|
| 62 |
+
true_color = s[6]
|
| 63 |
+
true_color_offset = 2 if true_color else 0
|
| 64 |
+
|
| 65 |
+
# transparency index
|
| 66 |
+
tindex = i32(s, 7 + true_color_offset)
|
| 67 |
+
if tindex < 256:
|
| 68 |
+
self.info["transparency"] = tindex
|
| 69 |
+
|
| 70 |
+
self.palette = ImagePalette.raw(
|
| 71 |
+
"XBGR", s[7 + true_color_offset + 4 : 7 + true_color_offset + 4 + 256 * 4]
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
self.tile = [
|
| 75 |
+
(
|
| 76 |
+
"raw",
|
| 77 |
+
(0, 0) + self.size,
|
| 78 |
+
7 + true_color_offset + 4 + 256 * 4,
|
| 79 |
+
("L", 0, 1),
|
| 80 |
+
)
|
| 81 |
+
]
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def open(fp: StrOrBytesPath | IO[bytes], mode: str = "r") -> GdImageFile:
|
| 85 |
+
"""
|
| 86 |
+
Load texture from a GD image file.
|
| 87 |
+
|
| 88 |
+
:param fp: GD file name, or an opened file handle.
|
| 89 |
+
:param mode: Optional mode. In this version, if the mode argument
|
| 90 |
+
is given, it must be "r".
|
| 91 |
+
:returns: An image instance.
|
| 92 |
+
:raises OSError: If the image could not be read.
|
| 93 |
+
"""
|
| 94 |
+
if mode != "r":
|
| 95 |
+
msg = "bad mode"
|
| 96 |
+
raise ValueError(msg)
|
| 97 |
+
|
| 98 |
+
try:
|
| 99 |
+
return GdImageFile(fp)
|
| 100 |
+
except SyntaxError as e:
|
| 101 |
+
msg = "cannot identify this image file"
|
| 102 |
+
raise UnidentifiedImageError(msg) from e
|
venv/Lib/site-packages/PIL/GifImagePlugin.py
ADDED
|
@@ -0,0 +1,1107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library.
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# GIF file handling
|
| 6 |
+
#
|
| 7 |
+
# History:
|
| 8 |
+
# 1995-09-01 fl Created
|
| 9 |
+
# 1996-12-14 fl Added interlace support
|
| 10 |
+
# 1996-12-30 fl Added animation support
|
| 11 |
+
# 1997-01-05 fl Added write support, fixed local colour map bug
|
| 12 |
+
# 1997-02-23 fl Make sure to load raster data in getdata()
|
| 13 |
+
# 1997-07-05 fl Support external decoder (0.4)
|
| 14 |
+
# 1998-07-09 fl Handle all modes when saving (0.5)
|
| 15 |
+
# 1998-07-15 fl Renamed offset attribute to avoid name clash
|
| 16 |
+
# 2001-04-16 fl Added rewind support (seek to frame 0) (0.6)
|
| 17 |
+
# 2001-04-17 fl Added palette optimization (0.7)
|
| 18 |
+
# 2002-06-06 fl Added transparency support for save (0.8)
|
| 19 |
+
# 2004-02-24 fl Disable interlacing for small images
|
| 20 |
+
#
|
| 21 |
+
# Copyright (c) 1997-2004 by Secret Labs AB
|
| 22 |
+
# Copyright (c) 1995-2004 by Fredrik Lundh
|
| 23 |
+
#
|
| 24 |
+
# See the README file for information on usage and redistribution.
|
| 25 |
+
#
|
| 26 |
+
from __future__ import annotations
|
| 27 |
+
|
| 28 |
+
import itertools
|
| 29 |
+
import math
|
| 30 |
+
import os
|
| 31 |
+
import subprocess
|
| 32 |
+
from enum import IntEnum
|
| 33 |
+
|
| 34 |
+
from . import (
|
| 35 |
+
Image,
|
| 36 |
+
ImageChops,
|
| 37 |
+
ImageFile,
|
| 38 |
+
ImageMath,
|
| 39 |
+
ImageOps,
|
| 40 |
+
ImagePalette,
|
| 41 |
+
ImageSequence,
|
| 42 |
+
)
|
| 43 |
+
from ._binary import i16le as i16
|
| 44 |
+
from ._binary import o8
|
| 45 |
+
from ._binary import o16le as o16
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
class LoadingStrategy(IntEnum):
|
| 49 |
+
""".. versionadded:: 9.1.0"""
|
| 50 |
+
|
| 51 |
+
RGB_AFTER_FIRST = 0
|
| 52 |
+
RGB_AFTER_DIFFERENT_PALETTE_ONLY = 1
|
| 53 |
+
RGB_ALWAYS = 2
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
#: .. versionadded:: 9.1.0
|
| 57 |
+
LOADING_STRATEGY = LoadingStrategy.RGB_AFTER_FIRST
|
| 58 |
+
|
| 59 |
+
# --------------------------------------------------------------------
|
| 60 |
+
# Identify/read GIF files
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def _accept(prefix):
|
| 64 |
+
return prefix[:6] in [b"GIF87a", b"GIF89a"]
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
##
|
| 68 |
+
# Image plugin for GIF images. This plugin supports both GIF87 and
|
| 69 |
+
# GIF89 images.
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
class GifImageFile(ImageFile.ImageFile):
|
| 73 |
+
format = "GIF"
|
| 74 |
+
format_description = "Compuserve GIF"
|
| 75 |
+
_close_exclusive_fp_after_loading = False
|
| 76 |
+
|
| 77 |
+
global_palette = None
|
| 78 |
+
|
| 79 |
+
def data(self):
|
| 80 |
+
s = self.fp.read(1)
|
| 81 |
+
if s and s[0]:
|
| 82 |
+
return self.fp.read(s[0])
|
| 83 |
+
return None
|
| 84 |
+
|
| 85 |
+
def _is_palette_needed(self, p):
|
| 86 |
+
for i in range(0, len(p), 3):
|
| 87 |
+
if not (i // 3 == p[i] == p[i + 1] == p[i + 2]):
|
| 88 |
+
return True
|
| 89 |
+
return False
|
| 90 |
+
|
| 91 |
+
def _open(self):
|
| 92 |
+
# Screen
|
| 93 |
+
s = self.fp.read(13)
|
| 94 |
+
if not _accept(s):
|
| 95 |
+
msg = "not a GIF file"
|
| 96 |
+
raise SyntaxError(msg)
|
| 97 |
+
|
| 98 |
+
self.info["version"] = s[:6]
|
| 99 |
+
self._size = i16(s, 6), i16(s, 8)
|
| 100 |
+
self.tile = []
|
| 101 |
+
flags = s[10]
|
| 102 |
+
bits = (flags & 7) + 1
|
| 103 |
+
|
| 104 |
+
if flags & 128:
|
| 105 |
+
# get global palette
|
| 106 |
+
self.info["background"] = s[11]
|
| 107 |
+
# check if palette contains colour indices
|
| 108 |
+
p = self.fp.read(3 << bits)
|
| 109 |
+
if self._is_palette_needed(p):
|
| 110 |
+
p = ImagePalette.raw("RGB", p)
|
| 111 |
+
self.global_palette = self.palette = p
|
| 112 |
+
|
| 113 |
+
self._fp = self.fp # FIXME: hack
|
| 114 |
+
self.__rewind = self.fp.tell()
|
| 115 |
+
self._n_frames = None
|
| 116 |
+
self._is_animated = None
|
| 117 |
+
self._seek(0) # get ready to read first frame
|
| 118 |
+
|
| 119 |
+
@property
|
| 120 |
+
def n_frames(self):
|
| 121 |
+
if self._n_frames is None:
|
| 122 |
+
current = self.tell()
|
| 123 |
+
try:
|
| 124 |
+
while True:
|
| 125 |
+
self._seek(self.tell() + 1, False)
|
| 126 |
+
except EOFError:
|
| 127 |
+
self._n_frames = self.tell() + 1
|
| 128 |
+
self.seek(current)
|
| 129 |
+
return self._n_frames
|
| 130 |
+
|
| 131 |
+
@property
|
| 132 |
+
def is_animated(self):
|
| 133 |
+
if self._is_animated is None:
|
| 134 |
+
if self._n_frames is not None:
|
| 135 |
+
self._is_animated = self._n_frames != 1
|
| 136 |
+
else:
|
| 137 |
+
current = self.tell()
|
| 138 |
+
if current:
|
| 139 |
+
self._is_animated = True
|
| 140 |
+
else:
|
| 141 |
+
try:
|
| 142 |
+
self._seek(1, False)
|
| 143 |
+
self._is_animated = True
|
| 144 |
+
except EOFError:
|
| 145 |
+
self._is_animated = False
|
| 146 |
+
|
| 147 |
+
self.seek(current)
|
| 148 |
+
return self._is_animated
|
| 149 |
+
|
| 150 |
+
def seek(self, frame):
|
| 151 |
+
if not self._seek_check(frame):
|
| 152 |
+
return
|
| 153 |
+
if frame < self.__frame:
|
| 154 |
+
self.im = None
|
| 155 |
+
self._seek(0)
|
| 156 |
+
|
| 157 |
+
last_frame = self.__frame
|
| 158 |
+
for f in range(self.__frame + 1, frame + 1):
|
| 159 |
+
try:
|
| 160 |
+
self._seek(f)
|
| 161 |
+
except EOFError as e:
|
| 162 |
+
self.seek(last_frame)
|
| 163 |
+
msg = "no more images in GIF file"
|
| 164 |
+
raise EOFError(msg) from e
|
| 165 |
+
|
| 166 |
+
def _seek(self, frame, update_image=True):
|
| 167 |
+
if frame == 0:
|
| 168 |
+
# rewind
|
| 169 |
+
self.__offset = 0
|
| 170 |
+
self.dispose = None
|
| 171 |
+
self.__frame = -1
|
| 172 |
+
self._fp.seek(self.__rewind)
|
| 173 |
+
self.disposal_method = 0
|
| 174 |
+
if "comment" in self.info:
|
| 175 |
+
del self.info["comment"]
|
| 176 |
+
else:
|
| 177 |
+
# ensure that the previous frame was loaded
|
| 178 |
+
if self.tile and update_image:
|
| 179 |
+
self.load()
|
| 180 |
+
|
| 181 |
+
if frame != self.__frame + 1:
|
| 182 |
+
msg = f"cannot seek to frame {frame}"
|
| 183 |
+
raise ValueError(msg)
|
| 184 |
+
|
| 185 |
+
self.fp = self._fp
|
| 186 |
+
if self.__offset:
|
| 187 |
+
# backup to last frame
|
| 188 |
+
self.fp.seek(self.__offset)
|
| 189 |
+
while self.data():
|
| 190 |
+
pass
|
| 191 |
+
self.__offset = 0
|
| 192 |
+
|
| 193 |
+
s = self.fp.read(1)
|
| 194 |
+
if not s or s == b";":
|
| 195 |
+
msg = "no more images in GIF file"
|
| 196 |
+
raise EOFError(msg)
|
| 197 |
+
|
| 198 |
+
palette = None
|
| 199 |
+
|
| 200 |
+
info = {}
|
| 201 |
+
frame_transparency = None
|
| 202 |
+
interlace = None
|
| 203 |
+
frame_dispose_extent = None
|
| 204 |
+
while True:
|
| 205 |
+
if not s:
|
| 206 |
+
s = self.fp.read(1)
|
| 207 |
+
if not s or s == b";":
|
| 208 |
+
break
|
| 209 |
+
|
| 210 |
+
elif s == b"!":
|
| 211 |
+
#
|
| 212 |
+
# extensions
|
| 213 |
+
#
|
| 214 |
+
s = self.fp.read(1)
|
| 215 |
+
block = self.data()
|
| 216 |
+
if s[0] == 249:
|
| 217 |
+
#
|
| 218 |
+
# graphic control extension
|
| 219 |
+
#
|
| 220 |
+
flags = block[0]
|
| 221 |
+
if flags & 1:
|
| 222 |
+
frame_transparency = block[3]
|
| 223 |
+
info["duration"] = i16(block, 1) * 10
|
| 224 |
+
|
| 225 |
+
# disposal method - find the value of bits 4 - 6
|
| 226 |
+
dispose_bits = 0b00011100 & flags
|
| 227 |
+
dispose_bits = dispose_bits >> 2
|
| 228 |
+
if dispose_bits:
|
| 229 |
+
# only set the dispose if it is not
|
| 230 |
+
# unspecified. I'm not sure if this is
|
| 231 |
+
# correct, but it seems to prevent the last
|
| 232 |
+
# frame from looking odd for some animations
|
| 233 |
+
self.disposal_method = dispose_bits
|
| 234 |
+
elif s[0] == 254:
|
| 235 |
+
#
|
| 236 |
+
# comment extension
|
| 237 |
+
#
|
| 238 |
+
comment = b""
|
| 239 |
+
|
| 240 |
+
# Read this comment block
|
| 241 |
+
while block:
|
| 242 |
+
comment += block
|
| 243 |
+
block = self.data()
|
| 244 |
+
|
| 245 |
+
if "comment" in info:
|
| 246 |
+
# If multiple comment blocks in frame, separate with \n
|
| 247 |
+
info["comment"] += b"\n" + comment
|
| 248 |
+
else:
|
| 249 |
+
info["comment"] = comment
|
| 250 |
+
s = None
|
| 251 |
+
continue
|
| 252 |
+
elif s[0] == 255 and frame == 0:
|
| 253 |
+
#
|
| 254 |
+
# application extension
|
| 255 |
+
#
|
| 256 |
+
info["extension"] = block, self.fp.tell()
|
| 257 |
+
if block[:11] == b"NETSCAPE2.0":
|
| 258 |
+
block = self.data()
|
| 259 |
+
if len(block) >= 3 and block[0] == 1:
|
| 260 |
+
self.info["loop"] = i16(block, 1)
|
| 261 |
+
while self.data():
|
| 262 |
+
pass
|
| 263 |
+
|
| 264 |
+
elif s == b",":
|
| 265 |
+
#
|
| 266 |
+
# local image
|
| 267 |
+
#
|
| 268 |
+
s = self.fp.read(9)
|
| 269 |
+
|
| 270 |
+
# extent
|
| 271 |
+
x0, y0 = i16(s, 0), i16(s, 2)
|
| 272 |
+
x1, y1 = x0 + i16(s, 4), y0 + i16(s, 6)
|
| 273 |
+
if (x1 > self.size[0] or y1 > self.size[1]) and update_image:
|
| 274 |
+
self._size = max(x1, self.size[0]), max(y1, self.size[1])
|
| 275 |
+
Image._decompression_bomb_check(self._size)
|
| 276 |
+
frame_dispose_extent = x0, y0, x1, y1
|
| 277 |
+
flags = s[8]
|
| 278 |
+
|
| 279 |
+
interlace = (flags & 64) != 0
|
| 280 |
+
|
| 281 |
+
if flags & 128:
|
| 282 |
+
bits = (flags & 7) + 1
|
| 283 |
+
p = self.fp.read(3 << bits)
|
| 284 |
+
if self._is_palette_needed(p):
|
| 285 |
+
palette = ImagePalette.raw("RGB", p)
|
| 286 |
+
else:
|
| 287 |
+
palette = False
|
| 288 |
+
|
| 289 |
+
# image data
|
| 290 |
+
bits = self.fp.read(1)[0]
|
| 291 |
+
self.__offset = self.fp.tell()
|
| 292 |
+
break
|
| 293 |
+
s = None
|
| 294 |
+
|
| 295 |
+
if interlace is None:
|
| 296 |
+
msg = "image not found in GIF frame"
|
| 297 |
+
raise EOFError(msg)
|
| 298 |
+
|
| 299 |
+
self.__frame = frame
|
| 300 |
+
if not update_image:
|
| 301 |
+
return
|
| 302 |
+
|
| 303 |
+
self.tile = []
|
| 304 |
+
|
| 305 |
+
if self.dispose:
|
| 306 |
+
self.im.paste(self.dispose, self.dispose_extent)
|
| 307 |
+
|
| 308 |
+
self._frame_palette = palette if palette is not None else self.global_palette
|
| 309 |
+
self._frame_transparency = frame_transparency
|
| 310 |
+
if frame == 0:
|
| 311 |
+
if self._frame_palette:
|
| 312 |
+
if LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS:
|
| 313 |
+
self._mode = "RGBA" if frame_transparency is not None else "RGB"
|
| 314 |
+
else:
|
| 315 |
+
self._mode = "P"
|
| 316 |
+
else:
|
| 317 |
+
self._mode = "L"
|
| 318 |
+
|
| 319 |
+
if not palette and self.global_palette:
|
| 320 |
+
from copy import copy
|
| 321 |
+
|
| 322 |
+
palette = copy(self.global_palette)
|
| 323 |
+
self.palette = palette
|
| 324 |
+
else:
|
| 325 |
+
if self.mode == "P":
|
| 326 |
+
if (
|
| 327 |
+
LOADING_STRATEGY != LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY
|
| 328 |
+
or palette
|
| 329 |
+
):
|
| 330 |
+
self.pyaccess = None
|
| 331 |
+
if "transparency" in self.info:
|
| 332 |
+
self.im.putpalettealpha(self.info["transparency"], 0)
|
| 333 |
+
self.im = self.im.convert("RGBA", Image.Dither.FLOYDSTEINBERG)
|
| 334 |
+
self._mode = "RGBA"
|
| 335 |
+
del self.info["transparency"]
|
| 336 |
+
else:
|
| 337 |
+
self._mode = "RGB"
|
| 338 |
+
self.im = self.im.convert("RGB", Image.Dither.FLOYDSTEINBERG)
|
| 339 |
+
|
| 340 |
+
def _rgb(color):
|
| 341 |
+
if self._frame_palette:
|
| 342 |
+
if color * 3 + 3 > len(self._frame_palette.palette):
|
| 343 |
+
color = 0
|
| 344 |
+
color = tuple(self._frame_palette.palette[color * 3 : color * 3 + 3])
|
| 345 |
+
else:
|
| 346 |
+
color = (color, color, color)
|
| 347 |
+
return color
|
| 348 |
+
|
| 349 |
+
self.dispose_extent = frame_dispose_extent
|
| 350 |
+
try:
|
| 351 |
+
if self.disposal_method < 2:
|
| 352 |
+
# do not dispose or none specified
|
| 353 |
+
self.dispose = None
|
| 354 |
+
elif self.disposal_method == 2:
|
| 355 |
+
# replace with background colour
|
| 356 |
+
|
| 357 |
+
# only dispose the extent in this frame
|
| 358 |
+
x0, y0, x1, y1 = self.dispose_extent
|
| 359 |
+
dispose_size = (x1 - x0, y1 - y0)
|
| 360 |
+
|
| 361 |
+
Image._decompression_bomb_check(dispose_size)
|
| 362 |
+
|
| 363 |
+
# by convention, attempt to use transparency first
|
| 364 |
+
dispose_mode = "P"
|
| 365 |
+
color = self.info.get("transparency", frame_transparency)
|
| 366 |
+
if color is not None:
|
| 367 |
+
if self.mode in ("RGB", "RGBA"):
|
| 368 |
+
dispose_mode = "RGBA"
|
| 369 |
+
color = _rgb(color) + (0,)
|
| 370 |
+
else:
|
| 371 |
+
color = self.info.get("background", 0)
|
| 372 |
+
if self.mode in ("RGB", "RGBA"):
|
| 373 |
+
dispose_mode = "RGB"
|
| 374 |
+
color = _rgb(color)
|
| 375 |
+
self.dispose = Image.core.fill(dispose_mode, dispose_size, color)
|
| 376 |
+
else:
|
| 377 |
+
# replace with previous contents
|
| 378 |
+
if self.im is not None:
|
| 379 |
+
# only dispose the extent in this frame
|
| 380 |
+
self.dispose = self._crop(self.im, self.dispose_extent)
|
| 381 |
+
elif frame_transparency is not None:
|
| 382 |
+
x0, y0, x1, y1 = self.dispose_extent
|
| 383 |
+
dispose_size = (x1 - x0, y1 - y0)
|
| 384 |
+
|
| 385 |
+
Image._decompression_bomb_check(dispose_size)
|
| 386 |
+
dispose_mode = "P"
|
| 387 |
+
color = frame_transparency
|
| 388 |
+
if self.mode in ("RGB", "RGBA"):
|
| 389 |
+
dispose_mode = "RGBA"
|
| 390 |
+
color = _rgb(frame_transparency) + (0,)
|
| 391 |
+
self.dispose = Image.core.fill(dispose_mode, dispose_size, color)
|
| 392 |
+
except AttributeError:
|
| 393 |
+
pass
|
| 394 |
+
|
| 395 |
+
if interlace is not None:
|
| 396 |
+
transparency = -1
|
| 397 |
+
if frame_transparency is not None:
|
| 398 |
+
if frame == 0:
|
| 399 |
+
if LOADING_STRATEGY != LoadingStrategy.RGB_ALWAYS:
|
| 400 |
+
self.info["transparency"] = frame_transparency
|
| 401 |
+
elif self.mode not in ("RGB", "RGBA"):
|
| 402 |
+
transparency = frame_transparency
|
| 403 |
+
self.tile = [
|
| 404 |
+
(
|
| 405 |
+
"gif",
|
| 406 |
+
(x0, y0, x1, y1),
|
| 407 |
+
self.__offset,
|
| 408 |
+
(bits, interlace, transparency),
|
| 409 |
+
)
|
| 410 |
+
]
|
| 411 |
+
|
| 412 |
+
if info.get("comment"):
|
| 413 |
+
self.info["comment"] = info["comment"]
|
| 414 |
+
for k in ["duration", "extension"]:
|
| 415 |
+
if k in info:
|
| 416 |
+
self.info[k] = info[k]
|
| 417 |
+
elif k in self.info:
|
| 418 |
+
del self.info[k]
|
| 419 |
+
|
| 420 |
+
def load_prepare(self):
|
| 421 |
+
temp_mode = "P" if self._frame_palette else "L"
|
| 422 |
+
self._prev_im = None
|
| 423 |
+
if self.__frame == 0:
|
| 424 |
+
if self._frame_transparency is not None:
|
| 425 |
+
self.im = Image.core.fill(
|
| 426 |
+
temp_mode, self.size, self._frame_transparency
|
| 427 |
+
)
|
| 428 |
+
elif self.mode in ("RGB", "RGBA"):
|
| 429 |
+
self._prev_im = self.im
|
| 430 |
+
if self._frame_palette:
|
| 431 |
+
self.im = Image.core.fill("P", self.size, self._frame_transparency or 0)
|
| 432 |
+
self.im.putpalette(*self._frame_palette.getdata())
|
| 433 |
+
else:
|
| 434 |
+
self.im = None
|
| 435 |
+
self._mode = temp_mode
|
| 436 |
+
self._frame_palette = None
|
| 437 |
+
|
| 438 |
+
super().load_prepare()
|
| 439 |
+
|
| 440 |
+
def load_end(self):
|
| 441 |
+
if self.__frame == 0:
|
| 442 |
+
if self.mode == "P" and LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS:
|
| 443 |
+
if self._frame_transparency is not None:
|
| 444 |
+
self.im.putpalettealpha(self._frame_transparency, 0)
|
| 445 |
+
self._mode = "RGBA"
|
| 446 |
+
else:
|
| 447 |
+
self._mode = "RGB"
|
| 448 |
+
self.im = self.im.convert(self.mode, Image.Dither.FLOYDSTEINBERG)
|
| 449 |
+
return
|
| 450 |
+
if not self._prev_im:
|
| 451 |
+
return
|
| 452 |
+
if self._frame_transparency is not None:
|
| 453 |
+
self.im.putpalettealpha(self._frame_transparency, 0)
|
| 454 |
+
frame_im = self.im.convert("RGBA")
|
| 455 |
+
else:
|
| 456 |
+
frame_im = self.im.convert("RGB")
|
| 457 |
+
frame_im = self._crop(frame_im, self.dispose_extent)
|
| 458 |
+
|
| 459 |
+
self.im = self._prev_im
|
| 460 |
+
self._mode = self.im.mode
|
| 461 |
+
if frame_im.mode == "RGBA":
|
| 462 |
+
self.im.paste(frame_im, self.dispose_extent, frame_im)
|
| 463 |
+
else:
|
| 464 |
+
self.im.paste(frame_im, self.dispose_extent)
|
| 465 |
+
|
| 466 |
+
def tell(self):
|
| 467 |
+
return self.__frame
|
| 468 |
+
|
| 469 |
+
|
| 470 |
+
# --------------------------------------------------------------------
|
| 471 |
+
# Write GIF files
|
| 472 |
+
|
| 473 |
+
|
| 474 |
+
RAWMODE = {"1": "L", "L": "L", "P": "P"}
|
| 475 |
+
|
| 476 |
+
|
| 477 |
+
def _normalize_mode(im):
|
| 478 |
+
"""
|
| 479 |
+
Takes an image (or frame), returns an image in a mode that is appropriate
|
| 480 |
+
for saving in a Gif.
|
| 481 |
+
|
| 482 |
+
It may return the original image, or it may return an image converted to
|
| 483 |
+
palette or 'L' mode.
|
| 484 |
+
|
| 485 |
+
:param im: Image object
|
| 486 |
+
:returns: Image object
|
| 487 |
+
"""
|
| 488 |
+
if im.mode in RAWMODE:
|
| 489 |
+
im.load()
|
| 490 |
+
return im
|
| 491 |
+
if Image.getmodebase(im.mode) == "RGB":
|
| 492 |
+
im = im.convert("P", palette=Image.Palette.ADAPTIVE)
|
| 493 |
+
if im.palette.mode == "RGBA":
|
| 494 |
+
for rgba in im.palette.colors:
|
| 495 |
+
if rgba[3] == 0:
|
| 496 |
+
im.info["transparency"] = im.palette.colors[rgba]
|
| 497 |
+
break
|
| 498 |
+
return im
|
| 499 |
+
return im.convert("L")
|
| 500 |
+
|
| 501 |
+
|
| 502 |
+
def _normalize_palette(im, palette, info):
|
| 503 |
+
"""
|
| 504 |
+
Normalizes the palette for image.
|
| 505 |
+
- Sets the palette to the incoming palette, if provided.
|
| 506 |
+
- Ensures that there's a palette for L mode images
|
| 507 |
+
- Optimizes the palette if necessary/desired.
|
| 508 |
+
|
| 509 |
+
:param im: Image object
|
| 510 |
+
:param palette: bytes object containing the source palette, or ....
|
| 511 |
+
:param info: encoderinfo
|
| 512 |
+
:returns: Image object
|
| 513 |
+
"""
|
| 514 |
+
source_palette = None
|
| 515 |
+
if palette:
|
| 516 |
+
# a bytes palette
|
| 517 |
+
if isinstance(palette, (bytes, bytearray, list)):
|
| 518 |
+
source_palette = bytearray(palette[:768])
|
| 519 |
+
if isinstance(palette, ImagePalette.ImagePalette):
|
| 520 |
+
source_palette = bytearray(palette.palette)
|
| 521 |
+
|
| 522 |
+
if im.mode == "P":
|
| 523 |
+
if not source_palette:
|
| 524 |
+
source_palette = im.im.getpalette("RGB")[:768]
|
| 525 |
+
else: # L-mode
|
| 526 |
+
if not source_palette:
|
| 527 |
+
source_palette = bytearray(i // 3 for i in range(768))
|
| 528 |
+
im.palette = ImagePalette.ImagePalette("RGB", palette=source_palette)
|
| 529 |
+
|
| 530 |
+
if palette:
|
| 531 |
+
used_palette_colors = []
|
| 532 |
+
for i in range(0, len(source_palette), 3):
|
| 533 |
+
source_color = tuple(source_palette[i : i + 3])
|
| 534 |
+
index = im.palette.colors.get(source_color)
|
| 535 |
+
if index in used_palette_colors:
|
| 536 |
+
index = None
|
| 537 |
+
used_palette_colors.append(index)
|
| 538 |
+
for i, index in enumerate(used_palette_colors):
|
| 539 |
+
if index is None:
|
| 540 |
+
for j in range(len(used_palette_colors)):
|
| 541 |
+
if j not in used_palette_colors:
|
| 542 |
+
used_palette_colors[i] = j
|
| 543 |
+
break
|
| 544 |
+
im = im.remap_palette(used_palette_colors)
|
| 545 |
+
else:
|
| 546 |
+
used_palette_colors = _get_optimize(im, info)
|
| 547 |
+
if used_palette_colors is not None:
|
| 548 |
+
im = im.remap_palette(used_palette_colors, source_palette)
|
| 549 |
+
if "transparency" in info:
|
| 550 |
+
try:
|
| 551 |
+
info["transparency"] = used_palette_colors.index(
|
| 552 |
+
info["transparency"]
|
| 553 |
+
)
|
| 554 |
+
except ValueError:
|
| 555 |
+
del info["transparency"]
|
| 556 |
+
return im
|
| 557 |
+
|
| 558 |
+
im.palette.palette = source_palette
|
| 559 |
+
return im
|
| 560 |
+
|
| 561 |
+
|
| 562 |
+
def _write_single_frame(im, fp, palette):
|
| 563 |
+
im_out = _normalize_mode(im)
|
| 564 |
+
for k, v in im_out.info.items():
|
| 565 |
+
im.encoderinfo.setdefault(k, v)
|
| 566 |
+
im_out = _normalize_palette(im_out, palette, im.encoderinfo)
|
| 567 |
+
|
| 568 |
+
for s in _get_global_header(im_out, im.encoderinfo):
|
| 569 |
+
fp.write(s)
|
| 570 |
+
|
| 571 |
+
# local image header
|
| 572 |
+
flags = 0
|
| 573 |
+
if get_interlace(im):
|
| 574 |
+
flags = flags | 64
|
| 575 |
+
_write_local_header(fp, im, (0, 0), flags)
|
| 576 |
+
|
| 577 |
+
im_out.encoderconfig = (8, get_interlace(im))
|
| 578 |
+
ImageFile._save(im_out, fp, [("gif", (0, 0) + im.size, 0, RAWMODE[im_out.mode])])
|
| 579 |
+
|
| 580 |
+
fp.write(b"\0") # end of image data
|
| 581 |
+
|
| 582 |
+
|
| 583 |
+
def _getbbox(base_im, im_frame):
|
| 584 |
+
if _get_palette_bytes(im_frame) != _get_palette_bytes(base_im):
|
| 585 |
+
im_frame = im_frame.convert("RGBA")
|
| 586 |
+
base_im = base_im.convert("RGBA")
|
| 587 |
+
delta = ImageChops.subtract_modulo(im_frame, base_im)
|
| 588 |
+
return delta, delta.getbbox(alpha_only=False)
|
| 589 |
+
|
| 590 |
+
|
| 591 |
+
def _write_multiple_frames(im, fp, palette):
|
| 592 |
+
duration = im.encoderinfo.get("duration")
|
| 593 |
+
disposal = im.encoderinfo.get("disposal", im.info.get("disposal"))
|
| 594 |
+
|
| 595 |
+
im_frames = []
|
| 596 |
+
previous_im = None
|
| 597 |
+
frame_count = 0
|
| 598 |
+
background_im = None
|
| 599 |
+
for imSequence in itertools.chain([im], im.encoderinfo.get("append_images", [])):
|
| 600 |
+
for im_frame in ImageSequence.Iterator(imSequence):
|
| 601 |
+
# a copy is required here since seek can still mutate the image
|
| 602 |
+
im_frame = _normalize_mode(im_frame.copy())
|
| 603 |
+
if frame_count == 0:
|
| 604 |
+
for k, v in im_frame.info.items():
|
| 605 |
+
if k == "transparency":
|
| 606 |
+
continue
|
| 607 |
+
im.encoderinfo.setdefault(k, v)
|
| 608 |
+
|
| 609 |
+
encoderinfo = im.encoderinfo.copy()
|
| 610 |
+
if "transparency" in im_frame.info:
|
| 611 |
+
encoderinfo.setdefault("transparency", im_frame.info["transparency"])
|
| 612 |
+
im_frame = _normalize_palette(im_frame, palette, encoderinfo)
|
| 613 |
+
if isinstance(duration, (list, tuple)):
|
| 614 |
+
encoderinfo["duration"] = duration[frame_count]
|
| 615 |
+
elif duration is None and "duration" in im_frame.info:
|
| 616 |
+
encoderinfo["duration"] = im_frame.info["duration"]
|
| 617 |
+
if isinstance(disposal, (list, tuple)):
|
| 618 |
+
encoderinfo["disposal"] = disposal[frame_count]
|
| 619 |
+
frame_count += 1
|
| 620 |
+
|
| 621 |
+
diff_frame = None
|
| 622 |
+
if im_frames:
|
| 623 |
+
# delta frame
|
| 624 |
+
delta, bbox = _getbbox(previous_im, im_frame)
|
| 625 |
+
if not bbox:
|
| 626 |
+
# This frame is identical to the previous frame
|
| 627 |
+
if encoderinfo.get("duration"):
|
| 628 |
+
im_frames[-1]["encoderinfo"]["duration"] += encoderinfo[
|
| 629 |
+
"duration"
|
| 630 |
+
]
|
| 631 |
+
continue
|
| 632 |
+
if im_frames[-1]["encoderinfo"].get("disposal") == 2:
|
| 633 |
+
if background_im is None:
|
| 634 |
+
color = im.encoderinfo.get(
|
| 635 |
+
"transparency", im.info.get("transparency", (0, 0, 0))
|
| 636 |
+
)
|
| 637 |
+
background = _get_background(im_frame, color)
|
| 638 |
+
background_im = Image.new("P", im_frame.size, background)
|
| 639 |
+
background_im.putpalette(im_frames[0]["im"].palette)
|
| 640 |
+
bbox = _getbbox(background_im, im_frame)[1]
|
| 641 |
+
elif encoderinfo.get("optimize") and im_frame.mode != "1":
|
| 642 |
+
if "transparency" not in encoderinfo:
|
| 643 |
+
try:
|
| 644 |
+
encoderinfo["transparency"] = (
|
| 645 |
+
im_frame.palette._new_color_index(im_frame)
|
| 646 |
+
)
|
| 647 |
+
except ValueError:
|
| 648 |
+
pass
|
| 649 |
+
if "transparency" in encoderinfo:
|
| 650 |
+
# When the delta is zero, fill the image with transparency
|
| 651 |
+
diff_frame = im_frame.copy()
|
| 652 |
+
fill = Image.new("P", delta.size, encoderinfo["transparency"])
|
| 653 |
+
if delta.mode == "RGBA":
|
| 654 |
+
r, g, b, a = delta.split()
|
| 655 |
+
mask = ImageMath.lambda_eval(
|
| 656 |
+
lambda args: args["convert"](
|
| 657 |
+
args["max"](
|
| 658 |
+
args["max"](
|
| 659 |
+
args["max"](args["r"], args["g"]), args["b"]
|
| 660 |
+
),
|
| 661 |
+
args["a"],
|
| 662 |
+
)
|
| 663 |
+
* 255,
|
| 664 |
+
"1",
|
| 665 |
+
),
|
| 666 |
+
r=r,
|
| 667 |
+
g=g,
|
| 668 |
+
b=b,
|
| 669 |
+
a=a,
|
| 670 |
+
)
|
| 671 |
+
else:
|
| 672 |
+
if delta.mode == "P":
|
| 673 |
+
# Convert to L without considering palette
|
| 674 |
+
delta_l = Image.new("L", delta.size)
|
| 675 |
+
delta_l.putdata(delta.getdata())
|
| 676 |
+
delta = delta_l
|
| 677 |
+
mask = ImageMath.lambda_eval(
|
| 678 |
+
lambda args: args["convert"](args["im"] * 255, "1"),
|
| 679 |
+
im=delta,
|
| 680 |
+
)
|
| 681 |
+
diff_frame.paste(fill, mask=ImageOps.invert(mask))
|
| 682 |
+
else:
|
| 683 |
+
bbox = None
|
| 684 |
+
previous_im = im_frame
|
| 685 |
+
im_frames.append(
|
| 686 |
+
{"im": diff_frame or im_frame, "bbox": bbox, "encoderinfo": encoderinfo}
|
| 687 |
+
)
|
| 688 |
+
|
| 689 |
+
if len(im_frames) == 1:
|
| 690 |
+
if "duration" in im.encoderinfo:
|
| 691 |
+
# Since multiple frames will not be written, use the combined duration
|
| 692 |
+
im.encoderinfo["duration"] = im_frames[0]["encoderinfo"]["duration"]
|
| 693 |
+
return
|
| 694 |
+
|
| 695 |
+
for frame_data in im_frames:
|
| 696 |
+
im_frame = frame_data["im"]
|
| 697 |
+
if not frame_data["bbox"]:
|
| 698 |
+
# global header
|
| 699 |
+
for s in _get_global_header(im_frame, frame_data["encoderinfo"]):
|
| 700 |
+
fp.write(s)
|
| 701 |
+
offset = (0, 0)
|
| 702 |
+
else:
|
| 703 |
+
# compress difference
|
| 704 |
+
if not palette:
|
| 705 |
+
frame_data["encoderinfo"]["include_color_table"] = True
|
| 706 |
+
|
| 707 |
+
im_frame = im_frame.crop(frame_data["bbox"])
|
| 708 |
+
offset = frame_data["bbox"][:2]
|
| 709 |
+
_write_frame_data(fp, im_frame, offset, frame_data["encoderinfo"])
|
| 710 |
+
return True
|
| 711 |
+
|
| 712 |
+
|
| 713 |
+
def _save_all(im, fp, filename):
|
| 714 |
+
_save(im, fp, filename, save_all=True)
|
| 715 |
+
|
| 716 |
+
|
| 717 |
+
def _save(im, fp, filename, save_all=False):
|
| 718 |
+
# header
|
| 719 |
+
if "palette" in im.encoderinfo or "palette" in im.info:
|
| 720 |
+
palette = im.encoderinfo.get("palette", im.info.get("palette"))
|
| 721 |
+
else:
|
| 722 |
+
palette = None
|
| 723 |
+
im.encoderinfo.setdefault("optimize", True)
|
| 724 |
+
|
| 725 |
+
if not save_all or not _write_multiple_frames(im, fp, palette):
|
| 726 |
+
_write_single_frame(im, fp, palette)
|
| 727 |
+
|
| 728 |
+
fp.write(b";") # end of file
|
| 729 |
+
|
| 730 |
+
if hasattr(fp, "flush"):
|
| 731 |
+
fp.flush()
|
| 732 |
+
|
| 733 |
+
|
| 734 |
+
def get_interlace(im):
|
| 735 |
+
interlace = im.encoderinfo.get("interlace", 1)
|
| 736 |
+
|
| 737 |
+
# workaround for @PIL153
|
| 738 |
+
if min(im.size) < 16:
|
| 739 |
+
interlace = 0
|
| 740 |
+
|
| 741 |
+
return interlace
|
| 742 |
+
|
| 743 |
+
|
| 744 |
+
def _write_local_header(fp, im, offset, flags):
|
| 745 |
+
try:
|
| 746 |
+
transparency = im.encoderinfo["transparency"]
|
| 747 |
+
except KeyError:
|
| 748 |
+
transparency = None
|
| 749 |
+
|
| 750 |
+
if "duration" in im.encoderinfo:
|
| 751 |
+
duration = int(im.encoderinfo["duration"] / 10)
|
| 752 |
+
else:
|
| 753 |
+
duration = 0
|
| 754 |
+
|
| 755 |
+
disposal = int(im.encoderinfo.get("disposal", 0))
|
| 756 |
+
|
| 757 |
+
if transparency is not None or duration != 0 or disposal:
|
| 758 |
+
packed_flag = 1 if transparency is not None else 0
|
| 759 |
+
packed_flag |= disposal << 2
|
| 760 |
+
|
| 761 |
+
fp.write(
|
| 762 |
+
b"!"
|
| 763 |
+
+ o8(249) # extension intro
|
| 764 |
+
+ o8(4) # length
|
| 765 |
+
+ o8(packed_flag) # packed fields
|
| 766 |
+
+ o16(duration) # duration
|
| 767 |
+
+ o8(transparency or 0) # transparency index
|
| 768 |
+
+ o8(0)
|
| 769 |
+
)
|
| 770 |
+
|
| 771 |
+
include_color_table = im.encoderinfo.get("include_color_table")
|
| 772 |
+
if include_color_table:
|
| 773 |
+
palette_bytes = _get_palette_bytes(im)
|
| 774 |
+
color_table_size = _get_color_table_size(palette_bytes)
|
| 775 |
+
if color_table_size:
|
| 776 |
+
flags = flags | 128 # local color table flag
|
| 777 |
+
flags = flags | color_table_size
|
| 778 |
+
|
| 779 |
+
fp.write(
|
| 780 |
+
b","
|
| 781 |
+
+ o16(offset[0]) # offset
|
| 782 |
+
+ o16(offset[1])
|
| 783 |
+
+ o16(im.size[0]) # size
|
| 784 |
+
+ o16(im.size[1])
|
| 785 |
+
+ o8(flags) # flags
|
| 786 |
+
)
|
| 787 |
+
if include_color_table and color_table_size:
|
| 788 |
+
fp.write(_get_header_palette(palette_bytes))
|
| 789 |
+
fp.write(o8(8)) # bits
|
| 790 |
+
|
| 791 |
+
|
| 792 |
+
def _save_netpbm(im, fp, filename):
|
| 793 |
+
# Unused by default.
|
| 794 |
+
# To use, uncomment the register_save call at the end of the file.
|
| 795 |
+
#
|
| 796 |
+
# If you need real GIF compression and/or RGB quantization, you
|
| 797 |
+
# can use the external NETPBM/PBMPLUS utilities. See comments
|
| 798 |
+
# below for information on how to enable this.
|
| 799 |
+
tempfile = im._dump()
|
| 800 |
+
|
| 801 |
+
try:
|
| 802 |
+
with open(filename, "wb") as f:
|
| 803 |
+
if im.mode != "RGB":
|
| 804 |
+
subprocess.check_call(
|
| 805 |
+
["ppmtogif", tempfile], stdout=f, stderr=subprocess.DEVNULL
|
| 806 |
+
)
|
| 807 |
+
else:
|
| 808 |
+
# Pipe ppmquant output into ppmtogif
|
| 809 |
+
# "ppmquant 256 %s | ppmtogif > %s" % (tempfile, filename)
|
| 810 |
+
quant_cmd = ["ppmquant", "256", tempfile]
|
| 811 |
+
togif_cmd = ["ppmtogif"]
|
| 812 |
+
quant_proc = subprocess.Popen(
|
| 813 |
+
quant_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL
|
| 814 |
+
)
|
| 815 |
+
togif_proc = subprocess.Popen(
|
| 816 |
+
togif_cmd,
|
| 817 |
+
stdin=quant_proc.stdout,
|
| 818 |
+
stdout=f,
|
| 819 |
+
stderr=subprocess.DEVNULL,
|
| 820 |
+
)
|
| 821 |
+
|
| 822 |
+
# Allow ppmquant to receive SIGPIPE if ppmtogif exits
|
| 823 |
+
quant_proc.stdout.close()
|
| 824 |
+
|
| 825 |
+
retcode = quant_proc.wait()
|
| 826 |
+
if retcode:
|
| 827 |
+
raise subprocess.CalledProcessError(retcode, quant_cmd)
|
| 828 |
+
|
| 829 |
+
retcode = togif_proc.wait()
|
| 830 |
+
if retcode:
|
| 831 |
+
raise subprocess.CalledProcessError(retcode, togif_cmd)
|
| 832 |
+
finally:
|
| 833 |
+
try:
|
| 834 |
+
os.unlink(tempfile)
|
| 835 |
+
except OSError:
|
| 836 |
+
pass
|
| 837 |
+
|
| 838 |
+
|
| 839 |
+
# Force optimization so that we can test performance against
|
| 840 |
+
# cases where it took lots of memory and time previously.
|
| 841 |
+
_FORCE_OPTIMIZE = False
|
| 842 |
+
|
| 843 |
+
|
| 844 |
+
def _get_optimize(im, info):
|
| 845 |
+
"""
|
| 846 |
+
Palette optimization is a potentially expensive operation.
|
| 847 |
+
|
| 848 |
+
This function determines if the palette should be optimized using
|
| 849 |
+
some heuristics, then returns the list of palette entries in use.
|
| 850 |
+
|
| 851 |
+
:param im: Image object
|
| 852 |
+
:param info: encoderinfo
|
| 853 |
+
:returns: list of indexes of palette entries in use, or None
|
| 854 |
+
"""
|
| 855 |
+
if im.mode in ("P", "L") and info and info.get("optimize"):
|
| 856 |
+
# Potentially expensive operation.
|
| 857 |
+
|
| 858 |
+
# The palette saves 3 bytes per color not used, but palette
|
| 859 |
+
# lengths are restricted to 3*(2**N) bytes. Max saving would
|
| 860 |
+
# be 768 -> 6 bytes if we went all the way down to 2 colors.
|
| 861 |
+
# * If we're over 128 colors, we can't save any space.
|
| 862 |
+
# * If there aren't any holes, it's not worth collapsing.
|
| 863 |
+
# * If we have a 'large' image, the palette is in the noise.
|
| 864 |
+
|
| 865 |
+
# create the new palette if not every color is used
|
| 866 |
+
optimise = _FORCE_OPTIMIZE or im.mode == "L"
|
| 867 |
+
if optimise or im.width * im.height < 512 * 512:
|
| 868 |
+
# check which colors are used
|
| 869 |
+
used_palette_colors = []
|
| 870 |
+
for i, count in enumerate(im.histogram()):
|
| 871 |
+
if count:
|
| 872 |
+
used_palette_colors.append(i)
|
| 873 |
+
|
| 874 |
+
if optimise or max(used_palette_colors) >= len(used_palette_colors):
|
| 875 |
+
return used_palette_colors
|
| 876 |
+
|
| 877 |
+
num_palette_colors = len(im.palette.palette) // Image.getmodebands(
|
| 878 |
+
im.palette.mode
|
| 879 |
+
)
|
| 880 |
+
current_palette_size = 1 << (num_palette_colors - 1).bit_length()
|
| 881 |
+
if (
|
| 882 |
+
# check that the palette would become smaller when saved
|
| 883 |
+
len(used_palette_colors) <= current_palette_size // 2
|
| 884 |
+
# check that the palette is not already the smallest possible size
|
| 885 |
+
and current_palette_size > 2
|
| 886 |
+
):
|
| 887 |
+
return used_palette_colors
|
| 888 |
+
|
| 889 |
+
|
| 890 |
+
def _get_color_table_size(palette_bytes):
|
| 891 |
+
# calculate the palette size for the header
|
| 892 |
+
if not palette_bytes:
|
| 893 |
+
return 0
|
| 894 |
+
elif len(palette_bytes) < 9:
|
| 895 |
+
return 1
|
| 896 |
+
else:
|
| 897 |
+
return math.ceil(math.log(len(palette_bytes) // 3, 2)) - 1
|
| 898 |
+
|
| 899 |
+
|
| 900 |
+
def _get_header_palette(palette_bytes):
|
| 901 |
+
"""
|
| 902 |
+
Returns the palette, null padded to the next power of 2 (*3) bytes
|
| 903 |
+
suitable for direct inclusion in the GIF header
|
| 904 |
+
|
| 905 |
+
:param palette_bytes: Unpadded palette bytes, in RGBRGB form
|
| 906 |
+
:returns: Null padded palette
|
| 907 |
+
"""
|
| 908 |
+
color_table_size = _get_color_table_size(palette_bytes)
|
| 909 |
+
|
| 910 |
+
# add the missing amount of bytes
|
| 911 |
+
# the palette has to be 2<<n in size
|
| 912 |
+
actual_target_size_diff = (2 << color_table_size) - len(palette_bytes) // 3
|
| 913 |
+
if actual_target_size_diff > 0:
|
| 914 |
+
palette_bytes += o8(0) * 3 * actual_target_size_diff
|
| 915 |
+
return palette_bytes
|
| 916 |
+
|
| 917 |
+
|
| 918 |
+
def _get_palette_bytes(im):
|
| 919 |
+
"""
|
| 920 |
+
Gets the palette for inclusion in the gif header
|
| 921 |
+
|
| 922 |
+
:param im: Image object
|
| 923 |
+
:returns: Bytes, len<=768 suitable for inclusion in gif header
|
| 924 |
+
"""
|
| 925 |
+
return im.palette.palette if im.palette else b""
|
| 926 |
+
|
| 927 |
+
|
| 928 |
+
def _get_background(im, info_background):
|
| 929 |
+
background = 0
|
| 930 |
+
if info_background:
|
| 931 |
+
if isinstance(info_background, tuple):
|
| 932 |
+
# WebPImagePlugin stores an RGBA value in info["background"]
|
| 933 |
+
# So it must be converted to the same format as GifImagePlugin's
|
| 934 |
+
# info["background"] - a global color table index
|
| 935 |
+
try:
|
| 936 |
+
background = im.palette.getcolor(info_background, im)
|
| 937 |
+
except ValueError as e:
|
| 938 |
+
if str(e) not in (
|
| 939 |
+
# If all 256 colors are in use,
|
| 940 |
+
# then there is no need for the background color
|
| 941 |
+
"cannot allocate more than 256 colors",
|
| 942 |
+
# Ignore non-opaque WebP background
|
| 943 |
+
"cannot add non-opaque RGBA color to RGB palette",
|
| 944 |
+
):
|
| 945 |
+
raise
|
| 946 |
+
else:
|
| 947 |
+
background = info_background
|
| 948 |
+
return background
|
| 949 |
+
|
| 950 |
+
|
| 951 |
+
def _get_global_header(im, info):
|
| 952 |
+
"""Return a list of strings representing a GIF header"""
|
| 953 |
+
|
| 954 |
+
# Header Block
|
| 955 |
+
# https://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
|
| 956 |
+
|
| 957 |
+
version = b"87a"
|
| 958 |
+
if im.info.get("version") == b"89a" or (
|
| 959 |
+
info
|
| 960 |
+
and (
|
| 961 |
+
"transparency" in info
|
| 962 |
+
or info.get("loop") is not None
|
| 963 |
+
or info.get("duration")
|
| 964 |
+
or info.get("comment")
|
| 965 |
+
)
|
| 966 |
+
):
|
| 967 |
+
version = b"89a"
|
| 968 |
+
|
| 969 |
+
background = _get_background(im, info.get("background"))
|
| 970 |
+
|
| 971 |
+
palette_bytes = _get_palette_bytes(im)
|
| 972 |
+
color_table_size = _get_color_table_size(palette_bytes)
|
| 973 |
+
|
| 974 |
+
header = [
|
| 975 |
+
b"GIF" # signature
|
| 976 |
+
+ version # version
|
| 977 |
+
+ o16(im.size[0]) # canvas width
|
| 978 |
+
+ o16(im.size[1]), # canvas height
|
| 979 |
+
# Logical Screen Descriptor
|
| 980 |
+
# size of global color table + global color table flag
|
| 981 |
+
o8(color_table_size + 128), # packed fields
|
| 982 |
+
# background + reserved/aspect
|
| 983 |
+
o8(background) + o8(0),
|
| 984 |
+
# Global Color Table
|
| 985 |
+
_get_header_palette(palette_bytes),
|
| 986 |
+
]
|
| 987 |
+
if info.get("loop") is not None:
|
| 988 |
+
header.append(
|
| 989 |
+
b"!"
|
| 990 |
+
+ o8(255) # extension intro
|
| 991 |
+
+ o8(11)
|
| 992 |
+
+ b"NETSCAPE2.0"
|
| 993 |
+
+ o8(3)
|
| 994 |
+
+ o8(1)
|
| 995 |
+
+ o16(info["loop"]) # number of loops
|
| 996 |
+
+ o8(0)
|
| 997 |
+
)
|
| 998 |
+
if info.get("comment"):
|
| 999 |
+
comment_block = b"!" + o8(254) # extension intro
|
| 1000 |
+
|
| 1001 |
+
comment = info["comment"]
|
| 1002 |
+
if isinstance(comment, str):
|
| 1003 |
+
comment = comment.encode()
|
| 1004 |
+
for i in range(0, len(comment), 255):
|
| 1005 |
+
subblock = comment[i : i + 255]
|
| 1006 |
+
comment_block += o8(len(subblock)) + subblock
|
| 1007 |
+
|
| 1008 |
+
comment_block += o8(0)
|
| 1009 |
+
header.append(comment_block)
|
| 1010 |
+
return header
|
| 1011 |
+
|
| 1012 |
+
|
| 1013 |
+
def _write_frame_data(fp, im_frame, offset, params):
|
| 1014 |
+
try:
|
| 1015 |
+
im_frame.encoderinfo = params
|
| 1016 |
+
|
| 1017 |
+
# local image header
|
| 1018 |
+
_write_local_header(fp, im_frame, offset, 0)
|
| 1019 |
+
|
| 1020 |
+
ImageFile._save(
|
| 1021 |
+
im_frame, fp, [("gif", (0, 0) + im_frame.size, 0, RAWMODE[im_frame.mode])]
|
| 1022 |
+
)
|
| 1023 |
+
|
| 1024 |
+
fp.write(b"\0") # end of image data
|
| 1025 |
+
finally:
|
| 1026 |
+
del im_frame.encoderinfo
|
| 1027 |
+
|
| 1028 |
+
|
| 1029 |
+
# --------------------------------------------------------------------
|
| 1030 |
+
# Legacy GIF utilities
|
| 1031 |
+
|
| 1032 |
+
|
| 1033 |
+
def getheader(im, palette=None, info=None):
|
| 1034 |
+
"""
|
| 1035 |
+
Legacy Method to get Gif data from image.
|
| 1036 |
+
|
| 1037 |
+
Warning:: May modify image data.
|
| 1038 |
+
|
| 1039 |
+
:param im: Image object
|
| 1040 |
+
:param palette: bytes object containing the source palette, or ....
|
| 1041 |
+
:param info: encoderinfo
|
| 1042 |
+
:returns: tuple of(list of header items, optimized palette)
|
| 1043 |
+
|
| 1044 |
+
"""
|
| 1045 |
+
used_palette_colors = _get_optimize(im, info)
|
| 1046 |
+
|
| 1047 |
+
if info is None:
|
| 1048 |
+
info = {}
|
| 1049 |
+
|
| 1050 |
+
if "background" not in info and "background" in im.info:
|
| 1051 |
+
info["background"] = im.info["background"]
|
| 1052 |
+
|
| 1053 |
+
im_mod = _normalize_palette(im, palette, info)
|
| 1054 |
+
im.palette = im_mod.palette
|
| 1055 |
+
im.im = im_mod.im
|
| 1056 |
+
header = _get_global_header(im, info)
|
| 1057 |
+
|
| 1058 |
+
return header, used_palette_colors
|
| 1059 |
+
|
| 1060 |
+
|
| 1061 |
+
def getdata(im, offset=(0, 0), **params):
|
| 1062 |
+
"""
|
| 1063 |
+
Legacy Method
|
| 1064 |
+
|
| 1065 |
+
Return a list of strings representing this image.
|
| 1066 |
+
The first string is a local image header, the rest contains
|
| 1067 |
+
encoded image data.
|
| 1068 |
+
|
| 1069 |
+
To specify duration, add the time in milliseconds,
|
| 1070 |
+
e.g. ``getdata(im_frame, duration=1000)``
|
| 1071 |
+
|
| 1072 |
+
:param im: Image object
|
| 1073 |
+
:param offset: Tuple of (x, y) pixels. Defaults to (0, 0)
|
| 1074 |
+
:param \\**params: e.g. duration or other encoder info parameters
|
| 1075 |
+
:returns: List of bytes containing GIF encoded frame data
|
| 1076 |
+
|
| 1077 |
+
"""
|
| 1078 |
+
|
| 1079 |
+
class Collector:
|
| 1080 |
+
data = []
|
| 1081 |
+
|
| 1082 |
+
def write(self, data):
|
| 1083 |
+
self.data.append(data)
|
| 1084 |
+
|
| 1085 |
+
im.load() # make sure raster data is available
|
| 1086 |
+
|
| 1087 |
+
fp = Collector()
|
| 1088 |
+
|
| 1089 |
+
_write_frame_data(fp, im, offset, params)
|
| 1090 |
+
|
| 1091 |
+
return fp.data
|
| 1092 |
+
|
| 1093 |
+
|
| 1094 |
+
# --------------------------------------------------------------------
|
| 1095 |
+
# Registry
|
| 1096 |
+
|
| 1097 |
+
Image.register_open(GifImageFile.format, GifImageFile, _accept)
|
| 1098 |
+
Image.register_save(GifImageFile.format, _save)
|
| 1099 |
+
Image.register_save_all(GifImageFile.format, _save_all)
|
| 1100 |
+
Image.register_extension(GifImageFile.format, ".gif")
|
| 1101 |
+
Image.register_mime(GifImageFile.format, "image/gif")
|
| 1102 |
+
|
| 1103 |
+
#
|
| 1104 |
+
# Uncomment the following line if you wish to use NETPBM/PBMPLUS
|
| 1105 |
+
# instead of the built-in "uncompressed" GIF encoder
|
| 1106 |
+
|
| 1107 |
+
# Image.register_save(GifImageFile.format, _save_netpbm)
|
venv/Lib/site-packages/PIL/GimpGradientFile.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# Python Imaging Library
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# stuff to read (and render) GIMP gradient files
|
| 6 |
+
#
|
| 7 |
+
# History:
|
| 8 |
+
# 97-08-23 fl Created
|
| 9 |
+
#
|
| 10 |
+
# Copyright (c) Secret Labs AB 1997.
|
| 11 |
+
# Copyright (c) Fredrik Lundh 1997.
|
| 12 |
+
#
|
| 13 |
+
# See the README file for information on usage and redistribution.
|
| 14 |
+
#
|
| 15 |
+
|
| 16 |
+
"""
|
| 17 |
+
Stuff to translate curve segments to palette values (derived from
|
| 18 |
+
the corresponding code in GIMP, written by Federico Mena Quintero.
|
| 19 |
+
See the GIMP distribution for more information.)
|
| 20 |
+
"""
|
| 21 |
+
from __future__ import annotations
|
| 22 |
+
|
| 23 |
+
from math import log, pi, sin, sqrt
|
| 24 |
+
|
| 25 |
+
from ._binary import o8
|
| 26 |
+
|
| 27 |
+
EPSILON = 1e-10
|
| 28 |
+
"""""" # Enable auto-doc for data member
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def linear(middle, pos):
|
| 32 |
+
if pos <= middle:
|
| 33 |
+
if middle < EPSILON:
|
| 34 |
+
return 0.0
|
| 35 |
+
else:
|
| 36 |
+
return 0.5 * pos / middle
|
| 37 |
+
else:
|
| 38 |
+
pos = pos - middle
|
| 39 |
+
middle = 1.0 - middle
|
| 40 |
+
if middle < EPSILON:
|
| 41 |
+
return 1.0
|
| 42 |
+
else:
|
| 43 |
+
return 0.5 + 0.5 * pos / middle
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def curved(middle, pos):
|
| 47 |
+
return pos ** (log(0.5) / log(max(middle, EPSILON)))
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def sine(middle, pos):
|
| 51 |
+
return (sin((-pi / 2.0) + pi * linear(middle, pos)) + 1.0) / 2.0
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def sphere_increasing(middle, pos):
|
| 55 |
+
return sqrt(1.0 - (linear(middle, pos) - 1.0) ** 2)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def sphere_decreasing(middle, pos):
|
| 59 |
+
return 1.0 - sqrt(1.0 - linear(middle, pos) ** 2)
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
SEGMENTS = [linear, curved, sine, sphere_increasing, sphere_decreasing]
|
| 63 |
+
"""""" # Enable auto-doc for data member
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
class GradientFile:
|
| 67 |
+
gradient = None
|
| 68 |
+
|
| 69 |
+
def getpalette(self, entries=256):
|
| 70 |
+
palette = []
|
| 71 |
+
|
| 72 |
+
ix = 0
|
| 73 |
+
x0, x1, xm, rgb0, rgb1, segment = self.gradient[ix]
|
| 74 |
+
|
| 75 |
+
for i in range(entries):
|
| 76 |
+
x = i / (entries - 1)
|
| 77 |
+
|
| 78 |
+
while x1 < x:
|
| 79 |
+
ix += 1
|
| 80 |
+
x0, x1, xm, rgb0, rgb1, segment = self.gradient[ix]
|
| 81 |
+
|
| 82 |
+
w = x1 - x0
|
| 83 |
+
|
| 84 |
+
if w < EPSILON:
|
| 85 |
+
scale = segment(0.5, 0.5)
|
| 86 |
+
else:
|
| 87 |
+
scale = segment((xm - x0) / w, (x - x0) / w)
|
| 88 |
+
|
| 89 |
+
# expand to RGBA
|
| 90 |
+
r = o8(int(255 * ((rgb1[0] - rgb0[0]) * scale + rgb0[0]) + 0.5))
|
| 91 |
+
g = o8(int(255 * ((rgb1[1] - rgb0[1]) * scale + rgb0[1]) + 0.5))
|
| 92 |
+
b = o8(int(255 * ((rgb1[2] - rgb0[2]) * scale + rgb0[2]) + 0.5))
|
| 93 |
+
a = o8(int(255 * ((rgb1[3] - rgb0[3]) * scale + rgb0[3]) + 0.5))
|
| 94 |
+
|
| 95 |
+
# add to palette
|
| 96 |
+
palette.append(r + g + b + a)
|
| 97 |
+
|
| 98 |
+
return b"".join(palette), "RGBA"
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
class GimpGradientFile(GradientFile):
|
| 102 |
+
"""File handler for GIMP's gradient format."""
|
| 103 |
+
|
| 104 |
+
def __init__(self, fp):
|
| 105 |
+
if fp.readline()[:13] != b"GIMP Gradient":
|
| 106 |
+
msg = "not a GIMP gradient file"
|
| 107 |
+
raise SyntaxError(msg)
|
| 108 |
+
|
| 109 |
+
line = fp.readline()
|
| 110 |
+
|
| 111 |
+
# GIMP 1.2 gradient files don't contain a name, but GIMP 1.3 files do
|
| 112 |
+
if line.startswith(b"Name: "):
|
| 113 |
+
line = fp.readline().strip()
|
| 114 |
+
|
| 115 |
+
count = int(line)
|
| 116 |
+
|
| 117 |
+
gradient = []
|
| 118 |
+
|
| 119 |
+
for i in range(count):
|
| 120 |
+
s = fp.readline().split()
|
| 121 |
+
w = [float(x) for x in s[:11]]
|
| 122 |
+
|
| 123 |
+
x0, x1 = w[0], w[2]
|
| 124 |
+
xm = w[1]
|
| 125 |
+
rgb0 = w[3:7]
|
| 126 |
+
rgb1 = w[7:11]
|
| 127 |
+
|
| 128 |
+
segment = SEGMENTS[int(s[11])]
|
| 129 |
+
cspace = int(s[12])
|
| 130 |
+
|
| 131 |
+
if cspace != 0:
|
| 132 |
+
msg = "cannot handle HSV colour space"
|
| 133 |
+
raise OSError(msg)
|
| 134 |
+
|
| 135 |
+
gradient.append((x0, x1, xm, rgb0, rgb1, segment))
|
| 136 |
+
|
| 137 |
+
self.gradient = gradient
|
venv/Lib/site-packages/PIL/GimpPaletteFile.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# Python Imaging Library
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# stuff to read GIMP palette files
|
| 6 |
+
#
|
| 7 |
+
# History:
|
| 8 |
+
# 1997-08-23 fl Created
|
| 9 |
+
# 2004-09-07 fl Support GIMP 2.0 palette files.
|
| 10 |
+
#
|
| 11 |
+
# Copyright (c) Secret Labs AB 1997-2004. All rights reserved.
|
| 12 |
+
# Copyright (c) Fredrik Lundh 1997-2004.
|
| 13 |
+
#
|
| 14 |
+
# See the README file for information on usage and redistribution.
|
| 15 |
+
#
|
| 16 |
+
from __future__ import annotations
|
| 17 |
+
|
| 18 |
+
import re
|
| 19 |
+
|
| 20 |
+
from ._binary import o8
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class GimpPaletteFile:
|
| 24 |
+
"""File handler for GIMP's palette format."""
|
| 25 |
+
|
| 26 |
+
rawmode = "RGB"
|
| 27 |
+
|
| 28 |
+
def __init__(self, fp):
|
| 29 |
+
self.palette = [o8(i) * 3 for i in range(256)]
|
| 30 |
+
|
| 31 |
+
if fp.readline()[:12] != b"GIMP Palette":
|
| 32 |
+
msg = "not a GIMP palette file"
|
| 33 |
+
raise SyntaxError(msg)
|
| 34 |
+
|
| 35 |
+
for i in range(256):
|
| 36 |
+
s = fp.readline()
|
| 37 |
+
if not s:
|
| 38 |
+
break
|
| 39 |
+
|
| 40 |
+
# skip fields and comment lines
|
| 41 |
+
if re.match(rb"\w+:|#", s):
|
| 42 |
+
continue
|
| 43 |
+
if len(s) > 100:
|
| 44 |
+
msg = "bad palette file"
|
| 45 |
+
raise SyntaxError(msg)
|
| 46 |
+
|
| 47 |
+
v = tuple(map(int, s.split()[:3]))
|
| 48 |
+
if len(v) != 3:
|
| 49 |
+
msg = "bad palette entry"
|
| 50 |
+
raise ValueError(msg)
|
| 51 |
+
|
| 52 |
+
self.palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2])
|
| 53 |
+
|
| 54 |
+
self.palette = b"".join(self.palette)
|
| 55 |
+
|
| 56 |
+
def getpalette(self):
|
| 57 |
+
return self.palette, self.rawmode
|
venv/Lib/site-packages/PIL/GribStubImagePlugin.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# GRIB stub adapter
|
| 6 |
+
#
|
| 7 |
+
# Copyright (c) 1996-2003 by Fredrik Lundh
|
| 8 |
+
#
|
| 9 |
+
# See the README file for information on usage and redistribution.
|
| 10 |
+
#
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
from . import Image, ImageFile
|
| 14 |
+
|
| 15 |
+
_handler = None
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def register_handler(handler):
|
| 19 |
+
"""
|
| 20 |
+
Install application-specific GRIB image handler.
|
| 21 |
+
|
| 22 |
+
:param handler: Handler object.
|
| 23 |
+
"""
|
| 24 |
+
global _handler
|
| 25 |
+
_handler = handler
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
# --------------------------------------------------------------------
|
| 29 |
+
# Image adapter
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _accept(prefix):
|
| 33 |
+
return prefix[:4] == b"GRIB" and prefix[7] == 1
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class GribStubImageFile(ImageFile.StubImageFile):
|
| 37 |
+
format = "GRIB"
|
| 38 |
+
format_description = "GRIB"
|
| 39 |
+
|
| 40 |
+
def _open(self):
|
| 41 |
+
offset = self.fp.tell()
|
| 42 |
+
|
| 43 |
+
if not _accept(self.fp.read(8)):
|
| 44 |
+
msg = "Not a GRIB file"
|
| 45 |
+
raise SyntaxError(msg)
|
| 46 |
+
|
| 47 |
+
self.fp.seek(offset)
|
| 48 |
+
|
| 49 |
+
# make something up
|
| 50 |
+
self._mode = "F"
|
| 51 |
+
self._size = 1, 1
|
| 52 |
+
|
| 53 |
+
loader = self._load()
|
| 54 |
+
if loader:
|
| 55 |
+
loader.open(self)
|
| 56 |
+
|
| 57 |
+
def _load(self):
|
| 58 |
+
return _handler
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def _save(im, fp, filename):
|
| 62 |
+
if _handler is None or not hasattr(_handler, "save"):
|
| 63 |
+
msg = "GRIB save handler not installed"
|
| 64 |
+
raise OSError(msg)
|
| 65 |
+
_handler.save(im, fp, filename)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
# --------------------------------------------------------------------
|
| 69 |
+
# Registry
|
| 70 |
+
|
| 71 |
+
Image.register_open(GribStubImageFile.format, GribStubImageFile, _accept)
|
| 72 |
+
Image.register_save(GribStubImageFile.format, _save)
|
| 73 |
+
|
| 74 |
+
Image.register_extension(GribStubImageFile.format, ".grib")
|
venv/Lib/site-packages/PIL/Hdf5StubImagePlugin.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# The Python Imaging Library
|
| 3 |
+
# $Id$
|
| 4 |
+
#
|
| 5 |
+
# HDF5 stub adapter
|
| 6 |
+
#
|
| 7 |
+
# Copyright (c) 2000-2003 by Fredrik Lundh
|
| 8 |
+
#
|
| 9 |
+
# See the README file for information on usage and redistribution.
|
| 10 |
+
#
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
from . import Image, ImageFile
|
| 14 |
+
|
| 15 |
+
_handler = None
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def register_handler(handler):
|
| 19 |
+
"""
|
| 20 |
+
Install application-specific HDF5 image handler.
|
| 21 |
+
|
| 22 |
+
:param handler: Handler object.
|
| 23 |
+
"""
|
| 24 |
+
global _handler
|
| 25 |
+
_handler = handler
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
# --------------------------------------------------------------------
|
| 29 |
+
# Image adapter
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _accept(prefix):
|
| 33 |
+
return prefix[:8] == b"\x89HDF\r\n\x1a\n"
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class HDF5StubImageFile(ImageFile.StubImageFile):
|
| 37 |
+
format = "HDF5"
|
| 38 |
+
format_description = "HDF5"
|
| 39 |
+
|
| 40 |
+
def _open(self):
|
| 41 |
+
offset = self.fp.tell()
|
| 42 |
+
|
| 43 |
+
if not _accept(self.fp.read(8)):
|
| 44 |
+
msg = "Not an HDF file"
|
| 45 |
+
raise SyntaxError(msg)
|
| 46 |
+
|
| 47 |
+
self.fp.seek(offset)
|
| 48 |
+
|
| 49 |
+
# make something up
|
| 50 |
+
self._mode = "F"
|
| 51 |
+
self._size = 1, 1
|
| 52 |
+
|
| 53 |
+
loader = self._load()
|
| 54 |
+
if loader:
|
| 55 |
+
loader.open(self)
|
| 56 |
+
|
| 57 |
+
def _load(self):
|
| 58 |
+
return _handler
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def _save(im, fp, filename):
|
| 62 |
+
if _handler is None or not hasattr(_handler, "save"):
|
| 63 |
+
msg = "HDF5 save handler not installed"
|
| 64 |
+
raise OSError(msg)
|
| 65 |
+
_handler.save(im, fp, filename)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
# --------------------------------------------------------------------
|
| 69 |
+
# Registry
|
| 70 |
+
|
| 71 |
+
Image.register_open(HDF5StubImageFile.format, HDF5StubImageFile, _accept)
|
| 72 |
+
Image.register_save(HDF5StubImageFile.format, _save)
|
| 73 |
+
|
| 74 |
+
Image.register_extensions(HDF5StubImageFile.format, [".h5", ".hdf"])
|