Spaces:
Running on Zero
Running on Zero
Upload folder using huggingface_hub
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +7 -0
- LICENSE +21 -0
- LICENSE-timm.txt +201 -0
- NOTICE +20 -0
- README.md +64 -7
- app.py +280 -0
- examples/acoustic_guitar.jpg +3 -0
- examples/bird_kingfisher.jpg +0 -0
- examples/chameleon.jpg +3 -0
- examples/hot_air_balloon.jpg +0 -0
- examples/husky_dog.jpg +0 -0
- examples/library_interior.jpg +3 -0
- examples/monstera_plant.jpg +3 -0
- examples/pizza_board.jpg +3 -0
- examples/red_fox.jpg +3 -0
- examples/spiral_staircase.jpg +3 -0
- examples/vintage_camera.jpg +0 -0
- imagenet_classes.json +1002 -0
- requirements.txt +5 -0
- timm/__init__.py +4 -0
- timm/data/__init__.py +23 -0
- timm/data/auto_augment.py +997 -0
- timm/data/config.py +129 -0
- timm/data/constants.py +10 -0
- timm/data/dataset.py +200 -0
- timm/data/dataset_factory.py +224 -0
- timm/data/dataset_info.py +73 -0
- timm/data/distributed_sampler.py +135 -0
- timm/data/loader.py +413 -0
- timm/data/mixup.py +315 -0
- timm/data/random_erasing.py +117 -0
- timm/data/readers/__init__.py +2 -0
- timm/data/readers/class_map.py +22 -0
- timm/data/readers/img_extensions.py +50 -0
- timm/data/readers/reader.py +16 -0
- timm/data/readers/reader_factory.py +45 -0
- timm/data/readers/reader_hfds.py +84 -0
- timm/data/readers/reader_hfids.py +215 -0
- timm/data/readers/reader_image_folder.py +99 -0
- timm/data/readers/reader_image_in_tar.py +229 -0
- timm/data/readers/reader_tfds.py +355 -0
- timm/data/readers/reader_wds.py +466 -0
- timm/data/readers/shared_count.py +14 -0
- timm/data/real_labels.py +47 -0
- timm/data/tf_preprocessing.py +233 -0
- timm/data/transforms.py +534 -0
- timm/data/transforms_factory.py +440 -0
- timm/layers/__init__.py +57 -0
- timm/layers/activations.py +173 -0
- timm/layers/activations_jit.py +90 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,10 @@ 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 |
+
examples/acoustic_guitar.jpg filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
examples/chameleon.jpg filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
examples/library_interior.jpg filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
examples/monstera_plant.jpg filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
examples/pizza_board.jpg filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
examples/red_fox.jpg filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
examples/spiral_staircase.jpg filter=lfs diff=lfs merge=lfs -text
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright 2026 Kiel University
|
| 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.
|
LICENSE-timm.txt
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 2019 Ross Wightman
|
| 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.
|
NOTICE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
=======================================================================
|
| 2 |
+
pytorch-image-models's Apache 2.0 license
|
| 3 |
+
=======================================================================
|
| 4 |
+
We modified and utilize the pytorch-image-models implementation from
|
| 5 |
+
https://github.com/huggingface/pytorch-image-models. The license application is as
|
| 6 |
+
follows:
|
| 7 |
+
|
| 8 |
+
Copyright 2019 Ross Wightman
|
| 9 |
+
|
| 10 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 11 |
+
you may not use this file except in compliance with the License.
|
| 12 |
+
You may obtain a copy of the License at
|
| 13 |
+
|
| 14 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 15 |
+
|
| 16 |
+
Unless required by applicable law or agreed to in writing, software
|
| 17 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 18 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 19 |
+
See the License for the specific language governing permissions and
|
| 20 |
+
limitations under the License.
|
README.md
CHANGED
|
@@ -1,13 +1,70 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.26.0
|
| 8 |
-
python_version: '3.12'
|
| 9 |
app_file: app.py
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: ProgResViT
|
| 3 |
+
emoji: 🪜
|
| 4 |
+
colorFrom: indigo
|
| 5 |
+
colorTo: purple
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.26.0
|
|
|
|
| 8 |
app_file: app.py
|
| 9 |
+
short_description: Adaptive-compute ViT that classifies in progressive rounds
|
| 10 |
+
python_version: "3.12"
|
| 11 |
+
startup_duration_timeout: 30m
|
| 12 |
+
license: mit
|
| 13 |
---
|
| 14 |
|
| 15 |
+
# ProgResViT: Progressive Resolution and Width for Adaptive Vision Transformers
|
| 16 |
+
|
| 17 |
+
Interactive ImageNet-1K demo of [ProgResViT](https://huggingface.co/papers/2609.03216)
|
| 18 |
+
(arXiv:2609.03216, Kiel University).
|
| 19 |
+
|
| 20 |
+
ProgResViT performs inference **progressively**. Round 1 processes a low-resolution
|
| 21 |
+
image with a narrow subnetwork (3 of 6 attention heads). If the round-1 prediction is
|
| 22 |
+
confident enough — measured by the entropy of its top-10 softmax — inference stops
|
| 23 |
+
there. Otherwise the model recycles the round-1 tokens and refines the prediction at a
|
| 24 |
+
higher input resolution with the full-width subnetwork. All rounds share a single
|
| 25 |
+
backbone, conditioned by **Progress-Conditioned Soft Gating (PSG)**.
|
| 26 |
+
|
| 27 |
+
The demo exposes that mechanism directly: it runs both rounds, shows each round's top-5
|
| 28 |
+
prediction, and reports which round the routing threshold would have stopped at, along
|
| 29 |
+
with the GMACs saved.
|
| 30 |
+
|
| 31 |
+
## Checkpoints
|
| 32 |
+
|
| 33 |
+
All four released DeiT-S checkpoints are available in the dropdown:
|
| 34 |
+
|
| 35 |
+
| Resolution schedule | Training | Top-1 | GMACs (full path) |
|
| 36 |
+
|---|---|---:|---:|
|
| 37 |
+
| 160 → 384 | KD | 84.90% | 16.152 |
|
| 38 |
+
| 160 → 384 | standard | 83.70% | 16.152 |
|
| 39 |
+
| 192 → 240 | KD | 83.80% | 6.267 |
|
| 40 |
+
| 192 → 240 | standard | 82.21% | 6.267 |
|
| 41 |
+
|
| 42 |
+
Weights: [NCPS on the Hub](https://huggingface.co/NCPS). Default routing thresholds are
|
| 43 |
+
the authors' reported operating points (≤0.03 pp top-1 drop).
|
| 44 |
+
|
| 45 |
+
## Implementation notes
|
| 46 |
+
|
| 47 |
+
- The ProgResViT model code is the authors' vendored `timm` fork, copied verbatim from
|
| 48 |
+
[ds-kiel/ProgResViT](https://github.com/ds-kiel/ProgResViT) (MIT; `NOTICE` and
|
| 49 |
+
`LICENSE-timm.txt` retained).
|
| 50 |
+
- Preprocessing matches `validate.py` upstream: bicubic resize with `crop_pct=0.9`,
|
| 51 |
+
center crop to the checkpoint's eval resolution, ImageNet mean/std.
|
| 52 |
+
- Rounds are run with `model._forward_stage(...)` exactly as the upstream evaluator
|
| 53 |
+
does, so both rounds are always computed and the routing decision is reported rather
|
| 54 |
+
than short-circuited — that is what makes the trade-off visible.
|
| 55 |
+
- GMACs figures are the authors' measured values from `results/RESULTS.md`.
|
| 56 |
+
|
| 57 |
+
## Credits
|
| 58 |
+
|
| 59 |
+
Example photographs come from
|
| 60 |
+
[linoyts/repo-to-space-example-inputs](https://huggingface.co/datasets/linoyts/repo-to-space-example-inputs).
|
| 61 |
+
|
| 62 |
+
## Citation
|
| 63 |
+
|
| 64 |
+
```bibtex
|
| 65 |
+
@article{progresvit2026,
|
| 66 |
+
title = {ProgResViT: Progressive Resolution and Width for Adaptive Vision Transformers},
|
| 67 |
+
year = {2026},
|
| 68 |
+
eprint = {2609.03216}
|
| 69 |
+
}
|
| 70 |
+
```
|
app.py
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""ProgResViT — progressive-resolution / progressive-width adaptive ViT.
|
| 2 |
+
|
| 3 |
+
Interactive ImageNet-1K classification demo that exposes the paper's
|
| 4 |
+
input-adaptive routing: round 1 runs a narrow subnetwork on a low-resolution
|
| 5 |
+
image, and only uncertain images continue to round 2 at higher resolution and
|
| 6 |
+
wider width.
|
| 7 |
+
|
| 8 |
+
Paper: https://huggingface.co/papers/2609.03216
|
| 9 |
+
Code: https://github.com/ds-kiel/ProgResViT
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
import json
|
| 13 |
+
import os
|
| 14 |
+
import time
|
| 15 |
+
|
| 16 |
+
import spaces # must precede torch
|
| 17 |
+
import torch
|
| 18 |
+
import gradio as gr
|
| 19 |
+
from PIL import Image
|
| 20 |
+
from huggingface_hub import hf_hub_download
|
| 21 |
+
from safetensors.torch import load_file
|
| 22 |
+
|
| 23 |
+
from timm.data.transforms_factory import create_transform
|
| 24 |
+
from timm.models import create_model
|
| 25 |
+
|
| 26 |
+
# ---------------------------------------------------------------------------
|
| 27 |
+
# Model registry
|
| 28 |
+
# ---------------------------------------------------------------------------
|
| 29 |
+
# GMACs come from the authors' measured sweeps (results/RESULTS.md in the
|
| 30 |
+
# upstream repo): the threshold=0 row is the full two-round cost, the
|
| 31 |
+
# threshold=10 row (every image exits after round 1) is the round-1 cost.
|
| 32 |
+
VARIANTS = {
|
| 33 |
+
"160 → 384 · KD (84.9% top-1)": {
|
| 34 |
+
"repo": "NCPS/progresvit-deit-s-160-384-kd-imagenet1k",
|
| 35 |
+
"sizes": (160, 384),
|
| 36 |
+
"gmacs": (0.615, 16.152),
|
| 37 |
+
"top1": (73.940, 84.894),
|
| 38 |
+
"amp": True,
|
| 39 |
+
"threshold": 0.226,
|
| 40 |
+
},
|
| 41 |
+
"160 → 384 (83.7% top-1)": {
|
| 42 |
+
"repo": "NCPS/progresvit-deit-s-160-384-imagenet1k",
|
| 43 |
+
"sizes": (160, 384),
|
| 44 |
+
"gmacs": (0.615, 16.152),
|
| 45 |
+
"top1": (70.616, 83.714),
|
| 46 |
+
"amp": False,
|
| 47 |
+
"threshold": 0.267,
|
| 48 |
+
},
|
| 49 |
+
"192 → 240 · KD (83.8% top-1)": {
|
| 50 |
+
"repo": "NCPS/progresvit-deit-s-192-240-kd-imagenet1k",
|
| 51 |
+
"sizes": (192, 240),
|
| 52 |
+
"gmacs": (0.912, 6.267),
|
| 53 |
+
"top1": (76.018, 83.794),
|
| 54 |
+
"amp": False,
|
| 55 |
+
"threshold": 0.209,
|
| 56 |
+
},
|
| 57 |
+
"192 → 240 (82.2% top-1)": {
|
| 58 |
+
"repo": "NCPS/progresvit-deit-s-192-240-imagenet1k",
|
| 59 |
+
"sizes": (192, 240),
|
| 60 |
+
"gmacs": (0.912, 6.267),
|
| 61 |
+
"top1": (73.238, 82.202),
|
| 62 |
+
"amp": False,
|
| 63 |
+
"threshold": 0.356,
|
| 64 |
+
},
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
DEFAULT_VARIANT = "160 → 384 · KD (84.9% top-1)"
|
| 68 |
+
DEFAULT_THRESHOLD = VARIANTS[DEFAULT_VARIANT]["threshold"]
|
| 69 |
+
PROGRESS_STAGES = (3, 6) # attention heads active in round 1 / round 2
|
| 70 |
+
CACHE_VERSION = 1
|
| 71 |
+
|
| 72 |
+
with open(os.path.join(os.path.dirname(__file__), "imagenet_classes.json")) as f:
|
| 73 |
+
IMAGENET_CLASSES = [json.load(f)[str(i)] for i in range(1000)]
|
| 74 |
+
|
| 75 |
+
MODELS = {}
|
| 76 |
+
TRANSFORMS = {}
|
| 77 |
+
CROPS = {}
|
| 78 |
+
|
| 79 |
+
for _name, _spec in VARIANTS.items():
|
| 80 |
+
_cfg = json.load(open(hf_hub_download(_spec["repo"], "config.json")))
|
| 81 |
+
_model = create_model(
|
| 82 |
+
"progresvit",
|
| 83 |
+
pretrained=False,
|
| 84 |
+
num_classes=_cfg["num_classes"],
|
| 85 |
+
**_cfg["model_args"],
|
| 86 |
+
)
|
| 87 |
+
_state = load_file(hf_hub_download(_spec["repo"], "model.safetensors"))
|
| 88 |
+
_model.load_state_dict(_state, strict=True)
|
| 89 |
+
_pc = _cfg["pretrained_cfg"]
|
| 90 |
+
TRANSFORMS[_name] = create_transform(
|
| 91 |
+
input_size=tuple(_pc["input_size"]),
|
| 92 |
+
is_training=False,
|
| 93 |
+
interpolation=_pc["interpolation"],
|
| 94 |
+
mean=tuple(_pc["mean"]),
|
| 95 |
+
std=tuple(_pc["std"]),
|
| 96 |
+
crop_pct=_pc["crop_pct"],
|
| 97 |
+
crop_mode=_pc["crop_mode"],
|
| 98 |
+
crop_border_pixels=0,
|
| 99 |
+
use_prefetcher=False,
|
| 100 |
+
)
|
| 101 |
+
CROPS[_name] = int(_pc["input_size"][-1])
|
| 102 |
+
MODELS[_name] = _model.eval().to("cuda")
|
| 103 |
+
print(f"loaded {_name} from {_spec['repo']} (eval crop {CROPS[_name]})", flush=True)
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def _topk_dict(logits: torch.Tensor, k: int = 5) -> dict:
|
| 107 |
+
probs = logits.float().softmax(dim=-1)[0]
|
| 108 |
+
values, indices = probs.topk(k)
|
| 109 |
+
return {IMAGENET_CLASSES[int(i)]: float(v) for v, i in zip(values, indices)}
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
@spaces.GPU(duration=30)
|
| 113 |
+
def classify(
|
| 114 |
+
image: Image.Image,
|
| 115 |
+
variant: str = DEFAULT_VARIANT,
|
| 116 |
+
threshold: float = DEFAULT_THRESHOLD,
|
| 117 |
+
) -> tuple:
|
| 118 |
+
"""Classify an image with ProgResViT's progressive, input-adaptive rounds.
|
| 119 |
+
|
| 120 |
+
Args:
|
| 121 |
+
image: input photograph to classify against the 1000 ImageNet-1K classes.
|
| 122 |
+
variant: which ProgResViT DeiT-S checkpoint to use (resolution schedule
|
| 123 |
+
and whether it was trained with knowledge distillation).
|
| 124 |
+
threshold: routing threshold on the round-1 top-10 prediction entropy.
|
| 125 |
+
The image exits after the cheap first round when its entropy falls
|
| 126 |
+
below this value; higher values exit more images and save more
|
| 127 |
+
compute.
|
| 128 |
+
|
| 129 |
+
Returns:
|
| 130 |
+
A tuple of (final top-5 prediction, routing report in markdown,
|
| 131 |
+
round-1 top-5 prediction, round-2 top-5 prediction).
|
| 132 |
+
"""
|
| 133 |
+
if image is None:
|
| 134 |
+
raise gr.Error("Please provide an image.")
|
| 135 |
+
|
| 136 |
+
spec = VARIANTS[variant]
|
| 137 |
+
model = MODELS[variant]
|
| 138 |
+
sizes = spec["sizes"]
|
| 139 |
+
g1, g2 = spec["gmacs"]
|
| 140 |
+
|
| 141 |
+
x = TRANSFORMS[variant](image.convert("RGB")).unsqueeze(0).to("cuda")
|
| 142 |
+
|
| 143 |
+
started = time.perf_counter()
|
| 144 |
+
with torch.inference_mode():
|
| 145 |
+
if spec["amp"]:
|
| 146 |
+
ctx = torch.autocast("cuda", dtype=torch.bfloat16)
|
| 147 |
+
else:
|
| 148 |
+
ctx = torch.autocast("cuda", enabled=False)
|
| 149 |
+
with ctx:
|
| 150 |
+
tokens1, logits1 = model._forward_stage(
|
| 151 |
+
x, 0, None, PROGRESS_STAGES, sizes
|
| 152 |
+
)
|
| 153 |
+
_, logits2 = model._forward_stage(
|
| 154 |
+
x, 1, tokens1, PROGRESS_STAGES, sizes
|
| 155 |
+
)
|
| 156 |
+
entropy = float(model.entropy(logits1.float())[0, 0])
|
| 157 |
+
elapsed = time.perf_counter() - started
|
| 158 |
+
|
| 159 |
+
exited_early = entropy < threshold
|
| 160 |
+
final_logits = logits1 if exited_early else logits2
|
| 161 |
+
used_gmacs = g1 if exited_early else g2
|
| 162 |
+
saving = 100.0 * (1.0 - used_gmacs / g2)
|
| 163 |
+
|
| 164 |
+
round1 = _topk_dict(logits1)
|
| 165 |
+
round2 = _topk_dict(logits2)
|
| 166 |
+
final = _topk_dict(final_logits)
|
| 167 |
+
|
| 168 |
+
if exited_early:
|
| 169 |
+
decision = (
|
| 170 |
+
f"**Exited after round 1.** Entropy `{entropy:.3f}` is below the "
|
| 171 |
+
f"threshold `{threshold:.3f}`, so the {sizes[1]} px round was skipped."
|
| 172 |
+
)
|
| 173 |
+
else:
|
| 174 |
+
decision = (
|
| 175 |
+
f"**Continued to round 2.** Entropy `{entropy:.3f}` is at or above the "
|
| 176 |
+
f"threshold `{threshold:.3f}`, so round 1's tokens were recycled and "
|
| 177 |
+
f"refined at {sizes[1]} px."
|
| 178 |
+
)
|
| 179 |
+
|
| 180 |
+
report = f"""### Routing
|
| 181 |
+
|
| 182 |
+
{decision}
|
| 183 |
+
|
| 184 |
+
| | Round 1 | Round 2 | This image |
|
| 185 |
+
|---|---|---|---|
|
| 186 |
+
| Input resolution | {sizes[0]} px | {sizes[1]} px | **{sizes[0] if exited_early else sizes[1]} px** |
|
| 187 |
+
| Active attention heads | {PROGRESS_STAGES[0]} / 6 | {PROGRESS_STAGES[1]} / 6 | **{PROGRESS_STAGES[0] if exited_early else PROGRESS_STAGES[1]} / 6** |
|
| 188 |
+
| Cumulative GMACs | {g1:.3f} | {g2:.3f} | **{used_gmacs:.3f}** |
|
| 189 |
+
| ImageNet top-1 if always stopped here | {spec['top1'][0]:.2f}% | {spec['top1'][1]:.2f}% | — |
|
| 190 |
+
|
| 191 |
+
Compute saved versus always running both rounds: **{saving:.1f}%** · inference {elapsed * 1000:.0f} ms
|
| 192 |
+
"""
|
| 193 |
+
return final, report, round1, round2
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
CSS = """
|
| 197 |
+
#col-container { max-width: 1180px; margin: 0 auto; }
|
| 198 |
+
.dark .gradio-container { color: var(--body-text-color); }
|
| 199 |
+
"""
|
| 200 |
+
|
| 201 |
+
# Ordered so the first rows tell the story: `red_fox` stays uncertain after round 1
|
| 202 |
+
# (which calls it a kit fox) and gets corrected in round 2, while `acoustic_guitar`
|
| 203 |
+
# is confident enough to exit after the cheap first round.
|
| 204 |
+
EXAMPLES = [
|
| 205 |
+
["examples/red_fox.jpg"],
|
| 206 |
+
["examples/acoustic_guitar.jpg"],
|
| 207 |
+
["examples/husky_dog.jpg"],
|
| 208 |
+
["examples/pizza_board.jpg"],
|
| 209 |
+
["examples/bird_kingfisher.jpg"],
|
| 210 |
+
["examples/chameleon.jpg"],
|
| 211 |
+
["examples/hot_air_balloon.jpg"],
|
| 212 |
+
["examples/vintage_camera.jpg"],
|
| 213 |
+
["examples/library_interior.jpg"],
|
| 214 |
+
["examples/spiral_staircase.jpg"],
|
| 215 |
+
["examples/monstera_plant.jpg"],
|
| 216 |
+
]
|
| 217 |
+
|
| 218 |
+
with gr.Blocks(theme=gr.themes.Citrus(), css=CSS) as demo:
|
| 219 |
+
with gr.Column(elem_id="col-container"):
|
| 220 |
+
gr.Markdown(
|
| 221 |
+
"""# ProgResViT — adaptive-compute image classification
|
| 222 |
+
|
| 223 |
+
An input-adaptive Vision Transformer that classifies progressively: round 1 runs a
|
| 224 |
+
**narrow** subnetwork on a **low-resolution** image, and only images whose prediction is
|
| 225 |
+
still uncertain continue to round 2 at **higher resolution** with a **wider** subnetwork,
|
| 226 |
+
reusing the tokens produced in round 1.
|
| 227 |
+
|
| 228 |
+
[Paper](https://huggingface.co/papers/2609.03216) · [Code](https://github.com/ds-kiel/ProgResViT) · [Checkpoints](https://huggingface.co/NCPS)
|
| 229 |
+
"""
|
| 230 |
+
)
|
| 231 |
+
with gr.Row():
|
| 232 |
+
with gr.Column():
|
| 233 |
+
image = gr.Image(label="Image", type="pil", height=340)
|
| 234 |
+
run = gr.Button("Classify", variant="primary")
|
| 235 |
+
variant = gr.Dropdown(
|
| 236 |
+
label="Checkpoint",
|
| 237 |
+
choices=list(VARIANTS),
|
| 238 |
+
value=DEFAULT_VARIANT,
|
| 239 |
+
)
|
| 240 |
+
threshold = gr.Slider(
|
| 241 |
+
label="Routing threshold (round-1 entropy)",
|
| 242 |
+
minimum=0.0,
|
| 243 |
+
maximum=2.0,
|
| 244 |
+
step=0.001,
|
| 245 |
+
value=DEFAULT_THRESHOLD,
|
| 246 |
+
info="0 = always run both rounds · higher = exit more images early",
|
| 247 |
+
)
|
| 248 |
+
with gr.Column():
|
| 249 |
+
final_out = gr.Label(label="Prediction", num_top_classes=5)
|
| 250 |
+
report_out = gr.Markdown()
|
| 251 |
+
|
| 252 |
+
with gr.Accordion("Round-by-round predictions", open=False):
|
| 253 |
+
with gr.Row():
|
| 254 |
+
round1_out = gr.Label(label="Round 1 (low-res, narrow)", num_top_classes=5)
|
| 255 |
+
round2_out = gr.Label(label="Round 2 (high-res, wide)", num_top_classes=5)
|
| 256 |
+
|
| 257 |
+
gr.Examples(
|
| 258 |
+
examples=EXAMPLES,
|
| 259 |
+
inputs=[image],
|
| 260 |
+
outputs=[final_out, report_out, round1_out, round2_out],
|
| 261 |
+
fn=classify,
|
| 262 |
+
cache_examples=True,
|
| 263 |
+
cache_mode="lazy",
|
| 264 |
+
examples_per_page=12,
|
| 265 |
+
)
|
| 266 |
+
|
| 267 |
+
def _sync_threshold(name: str) -> float:
|
| 268 |
+
"""Reset the routing threshold to the checkpoint's reported operating point."""
|
| 269 |
+
return VARIANTS[name]["threshold"]
|
| 270 |
+
|
| 271 |
+
variant.change(_sync_threshold, inputs=variant, outputs=threshold)
|
| 272 |
+
run.click(
|
| 273 |
+
classify,
|
| 274 |
+
inputs=[image, variant, threshold],
|
| 275 |
+
outputs=[final_out, report_out, round1_out, round2_out],
|
| 276 |
+
api_name="classify",
|
| 277 |
+
)
|
| 278 |
+
|
| 279 |
+
if __name__ == "__main__":
|
| 280 |
+
demo.launch(mcp_server=True)
|
examples/acoustic_guitar.jpg
ADDED
|
Git LFS Details
|
examples/bird_kingfisher.jpg
ADDED
|
examples/chameleon.jpg
ADDED
|
Git LFS Details
|
examples/hot_air_balloon.jpg
ADDED
|
examples/husky_dog.jpg
ADDED
|
examples/library_interior.jpg
ADDED
|
Git LFS Details
|
examples/monstera_plant.jpg
ADDED
|
Git LFS Details
|
examples/pizza_board.jpg
ADDED
|
Git LFS Details
|
examples/red_fox.jpg
ADDED
|
Git LFS Details
|
examples/spiral_staircase.jpg
ADDED
|
Git LFS Details
|
examples/vintage_camera.jpg
ADDED
|
imagenet_classes.json
ADDED
|
@@ -0,0 +1,1002 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"0": "tench, Tinca tinca",
|
| 3 |
+
"1": "goldfish, Carassius auratus",
|
| 4 |
+
"2": "great white shark, white shark, man-eater, man-eating shark, Carcharodon carcharias",
|
| 5 |
+
"3": "tiger shark, Galeocerdo cuvieri",
|
| 6 |
+
"4": "hammerhead, hammerhead shark",
|
| 7 |
+
"5": "electric ray, crampfish, numbfish, torpedo",
|
| 8 |
+
"6": "stingray",
|
| 9 |
+
"7": "cock",
|
| 10 |
+
"8": "hen",
|
| 11 |
+
"9": "ostrich, Struthio camelus",
|
| 12 |
+
"10": "brambling, Fringilla montifringilla",
|
| 13 |
+
"11": "goldfinch, Carduelis carduelis",
|
| 14 |
+
"12": "house finch, linnet, Carpodacus mexicanus",
|
| 15 |
+
"13": "junco, snowbird",
|
| 16 |
+
"14": "indigo bunting, indigo finch, indigo bird, Passerina cyanea",
|
| 17 |
+
"15": "robin, American robin, Turdus migratorius",
|
| 18 |
+
"16": "bulbul",
|
| 19 |
+
"17": "jay",
|
| 20 |
+
"18": "magpie",
|
| 21 |
+
"19": "chickadee",
|
| 22 |
+
"20": "water ouzel, dipper",
|
| 23 |
+
"21": "kite",
|
| 24 |
+
"22": "bald eagle, American eagle, Haliaeetus leucocephalus",
|
| 25 |
+
"23": "vulture",
|
| 26 |
+
"24": "great grey owl, great gray owl, Strix nebulosa",
|
| 27 |
+
"25": "European fire salamander, Salamandra salamandra",
|
| 28 |
+
"26": "common newt, Triturus vulgaris",
|
| 29 |
+
"27": "eft",
|
| 30 |
+
"28": "spotted salamander, Ambystoma maculatum",
|
| 31 |
+
"29": "axolotl, mud puppy, Ambystoma mexicanum",
|
| 32 |
+
"30": "bullfrog, Rana catesbeiana",
|
| 33 |
+
"31": "tree frog, tree-frog",
|
| 34 |
+
"32": "tailed frog, bell toad, ribbed toad, tailed toad, Ascaphus trui",
|
| 35 |
+
"33": "loggerhead, loggerhead turtle, Caretta caretta",
|
| 36 |
+
"34": "leatherback turtle, leatherback, leathery turtle, Dermochelys coriacea",
|
| 37 |
+
"35": "mud turtle",
|
| 38 |
+
"36": "terrapin",
|
| 39 |
+
"37": "box turtle, box tortoise",
|
| 40 |
+
"38": "banded gecko",
|
| 41 |
+
"39": "common iguana, iguana, Iguana iguana",
|
| 42 |
+
"40": "American chameleon, anole, Anolis carolinensis",
|
| 43 |
+
"41": "whiptail, whiptail lizard",
|
| 44 |
+
"42": "agama",
|
| 45 |
+
"43": "frilled lizard, Chlamydosaurus kingi",
|
| 46 |
+
"44": "alligator lizard",
|
| 47 |
+
"45": "Gila monster, Heloderma suspectum",
|
| 48 |
+
"46": "green lizard, Lacerta viridis",
|
| 49 |
+
"47": "African chameleon, Chamaeleo chamaeleon",
|
| 50 |
+
"48": "Komodo dragon, Komodo lizard, dragon lizard, giant lizard, Varanus komodoensis",
|
| 51 |
+
"49": "African crocodile, Nile crocodile, Crocodylus niloticus",
|
| 52 |
+
"50": "American alligator, Alligator mississipiensis",
|
| 53 |
+
"51": "triceratops",
|
| 54 |
+
"52": "thunder snake, worm snake, Carphophis amoenus",
|
| 55 |
+
"53": "ringneck snake, ring-necked snake, ring snake",
|
| 56 |
+
"54": "hognose snake, puff adder, sand viper",
|
| 57 |
+
"55": "green snake, grass snake",
|
| 58 |
+
"56": "king snake, kingsnake",
|
| 59 |
+
"57": "garter snake, grass snake",
|
| 60 |
+
"58": "water snake",
|
| 61 |
+
"59": "vine snake",
|
| 62 |
+
"60": "night snake, Hypsiglena torquata",
|
| 63 |
+
"61": "boa constrictor, Constrictor constrictor",
|
| 64 |
+
"62": "rock python, rock snake, Python sebae",
|
| 65 |
+
"63": "Indian cobra, Naja naja",
|
| 66 |
+
"64": "green mamba",
|
| 67 |
+
"65": "sea snake",
|
| 68 |
+
"66": "horned viper, cerastes, sand viper, horned asp, Cerastes cornutus",
|
| 69 |
+
"67": "diamondback, diamondback rattlesnake, Crotalus adamanteus",
|
| 70 |
+
"68": "sidewinder, horned rattlesnake, Crotalus cerastes",
|
| 71 |
+
"69": "trilobite",
|
| 72 |
+
"70": "harvestman, daddy longlegs, Phalangium opilio",
|
| 73 |
+
"71": "scorpion",
|
| 74 |
+
"72": "black and gold garden spider, Argiope aurantia",
|
| 75 |
+
"73": "barn spider, Araneus cavaticus",
|
| 76 |
+
"74": "garden spider, Aranea diademata",
|
| 77 |
+
"75": "black widow, Latrodectus mactans",
|
| 78 |
+
"76": "tarantula",
|
| 79 |
+
"77": "wolf spider, hunting spider",
|
| 80 |
+
"78": "tick",
|
| 81 |
+
"79": "centipede",
|
| 82 |
+
"80": "black grouse",
|
| 83 |
+
"81": "ptarmigan",
|
| 84 |
+
"82": "ruffed grouse, partridge, Bonasa umbellus",
|
| 85 |
+
"83": "prairie chicken, prairie grouse, prairie fowl",
|
| 86 |
+
"84": "peacock",
|
| 87 |
+
"85": "quail",
|
| 88 |
+
"86": "partridge",
|
| 89 |
+
"87": "African grey, African gray, Psittacus erithacus",
|
| 90 |
+
"88": "macaw",
|
| 91 |
+
"89": "sulphur-crested cockatoo, Kakatoe galerita, Cacatua galerita",
|
| 92 |
+
"90": "lorikeet",
|
| 93 |
+
"91": "coucal",
|
| 94 |
+
"92": "bee eater",
|
| 95 |
+
"93": "hornbill",
|
| 96 |
+
"94": "hummingbird",
|
| 97 |
+
"95": "jacamar",
|
| 98 |
+
"96": "toucan",
|
| 99 |
+
"97": "drake",
|
| 100 |
+
"98": "red-breasted merganser, Mergus serrator",
|
| 101 |
+
"99": "goose",
|
| 102 |
+
"100": "black swan, Cygnus atratus",
|
| 103 |
+
"101": "tusker",
|
| 104 |
+
"102": "echidna, spiny anteater, anteater",
|
| 105 |
+
"103": "platypus, duckbill, duckbilled platypus, duck-billed platypus, Ornithorhynchus anatinus",
|
| 106 |
+
"104": "wallaby, brush kangaroo",
|
| 107 |
+
"105": "koala, koala bear, kangaroo bear, native bear, Phascolarctos cinereus",
|
| 108 |
+
"106": "wombat",
|
| 109 |
+
"107": "jellyfish",
|
| 110 |
+
"108": "sea anemone, anemone",
|
| 111 |
+
"109": "brain coral",
|
| 112 |
+
"110": "flatworm, platyhelminth",
|
| 113 |
+
"111": "nematode, nematode worm, roundworm",
|
| 114 |
+
"112": "conch",
|
| 115 |
+
"113": "snail",
|
| 116 |
+
"114": "slug",
|
| 117 |
+
"115": "sea slug, nudibranch",
|
| 118 |
+
"116": "chiton, coat-of-mail shell, sea cradle, polyplacophore",
|
| 119 |
+
"117": "chambered nautilus, pearly nautilus, nautilus",
|
| 120 |
+
"118": "Dungeness crab, Cancer magister",
|
| 121 |
+
"119": "rock crab, Cancer irroratus",
|
| 122 |
+
"120": "fiddler crab",
|
| 123 |
+
"121": "king crab, Alaska crab, Alaskan king crab, Alaska king crab, Paralithodes camtschatica",
|
| 124 |
+
"122": "American lobster, Northern lobster, Maine lobster, Homarus americanus",
|
| 125 |
+
"123": "spiny lobster, langouste, rock lobster, crawfish, crayfish, sea crawfish",
|
| 126 |
+
"124": "crayfish, crawfish, crawdad, crawdaddy",
|
| 127 |
+
"125": "hermit crab",
|
| 128 |
+
"126": "isopod",
|
| 129 |
+
"127": "white stork, Ciconia ciconia",
|
| 130 |
+
"128": "black stork, Ciconia nigra",
|
| 131 |
+
"129": "spoonbill",
|
| 132 |
+
"130": "flamingo",
|
| 133 |
+
"131": "little blue heron, Egretta caerulea",
|
| 134 |
+
"132": "American egret, great white heron, Egretta albus",
|
| 135 |
+
"133": "bittern",
|
| 136 |
+
"134": "crane",
|
| 137 |
+
"135": "limpkin, Aramus pictus",
|
| 138 |
+
"136": "European gallinule, Porphyrio porphyrio",
|
| 139 |
+
"137": "American coot, marsh hen, mud hen, water hen, Fulica americana",
|
| 140 |
+
"138": "bustard",
|
| 141 |
+
"139": "ruddy turnstone, Arenaria interpres",
|
| 142 |
+
"140": "red-backed sandpiper, dunlin, Erolia alpina",
|
| 143 |
+
"141": "redshank, Tringa totanus",
|
| 144 |
+
"142": "dowitcher",
|
| 145 |
+
"143": "oystercatcher, oyster catcher",
|
| 146 |
+
"144": "pelican",
|
| 147 |
+
"145": "king penguin, Aptenodytes patagonica",
|
| 148 |
+
"146": "albatross, mollymawk",
|
| 149 |
+
"147": "grey whale, gray whale, devilfish, Eschrichtius gibbosus, Eschrichtius robustus",
|
| 150 |
+
"148": "killer whale, killer, orca, grampus, sea wolf, Orcinus orca",
|
| 151 |
+
"149": "dugong, Dugong dugon",
|
| 152 |
+
"150": "sea lion",
|
| 153 |
+
"151": "Chihuahua",
|
| 154 |
+
"152": "Japanese spaniel",
|
| 155 |
+
"153": "Maltese dog, Maltese terrier, Maltese",
|
| 156 |
+
"154": "Pekinese, Pekingese, Peke",
|
| 157 |
+
"155": "Shih-Tzu",
|
| 158 |
+
"156": "Blenheim spaniel",
|
| 159 |
+
"157": "papillon",
|
| 160 |
+
"158": "toy terrier",
|
| 161 |
+
"159": "Rhodesian ridgeback",
|
| 162 |
+
"160": "Afghan hound, Afghan",
|
| 163 |
+
"161": "basset, basset hound",
|
| 164 |
+
"162": "beagle",
|
| 165 |
+
"163": "bloodhound, sleuthhound",
|
| 166 |
+
"164": "bluetick",
|
| 167 |
+
"165": "black-and-tan coonhound",
|
| 168 |
+
"166": "Walker hound, Walker foxhound",
|
| 169 |
+
"167": "English foxhound",
|
| 170 |
+
"168": "redbone",
|
| 171 |
+
"169": "borzoi, Russian wolfhound",
|
| 172 |
+
"170": "Irish wolfhound",
|
| 173 |
+
"171": "Italian greyhound",
|
| 174 |
+
"172": "whippet",
|
| 175 |
+
"173": "Ibizan hound, Ibizan Podenco",
|
| 176 |
+
"174": "Norwegian elkhound, elkhound",
|
| 177 |
+
"175": "otterhound, otter hound",
|
| 178 |
+
"176": "Saluki, gazelle hound",
|
| 179 |
+
"177": "Scottish deerhound, deerhound",
|
| 180 |
+
"178": "Weimaraner",
|
| 181 |
+
"179": "Staffordshire bullterrier, Staffordshire bull terrier",
|
| 182 |
+
"180": "American Staffordshire terrier, Staffordshire terrier, American pit bull terrier, pit bull terrier",
|
| 183 |
+
"181": "Bedlington terrier",
|
| 184 |
+
"182": "Border terrier",
|
| 185 |
+
"183": "Kerry blue terrier",
|
| 186 |
+
"184": "Irish terrier",
|
| 187 |
+
"185": "Norfolk terrier",
|
| 188 |
+
"186": "Norwich terrier",
|
| 189 |
+
"187": "Yorkshire terrier",
|
| 190 |
+
"188": "wire-haired fox terrier",
|
| 191 |
+
"189": "Lakeland terrier",
|
| 192 |
+
"190": "Sealyham terrier, Sealyham",
|
| 193 |
+
"191": "Airedale, Airedale terrier",
|
| 194 |
+
"192": "cairn, cairn terrier",
|
| 195 |
+
"193": "Australian terrier",
|
| 196 |
+
"194": "Dandie Dinmont, Dandie Dinmont terrier",
|
| 197 |
+
"195": "Boston bull, Boston terrier",
|
| 198 |
+
"196": "miniature schnauzer",
|
| 199 |
+
"197": "giant schnauzer",
|
| 200 |
+
"198": "standard schnauzer",
|
| 201 |
+
"199": "Scotch terrier, Scottish terrier, Scottie",
|
| 202 |
+
"200": "Tibetan terrier, chrysanthemum dog",
|
| 203 |
+
"201": "silky terrier, Sydney silky",
|
| 204 |
+
"202": "soft-coated wheaten terrier",
|
| 205 |
+
"203": "West Highland white terrier",
|
| 206 |
+
"204": "Lhasa, Lhasa apso",
|
| 207 |
+
"205": "flat-coated retriever",
|
| 208 |
+
"206": "curly-coated retriever",
|
| 209 |
+
"207": "golden retriever",
|
| 210 |
+
"208": "Labrador retriever",
|
| 211 |
+
"209": "Chesapeake Bay retriever",
|
| 212 |
+
"210": "German short-haired pointer",
|
| 213 |
+
"211": "vizsla, Hungarian pointer",
|
| 214 |
+
"212": "English setter",
|
| 215 |
+
"213": "Irish setter, red setter",
|
| 216 |
+
"214": "Gordon setter",
|
| 217 |
+
"215": "Brittany spaniel",
|
| 218 |
+
"216": "clumber, clumber spaniel",
|
| 219 |
+
"217": "English springer, English springer spaniel",
|
| 220 |
+
"218": "Welsh springer spaniel",
|
| 221 |
+
"219": "cocker spaniel, English cocker spaniel, cocker",
|
| 222 |
+
"220": "Sussex spaniel",
|
| 223 |
+
"221": "Irish water spaniel",
|
| 224 |
+
"222": "kuvasz",
|
| 225 |
+
"223": "schipperke",
|
| 226 |
+
"224": "groenendael",
|
| 227 |
+
"225": "malinois",
|
| 228 |
+
"226": "briard",
|
| 229 |
+
"227": "kelpie",
|
| 230 |
+
"228": "komondor",
|
| 231 |
+
"229": "Old English sheepdog, bobtail",
|
| 232 |
+
"230": "Shetland sheepdog, Shetland sheep dog, Shetland",
|
| 233 |
+
"231": "collie",
|
| 234 |
+
"232": "Border collie",
|
| 235 |
+
"233": "Bouvier des Flandres, Bouviers des Flandres",
|
| 236 |
+
"234": "Rottweiler",
|
| 237 |
+
"235": "German shepherd, German shepherd dog, German police dog, alsatian",
|
| 238 |
+
"236": "Doberman, Doberman pinscher",
|
| 239 |
+
"237": "miniature pinscher",
|
| 240 |
+
"238": "Greater Swiss Mountain dog",
|
| 241 |
+
"239": "Bernese mountain dog",
|
| 242 |
+
"240": "Appenzeller",
|
| 243 |
+
"241": "EntleBucher",
|
| 244 |
+
"242": "boxer",
|
| 245 |
+
"243": "bull mastiff",
|
| 246 |
+
"244": "Tibetan mastiff",
|
| 247 |
+
"245": "French bulldog",
|
| 248 |
+
"246": "Great Dane",
|
| 249 |
+
"247": "Saint Bernard, St Bernard",
|
| 250 |
+
"248": "Eskimo dog, husky",
|
| 251 |
+
"249": "malamute, malemute, Alaskan malamute",
|
| 252 |
+
"250": "Siberian husky",
|
| 253 |
+
"251": "dalmatian, coach dog, carriage dog",
|
| 254 |
+
"252": "affenpinscher, monkey pinscher, monkey dog",
|
| 255 |
+
"253": "basenji",
|
| 256 |
+
"254": "pug, pug-dog",
|
| 257 |
+
"255": "Leonberg",
|
| 258 |
+
"256": "Newfoundland, Newfoundland dog",
|
| 259 |
+
"257": "Great Pyrenees",
|
| 260 |
+
"258": "Samoyed, Samoyede",
|
| 261 |
+
"259": "Pomeranian",
|
| 262 |
+
"260": "chow, chow chow",
|
| 263 |
+
"261": "keeshond",
|
| 264 |
+
"262": "Brabancon griffon",
|
| 265 |
+
"263": "Pembroke, Pembroke Welsh corgi",
|
| 266 |
+
"264": "Cardigan, Cardigan Welsh corgi",
|
| 267 |
+
"265": "toy poodle",
|
| 268 |
+
"266": "miniature poodle",
|
| 269 |
+
"267": "standard poodle",
|
| 270 |
+
"268": "Mexican hairless",
|
| 271 |
+
"269": "timber wolf, grey wolf, gray wolf, Canis lupus",
|
| 272 |
+
"270": "white wolf, Arctic wolf, Canis lupus tundrarum",
|
| 273 |
+
"271": "red wolf, maned wolf, Canis rufus, Canis niger",
|
| 274 |
+
"272": "coyote, prairie wolf, brush wolf, Canis latrans",
|
| 275 |
+
"273": "dingo, warrigal, warragal, Canis dingo",
|
| 276 |
+
"274": "dhole, Cuon alpinus",
|
| 277 |
+
"275": "African hunting dog, hyena dog, Cape hunting dog, Lycaon pictus",
|
| 278 |
+
"276": "hyena, hyaena",
|
| 279 |
+
"277": "red fox, Vulpes vulpes",
|
| 280 |
+
"278": "kit fox, Vulpes macrotis",
|
| 281 |
+
"279": "Arctic fox, white fox, Alopex lagopus",
|
| 282 |
+
"280": "grey fox, gray fox, Urocyon cinereoargenteus",
|
| 283 |
+
"281": "tabby, tabby cat",
|
| 284 |
+
"282": "tiger cat",
|
| 285 |
+
"283": "Persian cat",
|
| 286 |
+
"284": "Siamese cat, Siamese",
|
| 287 |
+
"285": "Egyptian cat",
|
| 288 |
+
"286": "cougar, puma, catamount, mountain lion, painter, panther, Felis concolor",
|
| 289 |
+
"287": "lynx, catamount",
|
| 290 |
+
"288": "leopard, Panthera pardus",
|
| 291 |
+
"289": "snow leopard, ounce, Panthera uncia",
|
| 292 |
+
"290": "jaguar, panther, Panthera onca, Felis onca",
|
| 293 |
+
"291": "lion, king of beasts, Panthera leo",
|
| 294 |
+
"292": "tiger, Panthera tigris",
|
| 295 |
+
"293": "cheetah, chetah, Acinonyx jubatus",
|
| 296 |
+
"294": "brown bear, bruin, Ursus arctos",
|
| 297 |
+
"295": "American black bear, black bear, Ursus americanus, Euarctos americanus",
|
| 298 |
+
"296": "ice bear, polar bear, Ursus Maritimus, Thalarctos maritimus",
|
| 299 |
+
"297": "sloth bear, Melursus ursinus, Ursus ursinus",
|
| 300 |
+
"298": "mongoose",
|
| 301 |
+
"299": "meerkat, mierkat",
|
| 302 |
+
"300": "tiger beetle",
|
| 303 |
+
"301": "ladybug, ladybeetle, lady beetle, ladybird, ladybird beetle",
|
| 304 |
+
"302": "ground beetle, carabid beetle",
|
| 305 |
+
"303": "long-horned beetle, longicorn, longicorn beetle",
|
| 306 |
+
"304": "leaf beetle, chrysomelid",
|
| 307 |
+
"305": "dung beetle",
|
| 308 |
+
"306": "rhinoceros beetle",
|
| 309 |
+
"307": "weevil",
|
| 310 |
+
"308": "fly",
|
| 311 |
+
"309": "bee",
|
| 312 |
+
"310": "ant, emmet, pismire",
|
| 313 |
+
"311": "grasshopper, hopper",
|
| 314 |
+
"312": "cricket",
|
| 315 |
+
"313": "walking stick, walkingstick, stick insect",
|
| 316 |
+
"314": "cockroach, roach",
|
| 317 |
+
"315": "mantis, mantid",
|
| 318 |
+
"316": "cicada, cicala",
|
| 319 |
+
"317": "leafhopper",
|
| 320 |
+
"318": "lacewing, lacewing fly",
|
| 321 |
+
"319": "dragonfly, darning needle, devil's darning needle, sewing needle, snake feeder, snake doctor, mosquito hawk, skeeter hawk",
|
| 322 |
+
"320": "damselfly",
|
| 323 |
+
"321": "admiral",
|
| 324 |
+
"322": "ringlet, ringlet butterfly",
|
| 325 |
+
"323": "monarch, monarch butterfly, milkweed butterfly, Danaus plexippus",
|
| 326 |
+
"324": "cabbage butterfly",
|
| 327 |
+
"325": "sulphur butterfly, sulfur butterfly",
|
| 328 |
+
"326": "lycaenid, lycaenid butterfly",
|
| 329 |
+
"327": "starfish, sea star",
|
| 330 |
+
"328": "sea urchin",
|
| 331 |
+
"329": "sea cucumber, holothurian",
|
| 332 |
+
"330": "wood rabbit, cottontail, cottontail rabbit",
|
| 333 |
+
"331": "hare",
|
| 334 |
+
"332": "Angora, Angora rabbit",
|
| 335 |
+
"333": "hamster",
|
| 336 |
+
"334": "porcupine, hedgehog",
|
| 337 |
+
"335": "fox squirrel, eastern fox squirrel, Sciurus niger",
|
| 338 |
+
"336": "marmot",
|
| 339 |
+
"337": "beaver",
|
| 340 |
+
"338": "guinea pig, Cavia cobaya",
|
| 341 |
+
"339": "sorrel",
|
| 342 |
+
"340": "zebra",
|
| 343 |
+
"341": "hog, pig, grunter, squealer, Sus scrofa",
|
| 344 |
+
"342": "wild boar, boar, Sus scrofa",
|
| 345 |
+
"343": "warthog",
|
| 346 |
+
"344": "hippopotamus, hippo, river horse, Hippopotamus amphibius",
|
| 347 |
+
"345": "ox",
|
| 348 |
+
"346": "water buffalo, water ox, Asiatic buffalo, Bubalus bubalis",
|
| 349 |
+
"347": "bison",
|
| 350 |
+
"348": "ram, tup",
|
| 351 |
+
"349": "bighorn, bighorn sheep, cimarron, Rocky Mountain bighorn, Rocky Mountain sheep, Ovis canadensis",
|
| 352 |
+
"350": "ibex, Capra ibex",
|
| 353 |
+
"351": "hartebeest",
|
| 354 |
+
"352": "impala, Aepyceros melampus",
|
| 355 |
+
"353": "gazelle",
|
| 356 |
+
"354": "Arabian camel, dromedary, Camelus dromedarius",
|
| 357 |
+
"355": "llama",
|
| 358 |
+
"356": "weasel",
|
| 359 |
+
"357": "mink",
|
| 360 |
+
"358": "polecat, fitch, foulmart, foumart, Mustela putorius",
|
| 361 |
+
"359": "black-footed ferret, ferret, Mustela nigripes",
|
| 362 |
+
"360": "otter",
|
| 363 |
+
"361": "skunk, polecat, wood pussy",
|
| 364 |
+
"362": "badger",
|
| 365 |
+
"363": "armadillo",
|
| 366 |
+
"364": "three-toed sloth, ai, Bradypus tridactylus",
|
| 367 |
+
"365": "orangutan, orang, orangutang, Pongo pygmaeus",
|
| 368 |
+
"366": "gorilla, Gorilla gorilla",
|
| 369 |
+
"367": "chimpanzee, chimp, Pan troglodytes",
|
| 370 |
+
"368": "gibbon, Hylobates lar",
|
| 371 |
+
"369": "siamang, Hylobates syndactylus, Symphalangus syndactylus",
|
| 372 |
+
"370": "guenon, guenon monkey",
|
| 373 |
+
"371": "patas, hussar monkey, Erythrocebus patas",
|
| 374 |
+
"372": "baboon",
|
| 375 |
+
"373": "macaque",
|
| 376 |
+
"374": "langur",
|
| 377 |
+
"375": "colobus, colobus monkey",
|
| 378 |
+
"376": "proboscis monkey, Nasalis larvatus",
|
| 379 |
+
"377": "marmoset",
|
| 380 |
+
"378": "capuchin, ringtail, Cebus capucinus",
|
| 381 |
+
"379": "howler monkey, howler",
|
| 382 |
+
"380": "titi, titi monkey",
|
| 383 |
+
"381": "spider monkey, Ateles geoffroyi",
|
| 384 |
+
"382": "squirrel monkey, Saimiri sciureus",
|
| 385 |
+
"383": "Madagascar cat, ring-tailed lemur, Lemur catta",
|
| 386 |
+
"384": "indri, indris, Indri indri, Indri brevicaudatus",
|
| 387 |
+
"385": "Indian elephant, Elephas maximus",
|
| 388 |
+
"386": "African elephant, Loxodonta africana",
|
| 389 |
+
"387": "lesser panda, red panda, panda, bear cat, cat bear, Ailurus fulgens",
|
| 390 |
+
"388": "giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca",
|
| 391 |
+
"389": "barracouta, snoek",
|
| 392 |
+
"390": "eel",
|
| 393 |
+
"391": "coho, cohoe, coho salmon, blue jack, silver salmon, Oncorhynchus kisutch",
|
| 394 |
+
"392": "rock beauty, Holocanthus tricolor",
|
| 395 |
+
"393": "anemone fish",
|
| 396 |
+
"394": "sturgeon",
|
| 397 |
+
"395": "gar, garfish, garpike, billfish, Lepisosteus osseus",
|
| 398 |
+
"396": "lionfish",
|
| 399 |
+
"397": "puffer, pufferfish, blowfish, globefish",
|
| 400 |
+
"398": "abacus",
|
| 401 |
+
"399": "abaya",
|
| 402 |
+
"400": "academic gown, academic robe, judge's robe",
|
| 403 |
+
"401": "accordion, piano accordion, squeeze box",
|
| 404 |
+
"402": "acoustic guitar",
|
| 405 |
+
"403": "aircraft carrier, carrier, flattop, attack aircraft carrier",
|
| 406 |
+
"404": "airliner",
|
| 407 |
+
"405": "airship, dirigible",
|
| 408 |
+
"406": "altar",
|
| 409 |
+
"407": "ambulance",
|
| 410 |
+
"408": "amphibian, amphibious vehicle",
|
| 411 |
+
"409": "analog clock",
|
| 412 |
+
"410": "apiary, bee house",
|
| 413 |
+
"411": "apron",
|
| 414 |
+
"412": "ashcan, trash can, garbage can, wastebin, ash bin, ash-bin, ashbin, dustbin, trash barrel, trash bin",
|
| 415 |
+
"413": "assault rifle, assault gun",
|
| 416 |
+
"414": "backpack, back pack, knapsack, packsack, rucksack, haversack",
|
| 417 |
+
"415": "bakery, bakeshop, bakehouse",
|
| 418 |
+
"416": "balance beam, beam",
|
| 419 |
+
"417": "balloon",
|
| 420 |
+
"418": "ballpoint, ballpoint pen, ballpen, Biro",
|
| 421 |
+
"419": "Band Aid",
|
| 422 |
+
"420": "banjo",
|
| 423 |
+
"421": "bannister, banister, balustrade, balusters, handrail",
|
| 424 |
+
"422": "barbell",
|
| 425 |
+
"423": "barber chair",
|
| 426 |
+
"424": "barbershop",
|
| 427 |
+
"425": "barn",
|
| 428 |
+
"426": "barometer",
|
| 429 |
+
"427": "barrel, cask",
|
| 430 |
+
"428": "barrow, garden cart, lawn cart, wheelbarrow",
|
| 431 |
+
"429": "baseball",
|
| 432 |
+
"430": "basketball",
|
| 433 |
+
"431": "bassinet",
|
| 434 |
+
"432": "bassoon",
|
| 435 |
+
"433": "bathing cap, swimming cap",
|
| 436 |
+
"434": "bath towel",
|
| 437 |
+
"435": "bathtub, bathing tub, bath, tub",
|
| 438 |
+
"436": "beach wagon, station wagon, wagon, estate car, beach waggon, station waggon, waggon",
|
| 439 |
+
"437": "beacon, lighthouse, beacon light, pharos",
|
| 440 |
+
"438": "beaker",
|
| 441 |
+
"439": "bearskin, busby, shako",
|
| 442 |
+
"440": "beer bottle",
|
| 443 |
+
"441": "beer glass",
|
| 444 |
+
"442": "bell cote, bell cot",
|
| 445 |
+
"443": "bib",
|
| 446 |
+
"444": "bicycle-built-for-two, tandem bicycle, tandem",
|
| 447 |
+
"445": "bikini, two-piece",
|
| 448 |
+
"446": "binder, ring-binder",
|
| 449 |
+
"447": "binoculars, field glasses, opera glasses",
|
| 450 |
+
"448": "birdhouse",
|
| 451 |
+
"449": "boathouse",
|
| 452 |
+
"450": "bobsled, bobsleigh, bob",
|
| 453 |
+
"451": "bolo tie, bolo, bola tie, bola",
|
| 454 |
+
"452": "bonnet, poke bonnet",
|
| 455 |
+
"453": "bookcase",
|
| 456 |
+
"454": "bookshop, bookstore, bookstall",
|
| 457 |
+
"455": "bottlecap",
|
| 458 |
+
"456": "bow",
|
| 459 |
+
"457": "bow tie, bow-tie, bowtie",
|
| 460 |
+
"458": "brass, memorial tablet, plaque",
|
| 461 |
+
"459": "brassiere, bra, bandeau",
|
| 462 |
+
"460": "breakwater, groin, groyne, mole, bulwark, seawall, jetty",
|
| 463 |
+
"461": "breastplate, aegis, egis",
|
| 464 |
+
"462": "broom",
|
| 465 |
+
"463": "bucket, pail",
|
| 466 |
+
"464": "buckle",
|
| 467 |
+
"465": "bulletproof vest",
|
| 468 |
+
"466": "bullet train, bullet",
|
| 469 |
+
"467": "butcher shop, meat market",
|
| 470 |
+
"468": "cab, hack, taxi, taxicab",
|
| 471 |
+
"469": "caldron, cauldron",
|
| 472 |
+
"470": "candle, taper, wax light",
|
| 473 |
+
"471": "cannon",
|
| 474 |
+
"472": "canoe",
|
| 475 |
+
"473": "can opener, tin opener",
|
| 476 |
+
"474": "cardigan",
|
| 477 |
+
"475": "car mirror",
|
| 478 |
+
"476": "carousel, carrousel, merry-go-round, roundabout, whirligig",
|
| 479 |
+
"477": "carpenter's kit, tool kit",
|
| 480 |
+
"478": "carton",
|
| 481 |
+
"479": "car wheel",
|
| 482 |
+
"480": "cash machine, cash dispenser, automated teller machine, automatic teller machine, automated teller, automatic teller, ATM",
|
| 483 |
+
"481": "cassette",
|
| 484 |
+
"482": "cassette player",
|
| 485 |
+
"483": "castle",
|
| 486 |
+
"484": "catamaran",
|
| 487 |
+
"485": "CD player",
|
| 488 |
+
"486": "cello, violoncello",
|
| 489 |
+
"487": "cellular telephone, cellular phone, cellphone, cell, mobile phone",
|
| 490 |
+
"488": "chain",
|
| 491 |
+
"489": "chainlink fence",
|
| 492 |
+
"490": "chain mail, ring mail, mail, chain armor, chain armour, ring armor, ring armour",
|
| 493 |
+
"491": "chain saw, chainsaw",
|
| 494 |
+
"492": "chest",
|
| 495 |
+
"493": "chiffonier, commode",
|
| 496 |
+
"494": "chime, bell, gong",
|
| 497 |
+
"495": "china cabinet, china closet",
|
| 498 |
+
"496": "Christmas stocking",
|
| 499 |
+
"497": "church, church building",
|
| 500 |
+
"498": "cinema, movie theater, movie theatre, movie house, picture palace",
|
| 501 |
+
"499": "cleaver, meat cleaver, chopper",
|
| 502 |
+
"500": "cliff dwelling",
|
| 503 |
+
"501": "cloak",
|
| 504 |
+
"502": "clog, geta, patten, sabot",
|
| 505 |
+
"503": "cocktail shaker",
|
| 506 |
+
"504": "coffee mug",
|
| 507 |
+
"505": "coffeepot",
|
| 508 |
+
"506": "coil, spiral, volute, whorl, helix",
|
| 509 |
+
"507": "combination lock",
|
| 510 |
+
"508": "computer keyboard, keypad",
|
| 511 |
+
"509": "confectionery, confectionary, candy store",
|
| 512 |
+
"510": "container ship, containership, container vessel",
|
| 513 |
+
"511": "convertible",
|
| 514 |
+
"512": "corkscrew, bottle screw",
|
| 515 |
+
"513": "cornet, horn, trumpet, trump",
|
| 516 |
+
"514": "cowboy boot",
|
| 517 |
+
"515": "cowboy hat, ten-gallon hat",
|
| 518 |
+
"516": "cradle",
|
| 519 |
+
"517": "crane",
|
| 520 |
+
"518": "crash helmet",
|
| 521 |
+
"519": "crate",
|
| 522 |
+
"520": "crib, cot",
|
| 523 |
+
"521": "Crock Pot",
|
| 524 |
+
"522": "croquet ball",
|
| 525 |
+
"523": "crutch",
|
| 526 |
+
"524": "cuirass",
|
| 527 |
+
"525": "dam, dike, dyke",
|
| 528 |
+
"526": "desk",
|
| 529 |
+
"527": "desktop computer",
|
| 530 |
+
"528": "dial telephone, dial phone",
|
| 531 |
+
"529": "diaper, nappy, napkin",
|
| 532 |
+
"530": "digital clock",
|
| 533 |
+
"531": "digital watch",
|
| 534 |
+
"532": "dining table, board",
|
| 535 |
+
"533": "dishrag, dishcloth",
|
| 536 |
+
"534": "dishwasher, dish washer, dishwashing machine",
|
| 537 |
+
"535": "disk brake, disc brake",
|
| 538 |
+
"536": "dock, dockage, docking facility",
|
| 539 |
+
"537": "dogsled, dog sled, dog sleigh",
|
| 540 |
+
"538": "dome",
|
| 541 |
+
"539": "doormat, welcome mat",
|
| 542 |
+
"540": "drilling platform, offshore rig",
|
| 543 |
+
"541": "drum, membranophone, tympan",
|
| 544 |
+
"542": "drumstick",
|
| 545 |
+
"543": "dumbbell",
|
| 546 |
+
"544": "Dutch oven",
|
| 547 |
+
"545": "electric fan, blower",
|
| 548 |
+
"546": "electric guitar",
|
| 549 |
+
"547": "electric locomotive",
|
| 550 |
+
"548": "entertainment center",
|
| 551 |
+
"549": "envelope",
|
| 552 |
+
"550": "espresso maker",
|
| 553 |
+
"551": "face powder",
|
| 554 |
+
"552": "feather boa, boa",
|
| 555 |
+
"553": "file, file cabinet, filing cabinet",
|
| 556 |
+
"554": "fireboat",
|
| 557 |
+
"555": "fire engine, fire truck",
|
| 558 |
+
"556": "fire screen, fireguard",
|
| 559 |
+
"557": "flagpole, flagstaff",
|
| 560 |
+
"558": "flute, transverse flute",
|
| 561 |
+
"559": "folding chair",
|
| 562 |
+
"560": "football helmet",
|
| 563 |
+
"561": "forklift",
|
| 564 |
+
"562": "fountain",
|
| 565 |
+
"563": "fountain pen",
|
| 566 |
+
"564": "four-poster",
|
| 567 |
+
"565": "freight car",
|
| 568 |
+
"566": "French horn, horn",
|
| 569 |
+
"567": "frying pan, frypan, skillet",
|
| 570 |
+
"568": "fur coat",
|
| 571 |
+
"569": "garbage truck, dustcart",
|
| 572 |
+
"570": "gasmask, respirator, gas helmet",
|
| 573 |
+
"571": "gas pump, gasoline pump, petrol pump, island dispenser",
|
| 574 |
+
"572": "goblet",
|
| 575 |
+
"573": "go-kart",
|
| 576 |
+
"574": "golf ball",
|
| 577 |
+
"575": "golfcart, golf cart",
|
| 578 |
+
"576": "gondola",
|
| 579 |
+
"577": "gong, tam-tam",
|
| 580 |
+
"578": "gown",
|
| 581 |
+
"579": "grand piano, grand",
|
| 582 |
+
"580": "greenhouse, nursery, glasshouse",
|
| 583 |
+
"581": "grille, radiator grille",
|
| 584 |
+
"582": "grocery store, grocery, food market, market",
|
| 585 |
+
"583": "guillotine",
|
| 586 |
+
"584": "hair slide",
|
| 587 |
+
"585": "hair spray",
|
| 588 |
+
"586": "half track",
|
| 589 |
+
"587": "hammer",
|
| 590 |
+
"588": "hamper",
|
| 591 |
+
"589": "hand blower, blow dryer, blow drier, hair dryer, hair drier",
|
| 592 |
+
"590": "hand-held computer, hand-held microcomputer",
|
| 593 |
+
"591": "handkerchief, hankie, hanky, hankey",
|
| 594 |
+
"592": "hard disc, hard disk, fixed disk",
|
| 595 |
+
"593": "harmonica, mouth organ, harp, mouth harp",
|
| 596 |
+
"594": "harp",
|
| 597 |
+
"595": "harvester, reaper",
|
| 598 |
+
"596": "hatchet",
|
| 599 |
+
"597": "holster",
|
| 600 |
+
"598": "home theater, home theatre",
|
| 601 |
+
"599": "honeycomb",
|
| 602 |
+
"600": "hook, claw",
|
| 603 |
+
"601": "hoopskirt, crinoline",
|
| 604 |
+
"602": "horizontal bar, high bar",
|
| 605 |
+
"603": "horse cart, horse-cart",
|
| 606 |
+
"604": "hourglass",
|
| 607 |
+
"605": "iPod",
|
| 608 |
+
"606": "iron, smoothing iron",
|
| 609 |
+
"607": "jack-o'-lantern",
|
| 610 |
+
"608": "jean, blue jean, denim",
|
| 611 |
+
"609": "jeep, landrover",
|
| 612 |
+
"610": "jersey, T-shirt, tee shirt",
|
| 613 |
+
"611": "jigsaw puzzle",
|
| 614 |
+
"612": "jinrikisha, ricksha, rickshaw",
|
| 615 |
+
"613": "joystick",
|
| 616 |
+
"614": "kimono",
|
| 617 |
+
"615": "knee pad",
|
| 618 |
+
"616": "knot",
|
| 619 |
+
"617": "lab coat, laboratory coat",
|
| 620 |
+
"618": "ladle",
|
| 621 |
+
"619": "lampshade, lamp shade",
|
| 622 |
+
"620": "laptop, laptop computer",
|
| 623 |
+
"621": "lawn mower, mower",
|
| 624 |
+
"622": "lens cap, lens cover",
|
| 625 |
+
"623": "letter opener, paper knife, paperknife",
|
| 626 |
+
"624": "library",
|
| 627 |
+
"625": "lifeboat",
|
| 628 |
+
"626": "lighter, light, igniter, ignitor",
|
| 629 |
+
"627": "limousine, limo",
|
| 630 |
+
"628": "liner, ocean liner",
|
| 631 |
+
"629": "lipstick, lip rouge",
|
| 632 |
+
"630": "Loafer",
|
| 633 |
+
"631": "lotion",
|
| 634 |
+
"632": "loudspeaker, speaker, speaker unit, loudspeaker system, speaker system",
|
| 635 |
+
"633": "loupe, jeweler's loupe",
|
| 636 |
+
"634": "lumbermill, sawmill",
|
| 637 |
+
"635": "magnetic compass",
|
| 638 |
+
"636": "mailbag, postbag",
|
| 639 |
+
"637": "mailbox, letter box",
|
| 640 |
+
"638": "maillot",
|
| 641 |
+
"639": "maillot, tank suit",
|
| 642 |
+
"640": "manhole cover",
|
| 643 |
+
"641": "maraca",
|
| 644 |
+
"642": "marimba, xylophone",
|
| 645 |
+
"643": "mask",
|
| 646 |
+
"644": "matchstick",
|
| 647 |
+
"645": "maypole",
|
| 648 |
+
"646": "maze, labyrinth",
|
| 649 |
+
"647": "measuring cup",
|
| 650 |
+
"648": "medicine chest, medicine cabinet",
|
| 651 |
+
"649": "megalith, megalithic structure",
|
| 652 |
+
"650": "microphone, mike",
|
| 653 |
+
"651": "microwave, microwave oven",
|
| 654 |
+
"652": "military uniform",
|
| 655 |
+
"653": "milk can",
|
| 656 |
+
"654": "minibus",
|
| 657 |
+
"655": "miniskirt, mini",
|
| 658 |
+
"656": "minivan",
|
| 659 |
+
"657": "missile",
|
| 660 |
+
"658": "mitten",
|
| 661 |
+
"659": "mixing bowl",
|
| 662 |
+
"660": "mobile home, manufactured home",
|
| 663 |
+
"661": "Model T",
|
| 664 |
+
"662": "modem",
|
| 665 |
+
"663": "monastery",
|
| 666 |
+
"664": "monitor",
|
| 667 |
+
"665": "moped",
|
| 668 |
+
"666": "mortar",
|
| 669 |
+
"667": "mortarboard",
|
| 670 |
+
"668": "mosque",
|
| 671 |
+
"669": "mosquito net",
|
| 672 |
+
"670": "motor scooter, scooter",
|
| 673 |
+
"671": "mountain bike, all-terrain bike, off-roader",
|
| 674 |
+
"672": "mountain tent",
|
| 675 |
+
"673": "mouse, computer mouse",
|
| 676 |
+
"674": "mousetrap",
|
| 677 |
+
"675": "moving van",
|
| 678 |
+
"676": "muzzle",
|
| 679 |
+
"677": "nail",
|
| 680 |
+
"678": "neck brace",
|
| 681 |
+
"679": "necklace",
|
| 682 |
+
"680": "nipple",
|
| 683 |
+
"681": "notebook, notebook computer",
|
| 684 |
+
"682": "obelisk",
|
| 685 |
+
"683": "oboe, hautboy, hautbois",
|
| 686 |
+
"684": "ocarina, sweet potato",
|
| 687 |
+
"685": "odometer, hodometer, mileometer, milometer",
|
| 688 |
+
"686": "oil filter",
|
| 689 |
+
"687": "organ, pipe organ",
|
| 690 |
+
"688": "oscilloscope, scope, cathode-ray oscilloscope, CRO",
|
| 691 |
+
"689": "overskirt",
|
| 692 |
+
"690": "oxcart",
|
| 693 |
+
"691": "oxygen mask",
|
| 694 |
+
"692": "packet",
|
| 695 |
+
"693": "paddle, boat paddle",
|
| 696 |
+
"694": "paddlewheel, paddle wheel",
|
| 697 |
+
"695": "padlock",
|
| 698 |
+
"696": "paintbrush",
|
| 699 |
+
"697": "pajama, pyjama, pj's, jammies",
|
| 700 |
+
"698": "palace",
|
| 701 |
+
"699": "panpipe, pandean pipe, syrinx",
|
| 702 |
+
"700": "paper towel",
|
| 703 |
+
"701": "parachute, chute",
|
| 704 |
+
"702": "parallel bars, bars",
|
| 705 |
+
"703": "park bench",
|
| 706 |
+
"704": "parking meter",
|
| 707 |
+
"705": "passenger car, coach, carriage",
|
| 708 |
+
"706": "patio, terrace",
|
| 709 |
+
"707": "pay-phone, pay-station",
|
| 710 |
+
"708": "pedestal, plinth, footstall",
|
| 711 |
+
"709": "pencil box, pencil case",
|
| 712 |
+
"710": "pencil sharpener",
|
| 713 |
+
"711": "perfume, essence",
|
| 714 |
+
"712": "Petri dish",
|
| 715 |
+
"713": "photocopier",
|
| 716 |
+
"714": "pick, plectrum, plectron",
|
| 717 |
+
"715": "pickelhaube",
|
| 718 |
+
"716": "picket fence, paling",
|
| 719 |
+
"717": "pickup, pickup truck",
|
| 720 |
+
"718": "pier",
|
| 721 |
+
"719": "piggy bank, penny bank",
|
| 722 |
+
"720": "pill bottle",
|
| 723 |
+
"721": "pillow",
|
| 724 |
+
"722": "ping-pong ball",
|
| 725 |
+
"723": "pinwheel",
|
| 726 |
+
"724": "pirate, pirate ship",
|
| 727 |
+
"725": "pitcher, ewer",
|
| 728 |
+
"726": "plane, carpenter's plane, woodworking plane",
|
| 729 |
+
"727": "planetarium",
|
| 730 |
+
"728": "plastic bag",
|
| 731 |
+
"729": "plate rack",
|
| 732 |
+
"730": "plow, plough",
|
| 733 |
+
"731": "plunger, plumber's helper",
|
| 734 |
+
"732": "Polaroid camera, Polaroid Land camera",
|
| 735 |
+
"733": "pole",
|
| 736 |
+
"734": "police van, police wagon, paddy wagon, patrol wagon, wagon, black Maria",
|
| 737 |
+
"735": "poncho",
|
| 738 |
+
"736": "pool table, billiard table, snooker table",
|
| 739 |
+
"737": "pop bottle, soda bottle",
|
| 740 |
+
"738": "pot, flowerpot",
|
| 741 |
+
"739": "potter's wheel",
|
| 742 |
+
"740": "power drill",
|
| 743 |
+
"741": "prayer rug, prayer mat",
|
| 744 |
+
"742": "printer",
|
| 745 |
+
"743": "prison, prison house",
|
| 746 |
+
"744": "projectile, missile",
|
| 747 |
+
"745": "projector",
|
| 748 |
+
"746": "puck, hockey puck",
|
| 749 |
+
"747": "punching bag, punch bag, punching ball, punchball",
|
| 750 |
+
"748": "purse",
|
| 751 |
+
"749": "quill, quill pen",
|
| 752 |
+
"750": "quilt, comforter, comfort, puff",
|
| 753 |
+
"751": "racer, race car, racing car",
|
| 754 |
+
"752": "racket, racquet",
|
| 755 |
+
"753": "radiator",
|
| 756 |
+
"754": "radio, wireless",
|
| 757 |
+
"755": "radio telescope, radio reflector",
|
| 758 |
+
"756": "rain barrel",
|
| 759 |
+
"757": "recreational vehicle, RV, R.V.",
|
| 760 |
+
"758": "reel",
|
| 761 |
+
"759": "reflex camera",
|
| 762 |
+
"760": "refrigerator, icebox",
|
| 763 |
+
"761": "remote control, remote",
|
| 764 |
+
"762": "restaurant, eating house, eating place, eatery",
|
| 765 |
+
"763": "revolver, six-gun, six-shooter",
|
| 766 |
+
"764": "rifle",
|
| 767 |
+
"765": "rocking chair, rocker",
|
| 768 |
+
"766": "rotisserie",
|
| 769 |
+
"767": "rubber eraser, rubber, pencil eraser",
|
| 770 |
+
"768": "rugby ball",
|
| 771 |
+
"769": "rule, ruler",
|
| 772 |
+
"770": "running shoe",
|
| 773 |
+
"771": "safe",
|
| 774 |
+
"772": "safety pin",
|
| 775 |
+
"773": "saltshaker, salt shaker",
|
| 776 |
+
"774": "sandal",
|
| 777 |
+
"775": "sarong",
|
| 778 |
+
"776": "sax, saxophone",
|
| 779 |
+
"777": "scabbard",
|
| 780 |
+
"778": "scale, weighing machine",
|
| 781 |
+
"779": "school bus",
|
| 782 |
+
"780": "schooner",
|
| 783 |
+
"781": "scoreboard",
|
| 784 |
+
"782": "screen, CRT screen",
|
| 785 |
+
"783": "screw",
|
| 786 |
+
"784": "screwdriver",
|
| 787 |
+
"785": "seat belt, seatbelt",
|
| 788 |
+
"786": "sewing machine",
|
| 789 |
+
"787": "shield, buckler",
|
| 790 |
+
"788": "shoe shop, shoe-shop, shoe store",
|
| 791 |
+
"789": "shoji",
|
| 792 |
+
"790": "shopping basket",
|
| 793 |
+
"791": "shopping cart",
|
| 794 |
+
"792": "shovel",
|
| 795 |
+
"793": "shower cap",
|
| 796 |
+
"794": "shower curtain",
|
| 797 |
+
"795": "ski",
|
| 798 |
+
"796": "ski mask",
|
| 799 |
+
"797": "sleeping bag",
|
| 800 |
+
"798": "slide rule, slipstick",
|
| 801 |
+
"799": "sliding door",
|
| 802 |
+
"800": "slot, one-armed bandit",
|
| 803 |
+
"801": "snorkel",
|
| 804 |
+
"802": "snowmobile",
|
| 805 |
+
"803": "snowplow, snowplough",
|
| 806 |
+
"804": "soap dispenser",
|
| 807 |
+
"805": "soccer ball",
|
| 808 |
+
"806": "sock",
|
| 809 |
+
"807": "solar dish, solar collector, solar furnace",
|
| 810 |
+
"808": "sombrero",
|
| 811 |
+
"809": "soup bowl",
|
| 812 |
+
"810": "space bar",
|
| 813 |
+
"811": "space heater",
|
| 814 |
+
"812": "space shuttle",
|
| 815 |
+
"813": "spatula",
|
| 816 |
+
"814": "speedboat",
|
| 817 |
+
"815": "spider web, spider's web",
|
| 818 |
+
"816": "spindle",
|
| 819 |
+
"817": "sports car, sport car",
|
| 820 |
+
"818": "spotlight, spot",
|
| 821 |
+
"819": "stage",
|
| 822 |
+
"820": "steam locomotive",
|
| 823 |
+
"821": "steel arch bridge",
|
| 824 |
+
"822": "steel drum",
|
| 825 |
+
"823": "stethoscope",
|
| 826 |
+
"824": "stole",
|
| 827 |
+
"825": "stone wall",
|
| 828 |
+
"826": "stopwatch, stop watch",
|
| 829 |
+
"827": "stove",
|
| 830 |
+
"828": "strainer",
|
| 831 |
+
"829": "streetcar, tram, tramcar, trolley, trolley car",
|
| 832 |
+
"830": "stretcher",
|
| 833 |
+
"831": "studio couch, day bed",
|
| 834 |
+
"832": "stupa, tope",
|
| 835 |
+
"833": "submarine, pigboat, sub, U-boat",
|
| 836 |
+
"834": "suit, suit of clothes",
|
| 837 |
+
"835": "sundial",
|
| 838 |
+
"836": "sunglass",
|
| 839 |
+
"837": "sunglasses, dark glasses, shades",
|
| 840 |
+
"838": "sunscreen, sunblock, sun blocker",
|
| 841 |
+
"839": "suspension bridge",
|
| 842 |
+
"840": "swab, swob, mop",
|
| 843 |
+
"841": "sweatshirt",
|
| 844 |
+
"842": "swimming trunks, bathing trunks",
|
| 845 |
+
"843": "swing",
|
| 846 |
+
"844": "switch, electric switch, electrical switch",
|
| 847 |
+
"845": "syringe",
|
| 848 |
+
"846": "table lamp",
|
| 849 |
+
"847": "tank, army tank, armored combat vehicle, armoured combat vehicle",
|
| 850 |
+
"848": "tape player",
|
| 851 |
+
"849": "teapot",
|
| 852 |
+
"850": "teddy, teddy bear",
|
| 853 |
+
"851": "television, television system",
|
| 854 |
+
"852": "tennis ball",
|
| 855 |
+
"853": "thatch, thatched roof",
|
| 856 |
+
"854": "theater curtain, theatre curtain",
|
| 857 |
+
"855": "thimble",
|
| 858 |
+
"856": "thresher, thrasher, threshing machine",
|
| 859 |
+
"857": "throne",
|
| 860 |
+
"858": "tile roof",
|
| 861 |
+
"859": "toaster",
|
| 862 |
+
"860": "tobacco shop, tobacconist shop, tobacconist",
|
| 863 |
+
"861": "toilet seat",
|
| 864 |
+
"862": "torch",
|
| 865 |
+
"863": "totem pole",
|
| 866 |
+
"864": "tow truck, tow car, wrecker",
|
| 867 |
+
"865": "toyshop",
|
| 868 |
+
"866": "tractor",
|
| 869 |
+
"867": "trailer truck, tractor trailer, trucking rig, rig, articulated lorry, semi",
|
| 870 |
+
"868": "tray",
|
| 871 |
+
"869": "trench coat",
|
| 872 |
+
"870": "tricycle, trike, velocipede",
|
| 873 |
+
"871": "trimaran",
|
| 874 |
+
"872": "tripod",
|
| 875 |
+
"873": "triumphal arch",
|
| 876 |
+
"874": "trolleybus, trolley coach, trackless trolley",
|
| 877 |
+
"875": "trombone",
|
| 878 |
+
"876": "tub, vat",
|
| 879 |
+
"877": "turnstile",
|
| 880 |
+
"878": "typewriter keyboard",
|
| 881 |
+
"879": "umbrella",
|
| 882 |
+
"880": "unicycle, monocycle",
|
| 883 |
+
"881": "upright, upright piano",
|
| 884 |
+
"882": "vacuum, vacuum cleaner",
|
| 885 |
+
"883": "vase",
|
| 886 |
+
"884": "vault",
|
| 887 |
+
"885": "velvet",
|
| 888 |
+
"886": "vending machine",
|
| 889 |
+
"887": "vestment",
|
| 890 |
+
"888": "viaduct",
|
| 891 |
+
"889": "violin, fiddle",
|
| 892 |
+
"890": "volleyball",
|
| 893 |
+
"891": "waffle iron",
|
| 894 |
+
"892": "wall clock",
|
| 895 |
+
"893": "wallet, billfold, notecase, pocketbook",
|
| 896 |
+
"894": "wardrobe, closet, press",
|
| 897 |
+
"895": "warplane, military plane",
|
| 898 |
+
"896": "washbasin, handbasin, washbowl, lavabo, wash-hand basin",
|
| 899 |
+
"897": "washer, automatic washer, washing machine",
|
| 900 |
+
"898": "water bottle",
|
| 901 |
+
"899": "water jug",
|
| 902 |
+
"900": "water tower",
|
| 903 |
+
"901": "whiskey jug",
|
| 904 |
+
"902": "whistle",
|
| 905 |
+
"903": "wig",
|
| 906 |
+
"904": "window screen",
|
| 907 |
+
"905": "window shade",
|
| 908 |
+
"906": "Windsor tie",
|
| 909 |
+
"907": "wine bottle",
|
| 910 |
+
"908": "wing",
|
| 911 |
+
"909": "wok",
|
| 912 |
+
"910": "wooden spoon",
|
| 913 |
+
"911": "wool, woolen, woollen",
|
| 914 |
+
"912": "worm fence, snake fence, snake-rail fence, Virginia fence",
|
| 915 |
+
"913": "wreck",
|
| 916 |
+
"914": "yawl",
|
| 917 |
+
"915": "yurt",
|
| 918 |
+
"916": "web site, website, internet site, site",
|
| 919 |
+
"917": "comic book",
|
| 920 |
+
"918": "crossword puzzle, crossword",
|
| 921 |
+
"919": "street sign",
|
| 922 |
+
"920": "traffic light, traffic signal, stoplight",
|
| 923 |
+
"921": "book jacket, dust cover, dust jacket, dust wrapper",
|
| 924 |
+
"922": "menu",
|
| 925 |
+
"923": "plate",
|
| 926 |
+
"924": "guacamole",
|
| 927 |
+
"925": "consomme",
|
| 928 |
+
"926": "hot pot, hotpot",
|
| 929 |
+
"927": "trifle",
|
| 930 |
+
"928": "ice cream, icecream",
|
| 931 |
+
"929": "ice lolly, lolly, lollipop, popsicle",
|
| 932 |
+
"930": "French loaf",
|
| 933 |
+
"931": "bagel, beigel",
|
| 934 |
+
"932": "pretzel",
|
| 935 |
+
"933": "cheeseburger",
|
| 936 |
+
"934": "hotdog, hot dog, red hot",
|
| 937 |
+
"935": "mashed potato",
|
| 938 |
+
"936": "head cabbage",
|
| 939 |
+
"937": "broccoli",
|
| 940 |
+
"938": "cauliflower",
|
| 941 |
+
"939": "zucchini, courgette",
|
| 942 |
+
"940": "spaghetti squash",
|
| 943 |
+
"941": "acorn squash",
|
| 944 |
+
"942": "butternut squash",
|
| 945 |
+
"943": "cucumber, cuke",
|
| 946 |
+
"944": "artichoke, globe artichoke",
|
| 947 |
+
"945": "bell pepper",
|
| 948 |
+
"946": "cardoon",
|
| 949 |
+
"947": "mushroom",
|
| 950 |
+
"948": "Granny Smith",
|
| 951 |
+
"949": "strawberry",
|
| 952 |
+
"950": "orange",
|
| 953 |
+
"951": "lemon",
|
| 954 |
+
"952": "fig",
|
| 955 |
+
"953": "pineapple, ananas",
|
| 956 |
+
"954": "banana",
|
| 957 |
+
"955": "jackfruit, jak, jack",
|
| 958 |
+
"956": "custard apple",
|
| 959 |
+
"957": "pomegranate",
|
| 960 |
+
"958": "hay",
|
| 961 |
+
"959": "carbonara",
|
| 962 |
+
"960": "chocolate sauce, chocolate syrup",
|
| 963 |
+
"961": "dough",
|
| 964 |
+
"962": "meat loaf, meatloaf",
|
| 965 |
+
"963": "pizza, pizza pie",
|
| 966 |
+
"964": "potpie",
|
| 967 |
+
"965": "burrito",
|
| 968 |
+
"966": "red wine",
|
| 969 |
+
"967": "espresso",
|
| 970 |
+
"968": "cup",
|
| 971 |
+
"969": "eggnog",
|
| 972 |
+
"970": "alp",
|
| 973 |
+
"971": "bubble",
|
| 974 |
+
"972": "cliff, drop, drop-off",
|
| 975 |
+
"973": "coral reef",
|
| 976 |
+
"974": "geyser",
|
| 977 |
+
"975": "lakeside, lakeshore",
|
| 978 |
+
"976": "promontory, headland, head, foreland",
|
| 979 |
+
"977": "sandbar, sand bar",
|
| 980 |
+
"978": "seashore, coast, seacoast, sea-coast",
|
| 981 |
+
"979": "valley, vale",
|
| 982 |
+
"980": "volcano",
|
| 983 |
+
"981": "ballplayer, baseball player",
|
| 984 |
+
"982": "groom, bridegroom",
|
| 985 |
+
"983": "scuba diver",
|
| 986 |
+
"984": "rapeseed",
|
| 987 |
+
"985": "daisy",
|
| 988 |
+
"986": "yellow lady's slipper, yellow lady-slipper, Cypripedium calceolus, Cypripedium parviflorum",
|
| 989 |
+
"987": "corn",
|
| 990 |
+
"988": "acorn",
|
| 991 |
+
"989": "hip, rose hip, rosehip",
|
| 992 |
+
"990": "buckeye, horse chestnut, conker",
|
| 993 |
+
"991": "coral fungus",
|
| 994 |
+
"992": "agaric",
|
| 995 |
+
"993": "gyromitra",
|
| 996 |
+
"994": "stinkhorn, carrion fungus",
|
| 997 |
+
"995": "earthstar",
|
| 998 |
+
"996": "hen-of-the-woods, hen of the woods, Polyporus frondosus, Grifola frondosa",
|
| 999 |
+
"997": "bolete",
|
| 1000 |
+
"998": "ear, spike, capitulum",
|
| 1001 |
+
"999": "toilet tissue, toilet paper, bathroom tissue"
|
| 1002 |
+
}
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torchvision
|
| 2 |
+
safetensors
|
| 3 |
+
pyyaml
|
| 4 |
+
numpy
|
| 5 |
+
pillow
|
timm/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .version import __version__
|
| 2 |
+
from .layers import is_scriptable, is_exportable, set_scriptable, set_exportable
|
| 3 |
+
from .models import create_model, list_models, list_pretrained, is_model, list_modules, model_entrypoint, \
|
| 4 |
+
is_model_pretrained, get_pretrained_cfg, get_pretrained_cfg_value
|
timm/data/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2026 Kiel University
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
# Based on pytorch-image-models (timm); see NOTICE.
|
| 6 |
+
#
|
| 7 |
+
# Modifications:
|
| 8 |
+
# - Pruned exports to the vendored timm subset used by ProgResViT.
|
| 9 |
+
|
| 10 |
+
from .auto_augment import RandAugment, AutoAugment, rand_augment_ops, auto_augment_policy,\
|
| 11 |
+
rand_augment_transform, auto_augment_transform
|
| 12 |
+
from .config import resolve_data_config, resolve_model_data_config
|
| 13 |
+
from .constants import *
|
| 14 |
+
from .dataset import ImageDataset, IterableImageDataset, AugMixDataset
|
| 15 |
+
from .dataset_factory import create_dataset
|
| 16 |
+
from .dataset_info import DatasetInfo, CustomDatasetInfo
|
| 17 |
+
from .loader import create_loader
|
| 18 |
+
from .mixup import Mixup, FastCollateMixup
|
| 19 |
+
from .readers import create_reader
|
| 20 |
+
from .readers import get_img_extensions, is_img_extension, set_img_extensions, add_img_extensions, del_img_extensions
|
| 21 |
+
from .real_labels import RealLabelsImagenet
|
| 22 |
+
from .transforms import *
|
| 23 |
+
from .transforms_factory import create_transform
|
timm/data/auto_augment.py
ADDED
|
@@ -0,0 +1,997 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" AutoAugment, RandAugment, AugMix, and 3-Augment for PyTorch
|
| 2 |
+
|
| 3 |
+
This code implements the searched ImageNet policies with various tweaks and improvements and
|
| 4 |
+
does not include any of the search code.
|
| 5 |
+
|
| 6 |
+
AA and RA Implementation adapted from:
|
| 7 |
+
https://github.com/tensorflow/tpu/blob/master/models/official/efficientnet/autoaugment.py
|
| 8 |
+
|
| 9 |
+
AugMix adapted from:
|
| 10 |
+
https://github.com/google-research/augmix
|
| 11 |
+
|
| 12 |
+
3-Augment based on: https://github.com/facebookresearch/deit/blob/main/README_revenge.md
|
| 13 |
+
|
| 14 |
+
Papers:
|
| 15 |
+
AutoAugment: Learning Augmentation Policies from Data - https://arxiv.org/abs/1805.09501
|
| 16 |
+
Learning Data Augmentation Strategies for Object Detection - https://arxiv.org/abs/1906.11172
|
| 17 |
+
RandAugment: Practical automated data augmentation... - https://arxiv.org/abs/1909.13719
|
| 18 |
+
AugMix: A Simple Data Processing Method to Improve Robustness and Uncertainty - https://arxiv.org/abs/1912.02781
|
| 19 |
+
3-Augment: DeiT III: Revenge of the ViT - https://arxiv.org/abs/2204.07118
|
| 20 |
+
|
| 21 |
+
Hacked together by / Copyright 2019, Ross Wightman
|
| 22 |
+
"""
|
| 23 |
+
import random
|
| 24 |
+
import math
|
| 25 |
+
import re
|
| 26 |
+
from functools import partial
|
| 27 |
+
from typing import Dict, List, Optional, Union
|
| 28 |
+
|
| 29 |
+
from PIL import Image, ImageOps, ImageEnhance, ImageChops, ImageFilter
|
| 30 |
+
import PIL
|
| 31 |
+
import numpy as np
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
_PIL_VER = tuple([int(x) for x in PIL.__version__.split('.')[:2]])
|
| 35 |
+
|
| 36 |
+
_FILL = (128, 128, 128)
|
| 37 |
+
|
| 38 |
+
_LEVEL_DENOM = 10. # denominator for conversion from 'Mx' magnitude scale to fractional aug level for op arguments
|
| 39 |
+
|
| 40 |
+
_HPARAMS_DEFAULT = dict(
|
| 41 |
+
translate_const=250,
|
| 42 |
+
img_mean=_FILL,
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
if hasattr(Image, "Resampling"):
|
| 46 |
+
_RANDOM_INTERPOLATION = (Image.Resampling.BILINEAR, Image.Resampling.BICUBIC)
|
| 47 |
+
_DEFAULT_INTERPOLATION = Image.Resampling.BICUBIC
|
| 48 |
+
else:
|
| 49 |
+
_RANDOM_INTERPOLATION = (Image.BILINEAR, Image.BICUBIC)
|
| 50 |
+
_DEFAULT_INTERPOLATION = Image.BICUBIC
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def _interpolation(kwargs):
|
| 54 |
+
interpolation = kwargs.pop('resample', _DEFAULT_INTERPOLATION)
|
| 55 |
+
if isinstance(interpolation, (list, tuple)):
|
| 56 |
+
return random.choice(interpolation)
|
| 57 |
+
return interpolation
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def _check_args_tf(kwargs):
|
| 61 |
+
if 'fillcolor' in kwargs and _PIL_VER < (5, 0):
|
| 62 |
+
kwargs.pop('fillcolor')
|
| 63 |
+
kwargs['resample'] = _interpolation(kwargs)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def shear_x(img, factor, **kwargs):
|
| 67 |
+
_check_args_tf(kwargs)
|
| 68 |
+
return img.transform(img.size, Image.AFFINE, (1, factor, 0, 0, 1, 0), **kwargs)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def shear_y(img, factor, **kwargs):
|
| 72 |
+
_check_args_tf(kwargs)
|
| 73 |
+
return img.transform(img.size, Image.AFFINE, (1, 0, 0, factor, 1, 0), **kwargs)
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def translate_x_rel(img, pct, **kwargs):
|
| 77 |
+
pixels = pct * img.size[0]
|
| 78 |
+
_check_args_tf(kwargs)
|
| 79 |
+
return img.transform(img.size, Image.AFFINE, (1, 0, pixels, 0, 1, 0), **kwargs)
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def translate_y_rel(img, pct, **kwargs):
|
| 83 |
+
pixels = pct * img.size[1]
|
| 84 |
+
_check_args_tf(kwargs)
|
| 85 |
+
return img.transform(img.size, Image.AFFINE, (1, 0, 0, 0, 1, pixels), **kwargs)
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def translate_x_abs(img, pixels, **kwargs):
|
| 89 |
+
_check_args_tf(kwargs)
|
| 90 |
+
return img.transform(img.size, Image.AFFINE, (1, 0, pixels, 0, 1, 0), **kwargs)
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def translate_y_abs(img, pixels, **kwargs):
|
| 94 |
+
_check_args_tf(kwargs)
|
| 95 |
+
return img.transform(img.size, Image.AFFINE, (1, 0, 0, 0, 1, pixels), **kwargs)
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def rotate(img, degrees, **kwargs):
|
| 99 |
+
_check_args_tf(kwargs)
|
| 100 |
+
if _PIL_VER >= (5, 2):
|
| 101 |
+
return img.rotate(degrees, **kwargs)
|
| 102 |
+
if _PIL_VER >= (5, 0):
|
| 103 |
+
w, h = img.size
|
| 104 |
+
post_trans = (0, 0)
|
| 105 |
+
rotn_center = (w / 2.0, h / 2.0)
|
| 106 |
+
angle = -math.radians(degrees)
|
| 107 |
+
matrix = [
|
| 108 |
+
round(math.cos(angle), 15),
|
| 109 |
+
round(math.sin(angle), 15),
|
| 110 |
+
0.0,
|
| 111 |
+
round(-math.sin(angle), 15),
|
| 112 |
+
round(math.cos(angle), 15),
|
| 113 |
+
0.0,
|
| 114 |
+
]
|
| 115 |
+
|
| 116 |
+
def transform(x, y, matrix):
|
| 117 |
+
(a, b, c, d, e, f) = matrix
|
| 118 |
+
return a * x + b * y + c, d * x + e * y + f
|
| 119 |
+
|
| 120 |
+
matrix[2], matrix[5] = transform(
|
| 121 |
+
-rotn_center[0] - post_trans[0], -rotn_center[1] - post_trans[1], matrix
|
| 122 |
+
)
|
| 123 |
+
matrix[2] += rotn_center[0]
|
| 124 |
+
matrix[5] += rotn_center[1]
|
| 125 |
+
return img.transform(img.size, Image.AFFINE, matrix, **kwargs)
|
| 126 |
+
return img.rotate(degrees, resample=kwargs['resample'])
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def auto_contrast(img, **__):
|
| 130 |
+
return ImageOps.autocontrast(img)
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def invert(img, **__):
|
| 134 |
+
return ImageOps.invert(img)
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
def equalize(img, **__):
|
| 138 |
+
return ImageOps.equalize(img)
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def solarize(img, thresh, **__):
|
| 142 |
+
return ImageOps.solarize(img, thresh)
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def solarize_add(img, add, thresh=128, **__):
|
| 146 |
+
lut = []
|
| 147 |
+
for i in range(256):
|
| 148 |
+
if i < thresh:
|
| 149 |
+
lut.append(min(255, i + add))
|
| 150 |
+
else:
|
| 151 |
+
lut.append(i)
|
| 152 |
+
|
| 153 |
+
if img.mode in ("L", "RGB"):
|
| 154 |
+
if img.mode == "RGB" and len(lut) == 256:
|
| 155 |
+
lut = lut + lut + lut
|
| 156 |
+
return img.point(lut)
|
| 157 |
+
|
| 158 |
+
return img
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
def posterize(img, bits_to_keep, **__):
|
| 162 |
+
if bits_to_keep >= 8:
|
| 163 |
+
return img
|
| 164 |
+
return ImageOps.posterize(img, bits_to_keep)
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def contrast(img, factor, **__):
|
| 168 |
+
return ImageEnhance.Contrast(img).enhance(factor)
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def color(img, factor, **__):
|
| 172 |
+
return ImageEnhance.Color(img).enhance(factor)
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
def brightness(img, factor, **__):
|
| 176 |
+
return ImageEnhance.Brightness(img).enhance(factor)
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
def sharpness(img, factor, **__):
|
| 180 |
+
return ImageEnhance.Sharpness(img).enhance(factor)
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
def gaussian_blur(img, factor, **__):
|
| 184 |
+
img = img.filter(ImageFilter.GaussianBlur(radius=factor))
|
| 185 |
+
return img
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def gaussian_blur_rand(img, factor, **__):
|
| 189 |
+
radius_min = 0.1
|
| 190 |
+
radius_max = 2.0
|
| 191 |
+
img = img.filter(ImageFilter.GaussianBlur(radius=random.uniform(radius_min, radius_max * factor)))
|
| 192 |
+
return img
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
def desaturate(img, factor, **_):
|
| 196 |
+
factor = min(1., max(0., 1. - factor))
|
| 197 |
+
# enhance factor 0 = grayscale, 1.0 = no-change
|
| 198 |
+
return ImageEnhance.Color(img).enhance(factor)
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
def _randomly_negate(v):
|
| 202 |
+
"""With 50% prob, negate the value"""
|
| 203 |
+
return -v if random.random() > 0.5 else v
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
def _rotate_level_to_arg(level, _hparams):
|
| 207 |
+
# range [-30, 30]
|
| 208 |
+
level = (level / _LEVEL_DENOM) * 30.
|
| 209 |
+
level = _randomly_negate(level)
|
| 210 |
+
return level,
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
def _enhance_level_to_arg(level, _hparams):
|
| 214 |
+
# range [0.1, 1.9]
|
| 215 |
+
return (level / _LEVEL_DENOM) * 1.8 + 0.1,
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
def _enhance_increasing_level_to_arg(level, _hparams):
|
| 219 |
+
# the 'no change' level is 1.0, moving away from that towards 0. or 2.0 increases the enhancement blend
|
| 220 |
+
# range [0.1, 1.9] if level <= _LEVEL_DENOM
|
| 221 |
+
level = (level / _LEVEL_DENOM) * .9
|
| 222 |
+
level = max(0.1, 1.0 + _randomly_negate(level)) # keep it >= 0.1
|
| 223 |
+
return level,
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
def _minmax_level_to_arg(level, _hparams, min_val=0., max_val=1.0, clamp=True):
|
| 227 |
+
level = (level / _LEVEL_DENOM)
|
| 228 |
+
level = min_val + (max_val - min_val) * level
|
| 229 |
+
if clamp:
|
| 230 |
+
level = max(min_val, min(max_val, level))
|
| 231 |
+
return level,
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
def _shear_level_to_arg(level, _hparams):
|
| 235 |
+
# range [-0.3, 0.3]
|
| 236 |
+
level = (level / _LEVEL_DENOM) * 0.3
|
| 237 |
+
level = _randomly_negate(level)
|
| 238 |
+
return level,
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
def _translate_abs_level_to_arg(level, hparams):
|
| 242 |
+
translate_const = hparams['translate_const']
|
| 243 |
+
level = (level / _LEVEL_DENOM) * float(translate_const)
|
| 244 |
+
level = _randomly_negate(level)
|
| 245 |
+
return level,
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
def _translate_rel_level_to_arg(level, hparams):
|
| 249 |
+
# default range [-0.45, 0.45]
|
| 250 |
+
translate_pct = hparams.get('translate_pct', 0.45)
|
| 251 |
+
level = (level / _LEVEL_DENOM) * translate_pct
|
| 252 |
+
level = _randomly_negate(level)
|
| 253 |
+
return level,
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
def _posterize_level_to_arg(level, _hparams):
|
| 257 |
+
# As per Tensorflow TPU EfficientNet impl
|
| 258 |
+
# range [0, 4], 'keep 0 up to 4 MSB of original image'
|
| 259 |
+
# intensity/severity of augmentation decreases with level
|
| 260 |
+
return int((level / _LEVEL_DENOM) * 4),
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
def _posterize_increasing_level_to_arg(level, hparams):
|
| 264 |
+
# As per Tensorflow models research and UDA impl
|
| 265 |
+
# range [4, 0], 'keep 4 down to 0 MSB of original image',
|
| 266 |
+
# intensity/severity of augmentation increases with level
|
| 267 |
+
return 4 - _posterize_level_to_arg(level, hparams)[0],
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
def _posterize_original_level_to_arg(level, _hparams):
|
| 271 |
+
# As per original AutoAugment paper description
|
| 272 |
+
# range [4, 8], 'keep 4 up to 8 MSB of image'
|
| 273 |
+
# intensity/severity of augmentation decreases with level
|
| 274 |
+
return int((level / _LEVEL_DENOM) * 4) + 4,
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
def _solarize_level_to_arg(level, _hparams):
|
| 278 |
+
# range [0, 256]
|
| 279 |
+
# intensity/severity of augmentation decreases with level
|
| 280 |
+
return min(256, int((level / _LEVEL_DENOM) * 256)),
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
def _solarize_increasing_level_to_arg(level, _hparams):
|
| 284 |
+
# range [0, 256]
|
| 285 |
+
# intensity/severity of augmentation increases with level
|
| 286 |
+
return 256 - _solarize_level_to_arg(level, _hparams)[0],
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
def _solarize_add_level_to_arg(level, _hparams):
|
| 290 |
+
# range [0, 110]
|
| 291 |
+
return min(128, int((level / _LEVEL_DENOM) * 110)),
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
LEVEL_TO_ARG = {
|
| 295 |
+
'AutoContrast': None,
|
| 296 |
+
'Equalize': None,
|
| 297 |
+
'Invert': None,
|
| 298 |
+
'Rotate': _rotate_level_to_arg,
|
| 299 |
+
# There are several variations of the posterize level scaling in various Tensorflow/Google repositories/papers
|
| 300 |
+
'Posterize': _posterize_level_to_arg,
|
| 301 |
+
'PosterizeIncreasing': _posterize_increasing_level_to_arg,
|
| 302 |
+
'PosterizeOriginal': _posterize_original_level_to_arg,
|
| 303 |
+
'Solarize': _solarize_level_to_arg,
|
| 304 |
+
'SolarizeIncreasing': _solarize_increasing_level_to_arg,
|
| 305 |
+
'SolarizeAdd': _solarize_add_level_to_arg,
|
| 306 |
+
'Color': _enhance_level_to_arg,
|
| 307 |
+
'ColorIncreasing': _enhance_increasing_level_to_arg,
|
| 308 |
+
'Contrast': _enhance_level_to_arg,
|
| 309 |
+
'ContrastIncreasing': _enhance_increasing_level_to_arg,
|
| 310 |
+
'Brightness': _enhance_level_to_arg,
|
| 311 |
+
'BrightnessIncreasing': _enhance_increasing_level_to_arg,
|
| 312 |
+
'Sharpness': _enhance_level_to_arg,
|
| 313 |
+
'SharpnessIncreasing': _enhance_increasing_level_to_arg,
|
| 314 |
+
'ShearX': _shear_level_to_arg,
|
| 315 |
+
'ShearY': _shear_level_to_arg,
|
| 316 |
+
'TranslateX': _translate_abs_level_to_arg,
|
| 317 |
+
'TranslateY': _translate_abs_level_to_arg,
|
| 318 |
+
'TranslateXRel': _translate_rel_level_to_arg,
|
| 319 |
+
'TranslateYRel': _translate_rel_level_to_arg,
|
| 320 |
+
'Desaturate': partial(_minmax_level_to_arg, min_val=0.5, max_val=1.0),
|
| 321 |
+
'GaussianBlur': partial(_minmax_level_to_arg, min_val=0.1, max_val=2.0),
|
| 322 |
+
'GaussianBlurRand': _minmax_level_to_arg,
|
| 323 |
+
}
|
| 324 |
+
|
| 325 |
+
|
| 326 |
+
NAME_TO_OP = {
|
| 327 |
+
'AutoContrast': auto_contrast,
|
| 328 |
+
'Equalize': equalize,
|
| 329 |
+
'Invert': invert,
|
| 330 |
+
'Rotate': rotate,
|
| 331 |
+
'Posterize': posterize,
|
| 332 |
+
'PosterizeIncreasing': posterize,
|
| 333 |
+
'PosterizeOriginal': posterize,
|
| 334 |
+
'Solarize': solarize,
|
| 335 |
+
'SolarizeIncreasing': solarize,
|
| 336 |
+
'SolarizeAdd': solarize_add,
|
| 337 |
+
'Color': color,
|
| 338 |
+
'ColorIncreasing': color,
|
| 339 |
+
'Contrast': contrast,
|
| 340 |
+
'ContrastIncreasing': contrast,
|
| 341 |
+
'Brightness': brightness,
|
| 342 |
+
'BrightnessIncreasing': brightness,
|
| 343 |
+
'Sharpness': sharpness,
|
| 344 |
+
'SharpnessIncreasing': sharpness,
|
| 345 |
+
'ShearX': shear_x,
|
| 346 |
+
'ShearY': shear_y,
|
| 347 |
+
'TranslateX': translate_x_abs,
|
| 348 |
+
'TranslateY': translate_y_abs,
|
| 349 |
+
'TranslateXRel': translate_x_rel,
|
| 350 |
+
'TranslateYRel': translate_y_rel,
|
| 351 |
+
'Desaturate': desaturate,
|
| 352 |
+
'GaussianBlur': gaussian_blur,
|
| 353 |
+
'GaussianBlurRand': gaussian_blur_rand,
|
| 354 |
+
}
|
| 355 |
+
|
| 356 |
+
|
| 357 |
+
class AugmentOp:
|
| 358 |
+
|
| 359 |
+
def __init__(self, name, prob=0.5, magnitude=10, hparams=None):
|
| 360 |
+
hparams = hparams or _HPARAMS_DEFAULT
|
| 361 |
+
self.name = name
|
| 362 |
+
self.aug_fn = NAME_TO_OP[name]
|
| 363 |
+
self.level_fn = LEVEL_TO_ARG[name]
|
| 364 |
+
self.prob = prob
|
| 365 |
+
self.magnitude = magnitude
|
| 366 |
+
self.hparams = hparams.copy()
|
| 367 |
+
self.kwargs = dict(
|
| 368 |
+
fillcolor=hparams['img_mean'] if 'img_mean' in hparams else _FILL,
|
| 369 |
+
resample=hparams['interpolation'] if 'interpolation' in hparams else _RANDOM_INTERPOLATION,
|
| 370 |
+
)
|
| 371 |
+
|
| 372 |
+
# If magnitude_std is > 0, we introduce some randomness
|
| 373 |
+
# in the usually fixed policy and sample magnitude from a normal distribution
|
| 374 |
+
# with mean `magnitude` and std-dev of `magnitude_std`.
|
| 375 |
+
# NOTE This is my own hack, being tested, not in papers or reference impls.
|
| 376 |
+
# If magnitude_std is inf, we sample magnitude from a uniform distribution
|
| 377 |
+
self.magnitude_std = self.hparams.get('magnitude_std', 0)
|
| 378 |
+
self.magnitude_max = self.hparams.get('magnitude_max', None)
|
| 379 |
+
|
| 380 |
+
def __call__(self, img):
|
| 381 |
+
if self.prob < 1.0 and random.random() > self.prob:
|
| 382 |
+
return img
|
| 383 |
+
magnitude = self.magnitude
|
| 384 |
+
if self.magnitude_std > 0:
|
| 385 |
+
# magnitude randomization enabled
|
| 386 |
+
if self.magnitude_std == float('inf'):
|
| 387 |
+
# inf == uniform sampling
|
| 388 |
+
magnitude = random.uniform(0, magnitude)
|
| 389 |
+
elif self.magnitude_std > 0:
|
| 390 |
+
magnitude = random.gauss(magnitude, self.magnitude_std)
|
| 391 |
+
# default upper_bound for the timm RA impl is _LEVEL_DENOM (10)
|
| 392 |
+
# setting magnitude_max overrides this to allow M > 10 (behaviour closer to Google TF RA impl)
|
| 393 |
+
upper_bound = self.magnitude_max or _LEVEL_DENOM
|
| 394 |
+
magnitude = max(0., min(magnitude, upper_bound))
|
| 395 |
+
level_args = self.level_fn(magnitude, self.hparams) if self.level_fn is not None else tuple()
|
| 396 |
+
return self.aug_fn(img, *level_args, **self.kwargs)
|
| 397 |
+
|
| 398 |
+
def __repr__(self):
|
| 399 |
+
fs = self.__class__.__name__ + f'(name={self.name}, p={self.prob}'
|
| 400 |
+
fs += f', m={self.magnitude}, mstd={self.magnitude_std}'
|
| 401 |
+
if self.magnitude_max is not None:
|
| 402 |
+
fs += f', mmax={self.magnitude_max}'
|
| 403 |
+
fs += ')'
|
| 404 |
+
return fs
|
| 405 |
+
|
| 406 |
+
|
| 407 |
+
def auto_augment_policy_v0(hparams):
|
| 408 |
+
# ImageNet v0 policy from TPU EfficientNet impl, cannot find a paper reference.
|
| 409 |
+
policy = [
|
| 410 |
+
[('Equalize', 0.8, 1), ('ShearY', 0.8, 4)],
|
| 411 |
+
[('Color', 0.4, 9), ('Equalize', 0.6, 3)],
|
| 412 |
+
[('Color', 0.4, 1), ('Rotate', 0.6, 8)],
|
| 413 |
+
[('Solarize', 0.8, 3), ('Equalize', 0.4, 7)],
|
| 414 |
+
[('Solarize', 0.4, 2), ('Solarize', 0.6, 2)],
|
| 415 |
+
[('Color', 0.2, 0), ('Equalize', 0.8, 8)],
|
| 416 |
+
[('Equalize', 0.4, 8), ('SolarizeAdd', 0.8, 3)],
|
| 417 |
+
[('ShearX', 0.2, 9), ('Rotate', 0.6, 8)],
|
| 418 |
+
[('Color', 0.6, 1), ('Equalize', 1.0, 2)],
|
| 419 |
+
[('Invert', 0.4, 9), ('Rotate', 0.6, 0)],
|
| 420 |
+
[('Equalize', 1.0, 9), ('ShearY', 0.6, 3)],
|
| 421 |
+
[('Color', 0.4, 7), ('Equalize', 0.6, 0)],
|
| 422 |
+
[('Posterize', 0.4, 6), ('AutoContrast', 0.4, 7)],
|
| 423 |
+
[('Solarize', 0.6, 8), ('Color', 0.6, 9)],
|
| 424 |
+
[('Solarize', 0.2, 4), ('Rotate', 0.8, 9)],
|
| 425 |
+
[('Rotate', 1.0, 7), ('TranslateYRel', 0.8, 9)],
|
| 426 |
+
[('ShearX', 0.0, 0), ('Solarize', 0.8, 4)],
|
| 427 |
+
[('ShearY', 0.8, 0), ('Color', 0.6, 4)],
|
| 428 |
+
[('Color', 1.0, 0), ('Rotate', 0.6, 2)],
|
| 429 |
+
[('Equalize', 0.8, 4), ('Equalize', 0.0, 8)],
|
| 430 |
+
[('Equalize', 1.0, 4), ('AutoContrast', 0.6, 2)],
|
| 431 |
+
[('ShearY', 0.4, 7), ('SolarizeAdd', 0.6, 7)],
|
| 432 |
+
[('Posterize', 0.8, 2), ('Solarize', 0.6, 10)], # This results in black image with Tpu posterize
|
| 433 |
+
[('Solarize', 0.6, 8), ('Equalize', 0.6, 1)],
|
| 434 |
+
[('Color', 0.8, 6), ('Rotate', 0.4, 5)],
|
| 435 |
+
]
|
| 436 |
+
pc = [[AugmentOp(*a, hparams=hparams) for a in sp] for sp in policy]
|
| 437 |
+
return pc
|
| 438 |
+
|
| 439 |
+
|
| 440 |
+
def auto_augment_policy_v0r(hparams):
|
| 441 |
+
# ImageNet v0 policy from TPU EfficientNet impl, with variation of Posterize used
|
| 442 |
+
# in Google research implementation (number of bits discarded increases with magnitude)
|
| 443 |
+
policy = [
|
| 444 |
+
[('Equalize', 0.8, 1), ('ShearY', 0.8, 4)],
|
| 445 |
+
[('Color', 0.4, 9), ('Equalize', 0.6, 3)],
|
| 446 |
+
[('Color', 0.4, 1), ('Rotate', 0.6, 8)],
|
| 447 |
+
[('Solarize', 0.8, 3), ('Equalize', 0.4, 7)],
|
| 448 |
+
[('Solarize', 0.4, 2), ('Solarize', 0.6, 2)],
|
| 449 |
+
[('Color', 0.2, 0), ('Equalize', 0.8, 8)],
|
| 450 |
+
[('Equalize', 0.4, 8), ('SolarizeAdd', 0.8, 3)],
|
| 451 |
+
[('ShearX', 0.2, 9), ('Rotate', 0.6, 8)],
|
| 452 |
+
[('Color', 0.6, 1), ('Equalize', 1.0, 2)],
|
| 453 |
+
[('Invert', 0.4, 9), ('Rotate', 0.6, 0)],
|
| 454 |
+
[('Equalize', 1.0, 9), ('ShearY', 0.6, 3)],
|
| 455 |
+
[('Color', 0.4, 7), ('Equalize', 0.6, 0)],
|
| 456 |
+
[('PosterizeIncreasing', 0.4, 6), ('AutoContrast', 0.4, 7)],
|
| 457 |
+
[('Solarize', 0.6, 8), ('Color', 0.6, 9)],
|
| 458 |
+
[('Solarize', 0.2, 4), ('Rotate', 0.8, 9)],
|
| 459 |
+
[('Rotate', 1.0, 7), ('TranslateYRel', 0.8, 9)],
|
| 460 |
+
[('ShearX', 0.0, 0), ('Solarize', 0.8, 4)],
|
| 461 |
+
[('ShearY', 0.8, 0), ('Color', 0.6, 4)],
|
| 462 |
+
[('Color', 1.0, 0), ('Rotate', 0.6, 2)],
|
| 463 |
+
[('Equalize', 0.8, 4), ('Equalize', 0.0, 8)],
|
| 464 |
+
[('Equalize', 1.0, 4), ('AutoContrast', 0.6, 2)],
|
| 465 |
+
[('ShearY', 0.4, 7), ('SolarizeAdd', 0.6, 7)],
|
| 466 |
+
[('PosterizeIncreasing', 0.8, 2), ('Solarize', 0.6, 10)],
|
| 467 |
+
[('Solarize', 0.6, 8), ('Equalize', 0.6, 1)],
|
| 468 |
+
[('Color', 0.8, 6), ('Rotate', 0.4, 5)],
|
| 469 |
+
]
|
| 470 |
+
pc = [[AugmentOp(*a, hparams=hparams) for a in sp] for sp in policy]
|
| 471 |
+
return pc
|
| 472 |
+
|
| 473 |
+
|
| 474 |
+
def auto_augment_policy_original(hparams):
|
| 475 |
+
# ImageNet policy from https://arxiv.org/abs/1805.09501
|
| 476 |
+
policy = [
|
| 477 |
+
[('PosterizeOriginal', 0.4, 8), ('Rotate', 0.6, 9)],
|
| 478 |
+
[('Solarize', 0.6, 5), ('AutoContrast', 0.6, 5)],
|
| 479 |
+
[('Equalize', 0.8, 8), ('Equalize', 0.6, 3)],
|
| 480 |
+
[('PosterizeOriginal', 0.6, 7), ('PosterizeOriginal', 0.6, 6)],
|
| 481 |
+
[('Equalize', 0.4, 7), ('Solarize', 0.2, 4)],
|
| 482 |
+
[('Equalize', 0.4, 4), ('Rotate', 0.8, 8)],
|
| 483 |
+
[('Solarize', 0.6, 3), ('Equalize', 0.6, 7)],
|
| 484 |
+
[('PosterizeOriginal', 0.8, 5), ('Equalize', 1.0, 2)],
|
| 485 |
+
[('Rotate', 0.2, 3), ('Solarize', 0.6, 8)],
|
| 486 |
+
[('Equalize', 0.6, 8), ('PosterizeOriginal', 0.4, 6)],
|
| 487 |
+
[('Rotate', 0.8, 8), ('Color', 0.4, 0)],
|
| 488 |
+
[('Rotate', 0.4, 9), ('Equalize', 0.6, 2)],
|
| 489 |
+
[('Equalize', 0.0, 7), ('Equalize', 0.8, 8)],
|
| 490 |
+
[('Invert', 0.6, 4), ('Equalize', 1.0, 8)],
|
| 491 |
+
[('Color', 0.6, 4), ('Contrast', 1.0, 8)],
|
| 492 |
+
[('Rotate', 0.8, 8), ('Color', 1.0, 2)],
|
| 493 |
+
[('Color', 0.8, 8), ('Solarize', 0.8, 7)],
|
| 494 |
+
[('Sharpness', 0.4, 7), ('Invert', 0.6, 8)],
|
| 495 |
+
[('ShearX', 0.6, 5), ('Equalize', 1.0, 9)],
|
| 496 |
+
[('Color', 0.4, 0), ('Equalize', 0.6, 3)],
|
| 497 |
+
[('Equalize', 0.4, 7), ('Solarize', 0.2, 4)],
|
| 498 |
+
[('Solarize', 0.6, 5), ('AutoContrast', 0.6, 5)],
|
| 499 |
+
[('Invert', 0.6, 4), ('Equalize', 1.0, 8)],
|
| 500 |
+
[('Color', 0.6, 4), ('Contrast', 1.0, 8)],
|
| 501 |
+
[('Equalize', 0.8, 8), ('Equalize', 0.6, 3)],
|
| 502 |
+
]
|
| 503 |
+
pc = [[AugmentOp(*a, hparams=hparams) for a in sp] for sp in policy]
|
| 504 |
+
return pc
|
| 505 |
+
|
| 506 |
+
|
| 507 |
+
def auto_augment_policy_originalr(hparams):
|
| 508 |
+
# ImageNet policy from https://arxiv.org/abs/1805.09501 with research posterize variation
|
| 509 |
+
policy = [
|
| 510 |
+
[('PosterizeIncreasing', 0.4, 8), ('Rotate', 0.6, 9)],
|
| 511 |
+
[('Solarize', 0.6, 5), ('AutoContrast', 0.6, 5)],
|
| 512 |
+
[('Equalize', 0.8, 8), ('Equalize', 0.6, 3)],
|
| 513 |
+
[('PosterizeIncreasing', 0.6, 7), ('PosterizeIncreasing', 0.6, 6)],
|
| 514 |
+
[('Equalize', 0.4, 7), ('Solarize', 0.2, 4)],
|
| 515 |
+
[('Equalize', 0.4, 4), ('Rotate', 0.8, 8)],
|
| 516 |
+
[('Solarize', 0.6, 3), ('Equalize', 0.6, 7)],
|
| 517 |
+
[('PosterizeIncreasing', 0.8, 5), ('Equalize', 1.0, 2)],
|
| 518 |
+
[('Rotate', 0.2, 3), ('Solarize', 0.6, 8)],
|
| 519 |
+
[('Equalize', 0.6, 8), ('PosterizeIncreasing', 0.4, 6)],
|
| 520 |
+
[('Rotate', 0.8, 8), ('Color', 0.4, 0)],
|
| 521 |
+
[('Rotate', 0.4, 9), ('Equalize', 0.6, 2)],
|
| 522 |
+
[('Equalize', 0.0, 7), ('Equalize', 0.8, 8)],
|
| 523 |
+
[('Invert', 0.6, 4), ('Equalize', 1.0, 8)],
|
| 524 |
+
[('Color', 0.6, 4), ('Contrast', 1.0, 8)],
|
| 525 |
+
[('Rotate', 0.8, 8), ('Color', 1.0, 2)],
|
| 526 |
+
[('Color', 0.8, 8), ('Solarize', 0.8, 7)],
|
| 527 |
+
[('Sharpness', 0.4, 7), ('Invert', 0.6, 8)],
|
| 528 |
+
[('ShearX', 0.6, 5), ('Equalize', 1.0, 9)],
|
| 529 |
+
[('Color', 0.4, 0), ('Equalize', 0.6, 3)],
|
| 530 |
+
[('Equalize', 0.4, 7), ('Solarize', 0.2, 4)],
|
| 531 |
+
[('Solarize', 0.6, 5), ('AutoContrast', 0.6, 5)],
|
| 532 |
+
[('Invert', 0.6, 4), ('Equalize', 1.0, 8)],
|
| 533 |
+
[('Color', 0.6, 4), ('Contrast', 1.0, 8)],
|
| 534 |
+
[('Equalize', 0.8, 8), ('Equalize', 0.6, 3)],
|
| 535 |
+
]
|
| 536 |
+
pc = [[AugmentOp(*a, hparams=hparams) for a in sp] for sp in policy]
|
| 537 |
+
return pc
|
| 538 |
+
|
| 539 |
+
|
| 540 |
+
def auto_augment_policy_3a(hparams):
|
| 541 |
+
policy = [
|
| 542 |
+
[('Solarize', 1.0, 5)], # 128 solarize threshold @ 5 magnitude
|
| 543 |
+
[('Desaturate', 1.0, 10)], # grayscale at 10 magnitude
|
| 544 |
+
[('GaussianBlurRand', 1.0, 10)],
|
| 545 |
+
]
|
| 546 |
+
pc = [[AugmentOp(*a, hparams=hparams) for a in sp] for sp in policy]
|
| 547 |
+
return pc
|
| 548 |
+
|
| 549 |
+
|
| 550 |
+
def auto_augment_policy(name='v0', hparams=None):
|
| 551 |
+
hparams = hparams or _HPARAMS_DEFAULT
|
| 552 |
+
if name == 'original':
|
| 553 |
+
return auto_augment_policy_original(hparams)
|
| 554 |
+
if name == 'originalr':
|
| 555 |
+
return auto_augment_policy_originalr(hparams)
|
| 556 |
+
if name == 'v0':
|
| 557 |
+
return auto_augment_policy_v0(hparams)
|
| 558 |
+
if name == 'v0r':
|
| 559 |
+
return auto_augment_policy_v0r(hparams)
|
| 560 |
+
if name == '3a':
|
| 561 |
+
return auto_augment_policy_3a(hparams)
|
| 562 |
+
assert False, f'Unknown AA policy {name}'
|
| 563 |
+
|
| 564 |
+
|
| 565 |
+
class AutoAugment:
|
| 566 |
+
|
| 567 |
+
def __init__(self, policy):
|
| 568 |
+
self.policy = policy
|
| 569 |
+
|
| 570 |
+
def __call__(self, img):
|
| 571 |
+
sub_policy = random.choice(self.policy)
|
| 572 |
+
for op in sub_policy:
|
| 573 |
+
img = op(img)
|
| 574 |
+
return img
|
| 575 |
+
|
| 576 |
+
def __repr__(self):
|
| 577 |
+
fs = self.__class__.__name__ + '(policy='
|
| 578 |
+
for p in self.policy:
|
| 579 |
+
fs += '\n\t['
|
| 580 |
+
fs += ', '.join([str(op) for op in p])
|
| 581 |
+
fs += ']'
|
| 582 |
+
fs += ')'
|
| 583 |
+
return fs
|
| 584 |
+
|
| 585 |
+
|
| 586 |
+
def auto_augment_transform(config_str: str, hparams: Optional[Dict] = None):
|
| 587 |
+
"""
|
| 588 |
+
Create a AutoAugment transform
|
| 589 |
+
|
| 590 |
+
Args:
|
| 591 |
+
config_str: String defining configuration of auto augmentation. Consists of multiple sections separated by
|
| 592 |
+
dashes ('-').
|
| 593 |
+
The first section defines the AutoAugment policy (one of 'v0', 'v0r', 'original', 'originalr').
|
| 594 |
+
|
| 595 |
+
The remaining sections:
|
| 596 |
+
'mstd' - float std deviation of magnitude noise applied
|
| 597 |
+
Ex 'original-mstd0.5' results in AutoAugment with original policy, magnitude_std 0.5
|
| 598 |
+
|
| 599 |
+
hparams: Other hparams (kwargs) for the AutoAugmentation scheme
|
| 600 |
+
|
| 601 |
+
Returns:
|
| 602 |
+
A PyTorch compatible Transform
|
| 603 |
+
"""
|
| 604 |
+
config = config_str.split('-')
|
| 605 |
+
policy_name = config[0]
|
| 606 |
+
config = config[1:]
|
| 607 |
+
for c in config:
|
| 608 |
+
cs = re.split(r'(\d.*)', c)
|
| 609 |
+
if len(cs) < 2:
|
| 610 |
+
continue
|
| 611 |
+
key, val = cs[:2]
|
| 612 |
+
if key == 'mstd':
|
| 613 |
+
# noise param injected via hparams for now
|
| 614 |
+
hparams.setdefault('magnitude_std', float(val))
|
| 615 |
+
else:
|
| 616 |
+
assert False, 'Unknown AutoAugment config section'
|
| 617 |
+
aa_policy = auto_augment_policy(policy_name, hparams=hparams)
|
| 618 |
+
return AutoAugment(aa_policy)
|
| 619 |
+
|
| 620 |
+
|
| 621 |
+
_RAND_TRANSFORMS = [
|
| 622 |
+
'AutoContrast',
|
| 623 |
+
'Equalize',
|
| 624 |
+
'Invert',
|
| 625 |
+
'Rotate',
|
| 626 |
+
'Posterize',
|
| 627 |
+
'Solarize',
|
| 628 |
+
'SolarizeAdd',
|
| 629 |
+
'Color',
|
| 630 |
+
'Contrast',
|
| 631 |
+
'Brightness',
|
| 632 |
+
'Sharpness',
|
| 633 |
+
'ShearX',
|
| 634 |
+
'ShearY',
|
| 635 |
+
'TranslateXRel',
|
| 636 |
+
'TranslateYRel',
|
| 637 |
+
# 'Cutout' # NOTE I've implement this as random erasing separately
|
| 638 |
+
]
|
| 639 |
+
|
| 640 |
+
|
| 641 |
+
_RAND_INCREASING_TRANSFORMS = [
|
| 642 |
+
'AutoContrast',
|
| 643 |
+
'Equalize',
|
| 644 |
+
'Invert',
|
| 645 |
+
'Rotate',
|
| 646 |
+
'PosterizeIncreasing',
|
| 647 |
+
'SolarizeIncreasing',
|
| 648 |
+
'SolarizeAdd',
|
| 649 |
+
'ColorIncreasing',
|
| 650 |
+
'ContrastIncreasing',
|
| 651 |
+
'BrightnessIncreasing',
|
| 652 |
+
'SharpnessIncreasing',
|
| 653 |
+
'ShearX',
|
| 654 |
+
'ShearY',
|
| 655 |
+
'TranslateXRel',
|
| 656 |
+
'TranslateYRel',
|
| 657 |
+
# 'Cutout' # NOTE I've implement this as random erasing separately
|
| 658 |
+
]
|
| 659 |
+
|
| 660 |
+
|
| 661 |
+
_RAND_3A = [
|
| 662 |
+
'SolarizeIncreasing',
|
| 663 |
+
'Desaturate',
|
| 664 |
+
'GaussianBlur',
|
| 665 |
+
]
|
| 666 |
+
|
| 667 |
+
|
| 668 |
+
_RAND_WEIGHTED_3A = {
|
| 669 |
+
'SolarizeIncreasing': 6,
|
| 670 |
+
'Desaturate': 6,
|
| 671 |
+
'GaussianBlur': 6,
|
| 672 |
+
'Rotate': 3,
|
| 673 |
+
'ShearX': 2,
|
| 674 |
+
'ShearY': 2,
|
| 675 |
+
'PosterizeIncreasing': 1,
|
| 676 |
+
'AutoContrast': 1,
|
| 677 |
+
'ColorIncreasing': 1,
|
| 678 |
+
'SharpnessIncreasing': 1,
|
| 679 |
+
'ContrastIncreasing': 1,
|
| 680 |
+
'BrightnessIncreasing': 1,
|
| 681 |
+
'Equalize': 1,
|
| 682 |
+
'Invert': 1,
|
| 683 |
+
}
|
| 684 |
+
|
| 685 |
+
|
| 686 |
+
# These experimental weights are based loosely on the relative improvements mentioned in paper.
|
| 687 |
+
# They may not result in increased performance, but could likely be tuned to so.
|
| 688 |
+
_RAND_WEIGHTED_0 = {
|
| 689 |
+
'Rotate': 3,
|
| 690 |
+
'ShearX': 2,
|
| 691 |
+
'ShearY': 2,
|
| 692 |
+
'TranslateXRel': 1,
|
| 693 |
+
'TranslateYRel': 1,
|
| 694 |
+
'ColorIncreasing': .25,
|
| 695 |
+
'SharpnessIncreasing': 0.25,
|
| 696 |
+
'AutoContrast': 0.25,
|
| 697 |
+
'SolarizeIncreasing': .05,
|
| 698 |
+
'SolarizeAdd': .05,
|
| 699 |
+
'ContrastIncreasing': .05,
|
| 700 |
+
'BrightnessIncreasing': .05,
|
| 701 |
+
'Equalize': .05,
|
| 702 |
+
'PosterizeIncreasing': 0.05,
|
| 703 |
+
'Invert': 0.05,
|
| 704 |
+
}
|
| 705 |
+
|
| 706 |
+
|
| 707 |
+
def _get_weighted_transforms(transforms: Dict):
|
| 708 |
+
transforms, probs = list(zip(*transforms.items()))
|
| 709 |
+
probs = np.array(probs)
|
| 710 |
+
probs = probs / np.sum(probs)
|
| 711 |
+
return transforms, probs
|
| 712 |
+
|
| 713 |
+
|
| 714 |
+
def rand_augment_choices(name: str, increasing=True):
|
| 715 |
+
if name == 'weights':
|
| 716 |
+
return _RAND_WEIGHTED_0
|
| 717 |
+
if name == '3aw':
|
| 718 |
+
return _RAND_WEIGHTED_3A
|
| 719 |
+
if name == '3a':
|
| 720 |
+
return _RAND_3A
|
| 721 |
+
return _RAND_INCREASING_TRANSFORMS if increasing else _RAND_TRANSFORMS
|
| 722 |
+
|
| 723 |
+
|
| 724 |
+
def rand_augment_ops(
|
| 725 |
+
magnitude: Union[int, float] = 10,
|
| 726 |
+
prob: float = 0.5,
|
| 727 |
+
hparams: Optional[Dict] = None,
|
| 728 |
+
transforms: Optional[Union[Dict, List]] = None,
|
| 729 |
+
):
|
| 730 |
+
hparams = hparams or _HPARAMS_DEFAULT
|
| 731 |
+
transforms = transforms or _RAND_TRANSFORMS
|
| 732 |
+
return [AugmentOp(
|
| 733 |
+
name, prob=prob, magnitude=magnitude, hparams=hparams) for name in transforms]
|
| 734 |
+
|
| 735 |
+
|
| 736 |
+
class RandAugment:
|
| 737 |
+
def __init__(self, ops, num_layers=2, choice_weights=None):
|
| 738 |
+
self.ops = ops
|
| 739 |
+
self.num_layers = num_layers
|
| 740 |
+
self.choice_weights = choice_weights
|
| 741 |
+
|
| 742 |
+
def __call__(self, img):
|
| 743 |
+
# no replacement when using weighted choice
|
| 744 |
+
ops = np.random.choice(
|
| 745 |
+
self.ops,
|
| 746 |
+
self.num_layers,
|
| 747 |
+
replace=self.choice_weights is None,
|
| 748 |
+
p=self.choice_weights,
|
| 749 |
+
)
|
| 750 |
+
for op in ops:
|
| 751 |
+
img = op(img)
|
| 752 |
+
return img
|
| 753 |
+
|
| 754 |
+
def __repr__(self):
|
| 755 |
+
fs = self.__class__.__name__ + f'(n={self.num_layers}, ops='
|
| 756 |
+
for op in self.ops:
|
| 757 |
+
fs += f'\n\t{op}'
|
| 758 |
+
fs += ')'
|
| 759 |
+
return fs
|
| 760 |
+
|
| 761 |
+
|
| 762 |
+
def rand_augment_transform(
|
| 763 |
+
config_str: str,
|
| 764 |
+
hparams: Optional[Dict] = None,
|
| 765 |
+
transforms: Optional[Union[str, Dict, List]] = None,
|
| 766 |
+
):
|
| 767 |
+
"""
|
| 768 |
+
Create a RandAugment transform
|
| 769 |
+
|
| 770 |
+
Args:
|
| 771 |
+
config_str (str): String defining configuration of random augmentation. Consists of multiple sections separated
|
| 772 |
+
by dashes ('-'). The first section defines the specific variant of rand augment (currently only 'rand').
|
| 773 |
+
The remaining sections, not order sepecific determine
|
| 774 |
+
'm' - integer magnitude of rand augment
|
| 775 |
+
'n' - integer num layers (number of transform ops selected per image)
|
| 776 |
+
'p' - float probability of applying each layer (default 0.5)
|
| 777 |
+
'mstd' - float std deviation of magnitude noise applied, or uniform sampling if infinity (or > 100)
|
| 778 |
+
'mmax' - set upper bound for magnitude to something other than default of _LEVEL_DENOM (10)
|
| 779 |
+
'inc' - integer (bool), use augmentations that increase in severity with magnitude (default: 0)
|
| 780 |
+
't' - str name of transform set to use
|
| 781 |
+
Ex 'rand-m9-n3-mstd0.5' results in RandAugment with magnitude 9, num_layers 3, magnitude_std 0.5
|
| 782 |
+
'rand-mstd1-tweights' results in mag std 1.0, weighted transforms, default mag of 10 and num_layers 2
|
| 783 |
+
|
| 784 |
+
hparams (dict): Other hparams (kwargs) for the RandAugmentation scheme
|
| 785 |
+
|
| 786 |
+
Returns:
|
| 787 |
+
A PyTorch compatible Transform
|
| 788 |
+
"""
|
| 789 |
+
magnitude = _LEVEL_DENOM # default to _LEVEL_DENOM for magnitude (currently 10)
|
| 790 |
+
num_layers = 2 # default to 2 ops per image
|
| 791 |
+
increasing = False
|
| 792 |
+
prob = 0.5
|
| 793 |
+
config = config_str.split('-')
|
| 794 |
+
assert config[0] == 'rand'
|
| 795 |
+
config = config[1:]
|
| 796 |
+
for c in config:
|
| 797 |
+
if c.startswith('t'):
|
| 798 |
+
# NOTE old 'w' key was removed, 'w0' is not equivalent to 'tweights'
|
| 799 |
+
val = str(c[1:])
|
| 800 |
+
if transforms is None:
|
| 801 |
+
transforms = val
|
| 802 |
+
else:
|
| 803 |
+
# numeric options
|
| 804 |
+
cs = re.split(r'(\d.*)', c)
|
| 805 |
+
if len(cs) < 2:
|
| 806 |
+
continue
|
| 807 |
+
key, val = cs[:2]
|
| 808 |
+
if key == 'mstd':
|
| 809 |
+
# noise param / randomization of magnitude values
|
| 810 |
+
mstd = float(val)
|
| 811 |
+
if mstd > 100:
|
| 812 |
+
# use uniform sampling in 0 to magnitude if mstd is > 100
|
| 813 |
+
mstd = float('inf')
|
| 814 |
+
hparams.setdefault('magnitude_std', mstd)
|
| 815 |
+
elif key == 'mmax':
|
| 816 |
+
# clip magnitude between [0, mmax] instead of default [0, _LEVEL_DENOM]
|
| 817 |
+
hparams.setdefault('magnitude_max', int(val))
|
| 818 |
+
elif key == 'inc':
|
| 819 |
+
if bool(val):
|
| 820 |
+
increasing = True
|
| 821 |
+
elif key == 'm':
|
| 822 |
+
magnitude = int(val)
|
| 823 |
+
elif key == 'n':
|
| 824 |
+
num_layers = int(val)
|
| 825 |
+
elif key == 'p':
|
| 826 |
+
prob = float(val)
|
| 827 |
+
else:
|
| 828 |
+
assert False, 'Unknown RandAugment config section'
|
| 829 |
+
|
| 830 |
+
if isinstance(transforms, str):
|
| 831 |
+
transforms = rand_augment_choices(transforms, increasing=increasing)
|
| 832 |
+
elif transforms is None:
|
| 833 |
+
transforms = _RAND_INCREASING_TRANSFORMS if increasing else _RAND_TRANSFORMS
|
| 834 |
+
|
| 835 |
+
choice_weights = None
|
| 836 |
+
if isinstance(transforms, Dict):
|
| 837 |
+
transforms, choice_weights = _get_weighted_transforms(transforms)
|
| 838 |
+
|
| 839 |
+
ra_ops = rand_augment_ops(magnitude=magnitude, prob=prob, hparams=hparams, transforms=transforms)
|
| 840 |
+
return RandAugment(ra_ops, num_layers, choice_weights=choice_weights)
|
| 841 |
+
|
| 842 |
+
|
| 843 |
+
_AUGMIX_TRANSFORMS = [
|
| 844 |
+
'AutoContrast',
|
| 845 |
+
'ColorIncreasing', # not in paper
|
| 846 |
+
'ContrastIncreasing', # not in paper
|
| 847 |
+
'BrightnessIncreasing', # not in paper
|
| 848 |
+
'SharpnessIncreasing', # not in paper
|
| 849 |
+
'Equalize',
|
| 850 |
+
'Rotate',
|
| 851 |
+
'PosterizeIncreasing',
|
| 852 |
+
'SolarizeIncreasing',
|
| 853 |
+
'ShearX',
|
| 854 |
+
'ShearY',
|
| 855 |
+
'TranslateXRel',
|
| 856 |
+
'TranslateYRel',
|
| 857 |
+
]
|
| 858 |
+
|
| 859 |
+
|
| 860 |
+
def augmix_ops(
|
| 861 |
+
magnitude: Union[int, float] = 10,
|
| 862 |
+
hparams: Optional[Dict] = None,
|
| 863 |
+
transforms: Optional[Union[str, Dict, List]] = None,
|
| 864 |
+
):
|
| 865 |
+
hparams = hparams or _HPARAMS_DEFAULT
|
| 866 |
+
transforms = transforms or _AUGMIX_TRANSFORMS
|
| 867 |
+
return [AugmentOp(
|
| 868 |
+
name,
|
| 869 |
+
prob=1.0,
|
| 870 |
+
magnitude=magnitude,
|
| 871 |
+
hparams=hparams
|
| 872 |
+
) for name in transforms]
|
| 873 |
+
|
| 874 |
+
|
| 875 |
+
class AugMixAugment:
|
| 876 |
+
""" AugMix Transform
|
| 877 |
+
Adapted and improved from impl here: https://github.com/google-research/augmix/blob/master/imagenet.py
|
| 878 |
+
From paper: 'AugMix: A Simple Data Processing Method to Improve Robustness and Uncertainty -
|
| 879 |
+
https://arxiv.org/abs/1912.02781
|
| 880 |
+
"""
|
| 881 |
+
def __init__(self, ops, alpha=1., width=3, depth=-1, blended=False):
|
| 882 |
+
self.ops = ops
|
| 883 |
+
self.alpha = alpha
|
| 884 |
+
self.width = width
|
| 885 |
+
self.depth = depth
|
| 886 |
+
self.blended = blended # blended mode is faster but not well tested
|
| 887 |
+
|
| 888 |
+
def _calc_blended_weights(self, ws, m):
|
| 889 |
+
ws = ws * m
|
| 890 |
+
cump = 1.
|
| 891 |
+
rws = []
|
| 892 |
+
for w in ws[::-1]:
|
| 893 |
+
alpha = w / cump
|
| 894 |
+
cump *= (1 - alpha)
|
| 895 |
+
rws.append(alpha)
|
| 896 |
+
return np.array(rws[::-1], dtype=np.float32)
|
| 897 |
+
|
| 898 |
+
def _apply_blended(self, img, mixing_weights, m):
|
| 899 |
+
# This is my first crack and implementing a slightly faster mixed augmentation. Instead
|
| 900 |
+
# of accumulating the mix for each chain in a Numpy array and then blending with original,
|
| 901 |
+
# it recomputes the blending coefficients and applies one PIL image blend per chain.
|
| 902 |
+
# TODO the results appear in the right ballpark but they differ by more than rounding.
|
| 903 |
+
img_orig = img.copy()
|
| 904 |
+
ws = self._calc_blended_weights(mixing_weights, m)
|
| 905 |
+
for w in ws:
|
| 906 |
+
depth = self.depth if self.depth > 0 else np.random.randint(1, 4)
|
| 907 |
+
ops = np.random.choice(self.ops, depth, replace=True)
|
| 908 |
+
img_aug = img_orig # no ops are in-place, deep copy not necessary
|
| 909 |
+
for op in ops:
|
| 910 |
+
img_aug = op(img_aug)
|
| 911 |
+
img = Image.blend(img, img_aug, w)
|
| 912 |
+
return img
|
| 913 |
+
|
| 914 |
+
def _apply_basic(self, img, mixing_weights, m):
|
| 915 |
+
# This is a literal adaptation of the paper/official implementation without normalizations and
|
| 916 |
+
# PIL <-> Numpy conversions between every op. It is still quite CPU compute heavy compared to the
|
| 917 |
+
# typical augmentation transforms, could use a GPU / Kornia implementation.
|
| 918 |
+
img_shape = img.size[0], img.size[1], len(img.getbands())
|
| 919 |
+
mixed = np.zeros(img_shape, dtype=np.float32)
|
| 920 |
+
for mw in mixing_weights:
|
| 921 |
+
depth = self.depth if self.depth > 0 else np.random.randint(1, 4)
|
| 922 |
+
ops = np.random.choice(self.ops, depth, replace=True)
|
| 923 |
+
img_aug = img # no ops are in-place, deep copy not necessary
|
| 924 |
+
for op in ops:
|
| 925 |
+
img_aug = op(img_aug)
|
| 926 |
+
mixed += mw * np.asarray(img_aug, dtype=np.float32)
|
| 927 |
+
np.clip(mixed, 0, 255., out=mixed)
|
| 928 |
+
mixed = Image.fromarray(mixed.astype(np.uint8))
|
| 929 |
+
return Image.blend(img, mixed, m)
|
| 930 |
+
|
| 931 |
+
def __call__(self, img):
|
| 932 |
+
mixing_weights = np.float32(np.random.dirichlet([self.alpha] * self.width))
|
| 933 |
+
m = np.float32(np.random.beta(self.alpha, self.alpha))
|
| 934 |
+
if self.blended:
|
| 935 |
+
mixed = self._apply_blended(img, mixing_weights, m)
|
| 936 |
+
else:
|
| 937 |
+
mixed = self._apply_basic(img, mixing_weights, m)
|
| 938 |
+
return mixed
|
| 939 |
+
|
| 940 |
+
def __repr__(self):
|
| 941 |
+
fs = self.__class__.__name__ + f'(alpha={self.alpha}, width={self.width}, depth={self.depth}, ops='
|
| 942 |
+
for op in self.ops:
|
| 943 |
+
fs += f'\n\t{op}'
|
| 944 |
+
fs += ')'
|
| 945 |
+
return fs
|
| 946 |
+
|
| 947 |
+
|
| 948 |
+
def augment_and_mix_transform(config_str: str, hparams: Optional[Dict] = None):
|
| 949 |
+
""" Create AugMix PyTorch transform
|
| 950 |
+
|
| 951 |
+
Args:
|
| 952 |
+
config_str (str): String defining configuration of random augmentation. Consists of multiple sections separated
|
| 953 |
+
by dashes ('-'). The first section defines the specific variant of rand augment (currently only 'rand').
|
| 954 |
+
The remaining sections, not order sepecific determine
|
| 955 |
+
'm' - integer magnitude (severity) of augmentation mix (default: 3)
|
| 956 |
+
'w' - integer width of augmentation chain (default: 3)
|
| 957 |
+
'd' - integer depth of augmentation chain (-1 is random [1, 3], default: -1)
|
| 958 |
+
'b' - integer (bool), blend each branch of chain into end result without a final blend, less CPU (default: 0)
|
| 959 |
+
'mstd' - float std deviation of magnitude noise applied (default: 0)
|
| 960 |
+
Ex 'augmix-m5-w4-d2' results in AugMix with severity 5, chain width 4, chain depth 2
|
| 961 |
+
|
| 962 |
+
hparams: Other hparams (kwargs) for the Augmentation transforms
|
| 963 |
+
|
| 964 |
+
Returns:
|
| 965 |
+
A PyTorch compatible Transform
|
| 966 |
+
"""
|
| 967 |
+
magnitude = 3
|
| 968 |
+
width = 3
|
| 969 |
+
depth = -1
|
| 970 |
+
alpha = 1.
|
| 971 |
+
blended = False
|
| 972 |
+
config = config_str.split('-')
|
| 973 |
+
assert config[0] == 'augmix'
|
| 974 |
+
config = config[1:]
|
| 975 |
+
for c in config:
|
| 976 |
+
cs = re.split(r'(\d.*)', c)
|
| 977 |
+
if len(cs) < 2:
|
| 978 |
+
continue
|
| 979 |
+
key, val = cs[:2]
|
| 980 |
+
if key == 'mstd':
|
| 981 |
+
# noise param injected via hparams for now
|
| 982 |
+
hparams.setdefault('magnitude_std', float(val))
|
| 983 |
+
elif key == 'm':
|
| 984 |
+
magnitude = int(val)
|
| 985 |
+
elif key == 'w':
|
| 986 |
+
width = int(val)
|
| 987 |
+
elif key == 'd':
|
| 988 |
+
depth = int(val)
|
| 989 |
+
elif key == 'a':
|
| 990 |
+
alpha = float(val)
|
| 991 |
+
elif key == 'b':
|
| 992 |
+
blended = bool(val)
|
| 993 |
+
else:
|
| 994 |
+
assert False, 'Unknown AugMix config section'
|
| 995 |
+
hparams.setdefault('magnitude_std', float('inf')) # default to uniform sampling (if not set via mstd arg)
|
| 996 |
+
ops = augmix_ops(magnitude=magnitude, hparams=hparams)
|
| 997 |
+
return AugMixAugment(ops, alpha=alpha, width=width, depth=depth, blended=blended)
|
timm/data/config.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
from .constants import *
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
_logger = logging.getLogger(__name__)
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def resolve_data_config(
|
| 9 |
+
args=None,
|
| 10 |
+
pretrained_cfg=None,
|
| 11 |
+
model=None,
|
| 12 |
+
use_test_size=False,
|
| 13 |
+
verbose=False
|
| 14 |
+
):
|
| 15 |
+
assert model or args or pretrained_cfg, "At least one of model, args, or pretrained_cfg required for data config."
|
| 16 |
+
args = args or {}
|
| 17 |
+
pretrained_cfg = pretrained_cfg or {}
|
| 18 |
+
if not pretrained_cfg and model is not None and hasattr(model, 'pretrained_cfg'):
|
| 19 |
+
pretrained_cfg = model.pretrained_cfg
|
| 20 |
+
data_config = {}
|
| 21 |
+
|
| 22 |
+
# Resolve input/image size
|
| 23 |
+
in_chans = 3
|
| 24 |
+
if args.get('in_chans', None) is not None:
|
| 25 |
+
in_chans = args['in_chans']
|
| 26 |
+
elif args.get('chans', None) is not None:
|
| 27 |
+
in_chans = args['chans']
|
| 28 |
+
|
| 29 |
+
input_size = (in_chans, 224, 224)
|
| 30 |
+
if args.get('input_size', None) is not None:
|
| 31 |
+
assert isinstance(args['input_size'], (tuple, list))
|
| 32 |
+
assert len(args['input_size']) == 3
|
| 33 |
+
input_size = tuple(args['input_size'])
|
| 34 |
+
in_chans = input_size[0] # input_size overrides in_chans
|
| 35 |
+
elif args.get('img_size', None) is not None:
|
| 36 |
+
assert isinstance(args['img_size'], int)
|
| 37 |
+
input_size = (in_chans, args['img_size'], args['img_size'])
|
| 38 |
+
else:
|
| 39 |
+
if use_test_size and pretrained_cfg.get('test_input_size', None) is not None:
|
| 40 |
+
input_size = pretrained_cfg['test_input_size']
|
| 41 |
+
elif pretrained_cfg.get('input_size', None) is not None:
|
| 42 |
+
input_size = pretrained_cfg['input_size']
|
| 43 |
+
data_config['input_size'] = input_size
|
| 44 |
+
|
| 45 |
+
# resolve interpolation method
|
| 46 |
+
data_config['interpolation'] = 'bicubic'
|
| 47 |
+
if args.get('interpolation', None):
|
| 48 |
+
data_config['interpolation'] = args['interpolation']
|
| 49 |
+
elif pretrained_cfg.get('interpolation', None):
|
| 50 |
+
data_config['interpolation'] = pretrained_cfg['interpolation']
|
| 51 |
+
|
| 52 |
+
# resolve dataset + model mean for normalization
|
| 53 |
+
data_config['mean'] = IMAGENET_DEFAULT_MEAN
|
| 54 |
+
if args.get('mean', None) is not None:
|
| 55 |
+
mean = tuple(args['mean'])
|
| 56 |
+
if len(mean) == 1:
|
| 57 |
+
mean = tuple(list(mean) * in_chans)
|
| 58 |
+
else:
|
| 59 |
+
assert len(mean) == in_chans
|
| 60 |
+
data_config['mean'] = mean
|
| 61 |
+
elif pretrained_cfg.get('mean', None):
|
| 62 |
+
data_config['mean'] = pretrained_cfg['mean']
|
| 63 |
+
|
| 64 |
+
# resolve dataset + model std deviation for normalization
|
| 65 |
+
data_config['std'] = IMAGENET_DEFAULT_STD
|
| 66 |
+
if args.get('std', None) is not None:
|
| 67 |
+
std = tuple(args['std'])
|
| 68 |
+
if len(std) == 1:
|
| 69 |
+
std = tuple(list(std) * in_chans)
|
| 70 |
+
else:
|
| 71 |
+
assert len(std) == in_chans
|
| 72 |
+
data_config['std'] = std
|
| 73 |
+
elif pretrained_cfg.get('std', None):
|
| 74 |
+
data_config['std'] = pretrained_cfg['std']
|
| 75 |
+
|
| 76 |
+
# resolve default inference crop
|
| 77 |
+
crop_pct = DEFAULT_CROP_PCT
|
| 78 |
+
if args.get('crop_pct', None):
|
| 79 |
+
crop_pct = args['crop_pct']
|
| 80 |
+
else:
|
| 81 |
+
if use_test_size and pretrained_cfg.get('test_crop_pct', None):
|
| 82 |
+
crop_pct = pretrained_cfg['test_crop_pct']
|
| 83 |
+
elif pretrained_cfg.get('crop_pct', None):
|
| 84 |
+
crop_pct = pretrained_cfg['crop_pct']
|
| 85 |
+
data_config['crop_pct'] = crop_pct
|
| 86 |
+
|
| 87 |
+
# resolve default crop percentage
|
| 88 |
+
crop_mode = DEFAULT_CROP_MODE
|
| 89 |
+
if args.get('crop_mode', None):
|
| 90 |
+
crop_mode = args['crop_mode']
|
| 91 |
+
elif pretrained_cfg.get('crop_mode', None):
|
| 92 |
+
crop_mode = pretrained_cfg['crop_mode']
|
| 93 |
+
data_config['crop_mode'] = crop_mode
|
| 94 |
+
|
| 95 |
+
if verbose:
|
| 96 |
+
_logger.info('Data processing configuration for current model + dataset:')
|
| 97 |
+
for n, v in data_config.items():
|
| 98 |
+
_logger.info('\t%s: %s' % (n, str(v)))
|
| 99 |
+
|
| 100 |
+
return data_config
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def resolve_model_data_config(
|
| 104 |
+
model,
|
| 105 |
+
args=None,
|
| 106 |
+
pretrained_cfg=None,
|
| 107 |
+
use_test_size=False,
|
| 108 |
+
verbose=False,
|
| 109 |
+
):
|
| 110 |
+
""" Resolve Model Data Config
|
| 111 |
+
This is equivalent to resolve_data_config() but with arguments re-ordered to put model first.
|
| 112 |
+
|
| 113 |
+
Args:
|
| 114 |
+
model (nn.Module): the model instance
|
| 115 |
+
args (dict): command line arguments / configuration in dict form (overrides pretrained_cfg)
|
| 116 |
+
pretrained_cfg (dict): pretrained model config (overrides pretrained_cfg attached to model)
|
| 117 |
+
use_test_size (bool): use the test time input resolution (if one exists) instead of default train resolution
|
| 118 |
+
verbose (bool): enable extra logging of resolved values
|
| 119 |
+
|
| 120 |
+
Returns:
|
| 121 |
+
dictionary of config
|
| 122 |
+
"""
|
| 123 |
+
return resolve_data_config(
|
| 124 |
+
args=args,
|
| 125 |
+
pretrained_cfg=pretrained_cfg,
|
| 126 |
+
model=model,
|
| 127 |
+
use_test_size=use_test_size,
|
| 128 |
+
verbose=verbose,
|
| 129 |
+
)
|
timm/data/constants.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
DEFAULT_CROP_PCT = 0.875
|
| 2 |
+
DEFAULT_CROP_MODE = 'center'
|
| 3 |
+
IMAGENET_DEFAULT_MEAN = (0.485, 0.456, 0.406)
|
| 4 |
+
IMAGENET_DEFAULT_STD = (0.229, 0.224, 0.225)
|
| 5 |
+
IMAGENET_INCEPTION_MEAN = (0.5, 0.5, 0.5)
|
| 6 |
+
IMAGENET_INCEPTION_STD = (0.5, 0.5, 0.5)
|
| 7 |
+
IMAGENET_DPN_MEAN = (124 / 255, 117 / 255, 104 / 255)
|
| 8 |
+
IMAGENET_DPN_STD = tuple([1 / (.0167 * 255)] * 3)
|
| 9 |
+
OPENAI_CLIP_MEAN = (0.48145466, 0.4578275, 0.40821073)
|
| 10 |
+
OPENAI_CLIP_STD = (0.26862954, 0.26130258, 0.27577711)
|
timm/data/dataset.py
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" Quick n Simple Image Folder, Tarfile based DataSet
|
| 2 |
+
|
| 3 |
+
Hacked together by / Copyright 2019, Ross Wightman
|
| 4 |
+
"""
|
| 5 |
+
import io
|
| 6 |
+
import logging
|
| 7 |
+
from typing import Optional
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
import torch.utils.data as data
|
| 11 |
+
from PIL import Image
|
| 12 |
+
|
| 13 |
+
from .readers import create_reader
|
| 14 |
+
|
| 15 |
+
_logger = logging.getLogger(__name__)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
_ERROR_RETRY = 50
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class ImageDataset(data.Dataset):
|
| 22 |
+
|
| 23 |
+
def __init__(
|
| 24 |
+
self,
|
| 25 |
+
root,
|
| 26 |
+
reader=None,
|
| 27 |
+
split='train',
|
| 28 |
+
class_map=None,
|
| 29 |
+
load_bytes=False,
|
| 30 |
+
input_img_mode='RGB',
|
| 31 |
+
transform=None,
|
| 32 |
+
target_transform=None,
|
| 33 |
+
):
|
| 34 |
+
if reader is None or isinstance(reader, str):
|
| 35 |
+
reader = create_reader(
|
| 36 |
+
reader or '',
|
| 37 |
+
root=root,
|
| 38 |
+
split=split,
|
| 39 |
+
class_map=class_map
|
| 40 |
+
)
|
| 41 |
+
self.reader = reader
|
| 42 |
+
self.load_bytes = load_bytes
|
| 43 |
+
self.input_img_mode = input_img_mode
|
| 44 |
+
self.transform = transform
|
| 45 |
+
self.target_transform = target_transform
|
| 46 |
+
self._consecutive_errors = 0
|
| 47 |
+
|
| 48 |
+
def __getitem__(self, index):
|
| 49 |
+
img, target = self.reader[index]
|
| 50 |
+
|
| 51 |
+
try:
|
| 52 |
+
img = img.read() if self.load_bytes else Image.open(img)
|
| 53 |
+
except Exception as e:
|
| 54 |
+
_logger.warning(f'Skipped sample (index {index}, file {self.reader.filename(index)}). {str(e)}')
|
| 55 |
+
self._consecutive_errors += 1
|
| 56 |
+
if self._consecutive_errors < _ERROR_RETRY:
|
| 57 |
+
return self.__getitem__((index + 1) % len(self.reader))
|
| 58 |
+
else:
|
| 59 |
+
raise e
|
| 60 |
+
self._consecutive_errors = 0
|
| 61 |
+
|
| 62 |
+
if self.input_img_mode and not self.load_bytes:
|
| 63 |
+
img = img.convert(self.input_img_mode)
|
| 64 |
+
if self.transform is not None:
|
| 65 |
+
img = self.transform(img)
|
| 66 |
+
|
| 67 |
+
if target is None:
|
| 68 |
+
target = -1
|
| 69 |
+
elif self.target_transform is not None:
|
| 70 |
+
target = self.target_transform(target)
|
| 71 |
+
|
| 72 |
+
return img, target
|
| 73 |
+
|
| 74 |
+
def __len__(self):
|
| 75 |
+
return len(self.reader)
|
| 76 |
+
|
| 77 |
+
def filename(self, index, basename=False, absolute=False):
|
| 78 |
+
return self.reader.filename(index, basename, absolute)
|
| 79 |
+
|
| 80 |
+
def filenames(self, basename=False, absolute=False):
|
| 81 |
+
return self.reader.filenames(basename, absolute)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
class IterableImageDataset(data.IterableDataset):
|
| 85 |
+
|
| 86 |
+
def __init__(
|
| 87 |
+
self,
|
| 88 |
+
root,
|
| 89 |
+
reader=None,
|
| 90 |
+
split='train',
|
| 91 |
+
class_map=None,
|
| 92 |
+
is_training=False,
|
| 93 |
+
batch_size=1,
|
| 94 |
+
num_samples=None,
|
| 95 |
+
seed=42,
|
| 96 |
+
repeats=0,
|
| 97 |
+
download=False,
|
| 98 |
+
input_img_mode='RGB',
|
| 99 |
+
input_key=None,
|
| 100 |
+
target_key=None,
|
| 101 |
+
transform=None,
|
| 102 |
+
target_transform=None,
|
| 103 |
+
max_steps=None,
|
| 104 |
+
):
|
| 105 |
+
assert reader is not None
|
| 106 |
+
if isinstance(reader, str):
|
| 107 |
+
self.reader = create_reader(
|
| 108 |
+
reader,
|
| 109 |
+
root=root,
|
| 110 |
+
split=split,
|
| 111 |
+
class_map=class_map,
|
| 112 |
+
is_training=is_training,
|
| 113 |
+
batch_size=batch_size,
|
| 114 |
+
num_samples=num_samples,
|
| 115 |
+
seed=seed,
|
| 116 |
+
repeats=repeats,
|
| 117 |
+
download=download,
|
| 118 |
+
input_img_mode=input_img_mode,
|
| 119 |
+
input_key=input_key,
|
| 120 |
+
target_key=target_key,
|
| 121 |
+
max_steps=max_steps,
|
| 122 |
+
)
|
| 123 |
+
else:
|
| 124 |
+
self.reader = reader
|
| 125 |
+
self.transform = transform
|
| 126 |
+
self.target_transform = target_transform
|
| 127 |
+
self._consecutive_errors = 0
|
| 128 |
+
|
| 129 |
+
def __iter__(self):
|
| 130 |
+
for img, target in self.reader:
|
| 131 |
+
if self.transform is not None:
|
| 132 |
+
img = self.transform(img)
|
| 133 |
+
if self.target_transform is not None:
|
| 134 |
+
target = self.target_transform(target)
|
| 135 |
+
yield img, target
|
| 136 |
+
|
| 137 |
+
def __len__(self):
|
| 138 |
+
if hasattr(self.reader, '__len__'):
|
| 139 |
+
return len(self.reader)
|
| 140 |
+
else:
|
| 141 |
+
return 0
|
| 142 |
+
|
| 143 |
+
def set_epoch(self, count):
|
| 144 |
+
# TFDS and WDS need external epoch count for deterministic cross process shuffle
|
| 145 |
+
if hasattr(self.reader, 'set_epoch'):
|
| 146 |
+
self.reader.set_epoch(count)
|
| 147 |
+
|
| 148 |
+
def set_loader_cfg(
|
| 149 |
+
self,
|
| 150 |
+
num_workers: Optional[int] = None,
|
| 151 |
+
):
|
| 152 |
+
# TFDS and WDS readers need # workers for correct # samples estimate before loader processes created
|
| 153 |
+
if hasattr(self.reader, 'set_loader_cfg'):
|
| 154 |
+
self.reader.set_loader_cfg(num_workers=num_workers)
|
| 155 |
+
|
| 156 |
+
def filename(self, index, basename=False, absolute=False):
|
| 157 |
+
assert False, 'Filename lookup by index not supported, use filenames().'
|
| 158 |
+
|
| 159 |
+
def filenames(self, basename=False, absolute=False):
|
| 160 |
+
return self.reader.filenames(basename, absolute)
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
class AugMixDataset(torch.utils.data.Dataset):
|
| 164 |
+
"""Dataset wrapper to perform AugMix or other clean/augmentation mixes"""
|
| 165 |
+
|
| 166 |
+
def __init__(self, dataset, num_splits=2):
|
| 167 |
+
self.augmentation = None
|
| 168 |
+
self.normalize = None
|
| 169 |
+
self.dataset = dataset
|
| 170 |
+
if self.dataset.transform is not None:
|
| 171 |
+
self._set_transforms(self.dataset.transform)
|
| 172 |
+
self.num_splits = num_splits
|
| 173 |
+
|
| 174 |
+
def _set_transforms(self, x):
|
| 175 |
+
assert isinstance(x, (list, tuple)) and len(x) == 3, 'Expecting a tuple/list of 3 transforms'
|
| 176 |
+
self.dataset.transform = x[0]
|
| 177 |
+
self.augmentation = x[1]
|
| 178 |
+
self.normalize = x[2]
|
| 179 |
+
|
| 180 |
+
@property
|
| 181 |
+
def transform(self):
|
| 182 |
+
return self.dataset.transform
|
| 183 |
+
|
| 184 |
+
@transform.setter
|
| 185 |
+
def transform(self, x):
|
| 186 |
+
self._set_transforms(x)
|
| 187 |
+
|
| 188 |
+
def _normalize(self, x):
|
| 189 |
+
return x if self.normalize is None else self.normalize(x)
|
| 190 |
+
|
| 191 |
+
def __getitem__(self, i):
|
| 192 |
+
x, y = self.dataset[i] # all splits share the same dataset base transform
|
| 193 |
+
x_list = [self._normalize(x)] # first split only normalizes (this is the 'clean' split)
|
| 194 |
+
# run the full augmentation on the remaining splits
|
| 195 |
+
for _ in range(self.num_splits - 1):
|
| 196 |
+
x_list.append(self._normalize(self.augmentation(x)))
|
| 197 |
+
return tuple(x_list), y
|
| 198 |
+
|
| 199 |
+
def __len__(self):
|
| 200 |
+
return len(self.dataset)
|
timm/data/dataset_factory.py
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" Dataset Factory
|
| 2 |
+
|
| 3 |
+
Hacked together by / Copyright 2021, Ross Wightman
|
| 4 |
+
"""
|
| 5 |
+
import os
|
| 6 |
+
from typing import Optional
|
| 7 |
+
|
| 8 |
+
from torchvision.datasets import CIFAR100, CIFAR10, MNIST, KMNIST, FashionMNIST, ImageFolder
|
| 9 |
+
try:
|
| 10 |
+
from torchvision.datasets import Places365
|
| 11 |
+
has_places365 = True
|
| 12 |
+
except ImportError:
|
| 13 |
+
has_places365 = False
|
| 14 |
+
try:
|
| 15 |
+
from torchvision.datasets import INaturalist
|
| 16 |
+
has_inaturalist = True
|
| 17 |
+
except ImportError:
|
| 18 |
+
has_inaturalist = False
|
| 19 |
+
try:
|
| 20 |
+
from torchvision.datasets import QMNIST
|
| 21 |
+
has_qmnist = True
|
| 22 |
+
except ImportError:
|
| 23 |
+
has_qmnist = False
|
| 24 |
+
try:
|
| 25 |
+
from torchvision.datasets import ImageNet
|
| 26 |
+
has_imagenet = True
|
| 27 |
+
except ImportError:
|
| 28 |
+
has_imagenet = False
|
| 29 |
+
|
| 30 |
+
from .dataset import IterableImageDataset, ImageDataset
|
| 31 |
+
|
| 32 |
+
_TORCH_BASIC_DS = dict(
|
| 33 |
+
cifar10=CIFAR10,
|
| 34 |
+
cifar100=CIFAR100,
|
| 35 |
+
mnist=MNIST,
|
| 36 |
+
kmnist=KMNIST,
|
| 37 |
+
fashion_mnist=FashionMNIST,
|
| 38 |
+
)
|
| 39 |
+
_TRAIN_SYNONYM = dict(train=None, training=None)
|
| 40 |
+
_EVAL_SYNONYM = dict(val=None, valid=None, validation=None, eval=None, evaluation=None)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def _search_split(root, split):
|
| 44 |
+
# look for sub-folder with name of split in root and use that if it exists
|
| 45 |
+
split_name = split.split('[')[0]
|
| 46 |
+
try_root = os.path.join(root, split_name)
|
| 47 |
+
if os.path.exists(try_root):
|
| 48 |
+
return try_root
|
| 49 |
+
|
| 50 |
+
def _try(syn):
|
| 51 |
+
for s in syn:
|
| 52 |
+
try_root = os.path.join(root, s)
|
| 53 |
+
if os.path.exists(try_root):
|
| 54 |
+
return try_root
|
| 55 |
+
return root
|
| 56 |
+
if split_name in _TRAIN_SYNONYM:
|
| 57 |
+
root = _try(_TRAIN_SYNONYM)
|
| 58 |
+
elif split_name in _EVAL_SYNONYM:
|
| 59 |
+
root = _try(_EVAL_SYNONYM)
|
| 60 |
+
return root
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def create_dataset(
|
| 64 |
+
name: str,
|
| 65 |
+
root: Optional[str] = None,
|
| 66 |
+
split: str = 'validation',
|
| 67 |
+
search_split: bool = True,
|
| 68 |
+
class_map: dict = None,
|
| 69 |
+
load_bytes: bool = False,
|
| 70 |
+
is_training: bool = False,
|
| 71 |
+
download: bool = False,
|
| 72 |
+
batch_size: int = 1,
|
| 73 |
+
num_samples: Optional[int] = None,
|
| 74 |
+
seed: int = 42,
|
| 75 |
+
repeats: int = 0,
|
| 76 |
+
input_img_mode: str = 'RGB',
|
| 77 |
+
**kwargs,
|
| 78 |
+
):
|
| 79 |
+
""" Dataset factory method
|
| 80 |
+
|
| 81 |
+
In parentheses after each arg are the type of dataset supported for each arg, one of:
|
| 82 |
+
* folder - default, timm folder (or tar) based ImageDataset
|
| 83 |
+
* torch - torchvision based datasets
|
| 84 |
+
* HFDS - Hugging Face Datasets
|
| 85 |
+
* TFDS - Tensorflow-datasets wrapper in IterabeDataset interface via IterableImageDataset
|
| 86 |
+
* WDS - Webdataset
|
| 87 |
+
* all - any of the above
|
| 88 |
+
|
| 89 |
+
Args:
|
| 90 |
+
name: dataset name, empty is okay for folder based datasets
|
| 91 |
+
root: root folder of dataset (all)
|
| 92 |
+
split: dataset split (all)
|
| 93 |
+
search_split: search for split specific child fold from root so one can specify
|
| 94 |
+
`imagenet/` instead of `/imagenet/val`, etc on cmd line / config. (folder, torch/folder)
|
| 95 |
+
class_map: specify class -> index mapping via text file or dict (folder)
|
| 96 |
+
load_bytes: load data, return images as undecoded bytes (folder)
|
| 97 |
+
download: download dataset if not present and supported (HFDS, TFDS, torch)
|
| 98 |
+
is_training: create dataset in train mode, this is different from the split.
|
| 99 |
+
For Iterable / TDFS it enables shuffle, ignored for other datasets. (TFDS, WDS)
|
| 100 |
+
batch_size: batch size hint for (TFDS, WDS)
|
| 101 |
+
seed: seed for iterable datasets (TFDS, WDS)
|
| 102 |
+
repeats: dataset repeats per iteration i.e. epoch (TFDS, WDS)
|
| 103 |
+
input_img_mode: Input image color conversion mode e.g. 'RGB', 'L' (folder, TFDS, WDS, HFDS)
|
| 104 |
+
**kwargs: other args to pass to dataset
|
| 105 |
+
|
| 106 |
+
Returns:
|
| 107 |
+
Dataset object
|
| 108 |
+
"""
|
| 109 |
+
kwargs = {k: v for k, v in kwargs.items() if v is not None}
|
| 110 |
+
name = name.lower()
|
| 111 |
+
if name.startswith('torch/'):
|
| 112 |
+
name = name.split('/', 2)[-1]
|
| 113 |
+
torch_kwargs = dict(root=root, download=download, **kwargs)
|
| 114 |
+
if name in _TORCH_BASIC_DS:
|
| 115 |
+
ds_class = _TORCH_BASIC_DS[name]
|
| 116 |
+
use_train = split in _TRAIN_SYNONYM
|
| 117 |
+
ds = ds_class(train=use_train, **torch_kwargs)
|
| 118 |
+
elif name == 'inaturalist' or name == 'inat':
|
| 119 |
+
assert has_inaturalist, 'Please update to PyTorch 1.10, torchvision 0.11+ for Inaturalist'
|
| 120 |
+
target_type = 'full'
|
| 121 |
+
split_split = split.split('/')
|
| 122 |
+
if len(split_split) > 1:
|
| 123 |
+
target_type = split_split[0].split('_')
|
| 124 |
+
if len(target_type) == 1:
|
| 125 |
+
target_type = target_type[0]
|
| 126 |
+
split = split_split[-1]
|
| 127 |
+
if split in _TRAIN_SYNONYM:
|
| 128 |
+
split = '2021_train'
|
| 129 |
+
elif split in _EVAL_SYNONYM:
|
| 130 |
+
split = '2021_valid'
|
| 131 |
+
ds = INaturalist(version=split, target_type=target_type, **torch_kwargs)
|
| 132 |
+
elif name == 'places365':
|
| 133 |
+
assert has_places365, 'Please update to a newer PyTorch and torchvision for Places365 dataset.'
|
| 134 |
+
if split in _TRAIN_SYNONYM:
|
| 135 |
+
split = 'train-standard'
|
| 136 |
+
elif split in _EVAL_SYNONYM:
|
| 137 |
+
split = 'val'
|
| 138 |
+
ds = Places365(split=split, **torch_kwargs)
|
| 139 |
+
elif name == 'qmnist':
|
| 140 |
+
assert has_qmnist, 'Please update to a newer PyTorch and torchvision for QMNIST dataset.'
|
| 141 |
+
use_train = split in _TRAIN_SYNONYM
|
| 142 |
+
ds = QMNIST(train=use_train, **torch_kwargs)
|
| 143 |
+
elif name == 'imagenet':
|
| 144 |
+
assert has_imagenet, 'Please update to a newer PyTorch and torchvision for ImageNet dataset.'
|
| 145 |
+
if split in _EVAL_SYNONYM:
|
| 146 |
+
split = 'val'
|
| 147 |
+
ds = ImageNet(split=split, **torch_kwargs)
|
| 148 |
+
elif name == 'image_folder' or name == 'folder':
|
| 149 |
+
# in case torchvision ImageFolder is preferred over timm ImageDataset for some reason
|
| 150 |
+
if search_split and os.path.isdir(root):
|
| 151 |
+
# look for split specific sub-folder in root
|
| 152 |
+
root = _search_split(root, split)
|
| 153 |
+
ds = ImageFolder(root, **kwargs)
|
| 154 |
+
else:
|
| 155 |
+
assert False, f"Unknown torchvision dataset {name}"
|
| 156 |
+
elif name.startswith('hfds/'):
|
| 157 |
+
# NOTE right now, HF datasets default arrow format is a random-access Dataset,
|
| 158 |
+
# There will be a IterableDataset variant too, TBD
|
| 159 |
+
ds = ImageDataset(
|
| 160 |
+
root,
|
| 161 |
+
reader=name,
|
| 162 |
+
split=split,
|
| 163 |
+
class_map=class_map,
|
| 164 |
+
input_img_mode=input_img_mode,
|
| 165 |
+
**kwargs,
|
| 166 |
+
)
|
| 167 |
+
elif name.startswith('hfids/'):
|
| 168 |
+
ds = IterableImageDataset(
|
| 169 |
+
root,
|
| 170 |
+
reader=name,
|
| 171 |
+
split=split,
|
| 172 |
+
class_map=class_map,
|
| 173 |
+
is_training=is_training,
|
| 174 |
+
download=download,
|
| 175 |
+
batch_size=batch_size,
|
| 176 |
+
num_samples=num_samples,
|
| 177 |
+
repeats=repeats,
|
| 178 |
+
seed=seed,
|
| 179 |
+
input_img_mode=input_img_mode,
|
| 180 |
+
**kwargs
|
| 181 |
+
)
|
| 182 |
+
elif name.startswith('tfds/'):
|
| 183 |
+
ds = IterableImageDataset(
|
| 184 |
+
root,
|
| 185 |
+
reader=name,
|
| 186 |
+
split=split,
|
| 187 |
+
class_map=class_map,
|
| 188 |
+
is_training=is_training,
|
| 189 |
+
download=download,
|
| 190 |
+
batch_size=batch_size,
|
| 191 |
+
num_samples=num_samples,
|
| 192 |
+
repeats=repeats,
|
| 193 |
+
seed=seed,
|
| 194 |
+
input_img_mode=input_img_mode,
|
| 195 |
+
**kwargs
|
| 196 |
+
)
|
| 197 |
+
elif name.startswith('wds/'):
|
| 198 |
+
ds = IterableImageDataset(
|
| 199 |
+
root,
|
| 200 |
+
reader=name,
|
| 201 |
+
split=split,
|
| 202 |
+
class_map=class_map,
|
| 203 |
+
is_training=is_training,
|
| 204 |
+
batch_size=batch_size,
|
| 205 |
+
num_samples=num_samples,
|
| 206 |
+
repeats=repeats,
|
| 207 |
+
seed=seed,
|
| 208 |
+
input_img_mode=input_img_mode,
|
| 209 |
+
**kwargs
|
| 210 |
+
)
|
| 211 |
+
else:
|
| 212 |
+
# FIXME support more advance split cfg for ImageFolder/Tar datasets in the future
|
| 213 |
+
if search_split and os.path.isdir(root):
|
| 214 |
+
# look for split specific sub-folder in root
|
| 215 |
+
root = _search_split(root, split)
|
| 216 |
+
ds = ImageDataset(
|
| 217 |
+
root,
|
| 218 |
+
reader=name,
|
| 219 |
+
class_map=class_map,
|
| 220 |
+
load_bytes=load_bytes,
|
| 221 |
+
input_img_mode=input_img_mode,
|
| 222 |
+
**kwargs,
|
| 223 |
+
)
|
| 224 |
+
return ds
|
timm/data/dataset_info.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from abc import ABC, abstractmethod
|
| 2 |
+
from typing import Dict, List, Optional, Union
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class DatasetInfo(ABC):
|
| 6 |
+
|
| 7 |
+
def __init__(self):
|
| 8 |
+
pass
|
| 9 |
+
|
| 10 |
+
@abstractmethod
|
| 11 |
+
def num_classes(self):
|
| 12 |
+
pass
|
| 13 |
+
|
| 14 |
+
@abstractmethod
|
| 15 |
+
def label_names(self):
|
| 16 |
+
pass
|
| 17 |
+
|
| 18 |
+
@abstractmethod
|
| 19 |
+
def label_descriptions(self, detailed: bool = False, as_dict: bool = False) -> Union[List[str], Dict[str, str]]:
|
| 20 |
+
pass
|
| 21 |
+
|
| 22 |
+
@abstractmethod
|
| 23 |
+
def index_to_label_name(self, index) -> str:
|
| 24 |
+
pass
|
| 25 |
+
|
| 26 |
+
@abstractmethod
|
| 27 |
+
def index_to_description(self, index: int, detailed: bool = False) -> str:
|
| 28 |
+
pass
|
| 29 |
+
|
| 30 |
+
@abstractmethod
|
| 31 |
+
def label_name_to_description(self, label: str, detailed: bool = False) -> str:
|
| 32 |
+
pass
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class CustomDatasetInfo(DatasetInfo):
|
| 36 |
+
""" DatasetInfo that wraps passed values for custom datasets."""
|
| 37 |
+
|
| 38 |
+
def __init__(
|
| 39 |
+
self,
|
| 40 |
+
label_names: Union[List[str], Dict[int, str]],
|
| 41 |
+
label_descriptions: Optional[Dict[str, str]] = None
|
| 42 |
+
):
|
| 43 |
+
super().__init__()
|
| 44 |
+
assert len(label_names) > 0
|
| 45 |
+
self._label_names = label_names # label index => label name mapping
|
| 46 |
+
self._label_descriptions = label_descriptions # label name => label description mapping
|
| 47 |
+
if self._label_descriptions is not None:
|
| 48 |
+
# validate descriptions (label names required)
|
| 49 |
+
assert isinstance(self._label_descriptions, dict)
|
| 50 |
+
for n in self._label_names:
|
| 51 |
+
assert n in self._label_descriptions
|
| 52 |
+
|
| 53 |
+
def num_classes(self):
|
| 54 |
+
return len(self._label_names)
|
| 55 |
+
|
| 56 |
+
def label_names(self):
|
| 57 |
+
return self._label_names
|
| 58 |
+
|
| 59 |
+
def label_descriptions(self, detailed: bool = False, as_dict: bool = False) -> Union[List[str], Dict[str, str]]:
|
| 60 |
+
return self._label_descriptions
|
| 61 |
+
|
| 62 |
+
def label_name_to_description(self, label: str, detailed: bool = False) -> str:
|
| 63 |
+
if self._label_descriptions:
|
| 64 |
+
return self._label_descriptions[label]
|
| 65 |
+
return label # return label name itself if a descriptions is not present
|
| 66 |
+
|
| 67 |
+
def index_to_label_name(self, index) -> str:
|
| 68 |
+
assert 0 <= index < len(self._label_names)
|
| 69 |
+
return self._label_names[index]
|
| 70 |
+
|
| 71 |
+
def index_to_description(self, index: int, detailed: bool = False) -> str:
|
| 72 |
+
label = self.index_to_label_name(index)
|
| 73 |
+
return self.label_name_to_description(label, detailed=detailed)
|
timm/data/distributed_sampler.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
import torch
|
| 3 |
+
from torch.utils.data import Sampler
|
| 4 |
+
import torch.distributed as dist
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class OrderedDistributedSampler(Sampler):
|
| 8 |
+
"""Sampler that restricts data loading to a subset of the dataset.
|
| 9 |
+
It is especially useful in conjunction with
|
| 10 |
+
:class:`torch.nn.parallel.DistributedDataParallel`. In such case, each
|
| 11 |
+
process can pass a DistributedSampler instance as a DataLoader sampler,
|
| 12 |
+
and load a subset of the original dataset that is exclusive to it.
|
| 13 |
+
.. note::
|
| 14 |
+
Dataset is assumed to be of constant size.
|
| 15 |
+
Arguments:
|
| 16 |
+
dataset: Dataset used for sampling.
|
| 17 |
+
num_replicas (optional): Number of processes participating in
|
| 18 |
+
distributed training.
|
| 19 |
+
rank (optional): Rank of the current process within num_replicas.
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
def __init__(self, dataset, num_replicas=None, rank=None):
|
| 23 |
+
if num_replicas is None:
|
| 24 |
+
if not dist.is_available():
|
| 25 |
+
raise RuntimeError("Requires distributed package to be available")
|
| 26 |
+
num_replicas = dist.get_world_size()
|
| 27 |
+
if rank is None:
|
| 28 |
+
if not dist.is_available():
|
| 29 |
+
raise RuntimeError("Requires distributed package to be available")
|
| 30 |
+
rank = dist.get_rank()
|
| 31 |
+
self.dataset = dataset
|
| 32 |
+
self.num_replicas = num_replicas
|
| 33 |
+
self.rank = rank
|
| 34 |
+
self.num_samples = int(math.ceil(len(self.dataset) * 1.0 / self.num_replicas))
|
| 35 |
+
self.total_size = self.num_samples * self.num_replicas
|
| 36 |
+
|
| 37 |
+
def __iter__(self):
|
| 38 |
+
indices = list(range(len(self.dataset)))
|
| 39 |
+
|
| 40 |
+
# add extra samples to make it evenly divisible
|
| 41 |
+
indices += indices[:(self.total_size - len(indices))]
|
| 42 |
+
assert len(indices) == self.total_size
|
| 43 |
+
|
| 44 |
+
# subsample
|
| 45 |
+
indices = indices[self.rank:self.total_size:self.num_replicas]
|
| 46 |
+
assert len(indices) == self.num_samples
|
| 47 |
+
|
| 48 |
+
return iter(indices)
|
| 49 |
+
|
| 50 |
+
def __len__(self):
|
| 51 |
+
return self.num_samples
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
class RepeatAugSampler(Sampler):
|
| 55 |
+
"""Sampler that restricts data loading to a subset of the dataset for distributed,
|
| 56 |
+
with repeated augmentation.
|
| 57 |
+
It ensures that different each augmented version of a sample will be visible to a
|
| 58 |
+
different process (GPU). Heavily based on torch.utils.data.DistributedSampler
|
| 59 |
+
|
| 60 |
+
This sampler was taken from https://github.com/facebookresearch/deit/blob/0c4b8f60/samplers.py
|
| 61 |
+
Used in
|
| 62 |
+
Copyright (c) 2015-present, Facebook, Inc.
|
| 63 |
+
"""
|
| 64 |
+
|
| 65 |
+
def __init__(
|
| 66 |
+
self,
|
| 67 |
+
dataset,
|
| 68 |
+
num_replicas=None,
|
| 69 |
+
rank=None,
|
| 70 |
+
shuffle=True,
|
| 71 |
+
num_repeats=3,
|
| 72 |
+
selected_round=256,
|
| 73 |
+
selected_ratio=0,
|
| 74 |
+
):
|
| 75 |
+
if num_replicas is None:
|
| 76 |
+
if not dist.is_available():
|
| 77 |
+
raise RuntimeError("Requires distributed package to be available")
|
| 78 |
+
num_replicas = dist.get_world_size()
|
| 79 |
+
if rank is None:
|
| 80 |
+
if not dist.is_available():
|
| 81 |
+
raise RuntimeError("Requires distributed package to be available")
|
| 82 |
+
rank = dist.get_rank()
|
| 83 |
+
self.dataset = dataset
|
| 84 |
+
self.num_replicas = num_replicas
|
| 85 |
+
self.rank = rank
|
| 86 |
+
self.shuffle = shuffle
|
| 87 |
+
self.num_repeats = num_repeats
|
| 88 |
+
self.epoch = 0
|
| 89 |
+
self.num_samples = int(math.ceil(len(self.dataset) * num_repeats / self.num_replicas))
|
| 90 |
+
self.total_size = self.num_samples * self.num_replicas
|
| 91 |
+
# Determine the number of samples to select per epoch for each rank.
|
| 92 |
+
# num_selected logic defaults to be the same as original RASampler impl, but this one can be tweaked
|
| 93 |
+
# via selected_ratio and selected_round args.
|
| 94 |
+
selected_ratio = selected_ratio or num_replicas # ratio to reduce selected samples by, num_replicas if 0
|
| 95 |
+
if selected_round:
|
| 96 |
+
self.num_selected_samples = int(math.floor(
|
| 97 |
+
len(self.dataset) // selected_round * selected_round / selected_ratio))
|
| 98 |
+
else:
|
| 99 |
+
self.num_selected_samples = int(math.ceil(len(self.dataset) / selected_ratio))
|
| 100 |
+
|
| 101 |
+
def __iter__(self):
|
| 102 |
+
# deterministically shuffle based on epoch
|
| 103 |
+
g = torch.Generator()
|
| 104 |
+
g.manual_seed(self.epoch)
|
| 105 |
+
if self.shuffle:
|
| 106 |
+
indices = torch.randperm(len(self.dataset), generator=g)
|
| 107 |
+
else:
|
| 108 |
+
indices = torch.arange(start=0, end=len(self.dataset))
|
| 109 |
+
|
| 110 |
+
# produce repeats e.g. [0, 0, 0, 1, 1, 1, 2, 2, 2....]
|
| 111 |
+
if isinstance(self.num_repeats, float) and not self.num_repeats.is_integer():
|
| 112 |
+
# resample for repeats w/ non-integer ratio
|
| 113 |
+
repeat_size = math.ceil(self.num_repeats * len(self.dataset))
|
| 114 |
+
indices = indices[torch.tensor([int(i // self.num_repeats) for i in range(repeat_size)])]
|
| 115 |
+
else:
|
| 116 |
+
indices = torch.repeat_interleave(indices, repeats=int(self.num_repeats), dim=0)
|
| 117 |
+
indices = indices.tolist() # leaving as tensor thrashes dataloader memory
|
| 118 |
+
# add extra samples to make it evenly divisible
|
| 119 |
+
padding_size = self.total_size - len(indices)
|
| 120 |
+
if padding_size > 0:
|
| 121 |
+
indices += indices[:padding_size]
|
| 122 |
+
assert len(indices) == self.total_size
|
| 123 |
+
|
| 124 |
+
# subsample per rank
|
| 125 |
+
indices = indices[self.rank:self.total_size:self.num_replicas]
|
| 126 |
+
assert len(indices) == self.num_samples
|
| 127 |
+
|
| 128 |
+
# return up to num selected samples
|
| 129 |
+
return iter(indices[:self.num_selected_samples])
|
| 130 |
+
|
| 131 |
+
def __len__(self):
|
| 132 |
+
return self.num_selected_samples
|
| 133 |
+
|
| 134 |
+
def set_epoch(self, epoch):
|
| 135 |
+
self.epoch = epoch
|
timm/data/loader.py
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" Loader Factory, Fast Collate, CUDA Prefetcher
|
| 2 |
+
|
| 3 |
+
Prefetcher and Fast Collate inspired by NVIDIA APEX example at
|
| 4 |
+
https://github.com/NVIDIA/apex/commit/d5e2bb4bdeedd27b1dfaf5bb2b24d6c000dee9be#diff-cf86c282ff7fba81fad27a559379d5bf
|
| 5 |
+
|
| 6 |
+
Hacked together by / Copyright 2019, Ross Wightman
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
# Copyright 2026 Kiel University
|
| 10 |
+
#
|
| 11 |
+
# This source code is licensed under the MIT license found in the
|
| 12 |
+
# LICENSE file in the root directory of this source tree.
|
| 13 |
+
# Based on pytorch-image-models (timm); see NOTICE.
|
| 14 |
+
#
|
| 15 |
+
# Modifications:
|
| 16 |
+
# - Increased the DataLoader prefetch factor for ProgResViT training.
|
| 17 |
+
|
| 18 |
+
import logging
|
| 19 |
+
import random
|
| 20 |
+
from contextlib import suppress
|
| 21 |
+
from functools import partial
|
| 22 |
+
from itertools import repeat
|
| 23 |
+
from typing import Callable, Optional, Tuple, Union
|
| 24 |
+
|
| 25 |
+
import torch
|
| 26 |
+
import torch.utils.data
|
| 27 |
+
import numpy as np
|
| 28 |
+
|
| 29 |
+
from .constants import IMAGENET_DEFAULT_MEAN, IMAGENET_DEFAULT_STD
|
| 30 |
+
from .dataset import IterableImageDataset, ImageDataset
|
| 31 |
+
from .distributed_sampler import OrderedDistributedSampler, RepeatAugSampler
|
| 32 |
+
from .random_erasing import RandomErasing
|
| 33 |
+
from .mixup import FastCollateMixup
|
| 34 |
+
from .transforms_factory import create_transform
|
| 35 |
+
|
| 36 |
+
_logger = logging.getLogger(__name__)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def fast_collate(batch):
|
| 40 |
+
""" A fast collation function optimized for uint8 images (np array or torch) and int64 targets (labels)"""
|
| 41 |
+
assert isinstance(batch[0], tuple)
|
| 42 |
+
batch_size = len(batch)
|
| 43 |
+
if isinstance(batch[0][0], tuple):
|
| 44 |
+
# This branch 'deinterleaves' and flattens tuples of input tensors into one tensor ordered by position
|
| 45 |
+
# such that all tuple of position n will end up in a torch.split(tensor, batch_size) in nth position
|
| 46 |
+
inner_tuple_size = len(batch[0][0])
|
| 47 |
+
flattened_batch_size = batch_size * inner_tuple_size
|
| 48 |
+
targets = torch.zeros(flattened_batch_size, dtype=torch.int64)
|
| 49 |
+
tensor = torch.zeros((flattened_batch_size, *batch[0][0][0].shape), dtype=torch.uint8)
|
| 50 |
+
for i in range(batch_size):
|
| 51 |
+
assert len(batch[i][0]) == inner_tuple_size # all input tensor tuples must be same length
|
| 52 |
+
for j in range(inner_tuple_size):
|
| 53 |
+
targets[i + j * batch_size] = batch[i][1]
|
| 54 |
+
tensor[i + j * batch_size] += torch.from_numpy(batch[i][0][j])
|
| 55 |
+
return tensor, targets
|
| 56 |
+
elif isinstance(batch[0][0], np.ndarray):
|
| 57 |
+
targets = torch.tensor([b[1] for b in batch], dtype=torch.int64)
|
| 58 |
+
assert len(targets) == batch_size
|
| 59 |
+
tensor = torch.zeros((batch_size, *batch[0][0].shape), dtype=torch.uint8)
|
| 60 |
+
for i in range(batch_size):
|
| 61 |
+
tensor[i] += torch.from_numpy(batch[i][0])
|
| 62 |
+
return tensor, targets
|
| 63 |
+
elif isinstance(batch[0][0], torch.Tensor):
|
| 64 |
+
targets = torch.tensor([b[1] for b in batch], dtype=torch.int64)
|
| 65 |
+
assert len(targets) == batch_size
|
| 66 |
+
tensor = torch.zeros((batch_size, *batch[0][0].shape), dtype=torch.uint8)
|
| 67 |
+
for i in range(batch_size):
|
| 68 |
+
tensor[i].copy_(batch[i][0])
|
| 69 |
+
return tensor, targets
|
| 70 |
+
else:
|
| 71 |
+
assert False
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def adapt_to_chs(x, n):
|
| 75 |
+
if not isinstance(x, (tuple, list)):
|
| 76 |
+
x = tuple(repeat(x, n))
|
| 77 |
+
elif len(x) != n:
|
| 78 |
+
x_mean = np.mean(x).item()
|
| 79 |
+
x = (x_mean,) * n
|
| 80 |
+
_logger.warning(f'Pretrained mean/std different shape than model, using avg value {x}.')
|
| 81 |
+
else:
|
| 82 |
+
assert len(x) == n, 'normalization stats must match image channels'
|
| 83 |
+
return x
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
class PrefetchLoader:
|
| 87 |
+
|
| 88 |
+
def __init__(
|
| 89 |
+
self,
|
| 90 |
+
loader,
|
| 91 |
+
mean=IMAGENET_DEFAULT_MEAN,
|
| 92 |
+
std=IMAGENET_DEFAULT_STD,
|
| 93 |
+
channels=3,
|
| 94 |
+
device=torch.device('cuda'),
|
| 95 |
+
img_dtype=torch.float32,
|
| 96 |
+
fp16=False,
|
| 97 |
+
re_prob=0.,
|
| 98 |
+
re_mode='const',
|
| 99 |
+
re_count=1,
|
| 100 |
+
re_num_splits=0):
|
| 101 |
+
|
| 102 |
+
mean = adapt_to_chs(mean, channels)
|
| 103 |
+
std = adapt_to_chs(std, channels)
|
| 104 |
+
normalization_shape = (1, channels, 1, 1)
|
| 105 |
+
|
| 106 |
+
self.loader = loader
|
| 107 |
+
self.device = device
|
| 108 |
+
if fp16:
|
| 109 |
+
# fp16 arg is deprecated, but will override dtype arg if set for bwd compat
|
| 110 |
+
img_dtype = torch.float16
|
| 111 |
+
self.img_dtype = img_dtype
|
| 112 |
+
self.mean = torch.tensor(
|
| 113 |
+
[x * 255 for x in mean], device=device, dtype=img_dtype).view(normalization_shape)
|
| 114 |
+
self.std = torch.tensor(
|
| 115 |
+
[x * 255 for x in std], device=device, dtype=img_dtype).view(normalization_shape)
|
| 116 |
+
if re_prob > 0.:
|
| 117 |
+
self.random_erasing = RandomErasing(
|
| 118 |
+
probability=re_prob,
|
| 119 |
+
mode=re_mode,
|
| 120 |
+
max_count=re_count,
|
| 121 |
+
num_splits=re_num_splits,
|
| 122 |
+
device=device,
|
| 123 |
+
)
|
| 124 |
+
else:
|
| 125 |
+
self.random_erasing = None
|
| 126 |
+
self.is_cuda = torch.cuda.is_available() and device.type == 'cuda'
|
| 127 |
+
|
| 128 |
+
def __iter__(self):
|
| 129 |
+
first = True
|
| 130 |
+
if self.is_cuda:
|
| 131 |
+
stream = torch.cuda.Stream()
|
| 132 |
+
stream_context = partial(torch.cuda.stream, stream=stream)
|
| 133 |
+
else:
|
| 134 |
+
stream = None
|
| 135 |
+
stream_context = suppress
|
| 136 |
+
|
| 137 |
+
for next_input, next_target in self.loader:
|
| 138 |
+
|
| 139 |
+
with stream_context():
|
| 140 |
+
next_input = next_input.to(device=self.device, non_blocking=True)
|
| 141 |
+
next_target = next_target.to(device=self.device, non_blocking=True)
|
| 142 |
+
next_input = next_input.to(self.img_dtype).sub_(self.mean).div_(self.std)
|
| 143 |
+
if self.random_erasing is not None:
|
| 144 |
+
next_input = self.random_erasing(next_input)
|
| 145 |
+
|
| 146 |
+
if not first:
|
| 147 |
+
yield input, target
|
| 148 |
+
else:
|
| 149 |
+
first = False
|
| 150 |
+
|
| 151 |
+
if stream is not None:
|
| 152 |
+
torch.cuda.current_stream().wait_stream(stream)
|
| 153 |
+
|
| 154 |
+
input = next_input
|
| 155 |
+
target = next_target
|
| 156 |
+
|
| 157 |
+
yield input, target
|
| 158 |
+
|
| 159 |
+
def __len__(self):
|
| 160 |
+
return len(self.loader)
|
| 161 |
+
|
| 162 |
+
@property
|
| 163 |
+
def sampler(self):
|
| 164 |
+
return self.loader.sampler
|
| 165 |
+
|
| 166 |
+
@property
|
| 167 |
+
def dataset(self):
|
| 168 |
+
return self.loader.dataset
|
| 169 |
+
|
| 170 |
+
@property
|
| 171 |
+
def mixup_enabled(self):
|
| 172 |
+
if isinstance(self.loader.collate_fn, FastCollateMixup):
|
| 173 |
+
return self.loader.collate_fn.mixup_enabled
|
| 174 |
+
else:
|
| 175 |
+
return False
|
| 176 |
+
|
| 177 |
+
@mixup_enabled.setter
|
| 178 |
+
def mixup_enabled(self, x):
|
| 179 |
+
if isinstance(self.loader.collate_fn, FastCollateMixup):
|
| 180 |
+
self.loader.collate_fn.mixup_enabled = x
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
def _worker_init(worker_id, worker_seeding='all'):
|
| 184 |
+
worker_info = torch.utils.data.get_worker_info()
|
| 185 |
+
assert worker_info.id == worker_id
|
| 186 |
+
if isinstance(worker_seeding, Callable):
|
| 187 |
+
seed = worker_seeding(worker_info)
|
| 188 |
+
random.seed(seed)
|
| 189 |
+
torch.manual_seed(seed)
|
| 190 |
+
np.random.seed(seed % (2 ** 32 - 1))
|
| 191 |
+
else:
|
| 192 |
+
assert worker_seeding in ('all', 'part')
|
| 193 |
+
# random / torch seed already called in dataloader iter class w/ worker_info.seed
|
| 194 |
+
# to reproduce some old results (same seed + hparam combo), partial seeding is required (skip numpy re-seed)
|
| 195 |
+
if worker_seeding == 'all':
|
| 196 |
+
np.random.seed(worker_info.seed % (2 ** 32 - 1))
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def create_loader(
|
| 200 |
+
dataset: Union[ImageDataset, IterableImageDataset],
|
| 201 |
+
input_size: Union[int, Tuple[int, int], Tuple[int, int, int]],
|
| 202 |
+
batch_size: int,
|
| 203 |
+
is_training: bool = False,
|
| 204 |
+
no_aug: bool = False,
|
| 205 |
+
re_prob: float = 0.,
|
| 206 |
+
re_mode: str = 'const',
|
| 207 |
+
re_count: int = 1,
|
| 208 |
+
re_split: bool = False,
|
| 209 |
+
train_crop_mode: Optional[str] = None,
|
| 210 |
+
scale: Optional[Tuple[float, float]] = None,
|
| 211 |
+
ratio: Optional[Tuple[float, float]] = None,
|
| 212 |
+
hflip: float = 0.5,
|
| 213 |
+
vflip: float = 0.,
|
| 214 |
+
color_jitter: float = 0.4,
|
| 215 |
+
color_jitter_prob: Optional[float] = None,
|
| 216 |
+
grayscale_prob: float = 0.,
|
| 217 |
+
gaussian_blur_prob: float = 0.,
|
| 218 |
+
auto_augment: Optional[str] = None,
|
| 219 |
+
num_aug_repeats: int = 0,
|
| 220 |
+
num_aug_splits: int = 0,
|
| 221 |
+
interpolation: str = 'bilinear',
|
| 222 |
+
mean: Tuple[float, ...] = IMAGENET_DEFAULT_MEAN,
|
| 223 |
+
std: Tuple[float, ...] = IMAGENET_DEFAULT_STD,
|
| 224 |
+
num_workers: int = 1,
|
| 225 |
+
distributed: bool = False,
|
| 226 |
+
crop_pct: Optional[float] = None,
|
| 227 |
+
crop_mode: Optional[str] = None,
|
| 228 |
+
crop_border_pixels: Optional[int] = None,
|
| 229 |
+
collate_fn: Optional[Callable] = None,
|
| 230 |
+
pin_memory: bool = False,
|
| 231 |
+
fp16: bool = False, # deprecated, use img_dtype
|
| 232 |
+
img_dtype: torch.dtype = torch.float32,
|
| 233 |
+
device: torch.device = torch.device('cuda'),
|
| 234 |
+
use_prefetcher: bool = True,
|
| 235 |
+
use_multi_epochs_loader: bool = False,
|
| 236 |
+
persistent_workers: bool = True,
|
| 237 |
+
worker_seeding: str = 'all',
|
| 238 |
+
tf_preprocessing: bool = False,
|
| 239 |
+
):
|
| 240 |
+
"""
|
| 241 |
+
|
| 242 |
+
Args:
|
| 243 |
+
dataset: The image dataset to load.
|
| 244 |
+
input_size: Target input size (channels, height, width) tuple or size scalar.
|
| 245 |
+
batch_size: Number of samples in a batch.
|
| 246 |
+
is_training: Return training (random) transforms.
|
| 247 |
+
no_aug: Disable augmentation for training (useful for debug).
|
| 248 |
+
re_prob: Random erasing probability.
|
| 249 |
+
re_mode: Random erasing fill mode.
|
| 250 |
+
re_count: Number of random erasing regions.
|
| 251 |
+
re_split: Control split of random erasing across batch size.
|
| 252 |
+
scale: Random resize scale range (crop area, < 1.0 => zoom in).
|
| 253 |
+
ratio: Random aspect ratio range (crop ratio for RRC, ratio adjustment factor for RKR).
|
| 254 |
+
hflip: Horizontal flip probability.
|
| 255 |
+
vflip: Vertical flip probability.
|
| 256 |
+
color_jitter: Random color jitter component factors (brightness, contrast, saturation, hue).
|
| 257 |
+
Scalar is applied as (scalar,) * 3 (no hue).
|
| 258 |
+
color_jitter_prob: Apply color jitter with this probability if not None (for SimlCLR-like aug
|
| 259 |
+
grayscale_prob: Probability of converting image to grayscale (for SimCLR-like aug).
|
| 260 |
+
gaussian_blur_prob: Probability of applying gaussian blur (for SimCLR-like aug).
|
| 261 |
+
auto_augment: Auto augment configuration string (see auto_augment.py).
|
| 262 |
+
num_aug_repeats: Enable special sampler to repeat same augmentation across distributed GPUs.
|
| 263 |
+
num_aug_splits: Enable mode where augmentations can be split across the batch.
|
| 264 |
+
interpolation: Image interpolation mode.
|
| 265 |
+
mean: Image normalization mean.
|
| 266 |
+
std: Image normalization standard deviation.
|
| 267 |
+
num_workers: Num worker processes per DataLoader.
|
| 268 |
+
distributed: Enable dataloading for distributed training.
|
| 269 |
+
crop_pct: Inference crop percentage (output size / resize size).
|
| 270 |
+
crop_mode: Inference crop mode. One of ['squash', 'border', 'center']. Defaults to 'center' when None.
|
| 271 |
+
crop_border_pixels: Inference crop border of specified # pixels around edge of original image.
|
| 272 |
+
collate_fn: Override default collate_fn.
|
| 273 |
+
pin_memory: Pin memory for device transfer.
|
| 274 |
+
fp16: Deprecated argument for half-precision input dtype. Use img_dtype.
|
| 275 |
+
img_dtype: Data type for input image.
|
| 276 |
+
device: Device to transfer inputs and targets to.
|
| 277 |
+
use_prefetcher: Use efficient pre-fetcher to load samples onto device.
|
| 278 |
+
use_multi_epochs_loader:
|
| 279 |
+
persistent_workers: Enable persistent worker processes.
|
| 280 |
+
worker_seeding: Control worker random seeding at init.
|
| 281 |
+
tf_preprocessing: Use TF 1.0 inference preprocessing for testing model ports.
|
| 282 |
+
|
| 283 |
+
Returns:
|
| 284 |
+
DataLoader
|
| 285 |
+
"""
|
| 286 |
+
re_num_splits = 0
|
| 287 |
+
if re_split:
|
| 288 |
+
# apply RE to second half of batch if no aug split otherwise line up with aug split
|
| 289 |
+
re_num_splits = num_aug_splits or 2
|
| 290 |
+
dataset.transform = create_transform(
|
| 291 |
+
input_size,
|
| 292 |
+
is_training=is_training,
|
| 293 |
+
no_aug=no_aug,
|
| 294 |
+
train_crop_mode=train_crop_mode,
|
| 295 |
+
scale=scale,
|
| 296 |
+
ratio=ratio,
|
| 297 |
+
hflip=hflip,
|
| 298 |
+
vflip=vflip,
|
| 299 |
+
color_jitter=color_jitter,
|
| 300 |
+
color_jitter_prob=color_jitter_prob,
|
| 301 |
+
grayscale_prob=grayscale_prob,
|
| 302 |
+
gaussian_blur_prob=gaussian_blur_prob,
|
| 303 |
+
auto_augment=auto_augment,
|
| 304 |
+
interpolation=interpolation,
|
| 305 |
+
mean=mean,
|
| 306 |
+
std=std,
|
| 307 |
+
crop_pct=crop_pct,
|
| 308 |
+
crop_mode=crop_mode,
|
| 309 |
+
crop_border_pixels=crop_border_pixels,
|
| 310 |
+
re_prob=re_prob,
|
| 311 |
+
re_mode=re_mode,
|
| 312 |
+
re_count=re_count,
|
| 313 |
+
re_num_splits=re_num_splits,
|
| 314 |
+
tf_preprocessing=tf_preprocessing,
|
| 315 |
+
use_prefetcher=use_prefetcher,
|
| 316 |
+
separate=num_aug_splits > 0,
|
| 317 |
+
)
|
| 318 |
+
|
| 319 |
+
if isinstance(dataset, IterableImageDataset):
|
| 320 |
+
# give Iterable datasets early knowledge of num_workers so that sample estimates
|
| 321 |
+
# are correct before worker processes are launched
|
| 322 |
+
dataset.set_loader_cfg(num_workers=num_workers)
|
| 323 |
+
|
| 324 |
+
sampler = None
|
| 325 |
+
if distributed and not isinstance(dataset, torch.utils.data.IterableDataset):
|
| 326 |
+
if is_training:
|
| 327 |
+
if num_aug_repeats:
|
| 328 |
+
sampler = RepeatAugSampler(dataset, num_repeats=num_aug_repeats)
|
| 329 |
+
else:
|
| 330 |
+
sampler = torch.utils.data.distributed.DistributedSampler(dataset)
|
| 331 |
+
else:
|
| 332 |
+
# This will add extra duplicate entries to result in equal num
|
| 333 |
+
# of samples per-process, will slightly alter validation results
|
| 334 |
+
sampler = OrderedDistributedSampler(dataset)
|
| 335 |
+
else:
|
| 336 |
+
assert num_aug_repeats == 0, "RepeatAugment not currently supported in non-distributed or IterableDataset use"
|
| 337 |
+
|
| 338 |
+
if collate_fn is None:
|
| 339 |
+
collate_fn = fast_collate if use_prefetcher else torch.utils.data.dataloader.default_collate
|
| 340 |
+
|
| 341 |
+
loader_class = torch.utils.data.DataLoader
|
| 342 |
+
if use_multi_epochs_loader:
|
| 343 |
+
loader_class = MultiEpochsDataLoader
|
| 344 |
+
|
| 345 |
+
loader_args = dict(
|
| 346 |
+
batch_size=batch_size,
|
| 347 |
+
shuffle=not isinstance(dataset, torch.utils.data.IterableDataset) and sampler is None and is_training,
|
| 348 |
+
num_workers=num_workers,
|
| 349 |
+
sampler=sampler,
|
| 350 |
+
collate_fn=collate_fn,
|
| 351 |
+
prefetch_factor=20,
|
| 352 |
+
pin_memory=pin_memory,
|
| 353 |
+
drop_last=is_training,
|
| 354 |
+
worker_init_fn=partial(_worker_init, worker_seeding=worker_seeding),
|
| 355 |
+
persistent_workers=persistent_workers
|
| 356 |
+
)
|
| 357 |
+
try:
|
| 358 |
+
loader = loader_class(dataset, **loader_args)
|
| 359 |
+
except TypeError as e:
|
| 360 |
+
loader_args.pop('persistent_workers') # only in Pytorch 1.7+
|
| 361 |
+
loader = loader_class(dataset, **loader_args)
|
| 362 |
+
if use_prefetcher:
|
| 363 |
+
prefetch_re_prob = re_prob if is_training and not no_aug else 0.
|
| 364 |
+
loader = PrefetchLoader(
|
| 365 |
+
loader,
|
| 366 |
+
mean=mean,
|
| 367 |
+
std=std,
|
| 368 |
+
channels=input_size[0],
|
| 369 |
+
device=device,
|
| 370 |
+
fp16=fp16, # deprecated, use img_dtype
|
| 371 |
+
img_dtype=img_dtype,
|
| 372 |
+
re_prob=prefetch_re_prob,
|
| 373 |
+
re_mode=re_mode,
|
| 374 |
+
re_count=re_count,
|
| 375 |
+
re_num_splits=re_num_splits
|
| 376 |
+
)
|
| 377 |
+
|
| 378 |
+
return loader
|
| 379 |
+
|
| 380 |
+
|
| 381 |
+
class MultiEpochsDataLoader(torch.utils.data.DataLoader):
|
| 382 |
+
|
| 383 |
+
def __init__(self, *args, **kwargs):
|
| 384 |
+
super().__init__(*args, **kwargs)
|
| 385 |
+
self._DataLoader__initialized = False
|
| 386 |
+
if self.batch_sampler is None:
|
| 387 |
+
self.sampler = _RepeatSampler(self.sampler)
|
| 388 |
+
else:
|
| 389 |
+
self.batch_sampler = _RepeatSampler(self.batch_sampler)
|
| 390 |
+
self._DataLoader__initialized = True
|
| 391 |
+
self.iterator = super().__iter__()
|
| 392 |
+
|
| 393 |
+
def __len__(self):
|
| 394 |
+
return len(self.sampler) if self.batch_sampler is None else len(self.batch_sampler.sampler)
|
| 395 |
+
|
| 396 |
+
def __iter__(self):
|
| 397 |
+
for i in range(len(self)):
|
| 398 |
+
yield next(self.iterator)
|
| 399 |
+
|
| 400 |
+
|
| 401 |
+
class _RepeatSampler(object):
|
| 402 |
+
""" Sampler that repeats forever.
|
| 403 |
+
|
| 404 |
+
Args:
|
| 405 |
+
sampler (Sampler)
|
| 406 |
+
"""
|
| 407 |
+
|
| 408 |
+
def __init__(self, sampler):
|
| 409 |
+
self.sampler = sampler
|
| 410 |
+
|
| 411 |
+
def __iter__(self):
|
| 412 |
+
while True:
|
| 413 |
+
yield from iter(self.sampler)
|
timm/data/mixup.py
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" Mixup and Cutmix
|
| 2 |
+
|
| 3 |
+
Papers:
|
| 4 |
+
mixup: Beyond Empirical Risk Minimization (https://arxiv.org/abs/1710.09412)
|
| 5 |
+
|
| 6 |
+
CutMix: Regularization Strategy to Train Strong Classifiers with Localizable Features (https://arxiv.org/abs/1905.04899)
|
| 7 |
+
|
| 8 |
+
Code Reference:
|
| 9 |
+
CutMix: https://github.com/clovaai/CutMix-PyTorch
|
| 10 |
+
|
| 11 |
+
Hacked together by / Copyright 2019, Ross Wightman
|
| 12 |
+
"""
|
| 13 |
+
import numpy as np
|
| 14 |
+
import torch
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def one_hot(x, num_classes, on_value=1., off_value=0.):
|
| 18 |
+
x = x.long().view(-1, 1)
|
| 19 |
+
return torch.full((x.size()[0], num_classes), off_value, device=x.device).scatter_(1, x, on_value)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def mixup_target(target, num_classes, lam=1., smoothing=0.0):
|
| 23 |
+
off_value = smoothing / num_classes
|
| 24 |
+
on_value = 1. - smoothing + off_value
|
| 25 |
+
y1 = one_hot(target, num_classes, on_value=on_value, off_value=off_value)
|
| 26 |
+
y2 = one_hot(target.flip(0), num_classes, on_value=on_value, off_value=off_value)
|
| 27 |
+
return y1 * lam + y2 * (1. - lam)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def rand_bbox(img_shape, lam, margin=0., count=None):
|
| 31 |
+
""" Standard CutMix bounding-box
|
| 32 |
+
Generates a random square bbox based on lambda value. This impl includes
|
| 33 |
+
support for enforcing a border margin as percent of bbox dimensions.
|
| 34 |
+
|
| 35 |
+
Args:
|
| 36 |
+
img_shape (tuple): Image shape as tuple
|
| 37 |
+
lam (float): Cutmix lambda value
|
| 38 |
+
margin (float): Percentage of bbox dimension to enforce as margin (reduce amount of box outside image)
|
| 39 |
+
count (int): Number of bbox to generate
|
| 40 |
+
"""
|
| 41 |
+
ratio = np.sqrt(1 - lam)
|
| 42 |
+
img_h, img_w = img_shape[-2:]
|
| 43 |
+
cut_h, cut_w = int(img_h * ratio), int(img_w * ratio)
|
| 44 |
+
margin_y, margin_x = int(margin * cut_h), int(margin * cut_w)
|
| 45 |
+
cy = np.random.randint(0 + margin_y, img_h - margin_y, size=count)
|
| 46 |
+
cx = np.random.randint(0 + margin_x, img_w - margin_x, size=count)
|
| 47 |
+
yl = np.clip(cy - cut_h // 2, 0, img_h)
|
| 48 |
+
yh = np.clip(cy + cut_h // 2, 0, img_h)
|
| 49 |
+
xl = np.clip(cx - cut_w // 2, 0, img_w)
|
| 50 |
+
xh = np.clip(cx + cut_w // 2, 0, img_w)
|
| 51 |
+
return yl, yh, xl, xh
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def rand_bbox_minmax(img_shape, minmax, count=None):
|
| 55 |
+
""" Min-Max CutMix bounding-box
|
| 56 |
+
Inspired by Darknet cutmix impl, generates a random rectangular bbox
|
| 57 |
+
based on min/max percent values applied to each dimension of the input image.
|
| 58 |
+
|
| 59 |
+
Typical defaults for minmax are usually in the .2-.3 for min and .8-.9 range for max.
|
| 60 |
+
|
| 61 |
+
Args:
|
| 62 |
+
img_shape (tuple): Image shape as tuple
|
| 63 |
+
minmax (tuple or list): Min and max bbox ratios (as percent of image size)
|
| 64 |
+
count (int): Number of bbox to generate
|
| 65 |
+
"""
|
| 66 |
+
assert len(minmax) == 2
|
| 67 |
+
img_h, img_w = img_shape[-2:]
|
| 68 |
+
cut_h = np.random.randint(int(img_h * minmax[0]), int(img_h * minmax[1]), size=count)
|
| 69 |
+
cut_w = np.random.randint(int(img_w * minmax[0]), int(img_w * minmax[1]), size=count)
|
| 70 |
+
yl = np.random.randint(0, img_h - cut_h, size=count)
|
| 71 |
+
xl = np.random.randint(0, img_w - cut_w, size=count)
|
| 72 |
+
yu = yl + cut_h
|
| 73 |
+
xu = xl + cut_w
|
| 74 |
+
return yl, yu, xl, xu
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def cutmix_bbox_and_lam(img_shape, lam, ratio_minmax=None, correct_lam=True, count=None):
|
| 78 |
+
""" Generate bbox and apply lambda correction.
|
| 79 |
+
"""
|
| 80 |
+
if ratio_minmax is not None:
|
| 81 |
+
yl, yu, xl, xu = rand_bbox_minmax(img_shape, ratio_minmax, count=count)
|
| 82 |
+
else:
|
| 83 |
+
yl, yu, xl, xu = rand_bbox(img_shape, lam, count=count)
|
| 84 |
+
if correct_lam or ratio_minmax is not None:
|
| 85 |
+
bbox_area = (yu - yl) * (xu - xl)
|
| 86 |
+
lam = 1. - bbox_area / float(img_shape[-2] * img_shape[-1])
|
| 87 |
+
return (yl, yu, xl, xu), lam
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
class Mixup:
|
| 91 |
+
""" Mixup/Cutmix that applies different params to each element or whole batch
|
| 92 |
+
|
| 93 |
+
Args:
|
| 94 |
+
mixup_alpha (float): mixup alpha value, mixup is active if > 0.
|
| 95 |
+
cutmix_alpha (float): cutmix alpha value, cutmix is active if > 0.
|
| 96 |
+
cutmix_minmax (List[float]): cutmix min/max image ratio, cutmix is active and uses this vs alpha if not None.
|
| 97 |
+
prob (float): probability of applying mixup or cutmix per batch or element
|
| 98 |
+
switch_prob (float): probability of switching to cutmix instead of mixup when both are active
|
| 99 |
+
mode (str): how to apply mixup/cutmix params (per 'batch', 'pair' (pair of elements), 'elem' (element)
|
| 100 |
+
correct_lam (bool): apply lambda correction when cutmix bbox clipped by image borders
|
| 101 |
+
label_smoothing (float): apply label smoothing to the mixed target tensor
|
| 102 |
+
num_classes (int): number of classes for target
|
| 103 |
+
"""
|
| 104 |
+
def __init__(self, mixup_alpha=1., cutmix_alpha=0., cutmix_minmax=None, prob=1.0, switch_prob=0.5,
|
| 105 |
+
mode='batch', correct_lam=True, label_smoothing=0.1, num_classes=1000):
|
| 106 |
+
self.mixup_alpha = mixup_alpha
|
| 107 |
+
self.cutmix_alpha = cutmix_alpha
|
| 108 |
+
self.cutmix_minmax = cutmix_minmax
|
| 109 |
+
if self.cutmix_minmax is not None:
|
| 110 |
+
assert len(self.cutmix_minmax) == 2
|
| 111 |
+
# force cutmix alpha == 1.0 when minmax active to keep logic simple & safe
|
| 112 |
+
self.cutmix_alpha = 1.0
|
| 113 |
+
self.mix_prob = prob
|
| 114 |
+
self.switch_prob = switch_prob
|
| 115 |
+
self.label_smoothing = label_smoothing
|
| 116 |
+
self.num_classes = num_classes
|
| 117 |
+
self.mode = mode
|
| 118 |
+
self.correct_lam = correct_lam # correct lambda based on clipped area for cutmix
|
| 119 |
+
self.mixup_enabled = True # set to false to disable mixing (intended tp be set by train loop)
|
| 120 |
+
|
| 121 |
+
def _params_per_elem(self, batch_size):
|
| 122 |
+
lam = np.ones(batch_size, dtype=np.float32)
|
| 123 |
+
use_cutmix = np.zeros(batch_size, dtype=bool)
|
| 124 |
+
if self.mixup_enabled:
|
| 125 |
+
if self.mixup_alpha > 0. and self.cutmix_alpha > 0.:
|
| 126 |
+
use_cutmix = np.random.rand(batch_size) < self.switch_prob
|
| 127 |
+
lam_mix = np.where(
|
| 128 |
+
use_cutmix,
|
| 129 |
+
np.random.beta(self.cutmix_alpha, self.cutmix_alpha, size=batch_size),
|
| 130 |
+
np.random.beta(self.mixup_alpha, self.mixup_alpha, size=batch_size))
|
| 131 |
+
elif self.mixup_alpha > 0.:
|
| 132 |
+
lam_mix = np.random.beta(self.mixup_alpha, self.mixup_alpha, size=batch_size)
|
| 133 |
+
elif self.cutmix_alpha > 0.:
|
| 134 |
+
use_cutmix = np.ones(batch_size, dtype=bool)
|
| 135 |
+
lam_mix = np.random.beta(self.cutmix_alpha, self.cutmix_alpha, size=batch_size)
|
| 136 |
+
else:
|
| 137 |
+
assert False, "One of mixup_alpha > 0., cutmix_alpha > 0., cutmix_minmax not None should be true."
|
| 138 |
+
lam = np.where(np.random.rand(batch_size) < self.mix_prob, lam_mix.astype(np.float32), lam)
|
| 139 |
+
return lam, use_cutmix
|
| 140 |
+
|
| 141 |
+
def _params_per_batch(self):
|
| 142 |
+
lam = 1.
|
| 143 |
+
use_cutmix = False
|
| 144 |
+
if self.mixup_enabled and np.random.rand() < self.mix_prob:
|
| 145 |
+
if self.mixup_alpha > 0. and self.cutmix_alpha > 0.:
|
| 146 |
+
use_cutmix = np.random.rand() < self.switch_prob
|
| 147 |
+
lam_mix = np.random.beta(self.cutmix_alpha, self.cutmix_alpha) if use_cutmix else \
|
| 148 |
+
np.random.beta(self.mixup_alpha, self.mixup_alpha)
|
| 149 |
+
elif self.mixup_alpha > 0.:
|
| 150 |
+
lam_mix = np.random.beta(self.mixup_alpha, self.mixup_alpha)
|
| 151 |
+
elif self.cutmix_alpha > 0.:
|
| 152 |
+
use_cutmix = True
|
| 153 |
+
lam_mix = np.random.beta(self.cutmix_alpha, self.cutmix_alpha)
|
| 154 |
+
else:
|
| 155 |
+
assert False, "One of mixup_alpha > 0., cutmix_alpha > 0., cutmix_minmax not None should be true."
|
| 156 |
+
lam = float(lam_mix)
|
| 157 |
+
return lam, use_cutmix
|
| 158 |
+
|
| 159 |
+
def _mix_elem(self, x):
|
| 160 |
+
batch_size = len(x)
|
| 161 |
+
lam_batch, use_cutmix = self._params_per_elem(batch_size)
|
| 162 |
+
x_orig = x.clone() # need to keep an unmodified original for mixing source
|
| 163 |
+
for i in range(batch_size):
|
| 164 |
+
j = batch_size - i - 1
|
| 165 |
+
lam = lam_batch[i]
|
| 166 |
+
if lam != 1.:
|
| 167 |
+
if use_cutmix[i]:
|
| 168 |
+
(yl, yh, xl, xh), lam = cutmix_bbox_and_lam(
|
| 169 |
+
x[i].shape, lam, ratio_minmax=self.cutmix_minmax, correct_lam=self.correct_lam)
|
| 170 |
+
x[i][:, yl:yh, xl:xh] = x_orig[j][:, yl:yh, xl:xh]
|
| 171 |
+
lam_batch[i] = lam
|
| 172 |
+
else:
|
| 173 |
+
x[i] = x[i] * lam + x_orig[j] * (1 - lam)
|
| 174 |
+
return torch.tensor(lam_batch, device=x.device, dtype=x.dtype).unsqueeze(1)
|
| 175 |
+
|
| 176 |
+
def _mix_pair(self, x):
|
| 177 |
+
batch_size = len(x)
|
| 178 |
+
lam_batch, use_cutmix = self._params_per_elem(batch_size // 2)
|
| 179 |
+
x_orig = x.clone() # need to keep an unmodified original for mixing source
|
| 180 |
+
for i in range(batch_size // 2):
|
| 181 |
+
j = batch_size - i - 1
|
| 182 |
+
lam = lam_batch[i]
|
| 183 |
+
if lam != 1.:
|
| 184 |
+
if use_cutmix[i]:
|
| 185 |
+
(yl, yh, xl, xh), lam = cutmix_bbox_and_lam(
|
| 186 |
+
x[i].shape, lam, ratio_minmax=self.cutmix_minmax, correct_lam=self.correct_lam)
|
| 187 |
+
x[i][:, yl:yh, xl:xh] = x_orig[j][:, yl:yh, xl:xh]
|
| 188 |
+
x[j][:, yl:yh, xl:xh] = x_orig[i][:, yl:yh, xl:xh]
|
| 189 |
+
lam_batch[i] = lam
|
| 190 |
+
else:
|
| 191 |
+
x[i] = x[i] * lam + x_orig[j] * (1 - lam)
|
| 192 |
+
x[j] = x[j] * lam + x_orig[i] * (1 - lam)
|
| 193 |
+
lam_batch = np.concatenate((lam_batch, lam_batch[::-1]))
|
| 194 |
+
return torch.tensor(lam_batch, device=x.device, dtype=x.dtype).unsqueeze(1)
|
| 195 |
+
|
| 196 |
+
def _mix_batch(self, x):
|
| 197 |
+
lam, use_cutmix = self._params_per_batch()
|
| 198 |
+
if lam == 1.:
|
| 199 |
+
return 1.
|
| 200 |
+
if use_cutmix:
|
| 201 |
+
(yl, yh, xl, xh), lam = cutmix_bbox_and_lam(
|
| 202 |
+
x.shape, lam, ratio_minmax=self.cutmix_minmax, correct_lam=self.correct_lam)
|
| 203 |
+
x[:, :, yl:yh, xl:xh] = x.flip(0)[:, :, yl:yh, xl:xh]
|
| 204 |
+
else:
|
| 205 |
+
x_flipped = x.flip(0).mul_(1. - lam)
|
| 206 |
+
x.mul_(lam).add_(x_flipped)
|
| 207 |
+
return lam
|
| 208 |
+
|
| 209 |
+
def __call__(self, x, target):
|
| 210 |
+
assert len(x) % 2 == 0, 'Batch size should be even when using this'
|
| 211 |
+
if self.mode == 'elem':
|
| 212 |
+
lam = self._mix_elem(x)
|
| 213 |
+
elif self.mode == 'pair':
|
| 214 |
+
lam = self._mix_pair(x)
|
| 215 |
+
else:
|
| 216 |
+
lam = self._mix_batch(x)
|
| 217 |
+
target = mixup_target(target, self.num_classes, lam, self.label_smoothing)
|
| 218 |
+
return x, target
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
class FastCollateMixup(Mixup):
|
| 222 |
+
""" Fast Collate w/ Mixup/Cutmix that applies different params to each element or whole batch
|
| 223 |
+
|
| 224 |
+
A Mixup impl that's performed while collating the batches.
|
| 225 |
+
"""
|
| 226 |
+
|
| 227 |
+
def _mix_elem_collate(self, output, batch, half=False):
|
| 228 |
+
batch_size = len(batch)
|
| 229 |
+
num_elem = batch_size // 2 if half else batch_size
|
| 230 |
+
assert len(output) == num_elem
|
| 231 |
+
lam_batch, use_cutmix = self._params_per_elem(num_elem)
|
| 232 |
+
for i in range(num_elem):
|
| 233 |
+
j = batch_size - i - 1
|
| 234 |
+
lam = lam_batch[i]
|
| 235 |
+
mixed = batch[i][0]
|
| 236 |
+
if lam != 1.:
|
| 237 |
+
if use_cutmix[i]:
|
| 238 |
+
if not half:
|
| 239 |
+
mixed = mixed.copy()
|
| 240 |
+
(yl, yh, xl, xh), lam = cutmix_bbox_and_lam(
|
| 241 |
+
output.shape, lam, ratio_minmax=self.cutmix_minmax, correct_lam=self.correct_lam)
|
| 242 |
+
mixed[:, yl:yh, xl:xh] = batch[j][0][:, yl:yh, xl:xh]
|
| 243 |
+
lam_batch[i] = lam
|
| 244 |
+
else:
|
| 245 |
+
mixed = mixed.astype(np.float32) * lam + batch[j][0].astype(np.float32) * (1 - lam)
|
| 246 |
+
np.rint(mixed, out=mixed)
|
| 247 |
+
output[i] += torch.from_numpy(mixed.astype(np.uint8))
|
| 248 |
+
if half:
|
| 249 |
+
lam_batch = np.concatenate((lam_batch, np.ones(num_elem)))
|
| 250 |
+
return torch.tensor(lam_batch).unsqueeze(1)
|
| 251 |
+
|
| 252 |
+
def _mix_pair_collate(self, output, batch):
|
| 253 |
+
batch_size = len(batch)
|
| 254 |
+
lam_batch, use_cutmix = self._params_per_elem(batch_size // 2)
|
| 255 |
+
for i in range(batch_size // 2):
|
| 256 |
+
j = batch_size - i - 1
|
| 257 |
+
lam = lam_batch[i]
|
| 258 |
+
mixed_i = batch[i][0]
|
| 259 |
+
mixed_j = batch[j][0]
|
| 260 |
+
assert 0 <= lam <= 1.0
|
| 261 |
+
if lam < 1.:
|
| 262 |
+
if use_cutmix[i]:
|
| 263 |
+
(yl, yh, xl, xh), lam = cutmix_bbox_and_lam(
|
| 264 |
+
output.shape, lam, ratio_minmax=self.cutmix_minmax, correct_lam=self.correct_lam)
|
| 265 |
+
patch_i = mixed_i[:, yl:yh, xl:xh].copy()
|
| 266 |
+
mixed_i[:, yl:yh, xl:xh] = mixed_j[:, yl:yh, xl:xh]
|
| 267 |
+
mixed_j[:, yl:yh, xl:xh] = patch_i
|
| 268 |
+
lam_batch[i] = lam
|
| 269 |
+
else:
|
| 270 |
+
mixed_temp = mixed_i.astype(np.float32) * lam + mixed_j.astype(np.float32) * (1 - lam)
|
| 271 |
+
mixed_j = mixed_j.astype(np.float32) * lam + mixed_i.astype(np.float32) * (1 - lam)
|
| 272 |
+
mixed_i = mixed_temp
|
| 273 |
+
np.rint(mixed_j, out=mixed_j)
|
| 274 |
+
np.rint(mixed_i, out=mixed_i)
|
| 275 |
+
output[i] += torch.from_numpy(mixed_i.astype(np.uint8))
|
| 276 |
+
output[j] += torch.from_numpy(mixed_j.astype(np.uint8))
|
| 277 |
+
lam_batch = np.concatenate((lam_batch, lam_batch[::-1]))
|
| 278 |
+
return torch.tensor(lam_batch).unsqueeze(1)
|
| 279 |
+
|
| 280 |
+
def _mix_batch_collate(self, output, batch):
|
| 281 |
+
batch_size = len(batch)
|
| 282 |
+
lam, use_cutmix = self._params_per_batch()
|
| 283 |
+
if use_cutmix:
|
| 284 |
+
(yl, yh, xl, xh), lam = cutmix_bbox_and_lam(
|
| 285 |
+
output.shape, lam, ratio_minmax=self.cutmix_minmax, correct_lam=self.correct_lam)
|
| 286 |
+
for i in range(batch_size):
|
| 287 |
+
j = batch_size - i - 1
|
| 288 |
+
mixed = batch[i][0]
|
| 289 |
+
if lam != 1.:
|
| 290 |
+
if use_cutmix:
|
| 291 |
+
mixed = mixed.copy() # don't want to modify the original while iterating
|
| 292 |
+
mixed[:, yl:yh, xl:xh] = batch[j][0][:, yl:yh, xl:xh]
|
| 293 |
+
else:
|
| 294 |
+
mixed = mixed.astype(np.float32) * lam + batch[j][0].astype(np.float32) * (1 - lam)
|
| 295 |
+
np.rint(mixed, out=mixed)
|
| 296 |
+
output[i] += torch.from_numpy(mixed.astype(np.uint8))
|
| 297 |
+
return lam
|
| 298 |
+
|
| 299 |
+
def __call__(self, batch, _=None):
|
| 300 |
+
batch_size = len(batch)
|
| 301 |
+
assert batch_size % 2 == 0, 'Batch size should be even when using this'
|
| 302 |
+
half = 'half' in self.mode
|
| 303 |
+
if half:
|
| 304 |
+
batch_size //= 2
|
| 305 |
+
output = torch.zeros((batch_size, *batch[0][0].shape), dtype=torch.uint8)
|
| 306 |
+
if self.mode == 'elem' or self.mode == 'half':
|
| 307 |
+
lam = self._mix_elem_collate(output, batch, half=half)
|
| 308 |
+
elif self.mode == 'pair':
|
| 309 |
+
lam = self._mix_pair_collate(output, batch)
|
| 310 |
+
else:
|
| 311 |
+
lam = self._mix_batch_collate(output, batch)
|
| 312 |
+
target = torch.tensor([b[1] for b in batch], dtype=torch.int64)
|
| 313 |
+
target = mixup_target(target, self.num_classes, lam, self.label_smoothing)
|
| 314 |
+
target = target[:batch_size]
|
| 315 |
+
return output, target
|
timm/data/random_erasing.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" Random Erasing (Cutout)
|
| 2 |
+
|
| 3 |
+
Originally inspired by impl at https://github.com/zhunzhong07/Random-Erasing, Apache 2.0
|
| 4 |
+
Copyright Zhun Zhong & Liang Zheng
|
| 5 |
+
|
| 6 |
+
Hacked together by / Copyright 2019, Ross Wightman
|
| 7 |
+
"""
|
| 8 |
+
import random
|
| 9 |
+
import math
|
| 10 |
+
|
| 11 |
+
import torch
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def _get_pixels(per_pixel, rand_color, patch_size, dtype=torch.float32, device='cuda'):
|
| 15 |
+
# NOTE I've seen CUDA illegal memory access errors being caused by the normal_()
|
| 16 |
+
# paths, flip the order so normal is run on CPU if this becomes a problem
|
| 17 |
+
# Issue has been fixed in master https://github.com/pytorch/pytorch/issues/19508
|
| 18 |
+
if per_pixel:
|
| 19 |
+
return torch.empty(patch_size, dtype=dtype, device=device).normal_()
|
| 20 |
+
elif rand_color:
|
| 21 |
+
return torch.empty((patch_size[0], 1, 1), dtype=dtype, device=device).normal_()
|
| 22 |
+
else:
|
| 23 |
+
return torch.zeros((patch_size[0], 1, 1), dtype=dtype, device=device)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class RandomErasing:
|
| 27 |
+
""" Randomly selects a rectangle region in an image and erases its pixels.
|
| 28 |
+
'Random Erasing Data Augmentation' by Zhong et al.
|
| 29 |
+
See https://arxiv.org/pdf/1708.04896.pdf
|
| 30 |
+
|
| 31 |
+
This variant of RandomErasing is intended to be applied to either a batch
|
| 32 |
+
or single image tensor after it has been normalized by dataset mean and std.
|
| 33 |
+
Args:
|
| 34 |
+
probability: Probability that the Random Erasing operation will be performed.
|
| 35 |
+
min_area: Minimum percentage of erased area wrt input image area.
|
| 36 |
+
max_area: Maximum percentage of erased area wrt input image area.
|
| 37 |
+
min_aspect: Minimum aspect ratio of erased area.
|
| 38 |
+
mode: pixel color mode, one of 'const', 'rand', or 'pixel'
|
| 39 |
+
'const' - erase block is constant color of 0 for all channels
|
| 40 |
+
'rand' - erase block is same per-channel random (normal) color
|
| 41 |
+
'pixel' - erase block is per-pixel random (normal) color
|
| 42 |
+
max_count: maximum number of erasing blocks per image, area per box is scaled by count.
|
| 43 |
+
per-image count is randomly chosen between 1 and this value.
|
| 44 |
+
"""
|
| 45 |
+
|
| 46 |
+
def __init__(
|
| 47 |
+
self,
|
| 48 |
+
probability=0.5,
|
| 49 |
+
min_area=0.02,
|
| 50 |
+
max_area=1/3,
|
| 51 |
+
min_aspect=0.3,
|
| 52 |
+
max_aspect=None,
|
| 53 |
+
mode='const',
|
| 54 |
+
min_count=1,
|
| 55 |
+
max_count=None,
|
| 56 |
+
num_splits=0,
|
| 57 |
+
device='cuda',
|
| 58 |
+
):
|
| 59 |
+
self.probability = probability
|
| 60 |
+
self.min_area = min_area
|
| 61 |
+
self.max_area = max_area
|
| 62 |
+
max_aspect = max_aspect or 1 / min_aspect
|
| 63 |
+
self.log_aspect_ratio = (math.log(min_aspect), math.log(max_aspect))
|
| 64 |
+
self.min_count = min_count
|
| 65 |
+
self.max_count = max_count or min_count
|
| 66 |
+
self.num_splits = num_splits
|
| 67 |
+
self.mode = mode.lower()
|
| 68 |
+
self.rand_color = False
|
| 69 |
+
self.per_pixel = False
|
| 70 |
+
if self.mode == 'rand':
|
| 71 |
+
self.rand_color = True # per block random normal
|
| 72 |
+
elif self.mode == 'pixel':
|
| 73 |
+
self.per_pixel = True # per pixel random normal
|
| 74 |
+
else:
|
| 75 |
+
assert not self.mode or self.mode == 'const'
|
| 76 |
+
self.device = device
|
| 77 |
+
|
| 78 |
+
def _erase(self, img, chan, img_h, img_w, dtype):
|
| 79 |
+
if random.random() > self.probability:
|
| 80 |
+
return
|
| 81 |
+
area = img_h * img_w
|
| 82 |
+
count = self.min_count if self.min_count == self.max_count else \
|
| 83 |
+
random.randint(self.min_count, self.max_count)
|
| 84 |
+
for _ in range(count):
|
| 85 |
+
for attempt in range(10):
|
| 86 |
+
target_area = random.uniform(self.min_area, self.max_area) * area / count
|
| 87 |
+
aspect_ratio = math.exp(random.uniform(*self.log_aspect_ratio))
|
| 88 |
+
h = int(round(math.sqrt(target_area * aspect_ratio)))
|
| 89 |
+
w = int(round(math.sqrt(target_area / aspect_ratio)))
|
| 90 |
+
if w < img_w and h < img_h:
|
| 91 |
+
top = random.randint(0, img_h - h)
|
| 92 |
+
left = random.randint(0, img_w - w)
|
| 93 |
+
img[:, top:top + h, left:left + w] = _get_pixels(
|
| 94 |
+
self.per_pixel,
|
| 95 |
+
self.rand_color,
|
| 96 |
+
(chan, h, w),
|
| 97 |
+
dtype=dtype,
|
| 98 |
+
device=self.device,
|
| 99 |
+
)
|
| 100 |
+
break
|
| 101 |
+
|
| 102 |
+
def __call__(self, input):
|
| 103 |
+
if len(input.size()) == 3:
|
| 104 |
+
self._erase(input, *input.size(), input.dtype)
|
| 105 |
+
else:
|
| 106 |
+
batch_size, chan, img_h, img_w = input.size()
|
| 107 |
+
# skip first slice of batch if num_splits is set (for clean portion of samples)
|
| 108 |
+
batch_start = batch_size // self.num_splits if self.num_splits > 1 else 0
|
| 109 |
+
for i in range(batch_start, batch_size):
|
| 110 |
+
self._erase(input[i], chan, img_h, img_w, input.dtype)
|
| 111 |
+
return input
|
| 112 |
+
|
| 113 |
+
def __repr__(self):
|
| 114 |
+
# NOTE simplified state for repr
|
| 115 |
+
fs = self.__class__.__name__ + f'(p={self.probability}, mode={self.mode}'
|
| 116 |
+
fs += f', count=({self.min_count}, {self.max_count}))'
|
| 117 |
+
return fs
|
timm/data/readers/__init__.py
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .reader_factory import create_reader
|
| 2 |
+
from .img_extensions import *
|
timm/data/readers/class_map.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import pickle
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def load_class_map(map_or_filename, root=''):
|
| 6 |
+
if isinstance(map_or_filename, dict):
|
| 7 |
+
assert dict, 'class_map dict must be non-empty'
|
| 8 |
+
return map_or_filename
|
| 9 |
+
class_map_path = map_or_filename
|
| 10 |
+
if not os.path.exists(class_map_path):
|
| 11 |
+
class_map_path = os.path.join(root, class_map_path)
|
| 12 |
+
assert os.path.exists(class_map_path), 'Cannot locate specified class map file (%s)' % map_or_filename
|
| 13 |
+
class_map_ext = os.path.splitext(map_or_filename)[-1].lower()
|
| 14 |
+
if class_map_ext == '.txt':
|
| 15 |
+
with open(class_map_path) as f:
|
| 16 |
+
class_to_idx = {v.strip(): k for k, v in enumerate(f)}
|
| 17 |
+
elif class_map_ext == '.pkl':
|
| 18 |
+
with open(class_map_path, 'rb') as f:
|
| 19 |
+
class_to_idx = pickle.load(f)
|
| 20 |
+
else:
|
| 21 |
+
assert False, f'Unsupported class map file extension ({class_map_ext}).'
|
| 22 |
+
return class_to_idx
|
timm/data/readers/img_extensions.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from copy import deepcopy
|
| 2 |
+
|
| 3 |
+
__all__ = ['get_img_extensions', 'is_img_extension', 'set_img_extensions', 'add_img_extensions', 'del_img_extensions']
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
IMG_EXTENSIONS = ('.png', '.jpg', '.jpeg') # singleton, kept public for bwd compat use
|
| 7 |
+
_IMG_EXTENSIONS_SET = set(IMG_EXTENSIONS) # set version, private, kept in sync
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def _set_extensions(extensions):
|
| 11 |
+
global IMG_EXTENSIONS
|
| 12 |
+
global _IMG_EXTENSIONS_SET
|
| 13 |
+
dedupe = set() # NOTE de-duping tuple while keeping original order
|
| 14 |
+
IMG_EXTENSIONS = tuple(x for x in extensions if x not in dedupe and not dedupe.add(x))
|
| 15 |
+
_IMG_EXTENSIONS_SET = set(extensions)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def _valid_extension(x: str):
|
| 19 |
+
return x and isinstance(x, str) and len(x) >= 2 and x.startswith('.')
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def is_img_extension(ext):
|
| 23 |
+
return ext in _IMG_EXTENSIONS_SET
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def get_img_extensions(as_set=False):
|
| 27 |
+
return deepcopy(_IMG_EXTENSIONS_SET if as_set else IMG_EXTENSIONS)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def set_img_extensions(extensions):
|
| 31 |
+
assert len(extensions)
|
| 32 |
+
for x in extensions:
|
| 33 |
+
assert _valid_extension(x)
|
| 34 |
+
_set_extensions(extensions)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def add_img_extensions(ext):
|
| 38 |
+
if not isinstance(ext, (list, tuple, set)):
|
| 39 |
+
ext = (ext,)
|
| 40 |
+
for x in ext:
|
| 41 |
+
assert _valid_extension(x)
|
| 42 |
+
extensions = IMG_EXTENSIONS + tuple(ext)
|
| 43 |
+
_set_extensions(extensions)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def del_img_extensions(ext):
|
| 47 |
+
if not isinstance(ext, (list, tuple, set)):
|
| 48 |
+
ext = (ext,)
|
| 49 |
+
extensions = tuple(x for x in IMG_EXTENSIONS if x not in ext)
|
| 50 |
+
_set_extensions(extensions)
|
timm/data/readers/reader.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from abc import abstractmethod
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class Reader:
|
| 5 |
+
def __init__(self):
|
| 6 |
+
pass
|
| 7 |
+
|
| 8 |
+
@abstractmethod
|
| 9 |
+
def _filename(self, index, basename=False, absolute=False):
|
| 10 |
+
pass
|
| 11 |
+
|
| 12 |
+
def filename(self, index, basename=False, absolute=False):
|
| 13 |
+
return self._filename(index, basename=basename, absolute=absolute)
|
| 14 |
+
|
| 15 |
+
def filenames(self, basename=False, absolute=False):
|
| 16 |
+
return [self._filename(index, basename=basename, absolute=absolute) for index in range(len(self))]
|
timm/data/readers/reader_factory.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from typing import Optional
|
| 3 |
+
|
| 4 |
+
from .reader_image_folder import ReaderImageFolder
|
| 5 |
+
from .reader_image_in_tar import ReaderImageInTar
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def create_reader(
|
| 9 |
+
name: str,
|
| 10 |
+
root: Optional[str] = None,
|
| 11 |
+
split: str = 'train',
|
| 12 |
+
**kwargs,
|
| 13 |
+
):
|
| 14 |
+
kwargs = {k: v for k, v in kwargs.items() if v is not None}
|
| 15 |
+
name = name.lower()
|
| 16 |
+
name = name.split('/', 1)
|
| 17 |
+
prefix = ''
|
| 18 |
+
if len(name) > 1:
|
| 19 |
+
prefix = name[0]
|
| 20 |
+
name = name[-1]
|
| 21 |
+
|
| 22 |
+
# FIXME improve the selection right now just tfds prefix or fallback path, will need options to
|
| 23 |
+
# explicitly select other options shortly
|
| 24 |
+
if prefix == 'hfds':
|
| 25 |
+
from .reader_hfds import ReaderHfds # defer Hf datasets import
|
| 26 |
+
reader = ReaderHfds(name=name, root=root, split=split, **kwargs)
|
| 27 |
+
elif prefix == 'hfids':
|
| 28 |
+
from .reader_hfids import ReaderHfids # defer HF datasets import
|
| 29 |
+
reader = ReaderHfids(name=name, root=root, split=split, **kwargs)
|
| 30 |
+
elif prefix == 'tfds':
|
| 31 |
+
from .reader_tfds import ReaderTfds # defer tensorflow import
|
| 32 |
+
reader = ReaderTfds(name=name, root=root, split=split, **kwargs)
|
| 33 |
+
elif prefix == 'wds':
|
| 34 |
+
from .reader_wds import ReaderWds
|
| 35 |
+
kwargs.pop('download', False)
|
| 36 |
+
reader = ReaderWds(root=root, name=name, split=split, **kwargs)
|
| 37 |
+
else:
|
| 38 |
+
assert os.path.exists(root)
|
| 39 |
+
# default fallback path (backwards compat), use image tar if root is a .tar file, otherwise image folder
|
| 40 |
+
# FIXME support split here or in reader?
|
| 41 |
+
if os.path.isfile(root) and os.path.splitext(root)[1] == '.tar':
|
| 42 |
+
reader = ReaderImageInTar(root, **kwargs)
|
| 43 |
+
else:
|
| 44 |
+
reader = ReaderImageFolder(root, **kwargs)
|
| 45 |
+
return reader
|
timm/data/readers/reader_hfds.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" Dataset reader that wraps Hugging Face datasets
|
| 2 |
+
|
| 3 |
+
Hacked together by / Copyright 2022 Ross Wightman
|
| 4 |
+
"""
|
| 5 |
+
import io
|
| 6 |
+
import math
|
| 7 |
+
from typing import Optional
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
import torch.distributed as dist
|
| 11 |
+
from PIL import Image
|
| 12 |
+
|
| 13 |
+
try:
|
| 14 |
+
import datasets
|
| 15 |
+
except ImportError as e:
|
| 16 |
+
print("Please install Hugging Face datasets package `pip install datasets`.")
|
| 17 |
+
raise e
|
| 18 |
+
from .class_map import load_class_map
|
| 19 |
+
from .reader import Reader
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def get_class_labels(info, label_key='label'):
|
| 23 |
+
if 'label' not in info.features:
|
| 24 |
+
return {}
|
| 25 |
+
class_label = info.features[label_key]
|
| 26 |
+
class_to_idx = {n: class_label.str2int(n) for n in class_label.names}
|
| 27 |
+
return class_to_idx
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class ReaderHfds(Reader):
|
| 31 |
+
|
| 32 |
+
def __init__(
|
| 33 |
+
self,
|
| 34 |
+
name: str,
|
| 35 |
+
root: Optional[str] = None,
|
| 36 |
+
split: str = 'train',
|
| 37 |
+
class_map: dict = None,
|
| 38 |
+
image_key: str = 'image',
|
| 39 |
+
target_key: str = 'label',
|
| 40 |
+
download: bool = False,
|
| 41 |
+
):
|
| 42 |
+
"""
|
| 43 |
+
"""
|
| 44 |
+
super().__init__()
|
| 45 |
+
self.root = root
|
| 46 |
+
self.split = split
|
| 47 |
+
self.dataset = datasets.load_dataset(
|
| 48 |
+
name, # 'name' maps to path arg in hf datasets
|
| 49 |
+
split=split,
|
| 50 |
+
cache_dir=self.root, # timm doesn't expect hidden cache dir for datasets, specify a path
|
| 51 |
+
)
|
| 52 |
+
# leave decode for caller, plus we want easy access to original path names...
|
| 53 |
+
self.dataset = self.dataset.cast_column(image_key, datasets.Image(decode=False))
|
| 54 |
+
|
| 55 |
+
self.image_key = image_key
|
| 56 |
+
self.label_key = target_key
|
| 57 |
+
self.remap_class = False
|
| 58 |
+
if class_map:
|
| 59 |
+
self.class_to_idx = load_class_map(class_map)
|
| 60 |
+
self.remap_class = True
|
| 61 |
+
else:
|
| 62 |
+
self.class_to_idx = get_class_labels(self.dataset.info, self.label_key)
|
| 63 |
+
self.split_info = self.dataset.info.splits[split]
|
| 64 |
+
self.num_samples = self.split_info.num_examples
|
| 65 |
+
|
| 66 |
+
def __getitem__(self, index):
|
| 67 |
+
item = self.dataset[index]
|
| 68 |
+
image = item[self.image_key]
|
| 69 |
+
if 'bytes' in image and image['bytes']:
|
| 70 |
+
image = io.BytesIO(image['bytes'])
|
| 71 |
+
else:
|
| 72 |
+
assert 'path' in image and image['path']
|
| 73 |
+
image = open(image['path'], 'rb')
|
| 74 |
+
label = item[self.label_key]
|
| 75 |
+
if self.remap_class:
|
| 76 |
+
label = self.class_to_idx[label]
|
| 77 |
+
return image, label
|
| 78 |
+
|
| 79 |
+
def __len__(self):
|
| 80 |
+
return len(self.dataset)
|
| 81 |
+
|
| 82 |
+
def _filename(self, index, basename=False, absolute=False):
|
| 83 |
+
item = self.dataset[index]
|
| 84 |
+
return item[self.image_key]['path']
|
timm/data/readers/reader_hfids.py
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" Dataset reader for HF IterableDataset
|
| 2 |
+
"""
|
| 3 |
+
import math
|
| 4 |
+
import os
|
| 5 |
+
from itertools import repeat, chain
|
| 6 |
+
from typing import Optional
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
import torch.distributed as dist
|
| 10 |
+
from PIL import Image
|
| 11 |
+
|
| 12 |
+
try:
|
| 13 |
+
import datasets
|
| 14 |
+
from datasets.distributed import split_dataset_by_node
|
| 15 |
+
from datasets.splits import SplitInfo
|
| 16 |
+
except ImportError as e:
|
| 17 |
+
print("Please install Hugging Face datasets package `pip install datasets`.")
|
| 18 |
+
raise e
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
from .class_map import load_class_map
|
| 22 |
+
from .reader import Reader
|
| 23 |
+
from .shared_count import SharedCount
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
SHUFFLE_SIZE = int(os.environ.get('HFIDS_SHUFFLE_SIZE', 4096))
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class ReaderHfids(Reader):
|
| 30 |
+
def __init__(
|
| 31 |
+
self,
|
| 32 |
+
name: str,
|
| 33 |
+
root: Optional[str] = None,
|
| 34 |
+
split: str = 'train',
|
| 35 |
+
is_training: bool = False,
|
| 36 |
+
batch_size: int = 1,
|
| 37 |
+
download: bool = False,
|
| 38 |
+
repeats: int = 0,
|
| 39 |
+
seed: int = 42,
|
| 40 |
+
class_map: Optional[dict] = None,
|
| 41 |
+
input_key: str = 'image',
|
| 42 |
+
input_img_mode: str = 'RGB',
|
| 43 |
+
target_key: str = 'label',
|
| 44 |
+
target_img_mode: str = '',
|
| 45 |
+
shuffle_size: Optional[int] = None,
|
| 46 |
+
num_samples: Optional[int] = None,
|
| 47 |
+
):
|
| 48 |
+
super().__init__()
|
| 49 |
+
self.root = root
|
| 50 |
+
self.split = split
|
| 51 |
+
self.is_training = is_training
|
| 52 |
+
self.batch_size = batch_size
|
| 53 |
+
self.download = download
|
| 54 |
+
self.repeats = repeats
|
| 55 |
+
self.common_seed = seed # a seed that's fixed across all worker / distributed instances
|
| 56 |
+
self.shuffle_size = shuffle_size or SHUFFLE_SIZE
|
| 57 |
+
|
| 58 |
+
self.input_key = input_key
|
| 59 |
+
self.input_img_mode = input_img_mode
|
| 60 |
+
self.target_key = target_key
|
| 61 |
+
self.target_img_mode = target_img_mode
|
| 62 |
+
|
| 63 |
+
self.builder = datasets.load_dataset_builder(name, cache_dir=root)
|
| 64 |
+
if download:
|
| 65 |
+
self.builder.download_and_prepare()
|
| 66 |
+
|
| 67 |
+
split_info: Optional[SplitInfo] = None
|
| 68 |
+
if self.builder.info.splits and split in self.builder.info.splits:
|
| 69 |
+
if isinstance(self.builder.info.splits[split], SplitInfo):
|
| 70 |
+
split_info: Optional[SplitInfo] = self.builder.info.splits[split]
|
| 71 |
+
|
| 72 |
+
if num_samples:
|
| 73 |
+
self.num_samples = num_samples
|
| 74 |
+
elif split_info and split_info.num_examples:
|
| 75 |
+
self.num_samples = split_info.num_examples
|
| 76 |
+
else:
|
| 77 |
+
raise ValueError(
|
| 78 |
+
"Dataset length is unknown, please pass `num_samples` explicitely. "
|
| 79 |
+
"The number of steps needs to be known in advance for the learning rate scheduler."
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
self.remap_class = False
|
| 83 |
+
if class_map:
|
| 84 |
+
self.class_to_idx = load_class_map(class_map)
|
| 85 |
+
self.remap_class = True
|
| 86 |
+
else:
|
| 87 |
+
self.class_to_idx = {}
|
| 88 |
+
|
| 89 |
+
# Distributed world state
|
| 90 |
+
self.dist_rank = 0
|
| 91 |
+
self.dist_num_replicas = 1
|
| 92 |
+
if dist.is_available() and dist.is_initialized() and dist.get_world_size() > 1:
|
| 93 |
+
self.dist_rank = dist.get_rank()
|
| 94 |
+
self.dist_num_replicas = dist.get_world_size()
|
| 95 |
+
|
| 96 |
+
# Attributes that are updated in _lazy_init
|
| 97 |
+
self.worker_info = None
|
| 98 |
+
self.worker_id = 0
|
| 99 |
+
self.num_workers = 1
|
| 100 |
+
self.global_worker_id = 0
|
| 101 |
+
self.global_num_workers = 1
|
| 102 |
+
|
| 103 |
+
# Initialized lazily on each dataloader worker process
|
| 104 |
+
self.ds: Optional[datasets.IterableDataset] = None
|
| 105 |
+
self.epoch = SharedCount()
|
| 106 |
+
|
| 107 |
+
def set_epoch(self, count):
|
| 108 |
+
# to update the shuffling effective_seed = seed + epoch
|
| 109 |
+
self.epoch.value = count
|
| 110 |
+
|
| 111 |
+
def set_loader_cfg(
|
| 112 |
+
self,
|
| 113 |
+
num_workers: Optional[int] = None,
|
| 114 |
+
):
|
| 115 |
+
if self.ds is not None:
|
| 116 |
+
return
|
| 117 |
+
if num_workers is not None:
|
| 118 |
+
self.num_workers = num_workers
|
| 119 |
+
self.global_num_workers = self.dist_num_replicas * self.num_workers
|
| 120 |
+
|
| 121 |
+
def _lazy_init(self):
|
| 122 |
+
""" Lazily initialize worker (in worker processes)
|
| 123 |
+
"""
|
| 124 |
+
if self.worker_info is None:
|
| 125 |
+
worker_info = torch.utils.data.get_worker_info()
|
| 126 |
+
if worker_info is not None:
|
| 127 |
+
self.worker_info = worker_info
|
| 128 |
+
self.worker_id = worker_info.id
|
| 129 |
+
self.num_workers = worker_info.num_workers
|
| 130 |
+
self.global_num_workers = self.dist_num_replicas * self.num_workers
|
| 131 |
+
self.global_worker_id = self.dist_rank * self.num_workers + self.worker_id
|
| 132 |
+
|
| 133 |
+
if self.download:
|
| 134 |
+
dataset = self.builder.as_dataset(split=self.split)
|
| 135 |
+
# to distribute evenly to workers
|
| 136 |
+
ds = dataset.to_iterable_dataset(num_shards=self.global_num_workers)
|
| 137 |
+
else:
|
| 138 |
+
# in this case the number of shard is determined by the number of remote files
|
| 139 |
+
ds = self.builder.as_streaming_dataset(split=self.split)
|
| 140 |
+
|
| 141 |
+
if self.is_training:
|
| 142 |
+
# will shuffle the list of shards and use a shuffle buffer
|
| 143 |
+
ds = ds.shuffle(seed=self.common_seed, buffer_size=self.shuffle_size)
|
| 144 |
+
|
| 145 |
+
# Distributed:
|
| 146 |
+
# The dataset has a number of shards that is a factor of `dist_num_replicas` (i.e. if `ds.n_shards % dist_num_replicas == 0`),
|
| 147 |
+
# so the shards are evenly assigned across the nodes.
|
| 148 |
+
# If it's not the case for dataset streaming, each node keeps 1 example out of `dist_num_replicas`, skipping the other examples.
|
| 149 |
+
|
| 150 |
+
# Workers:
|
| 151 |
+
# In a node, datasets.IterableDataset assigns the shards assigned to the node as evenly as possible to workers.
|
| 152 |
+
self.ds = split_dataset_by_node(ds, rank=self.dist_rank, world_size=self.dist_num_replicas)
|
| 153 |
+
|
| 154 |
+
def _num_samples_per_worker(self):
|
| 155 |
+
num_worker_samples = \
|
| 156 |
+
max(1, self.repeats) * self.num_samples / max(self.global_num_workers, self.dist_num_replicas)
|
| 157 |
+
if self.is_training or self.dist_num_replicas > 1:
|
| 158 |
+
num_worker_samples = math.ceil(num_worker_samples)
|
| 159 |
+
if self.is_training and self.batch_size is not None:
|
| 160 |
+
num_worker_samples = math.ceil(num_worker_samples / self.batch_size) * self.batch_size
|
| 161 |
+
return int(num_worker_samples)
|
| 162 |
+
|
| 163 |
+
def __iter__(self):
|
| 164 |
+
if self.ds is None:
|
| 165 |
+
self._lazy_init()
|
| 166 |
+
self.ds.set_epoch(self.epoch.value)
|
| 167 |
+
|
| 168 |
+
target_sample_count = self._num_samples_per_worker()
|
| 169 |
+
sample_count = 0
|
| 170 |
+
|
| 171 |
+
if self.is_training:
|
| 172 |
+
ds_iter = chain.from_iterable(repeat(self.ds))
|
| 173 |
+
else:
|
| 174 |
+
ds_iter = iter(self.ds)
|
| 175 |
+
for sample in ds_iter:
|
| 176 |
+
input_data: Image.Image = sample[self.input_key]
|
| 177 |
+
if self.input_img_mode and input_data.mode != self.input_img_mode:
|
| 178 |
+
input_data = input_data.convert(self.input_img_mode)
|
| 179 |
+
target_data = sample[self.target_key]
|
| 180 |
+
if self.target_img_mode:
|
| 181 |
+
assert isinstance(target_data, Image.Image), "target_img_mode is specified but target is not an image"
|
| 182 |
+
if target_data.mode != self.target_img_mode:
|
| 183 |
+
target_data = target_data.convert(self.target_img_mode)
|
| 184 |
+
elif self.remap_class:
|
| 185 |
+
target_data = self.class_to_idx[target_data]
|
| 186 |
+
yield input_data, target_data
|
| 187 |
+
sample_count += 1
|
| 188 |
+
if self.is_training and sample_count >= target_sample_count:
|
| 189 |
+
break
|
| 190 |
+
|
| 191 |
+
def __len__(self):
|
| 192 |
+
num_samples = self._num_samples_per_worker() * self.num_workers
|
| 193 |
+
return num_samples
|
| 194 |
+
|
| 195 |
+
def _filename(self, index, basename=False, absolute=False):
|
| 196 |
+
assert False, "Not supported" # no random access to examples
|
| 197 |
+
|
| 198 |
+
def filenames(self, basename=False, absolute=False):
|
| 199 |
+
""" Return all filenames in dataset, overrides base"""
|
| 200 |
+
if self.ds is None:
|
| 201 |
+
self._lazy_init()
|
| 202 |
+
names = []
|
| 203 |
+
for sample in self.ds:
|
| 204 |
+
if 'file_name' in sample:
|
| 205 |
+
name = sample['file_name']
|
| 206 |
+
elif 'filename' in sample:
|
| 207 |
+
name = sample['filename']
|
| 208 |
+
elif 'id' in sample:
|
| 209 |
+
name = sample['id']
|
| 210 |
+
elif 'image_id' in sample:
|
| 211 |
+
name = sample['image_id']
|
| 212 |
+
else:
|
| 213 |
+
assert False, "No supported name field present"
|
| 214 |
+
names.append(name)
|
| 215 |
+
return names
|
timm/data/readers/reader_image_folder.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" A dataset reader that extracts images from folders
|
| 2 |
+
|
| 3 |
+
Folders are scanned recursively to find image files. Labels are based
|
| 4 |
+
on the folder hierarchy, just leaf folders by default.
|
| 5 |
+
|
| 6 |
+
Hacked together by / Copyright 2020 Ross Wightman
|
| 7 |
+
"""
|
| 8 |
+
import os
|
| 9 |
+
from typing import Dict, List, Optional, Set, Tuple, Union
|
| 10 |
+
|
| 11 |
+
from timm.utils.misc import natural_key
|
| 12 |
+
|
| 13 |
+
from .class_map import load_class_map
|
| 14 |
+
from .img_extensions import get_img_extensions
|
| 15 |
+
from .reader import Reader
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def find_images_and_targets(
|
| 19 |
+
folder: str,
|
| 20 |
+
types: Optional[Union[List, Tuple, Set]] = None,
|
| 21 |
+
class_to_idx: Optional[Dict] = None,
|
| 22 |
+
leaf_name_only: bool = True,
|
| 23 |
+
sort: bool = True
|
| 24 |
+
):
|
| 25 |
+
""" Walk folder recursively to discover images and map them to classes by folder names.
|
| 26 |
+
|
| 27 |
+
Args:
|
| 28 |
+
folder: root of folder to recrusively search
|
| 29 |
+
types: types (file extensions) to search for in path
|
| 30 |
+
class_to_idx: specify mapping for class (folder name) to class index if set
|
| 31 |
+
leaf_name_only: use only leaf-name of folder walk for class names
|
| 32 |
+
sort: re-sort found images by name (for consistent ordering)
|
| 33 |
+
|
| 34 |
+
Returns:
|
| 35 |
+
A list of image and target tuples, class_to_idx mapping
|
| 36 |
+
"""
|
| 37 |
+
types = get_img_extensions(as_set=True) if not types else set(types)
|
| 38 |
+
labels = []
|
| 39 |
+
filenames = []
|
| 40 |
+
for root, subdirs, files in os.walk(folder, topdown=False, followlinks=True):
|
| 41 |
+
rel_path = os.path.relpath(root, folder) if (root != folder) else ''
|
| 42 |
+
label = os.path.basename(rel_path) if leaf_name_only else rel_path.replace(os.path.sep, '_')
|
| 43 |
+
for f in files:
|
| 44 |
+
base, ext = os.path.splitext(f)
|
| 45 |
+
if ext.lower() in types:
|
| 46 |
+
filenames.append(os.path.join(root, f))
|
| 47 |
+
labels.append(label)
|
| 48 |
+
if class_to_idx is None:
|
| 49 |
+
# building class index
|
| 50 |
+
unique_labels = set(labels)
|
| 51 |
+
sorted_labels = list(sorted(unique_labels, key=natural_key))
|
| 52 |
+
class_to_idx = {c: idx for idx, c in enumerate(sorted_labels)}
|
| 53 |
+
images_and_targets = [(f, class_to_idx[l]) for f, l in zip(filenames, labels) if l in class_to_idx]
|
| 54 |
+
if sort:
|
| 55 |
+
images_and_targets = sorted(images_and_targets, key=lambda k: natural_key(k[0]))
|
| 56 |
+
return images_and_targets, class_to_idx
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
class ReaderImageFolder(Reader):
|
| 60 |
+
|
| 61 |
+
def __init__(
|
| 62 |
+
self,
|
| 63 |
+
root,
|
| 64 |
+
class_map='',
|
| 65 |
+
input_key=None,
|
| 66 |
+
):
|
| 67 |
+
super().__init__()
|
| 68 |
+
|
| 69 |
+
self.root = root
|
| 70 |
+
class_to_idx = None
|
| 71 |
+
if class_map:
|
| 72 |
+
class_to_idx = load_class_map(class_map, root)
|
| 73 |
+
find_types = None
|
| 74 |
+
if input_key:
|
| 75 |
+
find_types = input_key.split(';')
|
| 76 |
+
self.samples, self.class_to_idx = find_images_and_targets(
|
| 77 |
+
root,
|
| 78 |
+
class_to_idx=class_to_idx,
|
| 79 |
+
types=find_types,
|
| 80 |
+
)
|
| 81 |
+
if len(self.samples) == 0:
|
| 82 |
+
raise RuntimeError(
|
| 83 |
+
f'Found 0 images in subfolders of {root}. '
|
| 84 |
+
f'Supported image extensions are {", ".join(get_img_extensions())}')
|
| 85 |
+
|
| 86 |
+
def __getitem__(self, index):
|
| 87 |
+
path, target = self.samples[index]
|
| 88 |
+
return open(path, 'rb'), target
|
| 89 |
+
|
| 90 |
+
def __len__(self):
|
| 91 |
+
return len(self.samples)
|
| 92 |
+
|
| 93 |
+
def _filename(self, index, basename=False, absolute=False):
|
| 94 |
+
filename = self.samples[index][0]
|
| 95 |
+
if basename:
|
| 96 |
+
filename = os.path.basename(filename)
|
| 97 |
+
elif not absolute:
|
| 98 |
+
filename = os.path.relpath(filename, self.root)
|
| 99 |
+
return filename
|
timm/data/readers/reader_image_in_tar.py
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" A dataset reader that reads tarfile based datasets
|
| 2 |
+
|
| 3 |
+
This reader can extract image samples from:
|
| 4 |
+
* a single tar of image files
|
| 5 |
+
* a folder of multiple tarfiles containing imagefiles
|
| 6 |
+
* a tar of tars containing image files
|
| 7 |
+
|
| 8 |
+
Labels are based on the combined folder and/or tar name structure.
|
| 9 |
+
|
| 10 |
+
Hacked together by / Copyright 2020 Ross Wightman
|
| 11 |
+
"""
|
| 12 |
+
import logging
|
| 13 |
+
import os
|
| 14 |
+
import pickle
|
| 15 |
+
import tarfile
|
| 16 |
+
from glob import glob
|
| 17 |
+
from typing import List, Tuple, Dict, Set, Optional, Union
|
| 18 |
+
|
| 19 |
+
import numpy as np
|
| 20 |
+
|
| 21 |
+
from timm.utils.misc import natural_key
|
| 22 |
+
|
| 23 |
+
from .class_map import load_class_map
|
| 24 |
+
from .img_extensions import get_img_extensions
|
| 25 |
+
from .reader import Reader
|
| 26 |
+
|
| 27 |
+
_logger = logging.getLogger(__name__)
|
| 28 |
+
CACHE_FILENAME_SUFFIX = '_tarinfos.pickle'
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class TarState:
|
| 32 |
+
|
| 33 |
+
def __init__(self, tf: tarfile.TarFile = None, ti: tarfile.TarInfo = None):
|
| 34 |
+
self.tf: tarfile.TarFile = tf
|
| 35 |
+
self.ti: tarfile.TarInfo = ti
|
| 36 |
+
self.children: Dict[str, TarState] = {} # child states (tars within tars)
|
| 37 |
+
|
| 38 |
+
def reset(self):
|
| 39 |
+
self.tf = None
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def _extract_tarinfo(tf: tarfile.TarFile, parent_info: Dict, extensions: Set[str]):
|
| 43 |
+
sample_count = 0
|
| 44 |
+
for i, ti in enumerate(tf):
|
| 45 |
+
if not ti.isfile():
|
| 46 |
+
continue
|
| 47 |
+
dirname, basename = os.path.split(ti.path)
|
| 48 |
+
name, ext = os.path.splitext(basename)
|
| 49 |
+
ext = ext.lower()
|
| 50 |
+
if ext == '.tar':
|
| 51 |
+
with tarfile.open(fileobj=tf.extractfile(ti), mode='r|') as ctf:
|
| 52 |
+
child_info = dict(
|
| 53 |
+
name=ti.name, path=os.path.join(parent_info['path'], name), ti=ti, children=[], samples=[])
|
| 54 |
+
sample_count += _extract_tarinfo(ctf, child_info, extensions=extensions)
|
| 55 |
+
_logger.debug(f'{i}/?. Extracted child tarinfos from {ti.name}. {len(child_info["samples"])} images.')
|
| 56 |
+
parent_info['children'].append(child_info)
|
| 57 |
+
elif ext in extensions:
|
| 58 |
+
parent_info['samples'].append(ti)
|
| 59 |
+
sample_count += 1
|
| 60 |
+
return sample_count
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def extract_tarinfos(
|
| 64 |
+
root,
|
| 65 |
+
class_name_to_idx: Optional[Dict] = None,
|
| 66 |
+
cache_tarinfo: Optional[bool] = None,
|
| 67 |
+
extensions: Optional[Union[List, Tuple, Set]] = None,
|
| 68 |
+
sort: bool = True
|
| 69 |
+
):
|
| 70 |
+
extensions = get_img_extensions(as_set=True) if not extensions else set(extensions)
|
| 71 |
+
root_is_tar = False
|
| 72 |
+
if os.path.isfile(root):
|
| 73 |
+
assert os.path.splitext(root)[-1].lower() == '.tar'
|
| 74 |
+
tar_filenames = [root]
|
| 75 |
+
root, root_name = os.path.split(root)
|
| 76 |
+
root_name = os.path.splitext(root_name)[0]
|
| 77 |
+
root_is_tar = True
|
| 78 |
+
else:
|
| 79 |
+
root_name = root.strip(os.path.sep).split(os.path.sep)[-1]
|
| 80 |
+
tar_filenames = glob(os.path.join(root, '*.tar'), recursive=True)
|
| 81 |
+
num_tars = len(tar_filenames)
|
| 82 |
+
tar_bytes = sum([os.path.getsize(f) for f in tar_filenames])
|
| 83 |
+
assert num_tars, f'No .tar files found at specified path ({root}).'
|
| 84 |
+
|
| 85 |
+
_logger.info(f'Scanning {tar_bytes/1024**2:.2f}MB of tar files...')
|
| 86 |
+
info = dict(tartrees=[])
|
| 87 |
+
cache_path = ''
|
| 88 |
+
if cache_tarinfo is None:
|
| 89 |
+
cache_tarinfo = True if tar_bytes > 10*1024**3 else False # FIXME magic number, 10GB
|
| 90 |
+
if cache_tarinfo:
|
| 91 |
+
cache_filename = '_' + root_name + CACHE_FILENAME_SUFFIX
|
| 92 |
+
cache_path = os.path.join(root, cache_filename)
|
| 93 |
+
if os.path.exists(cache_path):
|
| 94 |
+
_logger.info(f'Reading tar info from cache file {cache_path}.')
|
| 95 |
+
with open(cache_path, 'rb') as pf:
|
| 96 |
+
info = pickle.load(pf)
|
| 97 |
+
assert len(info['tartrees']) == num_tars, "Cached tartree len doesn't match number of tarfiles"
|
| 98 |
+
else:
|
| 99 |
+
for i, fn in enumerate(tar_filenames):
|
| 100 |
+
path = '' if root_is_tar else os.path.splitext(os.path.basename(fn))[0]
|
| 101 |
+
with tarfile.open(fn, mode='r|') as tf: # tarinfo scans done in streaming mode
|
| 102 |
+
parent_info = dict(name=os.path.relpath(fn, root), path=path, ti=None, children=[], samples=[])
|
| 103 |
+
num_samples = _extract_tarinfo(tf, parent_info, extensions=extensions)
|
| 104 |
+
num_children = len(parent_info["children"])
|
| 105 |
+
_logger.debug(
|
| 106 |
+
f'{i}/{num_tars}. Extracted tarinfos from {fn}. {num_children} children, {num_samples} samples.')
|
| 107 |
+
info['tartrees'].append(parent_info)
|
| 108 |
+
if cache_path:
|
| 109 |
+
_logger.info(f'Writing tar info to cache file {cache_path}.')
|
| 110 |
+
with open(cache_path, 'wb') as pf:
|
| 111 |
+
pickle.dump(info, pf)
|
| 112 |
+
|
| 113 |
+
samples = []
|
| 114 |
+
labels = []
|
| 115 |
+
build_class_map = False
|
| 116 |
+
if class_name_to_idx is None:
|
| 117 |
+
build_class_map = True
|
| 118 |
+
|
| 119 |
+
# Flatten tartree info into lists of samples and targets w/ targets based on label id via
|
| 120 |
+
# class map arg or from unique paths.
|
| 121 |
+
# NOTE: currently only flattening up to two-levels, filesystem .tars and then one level of sub-tar children
|
| 122 |
+
# this covers my current use cases and keeps things a little easier to test for now.
|
| 123 |
+
tarfiles = []
|
| 124 |
+
|
| 125 |
+
def _label_from_paths(*path, leaf_only=True):
|
| 126 |
+
path = os.path.join(*path).strip(os.path.sep)
|
| 127 |
+
return path.split(os.path.sep)[-1] if leaf_only else path.replace(os.path.sep, '_')
|
| 128 |
+
|
| 129 |
+
def _add_samples(info, fn):
|
| 130 |
+
added = 0
|
| 131 |
+
for s in info['samples']:
|
| 132 |
+
label = _label_from_paths(info['path'], os.path.dirname(s.path))
|
| 133 |
+
if not build_class_map and label not in class_name_to_idx:
|
| 134 |
+
continue
|
| 135 |
+
samples.append((s, fn, info['ti']))
|
| 136 |
+
labels.append(label)
|
| 137 |
+
added += 1
|
| 138 |
+
return added
|
| 139 |
+
|
| 140 |
+
_logger.info(f'Collecting samples and building tar states.')
|
| 141 |
+
for parent_info in info['tartrees']:
|
| 142 |
+
# if tartree has children, we assume all samples are at the child level
|
| 143 |
+
tar_name = None if root_is_tar else parent_info['name']
|
| 144 |
+
tar_state = TarState()
|
| 145 |
+
parent_added = 0
|
| 146 |
+
for child_info in parent_info['children']:
|
| 147 |
+
child_added = _add_samples(child_info, fn=tar_name)
|
| 148 |
+
if child_added:
|
| 149 |
+
tar_state.children[child_info['name']] = TarState(ti=child_info['ti'])
|
| 150 |
+
parent_added += child_added
|
| 151 |
+
parent_added += _add_samples(parent_info, fn=tar_name)
|
| 152 |
+
if parent_added:
|
| 153 |
+
tarfiles.append((tar_name, tar_state))
|
| 154 |
+
del info
|
| 155 |
+
|
| 156 |
+
if build_class_map:
|
| 157 |
+
# build class index
|
| 158 |
+
sorted_labels = list(sorted(set(labels), key=natural_key))
|
| 159 |
+
class_name_to_idx = {c: idx for idx, c in enumerate(sorted_labels)}
|
| 160 |
+
|
| 161 |
+
_logger.info(f'Mapping targets and sorting samples.')
|
| 162 |
+
samples_and_targets = [(s, class_name_to_idx[l]) for s, l in zip(samples, labels) if l in class_name_to_idx]
|
| 163 |
+
if sort:
|
| 164 |
+
samples_and_targets = sorted(samples_and_targets, key=lambda k: natural_key(k[0][0].path))
|
| 165 |
+
samples, targets = zip(*samples_and_targets)
|
| 166 |
+
samples = np.array(samples)
|
| 167 |
+
targets = np.array(targets)
|
| 168 |
+
_logger.info(f'Finished processing {len(samples)} samples across {len(tarfiles)} tar files.')
|
| 169 |
+
return samples, targets, class_name_to_idx, tarfiles
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
class ReaderImageInTar(Reader):
|
| 173 |
+
""" Multi-tarfile dataset reader where there is one .tar file per class
|
| 174 |
+
"""
|
| 175 |
+
|
| 176 |
+
def __init__(self, root, class_map='', cache_tarfiles=True, cache_tarinfo=None):
|
| 177 |
+
super().__init__()
|
| 178 |
+
|
| 179 |
+
class_name_to_idx = None
|
| 180 |
+
if class_map:
|
| 181 |
+
class_name_to_idx = load_class_map(class_map, root)
|
| 182 |
+
self.root = root
|
| 183 |
+
self.samples, self.targets, self.class_name_to_idx, tarfiles = extract_tarinfos(
|
| 184 |
+
self.root,
|
| 185 |
+
class_name_to_idx=class_name_to_idx,
|
| 186 |
+
cache_tarinfo=cache_tarinfo
|
| 187 |
+
)
|
| 188 |
+
self.class_idx_to_name = {v: k for k, v in self.class_name_to_idx.items()}
|
| 189 |
+
if len(tarfiles) == 1 and tarfiles[0][0] is None:
|
| 190 |
+
self.root_is_tar = True
|
| 191 |
+
self.tar_state = tarfiles[0][1]
|
| 192 |
+
else:
|
| 193 |
+
self.root_is_tar = False
|
| 194 |
+
self.tar_state = dict(tarfiles)
|
| 195 |
+
self.cache_tarfiles = cache_tarfiles
|
| 196 |
+
|
| 197 |
+
def __len__(self):
|
| 198 |
+
return len(self.samples)
|
| 199 |
+
|
| 200 |
+
def __getitem__(self, index):
|
| 201 |
+
sample = self.samples[index]
|
| 202 |
+
target = self.targets[index]
|
| 203 |
+
sample_ti, parent_fn, child_ti = sample
|
| 204 |
+
parent_abs = os.path.join(self.root, parent_fn) if parent_fn else self.root
|
| 205 |
+
|
| 206 |
+
tf = None
|
| 207 |
+
cache_state = None
|
| 208 |
+
if self.cache_tarfiles:
|
| 209 |
+
cache_state = self.tar_state if self.root_is_tar else self.tar_state[parent_fn]
|
| 210 |
+
tf = cache_state.tf
|
| 211 |
+
if tf is None:
|
| 212 |
+
tf = tarfile.open(parent_abs)
|
| 213 |
+
if self.cache_tarfiles:
|
| 214 |
+
cache_state.tf = tf
|
| 215 |
+
if child_ti is not None:
|
| 216 |
+
ctf = cache_state.children[child_ti.name].tf if self.cache_tarfiles else None
|
| 217 |
+
if ctf is None:
|
| 218 |
+
ctf = tarfile.open(fileobj=tf.extractfile(child_ti))
|
| 219 |
+
if self.cache_tarfiles:
|
| 220 |
+
cache_state.children[child_ti.name].tf = ctf
|
| 221 |
+
tf = ctf
|
| 222 |
+
|
| 223 |
+
return tf.extractfile(sample_ti), target
|
| 224 |
+
|
| 225 |
+
def _filename(self, index, basename=False, absolute=False):
|
| 226 |
+
filename = self.samples[index][0].name
|
| 227 |
+
if basename:
|
| 228 |
+
filename = os.path.basename(filename)
|
| 229 |
+
return filename
|
timm/data/readers/reader_tfds.py
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" Dataset reader that wraps TFDS datasets
|
| 2 |
+
|
| 3 |
+
Wraps many (most?) TFDS image-classification datasets
|
| 4 |
+
from https://github.com/tensorflow/datasets
|
| 5 |
+
https://www.tensorflow.org/datasets/catalog/overview#image_classification
|
| 6 |
+
|
| 7 |
+
Hacked together by / Copyright 2020 Ross Wightman
|
| 8 |
+
"""
|
| 9 |
+
import math
|
| 10 |
+
import os
|
| 11 |
+
import sys
|
| 12 |
+
from typing import Optional
|
| 13 |
+
|
| 14 |
+
import torch
|
| 15 |
+
import torch.distributed as dist
|
| 16 |
+
from PIL import Image
|
| 17 |
+
|
| 18 |
+
try:
|
| 19 |
+
import tensorflow as tf
|
| 20 |
+
tf.config.set_visible_devices([], 'GPU') # Hands off my GPU! (or pip install tensorflow-cpu)
|
| 21 |
+
import tensorflow_datasets as tfds
|
| 22 |
+
try:
|
| 23 |
+
tfds.even_splits('', 1, drop_remainder=False) # non-buggy even_splits has drop_remainder arg
|
| 24 |
+
has_buggy_even_splits = False
|
| 25 |
+
except TypeError:
|
| 26 |
+
print("Warning: This version of tfds doesn't have the latest even_splits impl. "
|
| 27 |
+
"Please update or use tfds-nightly for better fine-grained split behaviour.")
|
| 28 |
+
has_buggy_even_splits = True
|
| 29 |
+
# NOTE uncomment below if having file limit issues on dataset build (or alter your OS defaults)
|
| 30 |
+
# import resource
|
| 31 |
+
# low, high = resource.getrlimit(resource.RLIMIT_NOFILE)
|
| 32 |
+
# resource.setrlimit(resource.RLIMIT_NOFILE, (high, high))
|
| 33 |
+
except ImportError as e:
|
| 34 |
+
print(e)
|
| 35 |
+
print("Please install tensorflow_datasets package `pip install tensorflow-datasets`.")
|
| 36 |
+
raise e
|
| 37 |
+
|
| 38 |
+
from .class_map import load_class_map
|
| 39 |
+
from .reader import Reader
|
| 40 |
+
from .shared_count import SharedCount
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
MAX_TP_SIZE = int(os.environ.get('TFDS_TP_SIZE', 8)) # maximum TF threadpool size, for jpeg decodes and queuing activities
|
| 44 |
+
SHUFFLE_SIZE = int(os.environ.get('TFDS_SHUFFLE_SIZE', 8192)) # samples to shuffle in DS queue
|
| 45 |
+
PREFETCH_SIZE = int(os.environ.get('TFDS_PREFETCH_SIZE', 2048)) # samples to prefetch
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
@tfds.decode.make_decoder()
|
| 49 |
+
def decode_example(serialized_image, feature, dct_method='INTEGER_ACCURATE', channels=3):
|
| 50 |
+
return tf.image.decode_jpeg(
|
| 51 |
+
serialized_image,
|
| 52 |
+
channels=channels,
|
| 53 |
+
dct_method=dct_method,
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def even_split_indices(split, n, num_samples):
|
| 58 |
+
partitions = [round(i * num_samples / n) for i in range(n + 1)]
|
| 59 |
+
return [f"{split}[{partitions[i]}:{partitions[i + 1]}]" for i in range(n)]
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def get_class_labels(info):
|
| 63 |
+
if 'label' not in info.features:
|
| 64 |
+
return {}
|
| 65 |
+
class_label = info.features['label']
|
| 66 |
+
class_to_idx = {n: class_label.str2int(n) for n in class_label.names}
|
| 67 |
+
return class_to_idx
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
class ReaderTfds(Reader):
|
| 71 |
+
""" Wrap Tensorflow Datasets for use in PyTorch
|
| 72 |
+
|
| 73 |
+
There several things to be aware of:
|
| 74 |
+
* To prevent excessive samples being dropped per epoch w/ distributed training or multiplicity of
|
| 75 |
+
dataloader workers, the train iterator wraps to avoid returning partial batches that trigger drop_last
|
| 76 |
+
https://github.com/pytorch/pytorch/issues/33413
|
| 77 |
+
* With PyTorch IterableDatasets, each worker in each replica operates in isolation, the final batch
|
| 78 |
+
from each worker could be a different size. For training this is worked around by option above, for
|
| 79 |
+
validation extra samples are inserted iff distributed mode is enabled so that the batches being reduced
|
| 80 |
+
across replicas are of same size. This will slightly alter the results, distributed validation will not be
|
| 81 |
+
100% correct. This is similar to common handling in DistributedSampler for normal Datasets but a bit worse
|
| 82 |
+
since there are up to N * J extra samples with IterableDatasets.
|
| 83 |
+
* The sharding (splitting of dataset into TFRecord) files imposes limitations on the number of
|
| 84 |
+
replicas and dataloader workers you can use. For really small datasets that only contain a few shards
|
| 85 |
+
you may have to train non-distributed w/ 1-2 dataloader workers. This is likely not a huge concern as the
|
| 86 |
+
benefit of distributed training or fast dataloading should be much less for small datasets.
|
| 87 |
+
* This wrapper is currently configured to return individual, decompressed image samples from the TFDS
|
| 88 |
+
dataset. The augmentation (transforms) and batching is still done in PyTorch. It would be possible
|
| 89 |
+
to specify TF augmentation fn and return augmented batches w/ some modifications to other downstream
|
| 90 |
+
components.
|
| 91 |
+
|
| 92 |
+
"""
|
| 93 |
+
|
| 94 |
+
def __init__(
|
| 95 |
+
self,
|
| 96 |
+
name,
|
| 97 |
+
root=None,
|
| 98 |
+
split='train',
|
| 99 |
+
class_map=None,
|
| 100 |
+
is_training=False,
|
| 101 |
+
batch_size=1,
|
| 102 |
+
download=False,
|
| 103 |
+
repeats=0,
|
| 104 |
+
seed=42,
|
| 105 |
+
input_key='image',
|
| 106 |
+
input_img_mode='RGB',
|
| 107 |
+
target_key='label',
|
| 108 |
+
target_img_mode='',
|
| 109 |
+
prefetch_size=None,
|
| 110 |
+
shuffle_size=None,
|
| 111 |
+
max_threadpool_size=None
|
| 112 |
+
):
|
| 113 |
+
""" Tensorflow-datasets Wrapper
|
| 114 |
+
|
| 115 |
+
Args:
|
| 116 |
+
root: root data dir (ie your TFDS_DATA_DIR. not dataset specific sub-dir)
|
| 117 |
+
name: tfds dataset name (eg `imagenet2012`)
|
| 118 |
+
split: tfds dataset split (can use all TFDS split strings eg `train[:10%]`)
|
| 119 |
+
is_training: training mode, shuffle enabled, dataset len rounded by batch_size
|
| 120 |
+
batch_size: batch_size to use to unsure total samples % batch_size == 0 in training across all dis nodes
|
| 121 |
+
download: download and build TFDS dataset if set, otherwise must use tfds CLI
|
| 122 |
+
repeats: iterate through (repeat) the dataset this many times per iteration (once if 0 or 1)
|
| 123 |
+
seed: common seed for shard shuffle across all distributed/worker instances
|
| 124 |
+
input_key: name of Feature to return as data (input)
|
| 125 |
+
input_img_mode: image mode if input is an image (currently PIL mode string)
|
| 126 |
+
target_key: name of Feature to return as target (label)
|
| 127 |
+
target_img_mode: image mode if target is an image (currently PIL mode string)
|
| 128 |
+
prefetch_size: override default tf.data prefetch buffer size
|
| 129 |
+
shuffle_size: override default tf.data shuffle buffer size
|
| 130 |
+
max_threadpool_size: override default threadpool size for tf.data
|
| 131 |
+
"""
|
| 132 |
+
super().__init__()
|
| 133 |
+
self.root = root
|
| 134 |
+
self.split = split
|
| 135 |
+
self.is_training = is_training
|
| 136 |
+
self.batch_size = batch_size
|
| 137 |
+
self.repeats = repeats
|
| 138 |
+
self.common_seed = seed # a seed that's fixed across all worker / distributed instances
|
| 139 |
+
|
| 140 |
+
# performance settings
|
| 141 |
+
self.prefetch_size = prefetch_size or PREFETCH_SIZE
|
| 142 |
+
self.shuffle_size = shuffle_size or SHUFFLE_SIZE
|
| 143 |
+
self.max_threadpool_size = max_threadpool_size or MAX_TP_SIZE
|
| 144 |
+
|
| 145 |
+
# TFDS builder and split information
|
| 146 |
+
self.input_key = input_key # FIXME support tuples / lists of inputs and targets and full range of Feature
|
| 147 |
+
self.input_img_mode = input_img_mode
|
| 148 |
+
self.target_key = target_key
|
| 149 |
+
self.target_img_mode = target_img_mode # for dense pixel targets
|
| 150 |
+
self.builder = tfds.builder(name, data_dir=root)
|
| 151 |
+
# NOTE: the tfds command line app can be used download & prepare datasets if you don't enable download flag
|
| 152 |
+
if download:
|
| 153 |
+
self.builder.download_and_prepare()
|
| 154 |
+
self.remap_class = False
|
| 155 |
+
if class_map:
|
| 156 |
+
self.class_to_idx = load_class_map(class_map)
|
| 157 |
+
self.remap_class = True
|
| 158 |
+
else:
|
| 159 |
+
self.class_to_idx = get_class_labels(self.builder.info) if self.target_key == 'label' else {}
|
| 160 |
+
self.split_info = self.builder.info.splits[split]
|
| 161 |
+
self.num_samples = self.split_info.num_examples
|
| 162 |
+
|
| 163 |
+
# Distributed world state
|
| 164 |
+
self.dist_rank = 0
|
| 165 |
+
self.dist_num_replicas = 1
|
| 166 |
+
if dist.is_available() and dist.is_initialized() and dist.get_world_size() > 1:
|
| 167 |
+
self.dist_rank = dist.get_rank()
|
| 168 |
+
self.dist_num_replicas = dist.get_world_size()
|
| 169 |
+
|
| 170 |
+
# Attributes that are updated in _lazy_init, including the tf.data pipeline itself
|
| 171 |
+
self.global_num_workers = 1
|
| 172 |
+
self.num_workers = 1
|
| 173 |
+
self.worker_info = None
|
| 174 |
+
self.worker_seed = 0 # seed unique to each work instance
|
| 175 |
+
self.subsplit = None # set when data is distributed across workers using sub-splits
|
| 176 |
+
self.ds = None # initialized lazily on each dataloader worker process
|
| 177 |
+
self.init_count = 0 # number of ds TF data pipeline initializations
|
| 178 |
+
self.epoch_count = SharedCount()
|
| 179 |
+
# FIXME need to determine if reinit_each_iter is necessary. I'm don't completely trust behaviour
|
| 180 |
+
# of `shuffle_reshuffle_each_iteration` when there are multiple workers / nodes across epochs
|
| 181 |
+
self.reinit_each_iter = self.is_training
|
| 182 |
+
|
| 183 |
+
def set_epoch(self, count):
|
| 184 |
+
self.epoch_count.value = count
|
| 185 |
+
|
| 186 |
+
def set_loader_cfg(
|
| 187 |
+
self,
|
| 188 |
+
num_workers: Optional[int] = None,
|
| 189 |
+
):
|
| 190 |
+
if self.ds is not None:
|
| 191 |
+
return
|
| 192 |
+
if num_workers is not None:
|
| 193 |
+
self.num_workers = num_workers
|
| 194 |
+
self.global_num_workers = self.dist_num_replicas * self.num_workers
|
| 195 |
+
|
| 196 |
+
def _lazy_init(self):
|
| 197 |
+
""" Lazily initialize the dataset.
|
| 198 |
+
|
| 199 |
+
This is necessary to init the Tensorflow dataset pipeline in the (dataloader) process that
|
| 200 |
+
will be using the dataset instance. The __init__ method is called on the main process,
|
| 201 |
+
this will be called in a dataloader worker process.
|
| 202 |
+
|
| 203 |
+
NOTE: There will be problems if you try to re-use this dataset across different loader/worker
|
| 204 |
+
instances once it has been initialized. Do not call any dataset methods that can call _lazy_init
|
| 205 |
+
before it is passed to dataloader.
|
| 206 |
+
"""
|
| 207 |
+
worker_info = torch.utils.data.get_worker_info()
|
| 208 |
+
|
| 209 |
+
# setup input context to split dataset across distributed processes
|
| 210 |
+
num_workers = 1
|
| 211 |
+
global_worker_id = 0
|
| 212 |
+
if worker_info is not None:
|
| 213 |
+
self.worker_info = worker_info
|
| 214 |
+
self.worker_seed = worker_info.seed
|
| 215 |
+
self.num_workers = worker_info.num_workers
|
| 216 |
+
self.global_num_workers = self.dist_num_replicas * self.num_workers
|
| 217 |
+
global_worker_id = self.dist_rank * self.num_workers + worker_info.id
|
| 218 |
+
|
| 219 |
+
""" Data sharding
|
| 220 |
+
InputContext will assign subset of underlying TFRecord files to each 'pipeline' if used.
|
| 221 |
+
My understanding is that using split, the underling TFRecord files will shuffle (shuffle_files=True)
|
| 222 |
+
between the splits each iteration, but that understanding could be wrong.
|
| 223 |
+
|
| 224 |
+
I am currently using a mix of InputContext shard assignment and fine-grained sub-splits for distributing
|
| 225 |
+
the data across workers. For training InputContext is used to assign shards to nodes unless num_shards
|
| 226 |
+
in dataset < total number of workers. Otherwise sub-split API is used for datasets without enough shards or
|
| 227 |
+
for validation where we can't drop samples and need to avoid minimize uneven splits to avoid padding.
|
| 228 |
+
"""
|
| 229 |
+
should_subsplit = self.global_num_workers > 1 and (
|
| 230 |
+
self.split_info.num_shards < self.global_num_workers or not self.is_training)
|
| 231 |
+
if should_subsplit:
|
| 232 |
+
# split the dataset w/o using sharding for more even samples / worker, can result in less optimal
|
| 233 |
+
# read patterns for distributed training (overlap across shards) so better to use InputContext there
|
| 234 |
+
if has_buggy_even_splits:
|
| 235 |
+
# my even_split workaround doesn't work on subsplits, upgrade tfds!
|
| 236 |
+
if not isinstance(self.split_info, tfds.core.splits.SubSplitInfo):
|
| 237 |
+
subsplits = even_split_indices(self.split, self.global_num_workers, self.num_samples)
|
| 238 |
+
self.subsplit = subsplits[global_worker_id]
|
| 239 |
+
else:
|
| 240 |
+
subsplits = tfds.even_splits(self.split, self.global_num_workers)
|
| 241 |
+
self.subsplit = subsplits[global_worker_id]
|
| 242 |
+
|
| 243 |
+
input_context = None
|
| 244 |
+
if self.global_num_workers > 1 and self.subsplit is None:
|
| 245 |
+
# set input context to divide shards among distributed replicas
|
| 246 |
+
input_context = tf.distribute.InputContext(
|
| 247 |
+
num_input_pipelines=self.global_num_workers,
|
| 248 |
+
input_pipeline_id=global_worker_id,
|
| 249 |
+
num_replicas_in_sync=self.dist_num_replicas # FIXME does this arg have any impact?
|
| 250 |
+
)
|
| 251 |
+
read_config = tfds.ReadConfig(
|
| 252 |
+
shuffle_seed=self.common_seed + self.epoch_count.value,
|
| 253 |
+
shuffle_reshuffle_each_iteration=True,
|
| 254 |
+
input_context=input_context,
|
| 255 |
+
)
|
| 256 |
+
ds = self.builder.as_dataset(
|
| 257 |
+
split=self.subsplit or self.split,
|
| 258 |
+
shuffle_files=self.is_training,
|
| 259 |
+
decoders=dict(image=decode_example(channels=1 if self.input_img_mode == 'L' else 3)),
|
| 260 |
+
read_config=read_config,
|
| 261 |
+
)
|
| 262 |
+
# avoid overloading threading w/ combo of TF ds threads + PyTorch workers
|
| 263 |
+
options = tf.data.Options()
|
| 264 |
+
thread_member = 'threading' if hasattr(options, 'threading') else 'experimental_threading'
|
| 265 |
+
getattr(options, thread_member).private_threadpool_size = max(1, self.max_threadpool_size // self.num_workers)
|
| 266 |
+
getattr(options, thread_member).max_intra_op_parallelism = 1
|
| 267 |
+
ds = ds.with_options(options)
|
| 268 |
+
if self.is_training or self.repeats > 1:
|
| 269 |
+
# to prevent excessive drop_last batch behaviour w/ IterableDatasets
|
| 270 |
+
# see warnings at https://pytorch.org/docs/stable/data.html#multi-process-data-loading
|
| 271 |
+
ds = ds.repeat() # allow wrap around and break iteration manually
|
| 272 |
+
if self.is_training:
|
| 273 |
+
ds = ds.shuffle(min(self.num_samples, self.shuffle_size) // self.global_num_workers, seed=self.worker_seed)
|
| 274 |
+
ds = ds.prefetch(min(self.num_samples // self.global_num_workers, self.prefetch_size))
|
| 275 |
+
self.ds = tfds.as_numpy(ds)
|
| 276 |
+
self.init_count += 1
|
| 277 |
+
|
| 278 |
+
def _num_samples_per_worker(self):
|
| 279 |
+
num_worker_samples = \
|
| 280 |
+
max(1, self.repeats) * self.num_samples / max(self.global_num_workers, self.dist_num_replicas)
|
| 281 |
+
if self.is_training or self.dist_num_replicas > 1:
|
| 282 |
+
num_worker_samples = math.ceil(num_worker_samples)
|
| 283 |
+
if self.is_training:
|
| 284 |
+
num_worker_samples = math.ceil(num_worker_samples / self.batch_size) * self.batch_size
|
| 285 |
+
return int(num_worker_samples)
|
| 286 |
+
|
| 287 |
+
def __iter__(self):
|
| 288 |
+
if self.ds is None or self.reinit_each_iter:
|
| 289 |
+
self._lazy_init()
|
| 290 |
+
|
| 291 |
+
# Compute a rounded up sample count that is used to:
|
| 292 |
+
# 1. make batches even cross workers & replicas in distributed validation.
|
| 293 |
+
# This adds extra samples and will slightly alter validation results.
|
| 294 |
+
# 2. determine loop ending condition in training w/ repeat enabled so that only full batch_size
|
| 295 |
+
# batches are produced (underlying tfds iter wraps around)
|
| 296 |
+
target_sample_count = self._num_samples_per_worker()
|
| 297 |
+
|
| 298 |
+
# Iterate until exhausted or sample count hits target when training (ds.repeat enabled)
|
| 299 |
+
sample_count = 0
|
| 300 |
+
for sample in self.ds:
|
| 301 |
+
input_data = sample[self.input_key]
|
| 302 |
+
if self.input_img_mode:
|
| 303 |
+
if self.input_img_mode == 'L' and input_data.ndim == 3:
|
| 304 |
+
input_data = input_data[:, :, 0]
|
| 305 |
+
input_data = Image.fromarray(input_data, mode=self.input_img_mode)
|
| 306 |
+
target_data = sample[self.target_key]
|
| 307 |
+
if self.target_img_mode:
|
| 308 |
+
# dense pixel target
|
| 309 |
+
target_data = Image.fromarray(target_data, mode=self.target_img_mode)
|
| 310 |
+
elif self.remap_class:
|
| 311 |
+
target_data = self.class_to_idx[target_data]
|
| 312 |
+
yield input_data, target_data
|
| 313 |
+
sample_count += 1
|
| 314 |
+
if self.is_training and sample_count >= target_sample_count:
|
| 315 |
+
# Need to break out of loop when repeat() is enabled for training w/ oversampling
|
| 316 |
+
# this results in extra samples per epoch but seems more desirable than dropping
|
| 317 |
+
# up to N*J batches per epoch (where N = num distributed processes, and J = num worker processes)
|
| 318 |
+
break
|
| 319 |
+
|
| 320 |
+
# Pad across distributed nodes (make counts equal by adding samples)
|
| 321 |
+
if not self.is_training and self.dist_num_replicas > 1 and self.subsplit is not None and \
|
| 322 |
+
0 < sample_count < target_sample_count:
|
| 323 |
+
# Validation batch padding only done for distributed training where results are reduced across nodes.
|
| 324 |
+
# For single process case, it won't matter if workers return different batch sizes.
|
| 325 |
+
# If using input_context or % based splits, sample count can vary significantly across workers and this
|
| 326 |
+
# approach should not be used (hence disabled if self.subsplit isn't set).
|
| 327 |
+
while sample_count < target_sample_count:
|
| 328 |
+
yield input_data, target_data # yield prev sample again
|
| 329 |
+
sample_count += 1
|
| 330 |
+
|
| 331 |
+
def __len__(self):
|
| 332 |
+
num_samples = self._num_samples_per_worker() * self.num_workers
|
| 333 |
+
return num_samples
|
| 334 |
+
|
| 335 |
+
def _filename(self, index, basename=False, absolute=False):
|
| 336 |
+
assert False, "Not supported" # no random access to samples
|
| 337 |
+
|
| 338 |
+
def filenames(self, basename=False, absolute=False):
|
| 339 |
+
""" Return all filenames in dataset, overrides base"""
|
| 340 |
+
if self.ds is None:
|
| 341 |
+
self._lazy_init()
|
| 342 |
+
names = []
|
| 343 |
+
for sample in self.ds:
|
| 344 |
+
if len(names) > self.num_samples:
|
| 345 |
+
break # safety for ds.repeat() case
|
| 346 |
+
if 'file_name' in sample:
|
| 347 |
+
name = sample['file_name']
|
| 348 |
+
elif 'filename' in sample:
|
| 349 |
+
name = sample['filename']
|
| 350 |
+
elif 'id' in sample:
|
| 351 |
+
name = sample['id']
|
| 352 |
+
else:
|
| 353 |
+
assert False, "No supported name field present"
|
| 354 |
+
names.append(name)
|
| 355 |
+
return names
|
timm/data/readers/reader_wds.py
ADDED
|
@@ -0,0 +1,466 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" Dataset reader for webdataset
|
| 2 |
+
|
| 3 |
+
Hacked together by / Copyright 2022 Ross Wightman
|
| 4 |
+
"""
|
| 5 |
+
import io
|
| 6 |
+
import json
|
| 7 |
+
import logging
|
| 8 |
+
import math
|
| 9 |
+
import os
|
| 10 |
+
import random
|
| 11 |
+
import sys
|
| 12 |
+
from dataclasses import dataclass
|
| 13 |
+
from functools import partial
|
| 14 |
+
from itertools import islice
|
| 15 |
+
from typing import Any, Callable, Dict, List, Optional, Tuple
|
| 16 |
+
|
| 17 |
+
import torch
|
| 18 |
+
import torch.distributed as dist
|
| 19 |
+
import yaml
|
| 20 |
+
from PIL import Image
|
| 21 |
+
from torch.utils.data import Dataset, IterableDataset, get_worker_info
|
| 22 |
+
|
| 23 |
+
try:
|
| 24 |
+
import webdataset as wds
|
| 25 |
+
from webdataset.filters import _shuffle, getfirst
|
| 26 |
+
from webdataset.shardlists import expand_urls
|
| 27 |
+
from webdataset.tariterators import base_plus_ext, url_opener, tar_file_expander, valid_sample
|
| 28 |
+
except ImportError:
|
| 29 |
+
wds = None
|
| 30 |
+
expand_urls = None
|
| 31 |
+
|
| 32 |
+
from .class_map import load_class_map
|
| 33 |
+
from .reader import Reader
|
| 34 |
+
from .shared_count import SharedCount
|
| 35 |
+
|
| 36 |
+
_logger = logging.getLogger(__name__)
|
| 37 |
+
|
| 38 |
+
SAMPLE_SHUFFLE_SIZE = int(os.environ.get('WDS_SHUFFLE_SIZE', 8192))
|
| 39 |
+
SAMPLE_INITIAL_SIZE = int(os.environ.get('WDS_INITIAL_SIZE', 2048))
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def _load_info(root, names=('_info.json', 'info.json')):
|
| 43 |
+
if isinstance(names, str):
|
| 44 |
+
names = (names,)
|
| 45 |
+
tried = []
|
| 46 |
+
err_str = ''
|
| 47 |
+
for n in names:
|
| 48 |
+
full_path = os.path.join(root, n)
|
| 49 |
+
try:
|
| 50 |
+
tried.append(full_path)
|
| 51 |
+
with wds.gopen(full_path) as f:
|
| 52 |
+
if n.endswith('.json'):
|
| 53 |
+
info_dict = json.load(f)
|
| 54 |
+
else:
|
| 55 |
+
info_dict = yaml.safe_load(f)
|
| 56 |
+
return info_dict
|
| 57 |
+
except Exception as e:
|
| 58 |
+
err_str = str(e)
|
| 59 |
+
|
| 60 |
+
_logger.warning(
|
| 61 |
+
f'Dataset info file not found at {tried}. Error: {err_str}. '
|
| 62 |
+
'Falling back to provided split and size arg.')
|
| 63 |
+
return {}
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
@dataclass
|
| 67 |
+
class SplitInfo:
|
| 68 |
+
num_samples: int
|
| 69 |
+
filenames: Tuple[str]
|
| 70 |
+
shard_lengths: Tuple[int] = ()
|
| 71 |
+
alt_label: str = ''
|
| 72 |
+
name: str = ''
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def _parse_split_info(split: str, info: Dict):
|
| 76 |
+
def _info_convert(dict_info):
|
| 77 |
+
return SplitInfo(
|
| 78 |
+
num_samples=dict_info['num_samples'],
|
| 79 |
+
filenames=tuple(dict_info['filenames']),
|
| 80 |
+
shard_lengths=tuple(dict_info['shard_lengths']),
|
| 81 |
+
alt_label=dict_info.get('alt_label', ''),
|
| 82 |
+
name=dict_info['name'],
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
if 'tar' in split or '..' in split:
|
| 86 |
+
# split in WDS string braceexpand format, sample count can be included with a | separator
|
| 87 |
+
# ex: `dataset-split-{0000..9999}.tar|100000` for 9999 shards, covering 100,000 samples
|
| 88 |
+
split = split.split('|')
|
| 89 |
+
num_samples = 0
|
| 90 |
+
split_name = ''
|
| 91 |
+
if len(split) > 1:
|
| 92 |
+
num_samples = int(split[1])
|
| 93 |
+
split = split[0]
|
| 94 |
+
if '::' not in split:
|
| 95 |
+
split_parts = split.split('-', 3)
|
| 96 |
+
split_idx = len(split_parts) - 1
|
| 97 |
+
if split_idx and 'splits' in info and split_parts[split_idx] in info['splits']:
|
| 98 |
+
split_name = split_parts[split_idx]
|
| 99 |
+
|
| 100 |
+
split_filenames = expand_urls(split)
|
| 101 |
+
if split_name:
|
| 102 |
+
split_info = info['splits'][split_name]
|
| 103 |
+
if not num_samples:
|
| 104 |
+
_fc = {f: c for f, c in zip(split_info['filenames'], split_info['shard_lengths'])}
|
| 105 |
+
num_samples = sum(_fc[f] for f in split_filenames)
|
| 106 |
+
split_info['filenames'] = tuple(_fc.keys())
|
| 107 |
+
split_info['shard_lengths'] = tuple(_fc.values())
|
| 108 |
+
split_info['num_samples'] = num_samples
|
| 109 |
+
split_info = _info_convert(split_info)
|
| 110 |
+
else:
|
| 111 |
+
split_info = SplitInfo(
|
| 112 |
+
name=split_name,
|
| 113 |
+
num_samples=num_samples,
|
| 114 |
+
filenames=split_filenames,
|
| 115 |
+
)
|
| 116 |
+
else:
|
| 117 |
+
if 'splits' not in info or split not in info['splits']:
|
| 118 |
+
raise RuntimeError(f"split {split} not found in info ({info.get('splits', {}).keys()})")
|
| 119 |
+
split = split
|
| 120 |
+
split_info = info['splits'][split]
|
| 121 |
+
split_info = _info_convert(split_info)
|
| 122 |
+
|
| 123 |
+
return split_info
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def log_and_continue(exn):
|
| 127 |
+
"""Call in an exception handler to ignore exceptions, isssue a warning, and continue."""
|
| 128 |
+
_logger.warning(f'Handling webdataset error ({repr(exn)}). Ignoring.')
|
| 129 |
+
# NOTE: try force an exit on errors that are clearly code / config and not transient
|
| 130 |
+
if isinstance(exn, TypeError):
|
| 131 |
+
raise exn
|
| 132 |
+
return True
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def _decode(
|
| 136 |
+
sample,
|
| 137 |
+
image_key='jpg',
|
| 138 |
+
image_mode='RGB',
|
| 139 |
+
target_key='cls',
|
| 140 |
+
alt_label=''
|
| 141 |
+
):
|
| 142 |
+
""" Custom sample decode
|
| 143 |
+
* decode and convert PIL Image
|
| 144 |
+
* cls byte string label to int
|
| 145 |
+
* pass through JSON byte string (if it exists) without parse
|
| 146 |
+
"""
|
| 147 |
+
# decode class label, skip if alternate label not valid
|
| 148 |
+
if alt_label:
|
| 149 |
+
# alternative labels are encoded in json metadata
|
| 150 |
+
meta = json.loads(sample['json'])
|
| 151 |
+
class_label = int(meta[alt_label])
|
| 152 |
+
if class_label < 0:
|
| 153 |
+
# skipped labels currently encoded as -1, may change to a null/None value
|
| 154 |
+
return None
|
| 155 |
+
else:
|
| 156 |
+
class_label = int(sample[target_key])
|
| 157 |
+
|
| 158 |
+
# decode image
|
| 159 |
+
img = getfirst(sample, image_key)
|
| 160 |
+
with io.BytesIO(img) as b:
|
| 161 |
+
img = Image.open(b)
|
| 162 |
+
img.load()
|
| 163 |
+
if image_mode:
|
| 164 |
+
img = img.convert(image_mode)
|
| 165 |
+
|
| 166 |
+
# json passed through in undecoded state
|
| 167 |
+
decoded = dict(jpg=img, cls=class_label, json=sample.get('json', None))
|
| 168 |
+
return decoded
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def pytorch_worker_seed():
|
| 172 |
+
"""get dataloader worker seed from pytorch"""
|
| 173 |
+
worker_info = get_worker_info()
|
| 174 |
+
if worker_info is not None:
|
| 175 |
+
# favour the seed already created for pytorch dataloader workers if it exists
|
| 176 |
+
return worker_info.seed
|
| 177 |
+
# fallback to wds rank based seed
|
| 178 |
+
return wds.utils.pytorch_worker_seed()
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
if wds is not None:
|
| 182 |
+
# conditional to avoid mandatory wds import (via inheritance of wds.PipelineStage)
|
| 183 |
+
|
| 184 |
+
class detshuffle2(wds.PipelineStage):
|
| 185 |
+
def __init__(
|
| 186 |
+
self,
|
| 187 |
+
bufsize=1000,
|
| 188 |
+
initial=100,
|
| 189 |
+
seed=0,
|
| 190 |
+
epoch=-1,
|
| 191 |
+
):
|
| 192 |
+
self.bufsize = bufsize
|
| 193 |
+
self.initial = initial
|
| 194 |
+
self.seed = seed
|
| 195 |
+
self.epoch = epoch
|
| 196 |
+
|
| 197 |
+
def run(self, src):
|
| 198 |
+
if isinstance(self.epoch, SharedCount):
|
| 199 |
+
epoch = self.epoch.value
|
| 200 |
+
else:
|
| 201 |
+
# NOTE: this is epoch tracking is problematic in a multiprocess (dataloader workers or train)
|
| 202 |
+
# situation as different workers may wrap at different times (or not at all).
|
| 203 |
+
self.epoch += 1
|
| 204 |
+
epoch = self.epoch
|
| 205 |
+
|
| 206 |
+
if self.seed < 0:
|
| 207 |
+
seed = pytorch_worker_seed() + epoch
|
| 208 |
+
else:
|
| 209 |
+
seed = self.seed + epoch
|
| 210 |
+
# _logger.info(f'shuffle seed: {self.seed}, {seed}, epoch: {epoch}') # FIXME temporary
|
| 211 |
+
rng = random.Random(seed)
|
| 212 |
+
return _shuffle(src, self.bufsize, self.initial, rng)
|
| 213 |
+
|
| 214 |
+
else:
|
| 215 |
+
detshuffle2 = None
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
class ResampledShards2(IterableDataset):
|
| 219 |
+
"""An iterable dataset yielding a list of urls."""
|
| 220 |
+
|
| 221 |
+
def __init__(
|
| 222 |
+
self,
|
| 223 |
+
urls,
|
| 224 |
+
nshards=sys.maxsize,
|
| 225 |
+
worker_seed=None,
|
| 226 |
+
deterministic=True,
|
| 227 |
+
epoch=-1,
|
| 228 |
+
):
|
| 229 |
+
"""Sample shards from the shard list with replacement.
|
| 230 |
+
|
| 231 |
+
:param urls: a list of URLs as a Python list or brace notation string
|
| 232 |
+
"""
|
| 233 |
+
super().__init__()
|
| 234 |
+
urls = wds.shardlists.expand_urls(urls)
|
| 235 |
+
self.urls = urls
|
| 236 |
+
assert isinstance(self.urls[0], str)
|
| 237 |
+
self.nshards = nshards
|
| 238 |
+
self.rng = random.Random()
|
| 239 |
+
self.worker_seed = pytorch_worker_seed if worker_seed is None else worker_seed
|
| 240 |
+
self.deterministic = deterministic
|
| 241 |
+
self.epoch = epoch
|
| 242 |
+
|
| 243 |
+
def __iter__(self):
|
| 244 |
+
"""Return an iterator over the shards."""
|
| 245 |
+
if isinstance(self.epoch, SharedCount):
|
| 246 |
+
epoch = self.epoch.value
|
| 247 |
+
else:
|
| 248 |
+
# NOTE: this is epoch tracking is problematic in a multiprocess (dataloader workers or train)
|
| 249 |
+
# situation as different workers may wrap at different times (or not at all).
|
| 250 |
+
self.epoch += 1
|
| 251 |
+
epoch = self.epoch
|
| 252 |
+
|
| 253 |
+
if self.deterministic:
|
| 254 |
+
# reset seed w/ epoch if deterministic, worker seed should be deterministic due to arg.seed
|
| 255 |
+
self.rng = random.Random(self.worker_seed() + epoch)
|
| 256 |
+
|
| 257 |
+
for _ in range(self.nshards):
|
| 258 |
+
index = self.rng.randint(0, len(self.urls) - 1)
|
| 259 |
+
yield dict(url=self.urls[index])
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
class ReaderWds(Reader):
|
| 263 |
+
def __init__(
|
| 264 |
+
self,
|
| 265 |
+
root: str,
|
| 266 |
+
name: Optional[str] = None,
|
| 267 |
+
split: str = 'train',
|
| 268 |
+
is_training: bool = False,
|
| 269 |
+
num_samples: Optional[int] = None,
|
| 270 |
+
batch_size: int = 1,
|
| 271 |
+
repeats: int = 0,
|
| 272 |
+
seed: int = 42,
|
| 273 |
+
class_map: Optional[dict] = None,
|
| 274 |
+
input_key: str = 'jpg;png;webp',
|
| 275 |
+
input_img_mode: str = 'RGB',
|
| 276 |
+
target_key: str = 'cls',
|
| 277 |
+
target_img_mode: str = '',
|
| 278 |
+
filename_key: str = 'filename',
|
| 279 |
+
sample_shuffle_size: Optional[int] = None,
|
| 280 |
+
smaple_initial_size: Optional[int] = None,
|
| 281 |
+
):
|
| 282 |
+
super().__init__()
|
| 283 |
+
if wds is None:
|
| 284 |
+
raise RuntimeError(
|
| 285 |
+
'Please install webdataset 0.2.x package `pip install git+https://github.com/webdataset/webdataset`.')
|
| 286 |
+
self.root = root
|
| 287 |
+
self.is_training = is_training
|
| 288 |
+
self.batch_size = batch_size
|
| 289 |
+
self.repeats = repeats
|
| 290 |
+
self.common_seed = seed # a seed that's fixed across all worker / distributed instances
|
| 291 |
+
self.shard_shuffle_size = 500
|
| 292 |
+
self.sample_shuffle_size = sample_shuffle_size or SAMPLE_SHUFFLE_SIZE
|
| 293 |
+
self.sample_initial_size = smaple_initial_size or SAMPLE_INITIAL_SIZE
|
| 294 |
+
|
| 295 |
+
self.input_key = input_key
|
| 296 |
+
self.input_img_mode = input_img_mode
|
| 297 |
+
self.target_key = target_key
|
| 298 |
+
self.filename_key = filename_key
|
| 299 |
+
self.key_ext = '.JPEG' # extension to add to key for original filenames (DS specific, default ImageNet)
|
| 300 |
+
|
| 301 |
+
self.info = _load_info(self.root)
|
| 302 |
+
self.split_info = _parse_split_info(split, self.info)
|
| 303 |
+
if num_samples is not None:
|
| 304 |
+
self.num_samples = num_samples
|
| 305 |
+
else:
|
| 306 |
+
self.num_samples = self.split_info.num_samples
|
| 307 |
+
if not self.num_samples:
|
| 308 |
+
raise RuntimeError(f'Invalid split definition, num_samples not specified.')
|
| 309 |
+
self.remap_class = False
|
| 310 |
+
if class_map:
|
| 311 |
+
self.class_to_idx = load_class_map(class_map)
|
| 312 |
+
self.remap_class = True
|
| 313 |
+
else:
|
| 314 |
+
self.class_to_idx = {}
|
| 315 |
+
|
| 316 |
+
# Distributed world state
|
| 317 |
+
self.dist_rank = 0
|
| 318 |
+
self.dist_num_replicas = 1
|
| 319 |
+
if dist.is_available() and dist.is_initialized() and dist.get_world_size() > 1:
|
| 320 |
+
self.dist_rank = dist.get_rank()
|
| 321 |
+
self.dist_num_replicas = dist.get_world_size()
|
| 322 |
+
|
| 323 |
+
# Attributes that are updated in _lazy_init
|
| 324 |
+
self.worker_info = None
|
| 325 |
+
self.worker_id = 0
|
| 326 |
+
self.worker_seed = seed # seed unique to each worker instance
|
| 327 |
+
self.num_workers = 1
|
| 328 |
+
self.global_worker_id = 0
|
| 329 |
+
self.global_num_workers = 1
|
| 330 |
+
self.init_count = 0
|
| 331 |
+
self.epoch_count = SharedCount()
|
| 332 |
+
|
| 333 |
+
# DataPipeline is lazy init, the majority of WDS DataPipeline could be init here, BUT, shuffle seed
|
| 334 |
+
# is not handled in manner where it can be deterministic for each worker AND initialized up front
|
| 335 |
+
self.ds = None
|
| 336 |
+
|
| 337 |
+
def set_epoch(self, count):
|
| 338 |
+
self.epoch_count.value = count
|
| 339 |
+
|
| 340 |
+
def set_loader_cfg(
|
| 341 |
+
self,
|
| 342 |
+
num_workers: Optional[int] = None,
|
| 343 |
+
):
|
| 344 |
+
if self.ds is not None:
|
| 345 |
+
return
|
| 346 |
+
if num_workers is not None:
|
| 347 |
+
self.num_workers = num_workers
|
| 348 |
+
self.global_num_workers = self.dist_num_replicas * self.num_workers
|
| 349 |
+
|
| 350 |
+
def _lazy_init(self):
|
| 351 |
+
""" Lazily initialize worker (in worker processes)
|
| 352 |
+
"""
|
| 353 |
+
if self.worker_info is None:
|
| 354 |
+
worker_info = torch.utils.data.get_worker_info()
|
| 355 |
+
if worker_info is not None:
|
| 356 |
+
self.worker_info = worker_info
|
| 357 |
+
self.worker_id = worker_info.id
|
| 358 |
+
self.worker_seed = worker_info.seed
|
| 359 |
+
self.num_workers = worker_info.num_workers
|
| 360 |
+
self.global_num_workers = self.dist_num_replicas * self.num_workers
|
| 361 |
+
self.global_worker_id = self.dist_rank * self.num_workers + self.worker_id
|
| 362 |
+
|
| 363 |
+
# init data pipeline
|
| 364 |
+
abs_shard_filenames = [os.path.join(self.root, f) for f in self.split_info.filenames]
|
| 365 |
+
pipeline = [wds.SimpleShardList(abs_shard_filenames)]
|
| 366 |
+
# at this point we have an iterator over all the shards
|
| 367 |
+
if self.is_training:
|
| 368 |
+
pipeline.extend([
|
| 369 |
+
detshuffle2(
|
| 370 |
+
self.shard_shuffle_size,
|
| 371 |
+
seed=self.common_seed,
|
| 372 |
+
epoch=self.epoch_count,
|
| 373 |
+
),
|
| 374 |
+
self._split_by_node_and_worker,
|
| 375 |
+
# at this point, we have an iterator over the shards assigned to each worker
|
| 376 |
+
wds.tarfile_to_samples(handler=log_and_continue),
|
| 377 |
+
wds.shuffle(
|
| 378 |
+
bufsize=self.sample_shuffle_size,
|
| 379 |
+
initial=self.sample_initial_size,
|
| 380 |
+
rng=random.Random(self.worker_seed) # this is why we lazy-init whole DataPipeline
|
| 381 |
+
),
|
| 382 |
+
])
|
| 383 |
+
else:
|
| 384 |
+
pipeline.extend([
|
| 385 |
+
self._split_by_node_and_worker,
|
| 386 |
+
# at this point, we have an iterator over the shards assigned to each worker
|
| 387 |
+
wds.tarfile_to_samples(handler=log_and_continue),
|
| 388 |
+
])
|
| 389 |
+
pipeline.extend([
|
| 390 |
+
wds.map(
|
| 391 |
+
partial(
|
| 392 |
+
_decode,
|
| 393 |
+
image_key=self.input_key,
|
| 394 |
+
image_mode=self.input_img_mode,
|
| 395 |
+
alt_label=self.split_info.alt_label,
|
| 396 |
+
),
|
| 397 |
+
handler=log_and_continue,
|
| 398 |
+
),
|
| 399 |
+
wds.rename(image=self.input_key, target=self.target_key)
|
| 400 |
+
])
|
| 401 |
+
self.ds = wds.DataPipeline(*pipeline)
|
| 402 |
+
|
| 403 |
+
def _split_by_node_and_worker(self, src):
|
| 404 |
+
if self.global_num_workers > 1:
|
| 405 |
+
for s in islice(src, self.global_worker_id, None, self.global_num_workers):
|
| 406 |
+
yield s
|
| 407 |
+
else:
|
| 408 |
+
for s in src:
|
| 409 |
+
yield s
|
| 410 |
+
|
| 411 |
+
def _num_samples_per_worker(self):
|
| 412 |
+
num_worker_samples = self.num_samples / max(self.global_num_workers, self.dist_num_replicas)
|
| 413 |
+
if self.is_training or self.dist_num_replicas > 1:
|
| 414 |
+
num_worker_samples = math.ceil(num_worker_samples)
|
| 415 |
+
if self.is_training:
|
| 416 |
+
num_worker_samples = math.ceil(num_worker_samples / self.batch_size) * self.batch_size
|
| 417 |
+
return int(num_worker_samples)
|
| 418 |
+
|
| 419 |
+
def __iter__(self):
|
| 420 |
+
if self.ds is None:
|
| 421 |
+
self._lazy_init()
|
| 422 |
+
|
| 423 |
+
num_worker_samples = self._num_samples_per_worker()
|
| 424 |
+
if self.is_training or self.dist_num_replicas > 1:
|
| 425 |
+
# NOTE: doing distributed validation w/ WDS is messy, hard to meet constraints that
|
| 426 |
+
# same # of batches needed across all replicas w/ seeing each sample once.
|
| 427 |
+
# with_epoch() is simple but could miss a shard's worth of samples in some workers,
|
| 428 |
+
# and duplicate in others. Best to keep num DL workers low and a divisor of #val shards.
|
| 429 |
+
ds = self.ds.with_epoch(num_worker_samples)
|
| 430 |
+
else:
|
| 431 |
+
ds = self.ds
|
| 432 |
+
|
| 433 |
+
i = 0
|
| 434 |
+
# _logger.info(f'start {i}, {self.worker_id}') # FIXME temporary debug
|
| 435 |
+
for sample in ds:
|
| 436 |
+
target = sample['target']
|
| 437 |
+
if self.remap_class:
|
| 438 |
+
target = self.class_to_idx[target]
|
| 439 |
+
yield sample['image'], target
|
| 440 |
+
i += 1
|
| 441 |
+
# _logger.info(f'end {i}, {self.worker_id}') # FIXME temporary debug
|
| 442 |
+
|
| 443 |
+
def __len__(self):
|
| 444 |
+
num_samples = self._num_samples_per_worker() * self.num_workers
|
| 445 |
+
return num_samples
|
| 446 |
+
|
| 447 |
+
def _filename(self, index, basename=False, absolute=False):
|
| 448 |
+
assert False, "Not supported" # no random access to examples
|
| 449 |
+
|
| 450 |
+
def filenames(self, basename=False, absolute=False):
|
| 451 |
+
""" Return all filenames in dataset, overrides base"""
|
| 452 |
+
if self.ds is None:
|
| 453 |
+
self._lazy_init()
|
| 454 |
+
|
| 455 |
+
names = []
|
| 456 |
+
for sample in self.ds:
|
| 457 |
+
if self.filename_key in sample:
|
| 458 |
+
name = sample[self.filename_key]
|
| 459 |
+
elif '__key__' in sample:
|
| 460 |
+
name = sample['__key__'] + self.key_ext
|
| 461 |
+
else:
|
| 462 |
+
assert False, "No supported name field present"
|
| 463 |
+
names.append(name)
|
| 464 |
+
if len(names) >= self.num_samples:
|
| 465 |
+
break # safety for ds.repeat() case
|
| 466 |
+
return names
|
timm/data/readers/shared_count.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from multiprocessing import Value
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class SharedCount:
|
| 5 |
+
def __init__(self, epoch: int = 0):
|
| 6 |
+
self.shared_epoch = Value('i', epoch)
|
| 7 |
+
|
| 8 |
+
@property
|
| 9 |
+
def value(self):
|
| 10 |
+
return self.shared_epoch.value
|
| 11 |
+
|
| 12 |
+
@value.setter
|
| 13 |
+
def value(self, epoch):
|
| 14 |
+
self.shared_epoch.value = epoch
|
timm/data/real_labels.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" Real labels evaluator for ImageNet
|
| 2 |
+
Paper: `Are we done with ImageNet?` - https://arxiv.org/abs/2006.07159
|
| 3 |
+
Based on Numpy example at https://github.com/google-research/reassessed-imagenet
|
| 4 |
+
|
| 5 |
+
Hacked together by / Copyright 2020 Ross Wightman
|
| 6 |
+
"""
|
| 7 |
+
import os
|
| 8 |
+
import json
|
| 9 |
+
import numpy as np
|
| 10 |
+
import pkgutil
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class RealLabelsImagenet:
|
| 14 |
+
|
| 15 |
+
def __init__(self, filenames, real_json=None, topk=(1, 5)):
|
| 16 |
+
if real_json is not None:
|
| 17 |
+
with open(real_json) as real_labels:
|
| 18 |
+
real_labels = json.load(real_labels)
|
| 19 |
+
else:
|
| 20 |
+
real_labels = json.loads(
|
| 21 |
+
pkgutil.get_data(__name__, os.path.join('_info', 'imagenet_real_labels.json')).decode('utf-8'))
|
| 22 |
+
real_labels = {f'ILSVRC2012_val_{i + 1:08d}.JPEG': labels for i, labels in enumerate(real_labels)}
|
| 23 |
+
self.real_labels = real_labels
|
| 24 |
+
self.filenames = filenames
|
| 25 |
+
assert len(self.filenames) == len(self.real_labels)
|
| 26 |
+
self.topk = topk
|
| 27 |
+
self.is_correct = {k: [] for k in topk}
|
| 28 |
+
self.sample_idx = 0
|
| 29 |
+
|
| 30 |
+
def add_result(self, output):
|
| 31 |
+
maxk = max(self.topk)
|
| 32 |
+
_, pred_batch = output.topk(maxk, 1, True, True)
|
| 33 |
+
pred_batch = pred_batch.cpu().numpy()
|
| 34 |
+
for pred in pred_batch:
|
| 35 |
+
filename = self.filenames[self.sample_idx]
|
| 36 |
+
filename = os.path.basename(filename)
|
| 37 |
+
if self.real_labels[filename]:
|
| 38 |
+
for k in self.topk:
|
| 39 |
+
self.is_correct[k].append(
|
| 40 |
+
any([p in self.real_labels[filename] for p in pred[:k]]))
|
| 41 |
+
self.sample_idx += 1
|
| 42 |
+
|
| 43 |
+
def get_accuracy(self, k=None):
|
| 44 |
+
if k is None:
|
| 45 |
+
return {k: float(np.mean(self.is_correct[k])) * 100 for k in self.topk}
|
| 46 |
+
else:
|
| 47 |
+
return float(np.mean(self.is_correct[k])) * 100
|
timm/data/tf_preprocessing.py
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" Tensorflow Preprocessing Adapter
|
| 2 |
+
|
| 3 |
+
Allows use of Tensorflow preprocessing pipeline in PyTorch Transform
|
| 4 |
+
|
| 5 |
+
Copyright of original Tensorflow code below.
|
| 6 |
+
|
| 7 |
+
Hacked together by / Copyright 2020 Ross Wightman
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
# Copyright 2018 The TensorFlow Authors. All Rights Reserved.
|
| 11 |
+
#
|
| 12 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 13 |
+
# you may not use this file except in compliance with the License.
|
| 14 |
+
# You may obtain a copy of the License at
|
| 15 |
+
#
|
| 16 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 17 |
+
#
|
| 18 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 19 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 20 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 21 |
+
# See the License for the specific language governing permissions and
|
| 22 |
+
# limitations under the License.
|
| 23 |
+
# ==============================================================================
|
| 24 |
+
"""ImageNet preprocessing for MnasNet."""
|
| 25 |
+
import tensorflow.compat.v1 as tf
|
| 26 |
+
import numpy as np
|
| 27 |
+
|
| 28 |
+
IMAGE_SIZE = 224
|
| 29 |
+
CROP_PADDING = 32
|
| 30 |
+
|
| 31 |
+
tf.compat.v1.disable_eager_execution()
|
| 32 |
+
|
| 33 |
+
def distorted_bounding_box_crop(image_bytes,
|
| 34 |
+
bbox,
|
| 35 |
+
min_object_covered=0.1,
|
| 36 |
+
aspect_ratio_range=(0.75, 1.33),
|
| 37 |
+
area_range=(0.05, 1.0),
|
| 38 |
+
max_attempts=100,
|
| 39 |
+
scope=None):
|
| 40 |
+
"""Generates cropped_image using one of the bboxes randomly distorted.
|
| 41 |
+
|
| 42 |
+
See `tf.image.sample_distorted_bounding_box` for more documentation.
|
| 43 |
+
|
| 44 |
+
Args:
|
| 45 |
+
image_bytes: `Tensor` of binary image data.
|
| 46 |
+
bbox: `Tensor` of bounding boxes arranged `[1, num_boxes, coords]`
|
| 47 |
+
where each coordinate is [0, 1) and the coordinates are arranged
|
| 48 |
+
as `[ymin, xmin, ymax, xmax]`. If num_boxes is 0 then use the whole
|
| 49 |
+
image.
|
| 50 |
+
min_object_covered: An optional `float`. Defaults to `0.1`. The cropped
|
| 51 |
+
area of the image must contain at least this fraction of any bounding
|
| 52 |
+
box supplied.
|
| 53 |
+
aspect_ratio_range: An optional list of `float`s. The cropped area of the
|
| 54 |
+
image must have an aspect ratio = width / height within this range.
|
| 55 |
+
area_range: An optional list of `float`s. The cropped area of the image
|
| 56 |
+
must contain a fraction of the supplied image within in this range.
|
| 57 |
+
max_attempts: An optional `int`. Number of attempts at generating a cropped
|
| 58 |
+
region of the image of the specified constraints. After `max_attempts`
|
| 59 |
+
failures, return the entire image.
|
| 60 |
+
scope: Optional `str` for name scope.
|
| 61 |
+
Returns:
|
| 62 |
+
cropped image `Tensor`
|
| 63 |
+
"""
|
| 64 |
+
with tf.name_scope(scope, 'distorted_bounding_box_crop', [image_bytes, bbox]):
|
| 65 |
+
shape = tf.image.extract_jpeg_shape(image_bytes)
|
| 66 |
+
sample_distorted_bounding_box = tf.image.sample_distorted_bounding_box(
|
| 67 |
+
shape,
|
| 68 |
+
bounding_boxes=bbox,
|
| 69 |
+
min_object_covered=min_object_covered,
|
| 70 |
+
aspect_ratio_range=aspect_ratio_range,
|
| 71 |
+
area_range=area_range,
|
| 72 |
+
max_attempts=max_attempts,
|
| 73 |
+
use_image_if_no_bounding_boxes=True)
|
| 74 |
+
bbox_begin, bbox_size, _ = sample_distorted_bounding_box
|
| 75 |
+
|
| 76 |
+
# Crop the image to the specified bounding box.
|
| 77 |
+
offset_y, offset_x, _ = tf.unstack(bbox_begin)
|
| 78 |
+
target_height, target_width, _ = tf.unstack(bbox_size)
|
| 79 |
+
crop_window = tf.stack([offset_y, offset_x, target_height, target_width])
|
| 80 |
+
image = tf.image.decode_and_crop_jpeg(image_bytes, crop_window, channels=3)
|
| 81 |
+
|
| 82 |
+
return image
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def _at_least_x_are_equal(a, b, x):
|
| 86 |
+
"""At least `x` of `a` and `b` `Tensors` are equal."""
|
| 87 |
+
match = tf.equal(a, b)
|
| 88 |
+
match = tf.cast(match, tf.int32)
|
| 89 |
+
return tf.greater_equal(tf.reduce_sum(match), x)
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def _decode_and_random_crop(image_bytes, image_size, resize_method):
|
| 93 |
+
"""Make a random crop of image_size."""
|
| 94 |
+
bbox = tf.constant([0.0, 0.0, 1.0, 1.0], dtype=tf.float32, shape=[1, 1, 4])
|
| 95 |
+
image = distorted_bounding_box_crop(
|
| 96 |
+
image_bytes,
|
| 97 |
+
bbox,
|
| 98 |
+
min_object_covered=0.1,
|
| 99 |
+
aspect_ratio_range=(3. / 4, 4. / 3.),
|
| 100 |
+
area_range=(0.08, 1.0),
|
| 101 |
+
max_attempts=10,
|
| 102 |
+
scope=None)
|
| 103 |
+
original_shape = tf.image.extract_jpeg_shape(image_bytes)
|
| 104 |
+
bad = _at_least_x_are_equal(original_shape, tf.shape(image), 3)
|
| 105 |
+
|
| 106 |
+
image = tf.cond(
|
| 107 |
+
bad,
|
| 108 |
+
lambda: _decode_and_center_crop(image_bytes, image_size),
|
| 109 |
+
lambda: tf.image.resize([image], [image_size, image_size], resize_method)[0])
|
| 110 |
+
|
| 111 |
+
return image
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def _decode_and_center_crop(image_bytes, image_size, resize_method):
|
| 115 |
+
"""Crops to center of image with padding then scales image_size."""
|
| 116 |
+
shape = tf.image.extract_jpeg_shape(image_bytes)
|
| 117 |
+
image_height = shape[0]
|
| 118 |
+
image_width = shape[1]
|
| 119 |
+
|
| 120 |
+
padded_center_crop_size = tf.cast(
|
| 121 |
+
((image_size / (image_size + CROP_PADDING)) *
|
| 122 |
+
tf.cast(tf.minimum(image_height, image_width), tf.float32)),
|
| 123 |
+
tf.int32)
|
| 124 |
+
|
| 125 |
+
offset_height = ((image_height - padded_center_crop_size) + 1) // 2
|
| 126 |
+
offset_width = ((image_width - padded_center_crop_size) + 1) // 2
|
| 127 |
+
crop_window = tf.stack([offset_height, offset_width,
|
| 128 |
+
padded_center_crop_size, padded_center_crop_size])
|
| 129 |
+
image = tf.image.decode_and_crop_jpeg(image_bytes, crop_window, channels=3)
|
| 130 |
+
image = tf.image.resize([image], [image_size, image_size], resize_method)[0]
|
| 131 |
+
|
| 132 |
+
return image
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def _flip(image):
|
| 136 |
+
"""Random horizontal image flip."""
|
| 137 |
+
image = tf.image.random_flip_left_right(image)
|
| 138 |
+
return image
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def preprocess_for_train(image_bytes, use_bfloat16, image_size=IMAGE_SIZE, interpolation='bicubic'):
|
| 142 |
+
"""Preprocesses the given image for evaluation.
|
| 143 |
+
|
| 144 |
+
Args:
|
| 145 |
+
image_bytes: `Tensor` representing an image binary of arbitrary size.
|
| 146 |
+
use_bfloat16: `bool` for whether to use bfloat16.
|
| 147 |
+
image_size: image size.
|
| 148 |
+
interpolation: image interpolation method
|
| 149 |
+
|
| 150 |
+
Returns:
|
| 151 |
+
A preprocessed image `Tensor`.
|
| 152 |
+
"""
|
| 153 |
+
resize_method = tf.image.ResizeMethod.BICUBIC if interpolation == 'bicubic' else tf.image.ResizeMethod.BILINEAR
|
| 154 |
+
image = _decode_and_random_crop(image_bytes, image_size, resize_method)
|
| 155 |
+
image = _flip(image)
|
| 156 |
+
image = tf.reshape(image, [image_size, image_size, 3])
|
| 157 |
+
image = tf.image.convert_image_dtype(
|
| 158 |
+
image, dtype=tf.bfloat16 if use_bfloat16 else tf.float32)
|
| 159 |
+
return image
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def preprocess_for_eval(image_bytes, use_bfloat16, image_size=IMAGE_SIZE, interpolation='bicubic'):
|
| 163 |
+
"""Preprocesses the given image for evaluation.
|
| 164 |
+
|
| 165 |
+
Args:
|
| 166 |
+
image_bytes: `Tensor` representing an image binary of arbitrary size.
|
| 167 |
+
use_bfloat16: `bool` for whether to use bfloat16.
|
| 168 |
+
image_size: image size.
|
| 169 |
+
interpolation: image interpolation method
|
| 170 |
+
|
| 171 |
+
Returns:
|
| 172 |
+
A preprocessed image `Tensor`.
|
| 173 |
+
"""
|
| 174 |
+
resize_method = tf.image.ResizeMethod.BICUBIC if interpolation == 'bicubic' else tf.image.ResizeMethod.BILINEAR
|
| 175 |
+
image = _decode_and_center_crop(image_bytes, image_size, resize_method)
|
| 176 |
+
image = tf.reshape(image, [image_size, image_size, 3])
|
| 177 |
+
image = tf.image.convert_image_dtype(
|
| 178 |
+
image, dtype=tf.bfloat16 if use_bfloat16 else tf.float32)
|
| 179 |
+
return image
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
def preprocess_image(image_bytes,
|
| 183 |
+
is_training=False,
|
| 184 |
+
use_bfloat16=False,
|
| 185 |
+
image_size=IMAGE_SIZE,
|
| 186 |
+
interpolation='bicubic'):
|
| 187 |
+
"""Preprocesses the given image.
|
| 188 |
+
|
| 189 |
+
Args:
|
| 190 |
+
image_bytes: `Tensor` representing an image binary of arbitrary size.
|
| 191 |
+
is_training: `bool` for whether the preprocessing is for training.
|
| 192 |
+
use_bfloat16: `bool` for whether to use bfloat16.
|
| 193 |
+
image_size: image size.
|
| 194 |
+
interpolation: image interpolation method
|
| 195 |
+
|
| 196 |
+
Returns:
|
| 197 |
+
A preprocessed image `Tensor` with value range of [0, 255].
|
| 198 |
+
"""
|
| 199 |
+
if is_training:
|
| 200 |
+
return preprocess_for_train(image_bytes, use_bfloat16, image_size, interpolation)
|
| 201 |
+
else:
|
| 202 |
+
return preprocess_for_eval(image_bytes, use_bfloat16, image_size, interpolation)
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
class TfPreprocessTransform:
|
| 206 |
+
|
| 207 |
+
def __init__(self, is_training=False, size=224, interpolation='bicubic'):
|
| 208 |
+
self.is_training = is_training
|
| 209 |
+
self.size = size[0] if isinstance(size, tuple) else size
|
| 210 |
+
self.interpolation = interpolation
|
| 211 |
+
self._image_bytes = None
|
| 212 |
+
self.process_image = self._build_tf_graph()
|
| 213 |
+
self.sess = None
|
| 214 |
+
|
| 215 |
+
def _build_tf_graph(self):
|
| 216 |
+
with tf.device('/cpu:0'):
|
| 217 |
+
self._image_bytes = tf.placeholder(
|
| 218 |
+
shape=[],
|
| 219 |
+
dtype=tf.string,
|
| 220 |
+
)
|
| 221 |
+
img = preprocess_image(
|
| 222 |
+
self._image_bytes, self.is_training, False, self.size, self.interpolation)
|
| 223 |
+
return img
|
| 224 |
+
|
| 225 |
+
def __call__(self, image_bytes):
|
| 226 |
+
if self.sess is None:
|
| 227 |
+
self.sess = tf.Session()
|
| 228 |
+
img = self.sess.run(self.process_image, feed_dict={self._image_bytes: image_bytes})
|
| 229 |
+
img = img.round().clip(0, 255).astype(np.uint8)
|
| 230 |
+
if img.ndim < 3:
|
| 231 |
+
img = np.expand_dims(img, axis=-1)
|
| 232 |
+
img = np.rollaxis(img, 2) # HWC to CHW
|
| 233 |
+
return img
|
timm/data/transforms.py
ADDED
|
@@ -0,0 +1,534 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
import numbers
|
| 3 |
+
import random
|
| 4 |
+
import warnings
|
| 5 |
+
from typing import List, Sequence, Tuple, Union
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
import torchvision.transforms.functional as F
|
| 9 |
+
try:
|
| 10 |
+
from torchvision.transforms.functional import InterpolationMode
|
| 11 |
+
has_interpolation_mode = True
|
| 12 |
+
except ImportError:
|
| 13 |
+
has_interpolation_mode = False
|
| 14 |
+
from PIL import Image
|
| 15 |
+
import numpy as np
|
| 16 |
+
|
| 17 |
+
__all__ = [
|
| 18 |
+
"ToNumpy", "ToTensor", "str_to_interp_mode", "str_to_pil_interp", "interp_mode_to_str",
|
| 19 |
+
"RandomResizedCropAndInterpolation", "CenterCropOrPad", "center_crop_or_pad", "crop_or_pad",
|
| 20 |
+
"RandomCropOrPad", "RandomPad", "ResizeKeepRatio", "TrimBorder"
|
| 21 |
+
]
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class ToNumpy:
|
| 25 |
+
|
| 26 |
+
def __call__(self, pil_img):
|
| 27 |
+
np_img = np.array(pil_img, dtype=np.uint8)
|
| 28 |
+
if np_img.ndim < 3:
|
| 29 |
+
np_img = np.expand_dims(np_img, axis=-1)
|
| 30 |
+
np_img = np.rollaxis(np_img, 2) # HWC to CHW
|
| 31 |
+
return np_img
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class ToTensor:
|
| 35 |
+
""" ToTensor with no rescaling of values"""
|
| 36 |
+
def __init__(self, dtype=torch.float32):
|
| 37 |
+
self.dtype = dtype
|
| 38 |
+
|
| 39 |
+
def __call__(self, pil_img):
|
| 40 |
+
return F.pil_to_tensor(pil_img).to(dtype=self.dtype)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
# Pillow is deprecating the top-level resampling attributes (e.g., Image.BILINEAR) in
|
| 44 |
+
# favor of the Image.Resampling enum. The top-level resampling attributes will be
|
| 45 |
+
# removed in Pillow 10.
|
| 46 |
+
if hasattr(Image, "Resampling"):
|
| 47 |
+
_pil_interpolation_to_str = {
|
| 48 |
+
Image.Resampling.NEAREST: 'nearest',
|
| 49 |
+
Image.Resampling.BILINEAR: 'bilinear',
|
| 50 |
+
Image.Resampling.BICUBIC: 'bicubic',
|
| 51 |
+
Image.Resampling.BOX: 'box',
|
| 52 |
+
Image.Resampling.HAMMING: 'hamming',
|
| 53 |
+
Image.Resampling.LANCZOS: 'lanczos',
|
| 54 |
+
}
|
| 55 |
+
else:
|
| 56 |
+
_pil_interpolation_to_str = {
|
| 57 |
+
Image.NEAREST: 'nearest',
|
| 58 |
+
Image.BILINEAR: 'bilinear',
|
| 59 |
+
Image.BICUBIC: 'bicubic',
|
| 60 |
+
Image.BOX: 'box',
|
| 61 |
+
Image.HAMMING: 'hamming',
|
| 62 |
+
Image.LANCZOS: 'lanczos',
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
_str_to_pil_interpolation = {b: a for a, b in _pil_interpolation_to_str.items()}
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
if has_interpolation_mode:
|
| 69 |
+
_torch_interpolation_to_str = {
|
| 70 |
+
InterpolationMode.NEAREST: 'nearest',
|
| 71 |
+
InterpolationMode.BILINEAR: 'bilinear',
|
| 72 |
+
InterpolationMode.BICUBIC: 'bicubic',
|
| 73 |
+
InterpolationMode.BOX: 'box',
|
| 74 |
+
InterpolationMode.HAMMING: 'hamming',
|
| 75 |
+
InterpolationMode.LANCZOS: 'lanczos',
|
| 76 |
+
}
|
| 77 |
+
_str_to_torch_interpolation = {b: a for a, b in _torch_interpolation_to_str.items()}
|
| 78 |
+
else:
|
| 79 |
+
_pil_interpolation_to_torch = {}
|
| 80 |
+
_torch_interpolation_to_str = {}
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def str_to_pil_interp(mode_str):
|
| 84 |
+
return _str_to_pil_interpolation[mode_str]
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def str_to_interp_mode(mode_str):
|
| 88 |
+
if has_interpolation_mode:
|
| 89 |
+
return _str_to_torch_interpolation[mode_str]
|
| 90 |
+
else:
|
| 91 |
+
return _str_to_pil_interpolation[mode_str]
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def interp_mode_to_str(mode):
|
| 95 |
+
if has_interpolation_mode:
|
| 96 |
+
return _torch_interpolation_to_str[mode]
|
| 97 |
+
else:
|
| 98 |
+
return _pil_interpolation_to_str[mode]
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
_RANDOM_INTERPOLATION = (str_to_interp_mode('bilinear'), str_to_interp_mode('bicubic'))
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def _setup_size(size, error_msg="Please provide only two dimensions (h, w) for size."):
|
| 105 |
+
if isinstance(size, numbers.Number):
|
| 106 |
+
return int(size), int(size)
|
| 107 |
+
|
| 108 |
+
if isinstance(size, Sequence) and len(size) == 1:
|
| 109 |
+
return size[0], size[0]
|
| 110 |
+
|
| 111 |
+
if len(size) != 2:
|
| 112 |
+
raise ValueError(error_msg)
|
| 113 |
+
|
| 114 |
+
return size
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
class RandomResizedCropAndInterpolation:
|
| 118 |
+
"""Crop the given PIL Image to random size and aspect ratio with random interpolation.
|
| 119 |
+
|
| 120 |
+
A crop of random size (default: of 0.08 to 1.0) of the original size and a random
|
| 121 |
+
aspect ratio (default: of 3/4 to 4/3) of the original aspect ratio is made. This crop
|
| 122 |
+
is finally resized to given size.
|
| 123 |
+
This is popularly used to train the Inception networks.
|
| 124 |
+
|
| 125 |
+
Args:
|
| 126 |
+
size: expected output size of each edge
|
| 127 |
+
scale: range of size of the origin size cropped
|
| 128 |
+
ratio: range of aspect ratio of the origin aspect ratio cropped
|
| 129 |
+
interpolation: Default: PIL.Image.BILINEAR
|
| 130 |
+
"""
|
| 131 |
+
|
| 132 |
+
def __init__(
|
| 133 |
+
self,
|
| 134 |
+
size,
|
| 135 |
+
scale=(0.08, 1.0),
|
| 136 |
+
ratio=(3. / 4., 4. / 3.),
|
| 137 |
+
interpolation='bilinear',
|
| 138 |
+
):
|
| 139 |
+
if isinstance(size, (list, tuple)):
|
| 140 |
+
self.size = tuple(size)
|
| 141 |
+
else:
|
| 142 |
+
self.size = (size, size)
|
| 143 |
+
if (scale[0] > scale[1]) or (ratio[0] > ratio[1]):
|
| 144 |
+
warnings.warn("range should be of kind (min, max)")
|
| 145 |
+
|
| 146 |
+
if interpolation == 'random':
|
| 147 |
+
self.interpolation = _RANDOM_INTERPOLATION
|
| 148 |
+
else:
|
| 149 |
+
self.interpolation = str_to_interp_mode(interpolation)
|
| 150 |
+
self.scale = scale
|
| 151 |
+
self.ratio = ratio
|
| 152 |
+
|
| 153 |
+
@staticmethod
|
| 154 |
+
def get_params(img, scale, ratio):
|
| 155 |
+
"""Get parameters for ``crop`` for a random sized crop.
|
| 156 |
+
|
| 157 |
+
Args:
|
| 158 |
+
img (PIL Image): Image to be cropped.
|
| 159 |
+
scale (tuple): range of size of the origin size cropped
|
| 160 |
+
ratio (tuple): range of aspect ratio of the origin aspect ratio cropped
|
| 161 |
+
|
| 162 |
+
Returns:
|
| 163 |
+
tuple: params (i, j, h, w) to be passed to ``crop`` for a random
|
| 164 |
+
sized crop.
|
| 165 |
+
"""
|
| 166 |
+
img_w, img_h = F.get_image_size(img)
|
| 167 |
+
area = img_w * img_h
|
| 168 |
+
|
| 169 |
+
for attempt in range(10):
|
| 170 |
+
target_area = random.uniform(*scale) * area
|
| 171 |
+
log_ratio = (math.log(ratio[0]), math.log(ratio[1]))
|
| 172 |
+
aspect_ratio = math.exp(random.uniform(*log_ratio))
|
| 173 |
+
|
| 174 |
+
target_w = int(round(math.sqrt(target_area * aspect_ratio)))
|
| 175 |
+
target_h = int(round(math.sqrt(target_area / aspect_ratio)))
|
| 176 |
+
if target_w <= img_w and target_h <= img_h:
|
| 177 |
+
i = random.randint(0, img_h - target_h)
|
| 178 |
+
j = random.randint(0, img_w - target_w)
|
| 179 |
+
return i, j, target_h, target_w
|
| 180 |
+
|
| 181 |
+
# Fallback to central crop
|
| 182 |
+
in_ratio = img_w / img_h
|
| 183 |
+
if in_ratio < min(ratio):
|
| 184 |
+
target_w = img_w
|
| 185 |
+
target_h = int(round(target_w / min(ratio)))
|
| 186 |
+
elif in_ratio > max(ratio):
|
| 187 |
+
target_h = img_h
|
| 188 |
+
target_w = int(round(target_h * max(ratio)))
|
| 189 |
+
else: # whole image
|
| 190 |
+
target_w = img_w
|
| 191 |
+
target_h = img_h
|
| 192 |
+
i = (img_h - target_h) // 2
|
| 193 |
+
j = (img_w - target_w) // 2
|
| 194 |
+
return i, j, target_h, target_w
|
| 195 |
+
|
| 196 |
+
def __call__(self, img):
|
| 197 |
+
"""
|
| 198 |
+
Args:
|
| 199 |
+
img (PIL Image): Image to be cropped and resized.
|
| 200 |
+
|
| 201 |
+
Returns:
|
| 202 |
+
PIL Image: Randomly cropped and resized image.
|
| 203 |
+
"""
|
| 204 |
+
i, j, h, w = self.get_params(img, self.scale, self.ratio)
|
| 205 |
+
if isinstance(self.interpolation, (tuple, list)):
|
| 206 |
+
interpolation = random.choice(self.interpolation)
|
| 207 |
+
else:
|
| 208 |
+
interpolation = self.interpolation
|
| 209 |
+
return F.resized_crop(img, i, j, h, w, self.size, interpolation)
|
| 210 |
+
|
| 211 |
+
def __repr__(self):
|
| 212 |
+
if isinstance(self.interpolation, (tuple, list)):
|
| 213 |
+
interpolate_str = ' '.join([interp_mode_to_str(x) for x in self.interpolation])
|
| 214 |
+
else:
|
| 215 |
+
interpolate_str = interp_mode_to_str(self.interpolation)
|
| 216 |
+
format_string = self.__class__.__name__ + '(size={0}'.format(self.size)
|
| 217 |
+
format_string += ', scale={0}'.format(tuple(round(s, 4) for s in self.scale))
|
| 218 |
+
format_string += ', ratio={0}'.format(tuple(round(r, 4) for r in self.ratio))
|
| 219 |
+
format_string += ', interpolation={0})'.format(interpolate_str)
|
| 220 |
+
return format_string
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
def center_crop_or_pad(
|
| 224 |
+
img: torch.Tensor,
|
| 225 |
+
output_size: Union[int, List[int]],
|
| 226 |
+
fill: Union[int, Tuple[int, int, int]] = 0,
|
| 227 |
+
padding_mode: str = 'constant',
|
| 228 |
+
) -> torch.Tensor:
|
| 229 |
+
"""Center crops and/or pads the given image.
|
| 230 |
+
|
| 231 |
+
If the image is torch Tensor, it is expected
|
| 232 |
+
to have [..., H, W] shape, where ... means an arbitrary number of leading dimensions.
|
| 233 |
+
If image size is smaller than output size along any edge, image is padded with 0 and then center cropped.
|
| 234 |
+
|
| 235 |
+
Args:
|
| 236 |
+
img (PIL Image or Tensor): Image to be cropped.
|
| 237 |
+
output_size (sequence or int): (height, width) of the crop box. If int or sequence with single int,
|
| 238 |
+
it is used for both directions.
|
| 239 |
+
fill (int, Tuple[int]): Padding color
|
| 240 |
+
|
| 241 |
+
Returns:
|
| 242 |
+
PIL Image or Tensor: Cropped image.
|
| 243 |
+
"""
|
| 244 |
+
output_size = _setup_size(output_size)
|
| 245 |
+
crop_height, crop_width = output_size
|
| 246 |
+
_, image_height, image_width = F.get_dimensions(img)
|
| 247 |
+
|
| 248 |
+
if crop_width > image_width or crop_height > image_height:
|
| 249 |
+
padding_ltrb = [
|
| 250 |
+
(crop_width - image_width) // 2 if crop_width > image_width else 0,
|
| 251 |
+
(crop_height - image_height) // 2 if crop_height > image_height else 0,
|
| 252 |
+
(crop_width - image_width + 1) // 2 if crop_width > image_width else 0,
|
| 253 |
+
(crop_height - image_height + 1) // 2 if crop_height > image_height else 0,
|
| 254 |
+
]
|
| 255 |
+
img = F.pad(img, padding_ltrb, fill=fill, padding_mode=padding_mode)
|
| 256 |
+
_, image_height, image_width = F.get_dimensions(img)
|
| 257 |
+
if crop_width == image_width and crop_height == image_height:
|
| 258 |
+
return img
|
| 259 |
+
|
| 260 |
+
crop_top = int(round((image_height - crop_height) / 2.0))
|
| 261 |
+
crop_left = int(round((image_width - crop_width) / 2.0))
|
| 262 |
+
return F.crop(img, crop_top, crop_left, crop_height, crop_width)
|
| 263 |
+
|
| 264 |
+
|
| 265 |
+
class CenterCropOrPad(torch.nn.Module):
|
| 266 |
+
"""Crops the given image at the center.
|
| 267 |
+
If the image is torch Tensor, it is expected
|
| 268 |
+
to have [..., H, W] shape, where ... means an arbitrary number of leading dimensions.
|
| 269 |
+
If image size is smaller than output size along any edge, image is padded with 0 and then center cropped.
|
| 270 |
+
|
| 271 |
+
Args:
|
| 272 |
+
size (sequence or int): Desired output size of the crop. If size is an
|
| 273 |
+
int instead of sequence like (h, w), a square crop (size, size) is
|
| 274 |
+
made. If provided a sequence of length 1, it will be interpreted as (size[0], size[0]).
|
| 275 |
+
"""
|
| 276 |
+
|
| 277 |
+
def __init__(
|
| 278 |
+
self,
|
| 279 |
+
size: Union[int, List[int]],
|
| 280 |
+
fill: Union[int, Tuple[int, int, int]] = 0,
|
| 281 |
+
padding_mode: str = 'constant',
|
| 282 |
+
):
|
| 283 |
+
super().__init__()
|
| 284 |
+
self.size = _setup_size(size)
|
| 285 |
+
self.fill = fill
|
| 286 |
+
self.padding_mode = padding_mode
|
| 287 |
+
|
| 288 |
+
def forward(self, img):
|
| 289 |
+
"""
|
| 290 |
+
Args:
|
| 291 |
+
img (PIL Image or Tensor): Image to be cropped.
|
| 292 |
+
|
| 293 |
+
Returns:
|
| 294 |
+
PIL Image or Tensor: Cropped image.
|
| 295 |
+
"""
|
| 296 |
+
return center_crop_or_pad(img, self.size, fill=self.fill, padding_mode=self.padding_mode)
|
| 297 |
+
|
| 298 |
+
def __repr__(self) -> str:
|
| 299 |
+
return f"{self.__class__.__name__}(size={self.size})"
|
| 300 |
+
|
| 301 |
+
|
| 302 |
+
def crop_or_pad(
|
| 303 |
+
img: torch.Tensor,
|
| 304 |
+
top: int,
|
| 305 |
+
left: int,
|
| 306 |
+
height: int,
|
| 307 |
+
width: int,
|
| 308 |
+
fill: Union[int, Tuple[int, int, int]] = 0,
|
| 309 |
+
padding_mode: str = 'constant',
|
| 310 |
+
) -> torch.Tensor:
|
| 311 |
+
""" Crops and/or pads image to meet target size, with control over fill and padding_mode.
|
| 312 |
+
"""
|
| 313 |
+
_, image_height, image_width = F.get_dimensions(img)
|
| 314 |
+
right = left + width
|
| 315 |
+
bottom = top + height
|
| 316 |
+
if left < 0 or top < 0 or right > image_width or bottom > image_height:
|
| 317 |
+
padding_ltrb = [
|
| 318 |
+
max(-left + min(0, right), 0),
|
| 319 |
+
max(-top + min(0, bottom), 0),
|
| 320 |
+
max(right - max(image_width, left), 0),
|
| 321 |
+
max(bottom - max(image_height, top), 0),
|
| 322 |
+
]
|
| 323 |
+
img = F.pad(img, padding_ltrb, fill=fill, padding_mode=padding_mode)
|
| 324 |
+
|
| 325 |
+
top = max(top, 0)
|
| 326 |
+
left = max(left, 0)
|
| 327 |
+
return F.crop(img, top, left, height, width)
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
class RandomCropOrPad(torch.nn.Module):
|
| 331 |
+
""" Crop and/or pad image with random placement within the crop or pad margin.
|
| 332 |
+
"""
|
| 333 |
+
|
| 334 |
+
def __init__(
|
| 335 |
+
self,
|
| 336 |
+
size: Union[int, List[int]],
|
| 337 |
+
fill: Union[int, Tuple[int, int, int]] = 0,
|
| 338 |
+
padding_mode: str = 'constant',
|
| 339 |
+
):
|
| 340 |
+
super().__init__()
|
| 341 |
+
self.size = _setup_size(size)
|
| 342 |
+
self.fill = fill
|
| 343 |
+
self.padding_mode = padding_mode
|
| 344 |
+
|
| 345 |
+
@staticmethod
|
| 346 |
+
def get_params(img, size):
|
| 347 |
+
_, image_height, image_width = F.get_dimensions(img)
|
| 348 |
+
delta_height = image_height - size[0]
|
| 349 |
+
delta_width = image_width - size[1]
|
| 350 |
+
top = int(math.copysign(random.randint(0, abs(delta_height)), delta_height))
|
| 351 |
+
left = int(math.copysign(random.randint(0, abs(delta_width)), delta_width))
|
| 352 |
+
return top, left
|
| 353 |
+
|
| 354 |
+
def forward(self, img):
|
| 355 |
+
"""
|
| 356 |
+
Args:
|
| 357 |
+
img (PIL Image or Tensor): Image to be cropped.
|
| 358 |
+
|
| 359 |
+
Returns:
|
| 360 |
+
PIL Image or Tensor: Cropped image.
|
| 361 |
+
"""
|
| 362 |
+
top, left = self.get_params(img, self.size)
|
| 363 |
+
return crop_or_pad(
|
| 364 |
+
img,
|
| 365 |
+
top=top,
|
| 366 |
+
left=left,
|
| 367 |
+
height=self.size[0],
|
| 368 |
+
width=self.size[1],
|
| 369 |
+
fill=self.fill,
|
| 370 |
+
padding_mode=self.padding_mode,
|
| 371 |
+
)
|
| 372 |
+
|
| 373 |
+
def __repr__(self) -> str:
|
| 374 |
+
return f"{self.__class__.__name__}(size={self.size})"
|
| 375 |
+
|
| 376 |
+
|
| 377 |
+
class RandomPad:
|
| 378 |
+
def __init__(self, input_size, fill=0):
|
| 379 |
+
self.input_size = input_size
|
| 380 |
+
self.fill = fill
|
| 381 |
+
|
| 382 |
+
@staticmethod
|
| 383 |
+
def get_params(img, input_size):
|
| 384 |
+
width, height = F.get_image_size(img)
|
| 385 |
+
delta_width = max(input_size[1] - width, 0)
|
| 386 |
+
delta_height = max(input_size[0] - height, 0)
|
| 387 |
+
pad_left = random.randint(0, delta_width)
|
| 388 |
+
pad_top = random.randint(0, delta_height)
|
| 389 |
+
pad_right = delta_width - pad_left
|
| 390 |
+
pad_bottom = delta_height - pad_top
|
| 391 |
+
return pad_left, pad_top, pad_right, pad_bottom
|
| 392 |
+
|
| 393 |
+
def __call__(self, img):
|
| 394 |
+
padding = self.get_params(img, self.input_size)
|
| 395 |
+
img = F.pad(img, padding, self.fill)
|
| 396 |
+
return img
|
| 397 |
+
|
| 398 |
+
|
| 399 |
+
class ResizeKeepRatio:
|
| 400 |
+
""" Resize and Keep Aspect Ratio
|
| 401 |
+
"""
|
| 402 |
+
|
| 403 |
+
def __init__(
|
| 404 |
+
self,
|
| 405 |
+
size,
|
| 406 |
+
longest=0.,
|
| 407 |
+
interpolation='bilinear',
|
| 408 |
+
random_scale_prob=0.,
|
| 409 |
+
random_scale_range=(0.85, 1.05),
|
| 410 |
+
random_scale_area=False,
|
| 411 |
+
random_aspect_prob=0.,
|
| 412 |
+
random_aspect_range=(0.9, 1.11),
|
| 413 |
+
):
|
| 414 |
+
"""
|
| 415 |
+
|
| 416 |
+
Args:
|
| 417 |
+
size:
|
| 418 |
+
longest:
|
| 419 |
+
interpolation:
|
| 420 |
+
random_scale_prob:
|
| 421 |
+
random_scale_range:
|
| 422 |
+
random_scale_area:
|
| 423 |
+
random_aspect_prob:
|
| 424 |
+
random_aspect_range:
|
| 425 |
+
"""
|
| 426 |
+
if isinstance(size, (list, tuple)):
|
| 427 |
+
self.size = tuple(size)
|
| 428 |
+
else:
|
| 429 |
+
self.size = (size, size)
|
| 430 |
+
if interpolation == 'random':
|
| 431 |
+
self.interpolation = _RANDOM_INTERPOLATION
|
| 432 |
+
else:
|
| 433 |
+
self.interpolation = str_to_interp_mode(interpolation)
|
| 434 |
+
self.longest = float(longest)
|
| 435 |
+
self.random_scale_prob = random_scale_prob
|
| 436 |
+
self.random_scale_range = random_scale_range
|
| 437 |
+
self.random_scale_area = random_scale_area
|
| 438 |
+
self.random_aspect_prob = random_aspect_prob
|
| 439 |
+
self.random_aspect_range = random_aspect_range
|
| 440 |
+
|
| 441 |
+
@staticmethod
|
| 442 |
+
def get_params(
|
| 443 |
+
img,
|
| 444 |
+
target_size,
|
| 445 |
+
longest,
|
| 446 |
+
random_scale_prob=0.,
|
| 447 |
+
random_scale_range=(1.0, 1.33),
|
| 448 |
+
random_scale_area=False,
|
| 449 |
+
random_aspect_prob=0.,
|
| 450 |
+
random_aspect_range=(0.9, 1.11)
|
| 451 |
+
):
|
| 452 |
+
"""Get parameters
|
| 453 |
+
"""
|
| 454 |
+
img_h, img_w = img_size = F.get_dimensions(img)[1:]
|
| 455 |
+
target_h, target_w = target_size
|
| 456 |
+
ratio_h = img_h / target_h
|
| 457 |
+
ratio_w = img_w / target_w
|
| 458 |
+
ratio = max(ratio_h, ratio_w) * longest + min(ratio_h, ratio_w) * (1. - longest)
|
| 459 |
+
|
| 460 |
+
if random_scale_prob > 0 and random.random() < random_scale_prob:
|
| 461 |
+
ratio_factor = random.uniform(random_scale_range[0], random_scale_range[1])
|
| 462 |
+
if random_scale_area:
|
| 463 |
+
# make ratio factor equivalent to RRC area crop where < 1.0 = area zoom,
|
| 464 |
+
# otherwise like affine scale where < 1.0 = linear zoom out
|
| 465 |
+
ratio_factor = 1. / math.sqrt(ratio_factor)
|
| 466 |
+
ratio_factor = (ratio_factor, ratio_factor)
|
| 467 |
+
else:
|
| 468 |
+
ratio_factor = (1., 1.)
|
| 469 |
+
|
| 470 |
+
if random_aspect_prob > 0 and random.random() < random_aspect_prob:
|
| 471 |
+
log_aspect = (math.log(random_aspect_range[0]), math.log(random_aspect_range[1]))
|
| 472 |
+
aspect_factor = math.exp(random.uniform(*log_aspect))
|
| 473 |
+
aspect_factor = math.sqrt(aspect_factor)
|
| 474 |
+
# currently applying random aspect adjustment equally to both dims,
|
| 475 |
+
# could change to keep output sizes above their target where possible
|
| 476 |
+
ratio_factor = (ratio_factor[0] / aspect_factor, ratio_factor[1] * aspect_factor)
|
| 477 |
+
|
| 478 |
+
size = [round(x * f / ratio) for x, f in zip(img_size, ratio_factor)]
|
| 479 |
+
return size
|
| 480 |
+
|
| 481 |
+
def __call__(self, img):
|
| 482 |
+
"""
|
| 483 |
+
Args:
|
| 484 |
+
img (PIL Image): Image to be cropped and resized.
|
| 485 |
+
|
| 486 |
+
Returns:
|
| 487 |
+
PIL Image: Resized, padded to at least target size, possibly cropped to exactly target size
|
| 488 |
+
"""
|
| 489 |
+
size = self.get_params(
|
| 490 |
+
img, self.size, self.longest,
|
| 491 |
+
self.random_scale_prob, self.random_scale_range, self.random_scale_area,
|
| 492 |
+
self.random_aspect_prob, self.random_aspect_range
|
| 493 |
+
)
|
| 494 |
+
if isinstance(self.interpolation, (tuple, list)):
|
| 495 |
+
interpolation = random.choice(self.interpolation)
|
| 496 |
+
else:
|
| 497 |
+
interpolation = self.interpolation
|
| 498 |
+
img = F.resize(img, size, interpolation)
|
| 499 |
+
return img
|
| 500 |
+
|
| 501 |
+
def __repr__(self):
|
| 502 |
+
if isinstance(self.interpolation, (tuple, list)):
|
| 503 |
+
interpolate_str = ' '.join([interp_mode_to_str(x) for x in self.interpolation])
|
| 504 |
+
else:
|
| 505 |
+
interpolate_str = interp_mode_to_str(self.interpolation)
|
| 506 |
+
format_string = self.__class__.__name__ + '(size={0}'.format(self.size)
|
| 507 |
+
format_string += f', interpolation={interpolate_str}'
|
| 508 |
+
format_string += f', longest={self.longest:.3f}'
|
| 509 |
+
format_string += f', random_scale_prob={self.random_scale_prob:.3f}'
|
| 510 |
+
format_string += f', random_scale_range=(' \
|
| 511 |
+
f'{self.random_scale_range[0]:.3f}, {self.random_aspect_range[1]:.3f})'
|
| 512 |
+
format_string += f', random_aspect_prob={self.random_aspect_prob:.3f}'
|
| 513 |
+
format_string += f', random_aspect_range=(' \
|
| 514 |
+
f'{self.random_aspect_range[0]:.3f}, {self.random_aspect_range[1]:.3f}))'
|
| 515 |
+
return format_string
|
| 516 |
+
|
| 517 |
+
|
| 518 |
+
class TrimBorder(torch.nn.Module):
|
| 519 |
+
|
| 520 |
+
def __init__(
|
| 521 |
+
self,
|
| 522 |
+
border_size: int,
|
| 523 |
+
):
|
| 524 |
+
super().__init__()
|
| 525 |
+
self.border_size = border_size
|
| 526 |
+
|
| 527 |
+
def forward(self, img):
|
| 528 |
+
w, h = F.get_image_size(img)
|
| 529 |
+
top = left = self.border_size
|
| 530 |
+
top = min(top, h)
|
| 531 |
+
left = min(left, h)
|
| 532 |
+
height = max(0, h - 2 * self.border_size)
|
| 533 |
+
width = max(0, w - 2 * self.border_size)
|
| 534 |
+
return F.crop(img, top, left, height, width)
|
timm/data/transforms_factory.py
ADDED
|
@@ -0,0 +1,440 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" Transforms Factory
|
| 2 |
+
Factory methods for building image transforms for use with TIMM (PyTorch Image Models)
|
| 3 |
+
|
| 4 |
+
Hacked together by / Copyright 2019, Ross Wightman
|
| 5 |
+
"""
|
| 6 |
+
import math
|
| 7 |
+
from typing import Optional, Tuple, Union
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
from torchvision import transforms
|
| 11 |
+
|
| 12 |
+
from timm.data.constants import IMAGENET_DEFAULT_MEAN, IMAGENET_DEFAULT_STD, DEFAULT_CROP_PCT
|
| 13 |
+
from timm.data.auto_augment import rand_augment_transform, augment_and_mix_transform, auto_augment_transform
|
| 14 |
+
from timm.data.transforms import str_to_interp_mode, str_to_pil_interp, RandomResizedCropAndInterpolation,\
|
| 15 |
+
ResizeKeepRatio, CenterCropOrPad, RandomCropOrPad, TrimBorder, ToNumpy
|
| 16 |
+
from timm.data.random_erasing import RandomErasing
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def transforms_noaug_train(
|
| 20 |
+
img_size: Union[int, Tuple[int, int]] = 224,
|
| 21 |
+
interpolation: str = 'bilinear',
|
| 22 |
+
use_prefetcher: bool = False,
|
| 23 |
+
mean: Tuple[float, ...] = IMAGENET_DEFAULT_MEAN,
|
| 24 |
+
std: Tuple[float, ...] = IMAGENET_DEFAULT_STD,
|
| 25 |
+
):
|
| 26 |
+
""" No-augmentation image transforms for training.
|
| 27 |
+
|
| 28 |
+
Args:
|
| 29 |
+
img_size: Target image size.
|
| 30 |
+
interpolation: Image interpolation mode.
|
| 31 |
+
mean: Image normalization mean.
|
| 32 |
+
std: Image normalization standard deviation.
|
| 33 |
+
use_prefetcher: Prefetcher enabled. Do not convert image to tensor or normalize.
|
| 34 |
+
|
| 35 |
+
Returns:
|
| 36 |
+
|
| 37 |
+
"""
|
| 38 |
+
if interpolation == 'random':
|
| 39 |
+
# random interpolation not supported with no-aug
|
| 40 |
+
interpolation = 'bilinear'
|
| 41 |
+
tfl = [
|
| 42 |
+
transforms.Resize(img_size, interpolation=str_to_interp_mode(interpolation)),
|
| 43 |
+
transforms.CenterCrop(img_size)
|
| 44 |
+
]
|
| 45 |
+
if use_prefetcher:
|
| 46 |
+
# prefetcher and collate will handle tensor conversion and norm
|
| 47 |
+
tfl += [ToNumpy()]
|
| 48 |
+
else:
|
| 49 |
+
tfl += [
|
| 50 |
+
transforms.ToTensor(),
|
| 51 |
+
transforms.Normalize(
|
| 52 |
+
mean=torch.tensor(mean),
|
| 53 |
+
std=torch.tensor(std)
|
| 54 |
+
)
|
| 55 |
+
]
|
| 56 |
+
return transforms.Compose(tfl)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def transforms_imagenet_train(
|
| 60 |
+
img_size: Union[int, Tuple[int, int]] = 224,
|
| 61 |
+
scale: Optional[Tuple[float, float]] = None,
|
| 62 |
+
ratio: Optional[Tuple[float, float]] = None,
|
| 63 |
+
train_crop_mode: Optional[str] = None,
|
| 64 |
+
hflip: float = 0.5,
|
| 65 |
+
vflip: float = 0.,
|
| 66 |
+
color_jitter: Union[float, Tuple[float, ...]] = 0.4,
|
| 67 |
+
color_jitter_prob: Optional[float] = None,
|
| 68 |
+
force_color_jitter: bool = False,
|
| 69 |
+
grayscale_prob: float = 0.,
|
| 70 |
+
gaussian_blur_prob: float = 0.,
|
| 71 |
+
auto_augment: Optional[str] = None,
|
| 72 |
+
interpolation: str = 'random',
|
| 73 |
+
mean: Tuple[float, ...] = IMAGENET_DEFAULT_MEAN,
|
| 74 |
+
std: Tuple[float, ...] = IMAGENET_DEFAULT_STD,
|
| 75 |
+
re_prob: float = 0.,
|
| 76 |
+
re_mode: str = 'const',
|
| 77 |
+
re_count: int = 1,
|
| 78 |
+
re_num_splits: int = 0,
|
| 79 |
+
use_prefetcher: bool = False,
|
| 80 |
+
separate: bool = False,
|
| 81 |
+
):
|
| 82 |
+
""" ImageNet-oriented image transforms for training.
|
| 83 |
+
|
| 84 |
+
Args:
|
| 85 |
+
img_size: Target image size.
|
| 86 |
+
train_crop_mode: Training random crop mode ('rrc', 'rkrc', 'rkrr').
|
| 87 |
+
scale: Random resize scale range (crop area, < 1.0 => zoom in).
|
| 88 |
+
ratio: Random aspect ratio range (crop ratio for RRC, ratio adjustment factor for RKR).
|
| 89 |
+
hflip: Horizontal flip probability.
|
| 90 |
+
vflip: Vertical flip probability.
|
| 91 |
+
color_jitter: Random color jitter component factors (brightness, contrast, saturation, hue).
|
| 92 |
+
Scalar is applied as (scalar,) * 3 (no hue).
|
| 93 |
+
color_jitter_prob: Apply color jitter with this probability if not None (for SimlCLR-like aug).
|
| 94 |
+
force_color_jitter: Force color jitter where it is normally disabled (ie with RandAugment on).
|
| 95 |
+
grayscale_prob: Probability of converting image to grayscale (for SimCLR-like aug).
|
| 96 |
+
gaussian_blur_prob: Probability of applying gaussian blur (for SimCLR-like aug).
|
| 97 |
+
auto_augment: Auto augment configuration string (see auto_augment.py).
|
| 98 |
+
interpolation: Image interpolation mode.
|
| 99 |
+
mean: Image normalization mean.
|
| 100 |
+
std: Image normalization standard deviation.
|
| 101 |
+
re_prob: Random erasing probability.
|
| 102 |
+
re_mode: Random erasing fill mode.
|
| 103 |
+
re_count: Number of random erasing regions.
|
| 104 |
+
re_num_splits: Control split of random erasing across batch size.
|
| 105 |
+
use_prefetcher: Prefetcher enabled. Do not convert image to tensor or normalize.
|
| 106 |
+
separate: Output transforms in 3-stage tuple.
|
| 107 |
+
|
| 108 |
+
Returns:
|
| 109 |
+
If separate==True, the transforms are returned as a tuple of 3 separate transforms
|
| 110 |
+
for use in a mixing dataset that passes
|
| 111 |
+
* all data through the first (primary) transform, called the 'clean' data
|
| 112 |
+
* a portion of the data through the secondary transform
|
| 113 |
+
* normalizes and converts the branches above with the third, final transform
|
| 114 |
+
"""
|
| 115 |
+
train_crop_mode = train_crop_mode or 'rrc'
|
| 116 |
+
assert train_crop_mode in {'rrc', 'rkrc', 'rkrr'}
|
| 117 |
+
if train_crop_mode in ('rkrc', 'rkrr'):
|
| 118 |
+
# FIXME integration of RKR is a WIP
|
| 119 |
+
scale = tuple(scale or (0.8, 1.00))
|
| 120 |
+
ratio = tuple(ratio or (0.9, 1/.9))
|
| 121 |
+
primary_tfl = [
|
| 122 |
+
ResizeKeepRatio(
|
| 123 |
+
img_size,
|
| 124 |
+
interpolation=interpolation,
|
| 125 |
+
random_scale_prob=0.5,
|
| 126 |
+
random_scale_range=scale,
|
| 127 |
+
random_scale_area=True, # scale compatible with RRC
|
| 128 |
+
random_aspect_prob=0.5,
|
| 129 |
+
random_aspect_range=ratio,
|
| 130 |
+
),
|
| 131 |
+
CenterCropOrPad(img_size, padding_mode='reflect')
|
| 132 |
+
if train_crop_mode == 'rkrc' else
|
| 133 |
+
RandomCropOrPad(img_size, padding_mode='reflect')
|
| 134 |
+
]
|
| 135 |
+
else:
|
| 136 |
+
scale = tuple(scale or (0.08, 1.0)) # default imagenet scale range
|
| 137 |
+
ratio = tuple(ratio or (3. / 4., 4. / 3.)) # default imagenet ratio range
|
| 138 |
+
primary_tfl = [
|
| 139 |
+
RandomResizedCropAndInterpolation(
|
| 140 |
+
img_size,
|
| 141 |
+
scale=scale,
|
| 142 |
+
ratio=ratio,
|
| 143 |
+
interpolation=interpolation,
|
| 144 |
+
)
|
| 145 |
+
]
|
| 146 |
+
if hflip > 0.:
|
| 147 |
+
primary_tfl += [transforms.RandomHorizontalFlip(p=hflip)]
|
| 148 |
+
if vflip > 0.:
|
| 149 |
+
primary_tfl += [transforms.RandomVerticalFlip(p=vflip)]
|
| 150 |
+
|
| 151 |
+
secondary_tfl = []
|
| 152 |
+
disable_color_jitter = False
|
| 153 |
+
if auto_augment:
|
| 154 |
+
assert isinstance(auto_augment, str)
|
| 155 |
+
# color jitter is typically disabled if AA/RA on,
|
| 156 |
+
# this allows override without breaking old hparm cfgs
|
| 157 |
+
disable_color_jitter = not (force_color_jitter or '3a' in auto_augment)
|
| 158 |
+
if isinstance(img_size, (tuple, list)):
|
| 159 |
+
img_size_min = min(img_size)
|
| 160 |
+
else:
|
| 161 |
+
img_size_min = img_size
|
| 162 |
+
aa_params = dict(
|
| 163 |
+
translate_const=int(img_size_min * 0.45),
|
| 164 |
+
img_mean=tuple([min(255, round(255 * x)) for x in mean]),
|
| 165 |
+
)
|
| 166 |
+
if interpolation and interpolation != 'random':
|
| 167 |
+
aa_params['interpolation'] = str_to_pil_interp(interpolation)
|
| 168 |
+
if auto_augment.startswith('rand'):
|
| 169 |
+
secondary_tfl += [rand_augment_transform(auto_augment, aa_params)]
|
| 170 |
+
elif auto_augment.startswith('augmix'):
|
| 171 |
+
aa_params['translate_pct'] = 0.3
|
| 172 |
+
secondary_tfl += [augment_and_mix_transform(auto_augment, aa_params)]
|
| 173 |
+
else:
|
| 174 |
+
secondary_tfl += [auto_augment_transform(auto_augment, aa_params)]
|
| 175 |
+
|
| 176 |
+
if color_jitter is not None and not disable_color_jitter:
|
| 177 |
+
# color jitter is enabled when not using AA or when forced
|
| 178 |
+
if isinstance(color_jitter, (list, tuple)):
|
| 179 |
+
# color jitter should be a 3-tuple/list if spec brightness/contrast/saturation
|
| 180 |
+
# or 4 if also augmenting hue
|
| 181 |
+
assert len(color_jitter) in (3, 4)
|
| 182 |
+
else:
|
| 183 |
+
# if it's a scalar, duplicate for brightness, contrast, and saturation, no hue
|
| 184 |
+
color_jitter = (float(color_jitter),) * 3
|
| 185 |
+
if color_jitter_prob is not None:
|
| 186 |
+
secondary_tfl += [
|
| 187 |
+
transforms.RandomApply([
|
| 188 |
+
transforms.ColorJitter(*color_jitter),
|
| 189 |
+
],
|
| 190 |
+
p=color_jitter_prob
|
| 191 |
+
)
|
| 192 |
+
]
|
| 193 |
+
else:
|
| 194 |
+
secondary_tfl += [transforms.ColorJitter(*color_jitter)]
|
| 195 |
+
|
| 196 |
+
if grayscale_prob:
|
| 197 |
+
secondary_tfl += [transforms.RandomGrayscale(p=grayscale_prob)]
|
| 198 |
+
|
| 199 |
+
if gaussian_blur_prob:
|
| 200 |
+
secondary_tfl += [
|
| 201 |
+
transforms.RandomApply([
|
| 202 |
+
transforms.GaussianBlur(kernel_size=23), # hardcoded for now
|
| 203 |
+
],
|
| 204 |
+
p=gaussian_blur_prob,
|
| 205 |
+
)
|
| 206 |
+
]
|
| 207 |
+
|
| 208 |
+
final_tfl = []
|
| 209 |
+
if use_prefetcher:
|
| 210 |
+
# prefetcher and collate will handle tensor conversion and norm
|
| 211 |
+
final_tfl += [ToNumpy()]
|
| 212 |
+
else:
|
| 213 |
+
final_tfl += [
|
| 214 |
+
transforms.ToTensor(),
|
| 215 |
+
transforms.Normalize(
|
| 216 |
+
mean=torch.tensor(mean),
|
| 217 |
+
std=torch.tensor(std)
|
| 218 |
+
),
|
| 219 |
+
]
|
| 220 |
+
if re_prob > 0.:
|
| 221 |
+
final_tfl += [
|
| 222 |
+
RandomErasing(
|
| 223 |
+
re_prob,
|
| 224 |
+
mode=re_mode,
|
| 225 |
+
max_count=re_count,
|
| 226 |
+
num_splits=re_num_splits,
|
| 227 |
+
device='cpu',
|
| 228 |
+
)
|
| 229 |
+
]
|
| 230 |
+
|
| 231 |
+
if separate:
|
| 232 |
+
return transforms.Compose(primary_tfl), transforms.Compose(secondary_tfl), transforms.Compose(final_tfl)
|
| 233 |
+
else:
|
| 234 |
+
return transforms.Compose(primary_tfl + secondary_tfl + final_tfl)
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
def transforms_imagenet_eval(
|
| 238 |
+
img_size: Union[int, Tuple[int, int]] = 224,
|
| 239 |
+
crop_pct: Optional[float] = None,
|
| 240 |
+
crop_mode: Optional[str] = None,
|
| 241 |
+
crop_border_pixels: Optional[int] = None,
|
| 242 |
+
interpolation: str = 'bilinear',
|
| 243 |
+
mean: Tuple[float, ...] = IMAGENET_DEFAULT_MEAN,
|
| 244 |
+
std: Tuple[float, ...] = IMAGENET_DEFAULT_STD,
|
| 245 |
+
use_prefetcher: bool = False,
|
| 246 |
+
):
|
| 247 |
+
""" ImageNet-oriented image transform for evaluation and inference.
|
| 248 |
+
|
| 249 |
+
Args:
|
| 250 |
+
img_size: Target image size.
|
| 251 |
+
crop_pct: Crop percentage. Defaults to 0.875 when None.
|
| 252 |
+
crop_mode: Crop mode. One of ['squash', 'border', 'center']. Defaults to 'center' when None.
|
| 253 |
+
crop_border_pixels: Trim a border of specified # pixels around edge of original image.
|
| 254 |
+
interpolation: Image interpolation mode.
|
| 255 |
+
mean: Image normalization mean.
|
| 256 |
+
std: Image normalization standard deviation.
|
| 257 |
+
use_prefetcher: Prefetcher enabled. Do not convert image to tensor or normalize.
|
| 258 |
+
|
| 259 |
+
Returns:
|
| 260 |
+
Composed transform pipeline
|
| 261 |
+
"""
|
| 262 |
+
crop_pct = crop_pct or DEFAULT_CROP_PCT
|
| 263 |
+
|
| 264 |
+
if isinstance(img_size, (tuple, list)):
|
| 265 |
+
assert len(img_size) == 2
|
| 266 |
+
scale_size = tuple([math.floor(x / crop_pct) for x in img_size])
|
| 267 |
+
else:
|
| 268 |
+
scale_size = math.floor(img_size / crop_pct)
|
| 269 |
+
scale_size = (scale_size, scale_size)
|
| 270 |
+
|
| 271 |
+
tfl = []
|
| 272 |
+
|
| 273 |
+
if crop_border_pixels:
|
| 274 |
+
tfl += [TrimBorder(crop_border_pixels)]
|
| 275 |
+
|
| 276 |
+
if crop_mode == 'squash':
|
| 277 |
+
# squash mode scales each edge to 1/pct of target, then crops
|
| 278 |
+
# aspect ratio is not preserved, no img lost if crop_pct == 1.0
|
| 279 |
+
tfl += [
|
| 280 |
+
transforms.Resize(scale_size, interpolation=str_to_interp_mode(interpolation)),
|
| 281 |
+
transforms.CenterCrop(img_size),
|
| 282 |
+
]
|
| 283 |
+
elif crop_mode == 'border':
|
| 284 |
+
# scale the longest edge of image to 1/pct of target edge, add borders to pad, then crop
|
| 285 |
+
# no image lost if crop_pct == 1.0
|
| 286 |
+
fill = [round(255 * v) for v in mean]
|
| 287 |
+
tfl += [
|
| 288 |
+
ResizeKeepRatio(scale_size, interpolation=interpolation, longest=1.0),
|
| 289 |
+
CenterCropOrPad(img_size, fill=fill),
|
| 290 |
+
]
|
| 291 |
+
else:
|
| 292 |
+
# default crop model is center
|
| 293 |
+
# aspect ratio is preserved, crops center within image, no borders are added, image is lost
|
| 294 |
+
if scale_size[0] == scale_size[1]:
|
| 295 |
+
# simple case, use torchvision built-in Resize w/ shortest edge mode (scalar size arg)
|
| 296 |
+
tfl += [
|
| 297 |
+
transforms.Resize(scale_size[0], interpolation=str_to_interp_mode(interpolation))
|
| 298 |
+
]
|
| 299 |
+
else:
|
| 300 |
+
# resize the shortest edge to matching target dim for non-square target
|
| 301 |
+
tfl += [ResizeKeepRatio(scale_size)]
|
| 302 |
+
tfl += [transforms.CenterCrop(img_size)]
|
| 303 |
+
|
| 304 |
+
if use_prefetcher:
|
| 305 |
+
# prefetcher and collate will handle tensor conversion and norm
|
| 306 |
+
tfl += [ToNumpy()]
|
| 307 |
+
else:
|
| 308 |
+
tfl += [
|
| 309 |
+
transforms.ToTensor(),
|
| 310 |
+
transforms.Normalize(
|
| 311 |
+
mean=torch.tensor(mean),
|
| 312 |
+
std=torch.tensor(std),
|
| 313 |
+
)
|
| 314 |
+
]
|
| 315 |
+
|
| 316 |
+
return transforms.Compose(tfl)
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
def create_transform(
|
| 320 |
+
input_size: Union[int, Tuple[int, int], Tuple[int, int, int]] = 224,
|
| 321 |
+
is_training: bool = False,
|
| 322 |
+
no_aug: bool = False,
|
| 323 |
+
train_crop_mode: Optional[str] = None,
|
| 324 |
+
scale: Optional[Tuple[float, float]] = None,
|
| 325 |
+
ratio: Optional[Tuple[float, float]] = None,
|
| 326 |
+
hflip: float = 0.5,
|
| 327 |
+
vflip: float = 0.,
|
| 328 |
+
color_jitter: Union[float, Tuple[float, ...]] = 0.4,
|
| 329 |
+
color_jitter_prob: Optional[float] = None,
|
| 330 |
+
grayscale_prob: float = 0.,
|
| 331 |
+
gaussian_blur_prob: float = 0.,
|
| 332 |
+
auto_augment: Optional[str] = None,
|
| 333 |
+
interpolation: str = 'bilinear',
|
| 334 |
+
mean: Tuple[float, ...] = IMAGENET_DEFAULT_MEAN,
|
| 335 |
+
std: Tuple[float, ...] = IMAGENET_DEFAULT_STD,
|
| 336 |
+
re_prob: float = 0.,
|
| 337 |
+
re_mode: str = 'const',
|
| 338 |
+
re_count: int = 1,
|
| 339 |
+
re_num_splits: int = 0,
|
| 340 |
+
crop_pct: Optional[float] = None,
|
| 341 |
+
crop_mode: Optional[str] = None,
|
| 342 |
+
crop_border_pixels: Optional[int] = None,
|
| 343 |
+
tf_preprocessing: bool = False,
|
| 344 |
+
use_prefetcher: bool = False,
|
| 345 |
+
separate: bool = False,
|
| 346 |
+
):
|
| 347 |
+
"""
|
| 348 |
+
|
| 349 |
+
Args:
|
| 350 |
+
input_size: Target input size (channels, height, width) tuple or size scalar.
|
| 351 |
+
is_training: Return training (random) transforms.
|
| 352 |
+
no_aug: Disable augmentation for training (useful for debug).
|
| 353 |
+
train_crop_mode: Training random crop mode ('rrc', 'rkrc', 'rkrr').
|
| 354 |
+
scale: Random resize scale range (crop area, < 1.0 => zoom in).
|
| 355 |
+
ratio: Random aspect ratio range (crop ratio for RRC, ratio adjustment factor for RKR).
|
| 356 |
+
hflip: Horizontal flip probability.
|
| 357 |
+
vflip: Vertical flip probability.
|
| 358 |
+
color_jitter: Random color jitter component factors (brightness, contrast, saturation, hue).
|
| 359 |
+
Scalar is applied as (scalar,) * 3 (no hue).
|
| 360 |
+
color_jitter_prob: Apply color jitter with this probability if not None (for SimlCLR-like aug).
|
| 361 |
+
grayscale_prob: Probability of converting image to grayscale (for SimCLR-like aug).
|
| 362 |
+
gaussian_blur_prob: Probability of applying gaussian blur (for SimCLR-like aug).
|
| 363 |
+
auto_augment: Auto augment configuration string (see auto_augment.py).
|
| 364 |
+
interpolation: Image interpolation mode.
|
| 365 |
+
mean: Image normalization mean.
|
| 366 |
+
std: Image normalization standard deviation.
|
| 367 |
+
re_prob: Random erasing probability.
|
| 368 |
+
re_mode: Random erasing fill mode.
|
| 369 |
+
re_count: Number of random erasing regions.
|
| 370 |
+
re_num_splits: Control split of random erasing across batch size.
|
| 371 |
+
crop_pct: Inference crop percentage (output size / resize size).
|
| 372 |
+
crop_mode: Inference crop mode. One of ['squash', 'border', 'center']. Defaults to 'center' when None.
|
| 373 |
+
crop_border_pixels: Inference crop border of specified # pixels around edge of original image.
|
| 374 |
+
tf_preprocessing: Use TF 1.0 inference preprocessing for testing model ports
|
| 375 |
+
use_prefetcher: Pre-fetcher enabled. Do not convert image to tensor or normalize.
|
| 376 |
+
separate: Output transforms in 3-stage tuple.
|
| 377 |
+
|
| 378 |
+
Returns:
|
| 379 |
+
Composed transforms or tuple thereof
|
| 380 |
+
"""
|
| 381 |
+
if isinstance(input_size, (tuple, list)):
|
| 382 |
+
img_size = input_size[-2:]
|
| 383 |
+
else:
|
| 384 |
+
img_size = input_size
|
| 385 |
+
|
| 386 |
+
if tf_preprocessing and use_prefetcher:
|
| 387 |
+
assert not separate, "Separate transforms not supported for TF preprocessing"
|
| 388 |
+
from timm.data.tf_preprocessing import TfPreprocessTransform
|
| 389 |
+
transform = TfPreprocessTransform(
|
| 390 |
+
is_training=is_training,
|
| 391 |
+
size=img_size,
|
| 392 |
+
interpolation=interpolation,
|
| 393 |
+
)
|
| 394 |
+
else:
|
| 395 |
+
if is_training and no_aug:
|
| 396 |
+
assert not separate, "Cannot perform split augmentation with no_aug"
|
| 397 |
+
transform = transforms_noaug_train(
|
| 398 |
+
img_size,
|
| 399 |
+
interpolation=interpolation,
|
| 400 |
+
use_prefetcher=use_prefetcher,
|
| 401 |
+
mean=mean,
|
| 402 |
+
std=std,
|
| 403 |
+
)
|
| 404 |
+
elif is_training:
|
| 405 |
+
transform = transforms_imagenet_train(
|
| 406 |
+
img_size,
|
| 407 |
+
train_crop_mode=train_crop_mode,
|
| 408 |
+
scale=scale,
|
| 409 |
+
ratio=ratio,
|
| 410 |
+
hflip=hflip,
|
| 411 |
+
vflip=vflip,
|
| 412 |
+
color_jitter=color_jitter,
|
| 413 |
+
color_jitter_prob=color_jitter_prob,
|
| 414 |
+
grayscale_prob=grayscale_prob,
|
| 415 |
+
gaussian_blur_prob=gaussian_blur_prob,
|
| 416 |
+
auto_augment=auto_augment,
|
| 417 |
+
interpolation=interpolation,
|
| 418 |
+
use_prefetcher=use_prefetcher,
|
| 419 |
+
mean=mean,
|
| 420 |
+
std=std,
|
| 421 |
+
re_prob=re_prob,
|
| 422 |
+
re_mode=re_mode,
|
| 423 |
+
re_count=re_count,
|
| 424 |
+
re_num_splits=re_num_splits,
|
| 425 |
+
separate=separate,
|
| 426 |
+
)
|
| 427 |
+
else:
|
| 428 |
+
assert not separate, "Separate transforms not supported for validation preprocessing"
|
| 429 |
+
transform = transforms_imagenet_eval(
|
| 430 |
+
img_size,
|
| 431 |
+
interpolation=interpolation,
|
| 432 |
+
use_prefetcher=use_prefetcher,
|
| 433 |
+
mean=mean,
|
| 434 |
+
std=std,
|
| 435 |
+
crop_pct=crop_pct,
|
| 436 |
+
crop_mode=crop_mode,
|
| 437 |
+
crop_border_pixels=crop_border_pixels,
|
| 438 |
+
)
|
| 439 |
+
|
| 440 |
+
return transform
|
timm/layers/__init__.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .activations import *
|
| 2 |
+
from .adaptive_avgmax_pool import \
|
| 3 |
+
adaptive_avgmax_pool2d, select_adaptive_pool2d, AdaptiveAvgMaxPool2d, SelectAdaptivePool2d
|
| 4 |
+
from .attention_pool import AttentionPoolLatent
|
| 5 |
+
from .attention_pool2d import AttentionPool2d, RotAttentionPool2d, RotaryEmbedding
|
| 6 |
+
from .blur_pool import BlurPool2d
|
| 7 |
+
from .classifier import ClassifierHead, create_classifier, NormMlpClassifierHead
|
| 8 |
+
from .cond_conv2d import CondConv2d, get_condconv_initializer
|
| 9 |
+
from .config import is_exportable, is_scriptable, is_no_jit, use_fused_attn, \
|
| 10 |
+
set_exportable, set_scriptable, set_no_jit, set_layer_config, set_fused_attn
|
| 11 |
+
from .conv2d_same import Conv2dSame, conv2d_same
|
| 12 |
+
from .conv_bn_act import ConvNormAct, ConvNormActAa, ConvBnAct
|
| 13 |
+
from .create_act import create_act_layer, get_act_layer, get_act_fn
|
| 14 |
+
from .create_attn import get_attn, create_attn
|
| 15 |
+
from .create_conv2d import create_conv2d
|
| 16 |
+
from .create_norm import get_norm_layer, create_norm_layer
|
| 17 |
+
from .create_norm_act import get_norm_act_layer, create_norm_act_layer, get_norm_act_layer
|
| 18 |
+
from .drop import DropBlock2d, DropPath, drop_block_2d, drop_path
|
| 19 |
+
from .eca import EcaModule, CecaModule, EfficientChannelAttn, CircularEfficientChannelAttn
|
| 20 |
+
from .evo_norm import EvoNorm2dB0, EvoNorm2dB1, EvoNorm2dB2,\
|
| 21 |
+
EvoNorm2dS0, EvoNorm2dS0a, EvoNorm2dS1, EvoNorm2dS1a, EvoNorm2dS2, EvoNorm2dS2a
|
| 22 |
+
from .fast_norm import is_fast_norm, set_fast_norm, fast_group_norm, fast_layer_norm
|
| 23 |
+
from .filter_response_norm import FilterResponseNormTlu2d, FilterResponseNormAct2d
|
| 24 |
+
from .format import Format, get_channel_dim, get_spatial_dim, nchw_to, nhwc_to
|
| 25 |
+
from .gather_excite import GatherExcite
|
| 26 |
+
from .global_context import GlobalContext
|
| 27 |
+
from .grid import ndgrid, meshgrid
|
| 28 |
+
from .helpers import to_ntuple, to_2tuple, to_3tuple, to_4tuple, make_divisible, extend_tuple
|
| 29 |
+
from .inplace_abn import InplaceAbn
|
| 30 |
+
from .linear import Linear
|
| 31 |
+
from .mixed_conv2d import MixedConv2d
|
| 32 |
+
from .mlp import Mlp, GluMlp, GatedMlp, SwiGLU, SwiGLUPacked, ConvMlp, GlobalResponseNormMlp
|
| 33 |
+
from .non_local_attn import NonLocalAttn, BatNonLocalAttn
|
| 34 |
+
from .norm import GroupNorm, GroupNorm1, LayerNorm, LayerNorm2d, RmsNorm
|
| 35 |
+
from .norm_act import BatchNormAct2d, GroupNormAct, GroupNorm1Act, LayerNormAct, LayerNormAct2d,\
|
| 36 |
+
SyncBatchNormAct, convert_sync_batchnorm, FrozenBatchNormAct2d, freeze_batch_norm_2d, unfreeze_batch_norm_2d
|
| 37 |
+
from .padding import get_padding, get_same_padding, pad_same
|
| 38 |
+
from .patch_dropout import PatchDropout
|
| 39 |
+
from .patch_embed import PatchEmbed, PatchEmbedWithSize, resample_patch_embed
|
| 40 |
+
from .pool2d_same import AvgPool2dSame, create_pool2d
|
| 41 |
+
from .pos_embed import resample_abs_pos_embed, resample_abs_pos_embed_nhwc
|
| 42 |
+
from .pos_embed_rel import RelPosMlp, RelPosBias, RelPosBiasTf, gen_relative_position_index, gen_relative_log_coords, \
|
| 43 |
+
resize_rel_pos_bias_table, resize_rel_pos_bias_table_simple, resize_rel_pos_bias_table_levit
|
| 44 |
+
from .pos_embed_sincos import pixel_freq_bands, freq_bands, build_sincos2d_pos_embed, build_fourier_pos_embed, \
|
| 45 |
+
build_rotary_pos_embed, apply_rot_embed, apply_rot_embed_cat, apply_rot_embed_list, apply_keep_indices_nlc, \
|
| 46 |
+
FourierEmbed, RotaryEmbedding, RotaryEmbeddingCat
|
| 47 |
+
from .squeeze_excite import SEModule, SqueezeExcite, EffectiveSEModule, EffectiveSqueezeExcite
|
| 48 |
+
from .selective_kernel import SelectiveKernel
|
| 49 |
+
from .separable_conv import SeparableConv2d, SeparableConvNormAct
|
| 50 |
+
from .space_to_depth import SpaceToDepthModule, SpaceToDepth, DepthToSpace
|
| 51 |
+
from .split_attn import SplitAttn
|
| 52 |
+
from .split_batchnorm import SplitBatchNorm2d, convert_splitbn_model
|
| 53 |
+
from .std_conv import StdConv2d, StdConv2dSame, ScaledStdConv2d, ScaledStdConv2dSame
|
| 54 |
+
from .test_time_pool import TestTimePoolHead, apply_test_time_pool
|
| 55 |
+
from .trace_utils import _assert, _float_to_int
|
| 56 |
+
from .typing import LayerType, PadType
|
| 57 |
+
from .weight_init import trunc_normal_, trunc_normal_tf_, variance_scaling_, lecun_normal_
|
timm/layers/activations.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" Activations
|
| 2 |
+
|
| 3 |
+
A collection of activations fn and modules with a common interface so that they can
|
| 4 |
+
easily be swapped. All have an `inplace` arg even if not used.
|
| 5 |
+
|
| 6 |
+
Hacked together by / Copyright 2020 Ross Wightman
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
from torch import nn as nn
|
| 11 |
+
from torch.nn import functional as F
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def swish(x, inplace: bool = False):
|
| 15 |
+
"""Swish - Described in: https://arxiv.org/abs/1710.05941
|
| 16 |
+
"""
|
| 17 |
+
return x.mul_(x.sigmoid()) if inplace else x.mul(x.sigmoid())
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class Swish(nn.Module):
|
| 21 |
+
def __init__(self, inplace: bool = False):
|
| 22 |
+
super(Swish, self).__init__()
|
| 23 |
+
self.inplace = inplace
|
| 24 |
+
|
| 25 |
+
def forward(self, x):
|
| 26 |
+
return swish(x, self.inplace)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def mish(x, inplace: bool = False):
|
| 30 |
+
"""Mish: A Self Regularized Non-Monotonic Neural Activation Function - https://arxiv.org/abs/1908.08681
|
| 31 |
+
NOTE: I don't have a working inplace variant
|
| 32 |
+
"""
|
| 33 |
+
return x.mul(F.softplus(x).tanh())
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class Mish(nn.Module):
|
| 37 |
+
"""Mish: A Self Regularized Non-Monotonic Neural Activation Function - https://arxiv.org/abs/1908.08681
|
| 38 |
+
"""
|
| 39 |
+
def __init__(self, inplace: bool = False):
|
| 40 |
+
super(Mish, self).__init__()
|
| 41 |
+
|
| 42 |
+
def forward(self, x):
|
| 43 |
+
return mish(x)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def sigmoid(x, inplace: bool = False):
|
| 47 |
+
return x.sigmoid_() if inplace else x.sigmoid()
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
# PyTorch has this, but not with a consistent inplace argmument interface
|
| 51 |
+
class Sigmoid(nn.Module):
|
| 52 |
+
def __init__(self, inplace: bool = False):
|
| 53 |
+
super(Sigmoid, self).__init__()
|
| 54 |
+
self.inplace = inplace
|
| 55 |
+
|
| 56 |
+
def forward(self, x):
|
| 57 |
+
return x.sigmoid_() if self.inplace else x.sigmoid()
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def tanh(x, inplace: bool = False):
|
| 61 |
+
return x.tanh_() if inplace else x.tanh()
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
# PyTorch has this, but not with a consistent inplace argmument interface
|
| 65 |
+
class Tanh(nn.Module):
|
| 66 |
+
def __init__(self, inplace: bool = False):
|
| 67 |
+
super(Tanh, self).__init__()
|
| 68 |
+
self.inplace = inplace
|
| 69 |
+
|
| 70 |
+
def forward(self, x):
|
| 71 |
+
return x.tanh_() if self.inplace else x.tanh()
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def hard_swish(x, inplace: bool = False):
|
| 75 |
+
inner = F.relu6(x + 3.).div_(6.)
|
| 76 |
+
return x.mul_(inner) if inplace else x.mul(inner)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
class HardSwish(nn.Module):
|
| 80 |
+
def __init__(self, inplace: bool = False):
|
| 81 |
+
super(HardSwish, self).__init__()
|
| 82 |
+
self.inplace = inplace
|
| 83 |
+
|
| 84 |
+
def forward(self, x):
|
| 85 |
+
return hard_swish(x, self.inplace)
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def hard_sigmoid(x, inplace: bool = False):
|
| 89 |
+
if inplace:
|
| 90 |
+
return x.add_(3.).clamp_(0., 6.).div_(6.)
|
| 91 |
+
else:
|
| 92 |
+
return F.relu6(x + 3.) / 6.
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
class HardSigmoid(nn.Module):
|
| 96 |
+
def __init__(self, inplace: bool = False):
|
| 97 |
+
super(HardSigmoid, self).__init__()
|
| 98 |
+
self.inplace = inplace
|
| 99 |
+
|
| 100 |
+
def forward(self, x):
|
| 101 |
+
return hard_sigmoid(x, self.inplace)
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def hard_mish(x, inplace: bool = False):
|
| 105 |
+
""" Hard Mish
|
| 106 |
+
Experimental, based on notes by Mish author Diganta Misra at
|
| 107 |
+
https://github.com/digantamisra98/H-Mish/blob/0da20d4bc58e696b6803f2523c58d3c8a82782d0/README.md
|
| 108 |
+
"""
|
| 109 |
+
if inplace:
|
| 110 |
+
return x.mul_(0.5 * (x + 2).clamp(min=0, max=2))
|
| 111 |
+
else:
|
| 112 |
+
return 0.5 * x * (x + 2).clamp(min=0, max=2)
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
class HardMish(nn.Module):
|
| 116 |
+
def __init__(self, inplace: bool = False):
|
| 117 |
+
super(HardMish, self).__init__()
|
| 118 |
+
self.inplace = inplace
|
| 119 |
+
|
| 120 |
+
def forward(self, x):
|
| 121 |
+
return hard_mish(x, self.inplace)
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
class PReLU(nn.PReLU):
|
| 125 |
+
"""Applies PReLU (w/ dummy inplace arg)
|
| 126 |
+
"""
|
| 127 |
+
def __init__(self, num_parameters: int = 1, init: float = 0.25, inplace: bool = False) -> None:
|
| 128 |
+
super(PReLU, self).__init__(num_parameters=num_parameters, init=init)
|
| 129 |
+
|
| 130 |
+
def forward(self, input: torch.Tensor) -> torch.Tensor:
|
| 131 |
+
return F.prelu(input, self.weight)
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def gelu(x: torch.Tensor, inplace: bool = False) -> torch.Tensor:
|
| 135 |
+
return F.gelu(x)
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
class GELU(nn.Module):
|
| 139 |
+
"""Applies the Gaussian Error Linear Units function (w/ dummy inplace arg)
|
| 140 |
+
"""
|
| 141 |
+
def __init__(self, inplace: bool = False):
|
| 142 |
+
super(GELU, self).__init__()
|
| 143 |
+
|
| 144 |
+
def forward(self, input: torch.Tensor) -> torch.Tensor:
|
| 145 |
+
return F.gelu(input)
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
def gelu_tanh(x: torch.Tensor, inplace: bool = False) -> torch.Tensor:
|
| 149 |
+
return F.gelu(x, approximate='tanh')
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
class GELUTanh(nn.Module):
|
| 153 |
+
"""Applies the Gaussian Error Linear Units function (w/ dummy inplace arg)
|
| 154 |
+
"""
|
| 155 |
+
def __init__(self, inplace: bool = False):
|
| 156 |
+
super(GELUTanh, self).__init__()
|
| 157 |
+
|
| 158 |
+
def forward(self, input: torch.Tensor) -> torch.Tensor:
|
| 159 |
+
return F.gelu(input, approximate='tanh')
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def quick_gelu(x: torch.Tensor, inplace: bool = False) -> torch.Tensor:
|
| 163 |
+
return x * torch.sigmoid(1.702 * x)
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
class QuickGELU(nn.Module):
|
| 167 |
+
"""Applies the Gaussian Error Linear Units function (w/ dummy inplace arg)
|
| 168 |
+
"""
|
| 169 |
+
def __init__(self, inplace: bool = False):
|
| 170 |
+
super(QuickGELU, self).__init__()
|
| 171 |
+
|
| 172 |
+
def forward(self, input: torch.Tensor) -> torch.Tensor:
|
| 173 |
+
return quick_gelu(input)
|
timm/layers/activations_jit.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" Activations
|
| 2 |
+
|
| 3 |
+
A collection of jit-scripted activations fn and modules with a common interface so that they can
|
| 4 |
+
easily be swapped. All have an `inplace` arg even if not used.
|
| 5 |
+
|
| 6 |
+
All jit scripted activations are lacking in-place variations on purpose, scripted kernel fusion does not
|
| 7 |
+
currently work across in-place op boundaries, thus performance is equal to or less than the non-scripted
|
| 8 |
+
versions if they contain in-place ops.
|
| 9 |
+
|
| 10 |
+
Hacked together by / Copyright 2020 Ross Wightman
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
from torch import nn as nn
|
| 15 |
+
from torch.nn import functional as F
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
@torch.jit.script
|
| 19 |
+
def swish_jit(x, inplace: bool = False):
|
| 20 |
+
"""Swish - Described in: https://arxiv.org/abs/1710.05941
|
| 21 |
+
"""
|
| 22 |
+
return x.mul(x.sigmoid())
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
@torch.jit.script
|
| 26 |
+
def mish_jit(x, _inplace: bool = False):
|
| 27 |
+
"""Mish: A Self Regularized Non-Monotonic Neural Activation Function - https://arxiv.org/abs/1908.08681
|
| 28 |
+
"""
|
| 29 |
+
return x.mul(F.softplus(x).tanh())
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class SwishJit(nn.Module):
|
| 33 |
+
def __init__(self, inplace: bool = False):
|
| 34 |
+
super(SwishJit, self).__init__()
|
| 35 |
+
|
| 36 |
+
def forward(self, x):
|
| 37 |
+
return swish_jit(x)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class MishJit(nn.Module):
|
| 41 |
+
def __init__(self, inplace: bool = False):
|
| 42 |
+
super(MishJit, self).__init__()
|
| 43 |
+
|
| 44 |
+
def forward(self, x):
|
| 45 |
+
return mish_jit(x)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
@torch.jit.script
|
| 49 |
+
def hard_sigmoid_jit(x, inplace: bool = False):
|
| 50 |
+
# return F.relu6(x + 3.) / 6.
|
| 51 |
+
return (x + 3).clamp(min=0, max=6).div(6.) # clamp seems ever so slightly faster?
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
class HardSigmoidJit(nn.Module):
|
| 55 |
+
def __init__(self, inplace: bool = False):
|
| 56 |
+
super(HardSigmoidJit, self).__init__()
|
| 57 |
+
|
| 58 |
+
def forward(self, x):
|
| 59 |
+
return hard_sigmoid_jit(x)
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
@torch.jit.script
|
| 63 |
+
def hard_swish_jit(x, inplace: bool = False):
|
| 64 |
+
# return x * (F.relu6(x + 3.) / 6)
|
| 65 |
+
return x * (x + 3).clamp(min=0, max=6).div(6.) # clamp seems ever so slightly faster?
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
class HardSwishJit(nn.Module):
|
| 69 |
+
def __init__(self, inplace: bool = False):
|
| 70 |
+
super(HardSwishJit, self).__init__()
|
| 71 |
+
|
| 72 |
+
def forward(self, x):
|
| 73 |
+
return hard_swish_jit(x)
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
@torch.jit.script
|
| 77 |
+
def hard_mish_jit(x, inplace: bool = False):
|
| 78 |
+
""" Hard Mish
|
| 79 |
+
Experimental, based on notes by Mish author Diganta Misra at
|
| 80 |
+
https://github.com/digantamisra98/H-Mish/blob/0da20d4bc58e696b6803f2523c58d3c8a82782d0/README.md
|
| 81 |
+
"""
|
| 82 |
+
return 0.5 * x * (x + 2).clamp(min=0, max=2)
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
class HardMishJit(nn.Module):
|
| 86 |
+
def __init__(self, inplace: bool = False):
|
| 87 |
+
super(HardMishJit, self).__init__()
|
| 88 |
+
|
| 89 |
+
def forward(self, x):
|
| 90 |
+
return hard_mish_jit(x)
|