multimodalart HF Staff commited on
Commit
940f90f
·
verified ·
1 Parent(s): d089e8a

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +7 -0
  2. LICENSE +21 -0
  3. LICENSE-timm.txt +201 -0
  4. NOTICE +20 -0
  5. README.md +64 -7
  6. app.py +280 -0
  7. examples/acoustic_guitar.jpg +3 -0
  8. examples/bird_kingfisher.jpg +0 -0
  9. examples/chameleon.jpg +3 -0
  10. examples/hot_air_balloon.jpg +0 -0
  11. examples/husky_dog.jpg +0 -0
  12. examples/library_interior.jpg +3 -0
  13. examples/monstera_plant.jpg +3 -0
  14. examples/pizza_board.jpg +3 -0
  15. examples/red_fox.jpg +3 -0
  16. examples/spiral_staircase.jpg +3 -0
  17. examples/vintage_camera.jpg +0 -0
  18. imagenet_classes.json +1002 -0
  19. requirements.txt +5 -0
  20. timm/__init__.py +4 -0
  21. timm/data/__init__.py +23 -0
  22. timm/data/auto_augment.py +997 -0
  23. timm/data/config.py +129 -0
  24. timm/data/constants.py +10 -0
  25. timm/data/dataset.py +200 -0
  26. timm/data/dataset_factory.py +224 -0
  27. timm/data/dataset_info.py +73 -0
  28. timm/data/distributed_sampler.py +135 -0
  29. timm/data/loader.py +413 -0
  30. timm/data/mixup.py +315 -0
  31. timm/data/random_erasing.py +117 -0
  32. timm/data/readers/__init__.py +2 -0
  33. timm/data/readers/class_map.py +22 -0
  34. timm/data/readers/img_extensions.py +50 -0
  35. timm/data/readers/reader.py +16 -0
  36. timm/data/readers/reader_factory.py +45 -0
  37. timm/data/readers/reader_hfds.py +84 -0
  38. timm/data/readers/reader_hfids.py +215 -0
  39. timm/data/readers/reader_image_folder.py +99 -0
  40. timm/data/readers/reader_image_in_tar.py +229 -0
  41. timm/data/readers/reader_tfds.py +355 -0
  42. timm/data/readers/reader_wds.py +466 -0
  43. timm/data/readers/shared_count.py +14 -0
  44. timm/data/real_labels.py +47 -0
  45. timm/data/tf_preprocessing.py +233 -0
  46. timm/data/transforms.py +534 -0
  47. timm/data/transforms_factory.py +440 -0
  48. timm/layers/__init__.py +57 -0
  49. timm/layers/activations.py +173 -0
  50. 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: Progresvit Classify
3
- emoji: 💻
4
- colorFrom: blue
5
- colorTo: green
6
  sdk: gradio
7
  sdk_version: 6.26.0
8
- python_version: '3.12'
9
  app_file: app.py
10
- pinned: false
 
 
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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

  • SHA256: c938cf403fd5d512e50e3ab13cd3a63a22f024477342de96411e180cc7de2b2a
  • Pointer size: 131 Bytes
  • Size of remote file: 150 kB
examples/bird_kingfisher.jpg ADDED
examples/chameleon.jpg ADDED

Git LFS Details

  • SHA256: 41fe1fab487688c06204caf243753c297c2538027a49b61ccc05a1b7889428bb
  • Pointer size: 131 Bytes
  • Size of remote file: 122 kB
examples/hot_air_balloon.jpg ADDED
examples/husky_dog.jpg ADDED
examples/library_interior.jpg ADDED

Git LFS Details

  • SHA256: ab228096c9acaae32597d58faa785e9fc44d5a27697a8af6bb0ebc994b700001
  • Pointer size: 131 Bytes
  • Size of remote file: 172 kB
examples/monstera_plant.jpg ADDED

Git LFS Details

  • SHA256: 862746a970f7535fce7ee959e134be227df71b2d82d7cc4111b13a83a6d275e1
  • Pointer size: 131 Bytes
  • Size of remote file: 103 kB
examples/pizza_board.jpg ADDED

Git LFS Details

  • SHA256: 527a1af77249c162cbd9cc58da1feeac551ee7fcd0ab15e3b8fe6c9c7a233271
  • Pointer size: 131 Bytes
  • Size of remote file: 327 kB
examples/red_fox.jpg ADDED

Git LFS Details

  • SHA256: 286130138fec79ac754b641c2b8b51cc8709c1ce1202dcc33ef0becdc418064d
  • Pointer size: 131 Bytes
  • Size of remote file: 167 kB
examples/spiral_staircase.jpg ADDED

Git LFS Details

  • SHA256: a4595f30a1438df19bb1f700147194e8eec79acb94186a3b2d63c84732455f83
  • Pointer size: 131 Bytes
  • Size of remote file: 280 kB
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)