monai-weekly 1.5.dev2513__py3-none-any.whl → 1.5.dev2515__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- monai/__init__.py +1 -1
- monai/_version.py +3 -3
- monai/apps/nnunet/__init__.py +8 -0
- monai/apps/nnunet/nnunet_bundle.py +594 -0
- monai/bundle/scripts.py +9 -18
- monai/networks/blocks/fft_utils_t.py +12 -20
- monai/transforms/inverse.py +39 -7
- monai/utils/misc.py +1 -1
- {monai_weekly-1.5.dev2513.dist-info → monai_weekly-1.5.dev2515.dist-info}/METADATA +3 -1
- {monai_weekly-1.5.dev2513.dist-info → monai_weekly-1.5.dev2515.dist-info}/RECORD +21 -18
- tests/bundle/test_bundle_download.py +5 -0
- tests/integration/test_integration_nnunet_bundle.py +150 -0
- tests/networks/nets/test_transchex.py +3 -2
- tests/transforms/inverse/test_inverse_dict.py +105 -0
- tests/transforms/inverse/test_traceable_transform.py +2 -2
- {monai_weekly-1.5.dev2513.dist-info → monai_weekly-1.5.dev2515.dist-info}/WHEEL +0 -0
- {monai_weekly-1.5.dev2513.dist-info → monai_weekly-1.5.dev2515.dist-info}/licenses/LICENSE +0 -0
- {monai_weekly-1.5.dev2513.dist-info → monai_weekly-1.5.dev2515.dist-info}/top_level.txt +0 -0
- /tests/transforms/{test_inverse.py → inverse/test_inverse.py} +0 -0
- /tests/transforms/{test_invert.py → inverse/test_invert.py} +0 -0
- /tests/transforms/{test_invertd.py → inverse/test_invertd.py} +0 -0
monai/bundle/scripts.py
CHANGED
@@ -312,21 +312,6 @@ def _get_ngc_token(api_key, retry=0):
|
|
312
312
|
return token
|
313
313
|
|
314
314
|
|
315
|
-
def _get_latest_bundle_version_monaihosting(name):
|
316
|
-
full_url = f"{MONAI_HOSTING_BASE_URL}/{name.lower()}"
|
317
|
-
if has_requests:
|
318
|
-
resp = requests.get(full_url)
|
319
|
-
try:
|
320
|
-
resp.raise_for_status()
|
321
|
-
model_info = json.loads(resp.text)
|
322
|
-
return model_info["model"]["latestVersionIdStr"]
|
323
|
-
except requests.exceptions.HTTPError:
|
324
|
-
# for monaihosting bundles, if cannot find the version, get from model zoo model_info.json
|
325
|
-
return get_bundle_versions(name)["latest_version"]
|
326
|
-
|
327
|
-
raise ValueError("NGC API requires requests package. Please install it.")
|
328
|
-
|
329
|
-
|
330
315
|
def _examine_monai_version(monai_version: str) -> tuple[bool, str]:
|
331
316
|
"""Examine if the package version is compatible with the MONAI version in the metadata."""
|
332
317
|
version_dict = get_versions()
|
@@ -430,7 +415,7 @@ def _get_latest_bundle_version(
|
|
430
415
|
name = _add_ngc_prefix(name)
|
431
416
|
return _get_latest_bundle_version_ngc(name)
|
432
417
|
elif source == "monaihosting":
|
433
|
-
return
|
418
|
+
return get_bundle_versions(name, repo="Project-MONAI/model-zoo", tag="dev")["latest_version"]
|
434
419
|
elif source == "ngc_private":
|
435
420
|
headers = kwargs.pop("headers", {})
|
436
421
|
name = _add_ngc_prefix(name)
|
@@ -528,7 +513,9 @@ def download(
|
|
528
513
|
If source is "ngc_private", you need specify the NGC_API_KEY in the environment variable.
|
529
514
|
repo: repo name. This argument is used when `url` is `None` and `source` is "github" or "huggingface_hub".
|
530
515
|
If `source` is "github", it should be in the form of "repo_owner/repo_name/release_tag".
|
531
|
-
If `source` is "huggingface_hub", it should be in the form of "repo_owner/repo_name".
|
516
|
+
If `source` is "huggingface_hub", it should be in the form of "repo_owner/repo_name". Please note that
|
517
|
+
bundles for "monaihosting" source are also hosted on Hugging Face Hub, but the "repo_id" is always in the form
|
518
|
+
of "MONAI/bundle_name", therefore, this argument is not required for "monaihosting" source.
|
532
519
|
If `source` is "ngc_private", it should be in the form of "org/org_name" or "org/org_name/team/team_name",
|
533
520
|
or you can specify the environment variable NGC_ORG and NGC_TEAM.
|
534
521
|
url: url to download the data. If not `None`, data will be downloaded directly
|
@@ -600,11 +587,15 @@ def download(
|
|
600
587
|
_download_from_github(repo=repo_, download_path=bundle_dir_, filename=name_ver, progress=progress_)
|
601
588
|
elif source_ == "monaihosting":
|
602
589
|
try:
|
590
|
+
extract_path = os.path.join(bundle_dir_, name_)
|
591
|
+
huggingface_hub.snapshot_download(repo_id=f"MONAI/{name_}", revision=version_, local_dir=extract_path)
|
592
|
+
except (huggingface_hub.errors.RevisionNotFoundError, huggingface_hub.errors.RepositoryNotFoundError):
|
593
|
+
# if bundle or version not found from huggingface, download from ngc monaihosting
|
603
594
|
_download_from_monaihosting(
|
604
595
|
download_path=bundle_dir_, filename=name_, version=version_, progress=progress_
|
605
596
|
)
|
606
597
|
except urllib.error.HTTPError:
|
607
|
-
#
|
598
|
+
# if also cannot download from ngc monaihosting, download according to bundle_info
|
608
599
|
_download_from_bundle_info(
|
609
600
|
download_path=bundle_dir_, filename=name_, version=version_, progress=progress_
|
610
601
|
)
|
@@ -139,21 +139,17 @@ def ifftn_centered_t(ksp: Tensor, spatial_dims: int, is_complex: bool = True) ->
|
|
139
139
|
output2 = ifftn_centered(ksp, spatial_dims=2, is_complex=True)
|
140
140
|
"""
|
141
141
|
# define spatial dims to perform ifftshift, fftshift, and ifft
|
142
|
-
|
142
|
+
dims = list(range(-spatial_dims, 0))
|
143
143
|
if is_complex:
|
144
144
|
if ksp.shape[-1] != 2:
|
145
145
|
raise ValueError(f"ksp.shape[-1] is not 2 ({ksp.shape[-1]}).")
|
146
|
-
|
147
|
-
dims = list(range(-spatial_dims, 0))
|
148
|
-
|
149
|
-
x = ifftshift(ksp, shift)
|
150
|
-
|
151
|
-
if is_complex:
|
152
|
-
x = torch.view_as_real(torch.fft.ifftn(torch.view_as_complex(x), dim=dims, norm="ortho"))
|
146
|
+
x = torch.view_as_complex(ifftshift(ksp, [d - 1 for d in dims]))
|
153
147
|
else:
|
154
|
-
x =
|
148
|
+
x = ifftshift(ksp, dims)
|
149
|
+
|
150
|
+
x = torch.view_as_real(torch.fft.ifftn(x, dim=dims, norm="ortho"))
|
155
151
|
|
156
|
-
out: Tensor = fftshift(x,
|
152
|
+
out: Tensor = fftshift(x, [d - 1 for d in dims])
|
157
153
|
|
158
154
|
return out
|
159
155
|
|
@@ -187,20 +183,16 @@ def fftn_centered_t(im: Tensor, spatial_dims: int, is_complex: bool = True) -> T
|
|
187
183
|
output2 = fftn_centered(im, spatial_dims=2, is_complex=True)
|
188
184
|
"""
|
189
185
|
# define spatial dims to perform ifftshift, fftshift, and fft
|
190
|
-
|
186
|
+
dims = list(range(-spatial_dims, 0))
|
191
187
|
if is_complex:
|
192
188
|
if im.shape[-1] != 2:
|
193
189
|
raise ValueError(f"img.shape[-1] is not 2 ({im.shape[-1]}).")
|
194
|
-
|
195
|
-
dims = list(range(-spatial_dims, 0))
|
196
|
-
|
197
|
-
x = ifftshift(im, shift)
|
198
|
-
|
199
|
-
if is_complex:
|
200
|
-
x = torch.view_as_real(torch.fft.fftn(torch.view_as_complex(x), dim=dims, norm="ortho"))
|
190
|
+
x = torch.view_as_complex(ifftshift(im, [d - 1 for d in dims]))
|
201
191
|
else:
|
202
|
-
x =
|
192
|
+
x = ifftshift(im, dims)
|
193
|
+
|
194
|
+
x = torch.view_as_real(torch.fft.fftn(x, dim=dims, norm="ortho"))
|
203
195
|
|
204
|
-
out: Tensor = fftshift(x,
|
196
|
+
out: Tensor = fftshift(x, [d - 1 for d in dims])
|
205
197
|
|
206
198
|
return out
|
monai/transforms/inverse.py
CHANGED
@@ -11,6 +11,7 @@
|
|
11
11
|
|
12
12
|
from __future__ import annotations
|
13
13
|
|
14
|
+
import threading
|
14
15
|
import warnings
|
15
16
|
from collections.abc import Hashable, Mapping
|
16
17
|
from contextlib import contextmanager
|
@@ -66,15 +67,41 @@ class TraceableTransform(Transform):
|
|
66
67
|
The information in the stack of applied transforms must be compatible with the
|
67
68
|
default collate, by only storing strings, numbers and arrays.
|
68
69
|
|
69
|
-
`tracing` could be enabled by `self.
|
70
|
+
`tracing` could be enabled by assigning to `self.tracing` or setting
|
70
71
|
`MONAI_TRACE_TRANSFORM` when initializing the class.
|
71
72
|
"""
|
72
73
|
|
73
|
-
|
74
|
+
def _init_trace_threadlocal(self):
|
75
|
+
"""Create a `_tracing` instance member to store the thread-local tracing state value."""
|
76
|
+
# needed since this class is meant to be a trait with no constructor
|
77
|
+
if not hasattr(self, "_tracing"):
|
78
|
+
self._tracing = threading.local()
|
79
|
+
|
80
|
+
# This is True while the above initialising _tracing is False when this is
|
81
|
+
# called from a different thread than the one initialising _tracing.
|
82
|
+
if not hasattr(self._tracing, "value"):
|
83
|
+
self._tracing.value = MONAIEnvVars.trace_transform() != "0"
|
84
|
+
|
85
|
+
def __getstate__(self):
|
86
|
+
"""When pickling, remove the `_tracing` member from the output, if present, since it's not picklable."""
|
87
|
+
_dict = dict(getattr(self, "__dict__", {})) # this makes __dict__ always present in the unpickled object
|
88
|
+
_slots = {k: getattr(self, k) for k in getattr(self, "__slots__", [])}
|
89
|
+
_dict.pop("_tracing", None) # remove tracing
|
90
|
+
return _dict if len(_slots) == 0 else (_dict, _slots)
|
91
|
+
|
92
|
+
@property
|
93
|
+
def tracing(self) -> bool:
|
94
|
+
"""
|
95
|
+
Returns the tracing state, which is thread-local and initialised to `MONAIEnvVars.trace_transform() != "0"`.
|
96
|
+
"""
|
97
|
+
self._init_trace_threadlocal()
|
98
|
+
return bool(self._tracing.value)
|
74
99
|
|
75
|
-
|
76
|
-
|
77
|
-
|
100
|
+
@tracing.setter
|
101
|
+
def tracing(self, val: bool):
|
102
|
+
"""Sets the thread-local tracing state to `val`."""
|
103
|
+
self._init_trace_threadlocal()
|
104
|
+
self._tracing.value = val
|
78
105
|
|
79
106
|
@staticmethod
|
80
107
|
def trace_key(key: Hashable = None):
|
@@ -291,7 +318,7 @@ class TraceableTransform(Transform):
|
|
291
318
|
|
292
319
|
def get_most_recent_transform(self, data, key: Hashable = None, check: bool = True, pop: bool = False):
|
293
320
|
"""
|
294
|
-
Get most recent transform for the
|
321
|
+
Get most recent matching transform for the current class from the sequence of applied operations.
|
295
322
|
|
296
323
|
Args:
|
297
324
|
data: dictionary of data or `MetaTensor`.
|
@@ -316,9 +343,14 @@ class TraceableTransform(Transform):
|
|
316
343
|
all_transforms = data.get(self.trace_key(key), MetaTensor.get_default_applied_operations())
|
317
344
|
else:
|
318
345
|
raise ValueError(f"`data` should be either `MetaTensor` or dictionary, got {type(data)}.")
|
346
|
+
|
347
|
+
if not all_transforms:
|
348
|
+
raise ValueError(f"Item of type {type(data)} (key: {key}, pop: {pop}) has empty 'applied_operations'")
|
349
|
+
|
319
350
|
if check:
|
320
351
|
self.check_transforms_match(all_transforms[-1])
|
321
|
-
|
352
|
+
|
353
|
+
return all_transforms.pop(-1) if pop else all_transforms[-1]
|
322
354
|
|
323
355
|
def pop_transform(self, data, key: Hashable = None, check: bool = True):
|
324
356
|
"""
|
monai/utils/misc.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: monai-weekly
|
3
|
-
Version: 1.5.
|
3
|
+
Version: 1.5.dev2515
|
4
4
|
Summary: AI Toolkit for Healthcare Imaging
|
5
5
|
Home-page: https://monai.io/
|
6
6
|
Author: MONAI Consortium
|
@@ -49,6 +49,7 @@ Requires-Dist: lmdb; extra == "all"
|
|
49
49
|
Requires-Dist: psutil; extra == "all"
|
50
50
|
Requires-Dist: cucim-cu12; (platform_system == "Linux" and python_version >= "3.9" and python_version <= "3.10") and extra == "all"
|
51
51
|
Requires-Dist: openslide-python; extra == "all"
|
52
|
+
Requires-Dist: openslide-bin; extra == "all"
|
52
53
|
Requires-Dist: tifffile; (platform_system == "Linux" or platform_system == "Darwin") and extra == "all"
|
53
54
|
Requires-Dist: imagecodecs; (platform_system == "Linux" or platform_system == "Darwin") and extra == "all"
|
54
55
|
Requires-Dist: pandas; extra == "all"
|
@@ -105,6 +106,7 @@ Provides-Extra: cucim
|
|
105
106
|
Requires-Dist: cucim-cu12; (platform_system == "Linux" and python_version >= "3.9" and python_version <= "3.10") and extra == "cucim"
|
106
107
|
Provides-Extra: openslide
|
107
108
|
Requires-Dist: openslide-python; extra == "openslide"
|
109
|
+
Requires-Dist: openslide-bin; extra == "openslide"
|
108
110
|
Provides-Extra: tifffile
|
109
111
|
Requires-Dist: tifffile; (platform_system == "Linux" or platform_system == "Darwin") and extra == "tifffile"
|
110
112
|
Provides-Extra: imagecodecs
|
@@ -1,5 +1,5 @@
|
|
1
|
-
monai/__init__.py,sha256=
|
2
|
-
monai/_version.py,sha256=
|
1
|
+
monai/__init__.py,sha256=cz2IbPvV5WFZb9B6iwGQYTcCSUfAkFQgg6Z7EyBu82E,4095
|
2
|
+
monai/_version.py,sha256=PvcDKbDCwlMsws9Aqn0bDOjHV15xcRqSmMGahjEBO5k,503
|
3
3
|
monai/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
4
|
monai/_extensions/__init__.py,sha256=NEBPreRhQ8H9gVvgrLr_y52_TmqB96u_u4VQmeNT93I,642
|
5
5
|
monai/_extensions/loader.py,sha256=7SiKw36q-nOzH8CRbBurFrz7GM40GCu7rc93Tm8XpnI,3643
|
@@ -55,8 +55,9 @@ monai/apps/generation/maisi/networks/diffusion_model_unet_maisi.py,sha256=XFOiy6
|
|
55
55
|
monai/apps/mmars/__init__.py,sha256=BolpgEi9jNBgrOQd3Kwp-9QQLeWQwQtlN_MJkK1eu5s,726
|
56
56
|
monai/apps/mmars/mmars.py,sha256=24JylLuw-qTDsTnTK4Y5kAbF_nWdivrSRS8EMGy69oQ,13134
|
57
57
|
monai/apps/mmars/model_desc.py,sha256=k7WSMRuyQN8xPax8aUmGKiTNZmcVatdqPYCgxDih-x4,9996
|
58
|
-
monai/apps/nnunet/__init__.py,sha256=
|
58
|
+
monai/apps/nnunet/__init__.py,sha256=u_VSta8R-Ta0oKUxxzDKXo9BK6WL7kAsGPRee2Gp9GY,963
|
59
59
|
monai/apps/nnunet/__main__.py,sha256=qrloBLymK98OPcaBKocrlF8io2h4mUuXJPFVLZT-XDo,832
|
60
|
+
monai/apps/nnunet/nnunet_bundle.py,sha256=ucqx9P2oDC0G5AYbNYwZvs4TgcoNVmOEIDTGZpi4jbw,24758
|
60
61
|
monai/apps/nnunet/nnunetv2_runner.py,sha256=0VZTpzmjkOhamdqYGd9bhTdZcRDeOsMOBIjaW1Upi8w,48001
|
61
62
|
monai/apps/nnunet/utils.py,sha256=OwLBcc0LZ_n7-ofE8EgkgmIHT23wq1xySCD6lphSjz0,6761
|
62
63
|
monai/apps/nuclick/__init__.py,sha256=s9djSd6kvViPnFvMR11Dgd30Lv4oY6FaPJr4ZZJZLq0,573
|
@@ -114,7 +115,7 @@ monai/bundle/config_item.py,sha256=rMjXSGkjJZdi04BwSHwCcIwzIb_TflmC3xDhC3SVJRs,1
|
|
114
115
|
monai/bundle/config_parser.py,sha256=cGyEn-cqNk0rEEZ1Qiv6UydmIDvtWZcMVljyfVm5i50,23025
|
115
116
|
monai/bundle/properties.py,sha256=iN3K4FVmN9ny1Hw9p5j7_ULcCdSD8PmrR7qXxbNz49k,11582
|
116
117
|
monai/bundle/reference_resolver.py,sha256=GXCMK4iogxxE6VocsmAbUrcXosmC5arnjeG9zYhHKpg,16748
|
117
|
-
monai/bundle/scripts.py,sha256=
|
118
|
+
monai/bundle/scripts.py,sha256=69sZ_W9xQzuVnRL0Ma64Ac742UIe9iCvTKHwkuCU0a8,91064
|
118
119
|
monai/bundle/utils.py,sha256=t-22uFvLn7Yy-dr1v1U33peNOxgAmU4TJiGAbsBrUKs,10108
|
119
120
|
monai/bundle/workflows.py,sha256=CuhmFq1AWsN3ATiYJCSakPOxrOdGutl6vkpo9sxe8gU,34369
|
120
121
|
monai/config/__init__.py,sha256=CN28CfTdsp301gv8YXfVvkbztCfbAqrLKrJi_C8oP9s,1048
|
@@ -263,7 +264,7 @@ monai/networks/blocks/dynunet_block.py,sha256=kg8NNTL4nBqsy6gBcxmS5ZCPxlhWM_iB0B
|
|
263
264
|
monai/networks/blocks/encoder.py,sha256=NwH5VSQLwamJqrSbZSdQqMwZCk80CPbSpMGEE0r0Cwo,3669
|
264
265
|
monai/networks/blocks/fcn.py,sha256=mnCMrxhUdj2yZ0DPIj0Xf9OKVdv-qhG1BpnAg5j7q6c,9024
|
265
266
|
monai/networks/blocks/feature_pyramid_network.py,sha256=zHMXB_hl92kmuJIe0rTvQlzQn1W77WTQZ7XaoivktEw,10631
|
266
|
-
monai/networks/blocks/fft_utils_t.py,sha256=
|
267
|
+
monai/networks/blocks/fft_utils_t.py,sha256=rarC7BUw9TeawhDQESC5oJhPvbg_OFFNTa1HsNs3GKg,8017
|
267
268
|
monai/networks/blocks/localnet_block.py,sha256=b2-ZZvkMPphHJZYTbwEZDhqA-mMBSFM5WQOoohk_6W4,11456
|
268
269
|
monai/networks/blocks/mednext_block.py,sha256=GKaFkRvmho79yxwfYyeSaJtHFtk185dY0tA4_rPnsQA,10487
|
269
270
|
monai/networks/blocks/mlp.py,sha256=qw_jgyrYwoQ5WYBM1rtSSaO4C837ZbctoRKhh_BQQFI,3341
|
@@ -357,7 +358,7 @@ monai/optimizers/utils.py,sha256=GVsJsZWO2aAP9IzwhXgca_9gUNHFClup6qG4ZFs42z4,413
|
|
357
358
|
monai/transforms/__init__.py,sha256=-LmAa_W5fJxm5I_btvAONNebWe2exa7IWwcvYrNxzCc,16744
|
358
359
|
monai/transforms/adaptors.py,sha256=LpYChldlOur-VFgu_nBIBze0J841-NWgf0UHvvHRNPU,8796
|
359
360
|
monai/transforms/compose.py,sha256=EQPQkxdzrLoVvoeKVa8KABmB_Oc9pg-G0EsSS8aTraw,39189
|
360
|
-
monai/transforms/inverse.py,sha256=
|
361
|
+
monai/transforms/inverse.py,sha256=BARCRaUYabIajmOfsyNbN47v1v0eQiFAU7Vyvz-IddU,20287
|
361
362
|
monai/transforms/inverse_batch_transform.py,sha256=fMbukZq2P99BhqqMuWZFJ9uboZ5dN61MBvvicwf40V0,7055
|
362
363
|
monai/transforms/nvtx.py,sha256=1EKEXZIhTUFKoIrJmd_fevwrHwo731dVFUFJQFiOk3w,3386
|
363
364
|
monai/transforms/traits.py,sha256=F8kmhnekTyaAdo8wIFjO3-uqpVtmFym3mNxbYbyvkFI,3563
|
@@ -410,7 +411,7 @@ monai/utils/deprecate_utils.py,sha256=gKeEV4MsI51qeQ5gci2me_C-0e-tDwa3VZzd3XPQqL
|
|
410
411
|
monai/utils/dist.py,sha256=7brB42CvdS8Jvr8Y7hfqov1uk6NNnYea9dYfgMYy0BY,8578
|
411
412
|
monai/utils/enums.py,sha256=aupxnORUHqVPF2Ac5nxstsP5aIyewMoqgGb88D62yxg,19931
|
412
413
|
monai/utils/jupyter_utils.py,sha256=BYtj80LWQAYg5RWPj5g4j2AMCzLECvAcnZdXns0Ruw8,15651
|
413
|
-
monai/utils/misc.py,sha256=
|
414
|
+
monai/utils/misc.py,sha256=M0oCfj55pZTrcYF0QgyS91JflqBwxSuNnOifl2HRSZk,31759
|
414
415
|
monai/utils/module.py,sha256=R37PpCNCcHQvjjZFbNjNyzWb3FURaKLxQucjhzQk0eU,26087
|
415
416
|
monai/utils/nvtx.py,sha256=i9JBxR1uhW1ZCgLPLlTx8b907QlXkFzJyTBLMlFjhtU,6876
|
416
417
|
monai/utils/ordering.py,sha256=0nlA5b5QpVCHbtiCbTC-YsqjTmjm0bub0IeJhGFBOes,8270
|
@@ -425,7 +426,7 @@ monai/visualize/img2tensorboard.py,sha256=n4ztSa5BQAUxSTGvi2tp45v-F7-RNgSlbsdy-9
|
|
425
426
|
monai/visualize/occlusion_sensitivity.py,sha256=OQHEJLyIhB8zWqQsfKaX-1kvCjWFVYtLfS4dFC0nKFI,18160
|
426
427
|
monai/visualize/utils.py,sha256=B-MhTVs7sQbIqYS3yPnpBwPw2K82rE2PBtGIfpwZtWM,9894
|
427
428
|
monai/visualize/visualizer.py,sha256=qckyaMZCbezYUwE20k5yc-Pb7UozVavMDbrmyQwfYHY,1377
|
428
|
-
monai_weekly-1.5.
|
429
|
+
monai_weekly-1.5.dev2515.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
429
430
|
tests/apps/__init__.py,sha256=s9djSd6kvViPnFvMR11Dgd30Lv4oY6FaPJr4ZZJZLq0,573
|
430
431
|
tests/apps/test_auto3dseg_bundlegen.py,sha256=FpTJo9Lfe8vdhGuWeZ9y1BQmqYwTt-s8mDVtoLGAz_I,5594
|
431
432
|
tests/apps/test_check_hash.py,sha256=MuZslW2DDCxHKEo6-PiL7hnbxGuZRRYf6HOh3ZQv1qQ,1761
|
@@ -507,7 +508,7 @@ tests/apps/vista3d/test_vista3d_sampler.py,sha256=-luQCe3Hhle2PC9AkFCUgK8gozOD0O
|
|
507
508
|
tests/apps/vista3d/test_vista3d_transforms.py,sha256=nAPiDBNWeXLoW7ax3HHL63t5jqzQ3HFa-6wTvdyqVJk,3280
|
508
509
|
tests/bundle/__init__.py,sha256=s9djSd6kvViPnFvMR11Dgd30Lv4oY6FaPJr4ZZJZLq0,573
|
509
510
|
tests/bundle/test_bundle_ckpt_export.py,sha256=VnpigCoBAAc2lo0rWOpVMg0IYGB6vbHXL8xLtB1Pkio,4622
|
510
|
-
tests/bundle/test_bundle_download.py,sha256=
|
511
|
+
tests/bundle/test_bundle_download.py,sha256=QaQOde6MCpUeHhoYNTd1GjDQmT0j01kTMw53qocOzxQ,20695
|
511
512
|
tests/bundle/test_bundle_get_data.py,sha256=lQh321mev_7fsLXRg0Tq5uEjuQILethDHRKzB6VV0o4,3667
|
512
513
|
tests/bundle/test_bundle_push_to_hf_hub.py,sha256=Zjl6xDwRKgkS6jvO5dzMBaTLEd4EXyMXp0_wzDNSY3g,1740
|
513
514
|
tests/bundle/test_bundle_trt_export.py,sha256=png-2SGjBSt46LXSz-PLprOXwJ0WkC_3YLR3Ibk_WBc,6344
|
@@ -596,6 +597,7 @@ tests/integration/test_integration_determinism.py,sha256=AiSBXHcPwDtKRbt_lejI-ID
|
|
596
597
|
tests/integration/test_integration_fast_train.py,sha256=WxEIJV52F0Cf2wmGlIQDiVs1m2QZrvxmta_UAsa0OCI,9736
|
597
598
|
tests/integration/test_integration_gpu_customization.py,sha256=z-w6iBaY72LEi8TBVxZuzvsEBgBecZAP2YPwl6KFUhA,5547
|
598
599
|
tests/integration/test_integration_lazy_samples.py,sha256=d_4GNy_ixiizvehIYJBht4dQropRsqQy7rJOpW7OkZ8,9198
|
600
|
+
tests/integration/test_integration_nnunet_bundle.py,sha256=V1X57m7cJwFsR5YfgzFLtCRe8vWx_wP5b2OZkb3Ux9Y,6813
|
599
601
|
tests/integration/test_integration_nnunetv2_runner.py,sha256=KgyAI0Irl93KDLZyo8fGZjEL8dS5UXPKQz_osRfhtSU,4332
|
600
602
|
tests/integration/test_integration_segmentation_3d.py,sha256=TSV8tdiloK4_E03DgM1SqJxMo4fcH-Ta1NutG-3cPFc,13229
|
601
603
|
tests/integration/test_integration_sliding_window.py,sha256=N0CYquebXk8N3KiPcGWbD9KAf5UHuXx2pqAZY5PQVSE,3769
|
@@ -785,7 +787,7 @@ tests/networks/nets/test_spade_diffusion_model_unet.py,sha256=LEN1PAGid0DMdP2NyS
|
|
785
787
|
tests/networks/nets/test_spade_vaegan.py,sha256=ur1SPoXEmpr_8KwVS6-E_1tIPMBKpNqsvHJ7z5-obzA,5632
|
786
788
|
tests/networks/nets/test_swin_unetr.py,sha256=gj1Jqg8xTBYdCZWCR4Y9_ZlGNNYVTkCPmB2sdF2xIDM,5690
|
787
789
|
tests/networks/nets/test_torchvision_fc_model.py,sha256=oNb-PaOhIAjOrpnsXApC2hKSUK6lMutIEinMrCOKQoA,6397
|
788
|
-
tests/networks/nets/test_transchex.py,sha256=
|
790
|
+
tests/networks/nets/test_transchex.py,sha256=G8WHEAlZlovlpJjlWD5cfeGD6FTFzUM6Y7_bVehHNY0,3293
|
789
791
|
tests/networks/nets/test_transformer.py,sha256=rsGjemv0JV9SMTTWiZ8Sz_w5t5Rkz15b2rjJit4R2XA,4218
|
790
792
|
tests/networks/nets/test_unet.py,sha256=wXwaXkufYDjFXzQ-AygbePAwigZLLaY58sGygizF3Q4,5801
|
791
793
|
tests/networks/nets/test_unetr.py,sha256=3_V4VWfsQVB22-T8XTSRra3Her2XrLx5gzIRHis2zPs,5325
|
@@ -904,10 +906,7 @@ tests/transforms/test_histogram_normalized.py,sha256=dwaZ9ux-sgYKx0cy6aQqt5L07ts
|
|
904
906
|
tests/transforms/test_image_filter.py,sha256=ERomnuZRJkdFL7wAObos22qxry2jpPCtT2QXML6hIWY,11619
|
905
907
|
tests/transforms/test_intensity_stats.py,sha256=MjWFd_Oeu0Ay-rd9Asp2ZLiwg75u4SFmHvj-VwBGiHA,2795
|
906
908
|
tests/transforms/test_intensity_statsd.py,sha256=tLuq1EQ6aVjGP_0xCCRVDZ1kPvtaqBuRwWy-EuxMhmQ,3595
|
907
|
-
tests/transforms/test_inverse.py,sha256=vCj7yrm1L15HBEUEUdUgkXbfPckCnvslIltHncee7dk,18982
|
908
909
|
tests/transforms/test_inverse_collation.py,sha256=8iptVqniLP77pfVJnLpE13xcthO-BgXoZCza8sGPAcA,5451
|
909
|
-
tests/transforms/test_invert.py,sha256=gBJgAaVs4WKTkaYS8JNXH3qeKM8DBAZvF_t3GoPmVPU,4292
|
910
|
-
tests/transforms/test_invertd.py,sha256=J7e_unCTiD1fXzAXLvCCKkVCGqNMJcIPentoOjmwxq4,6094
|
911
910
|
tests/transforms/test_k_space_spike_noise.py,sha256=9jP6fFggTKScbR8orjJacSYtzwv1gQMuomPiaz1o1_0,2863
|
912
911
|
tests/transforms/test_k_space_spike_noised.py,sha256=_n7b-0S6N5HEJBTLYQVKka8bpKZ2eL1xqOMUqB1cSi0,3511
|
913
912
|
tests/transforms/test_keep_largest_connected_component.py,sha256=yXLKF3FdaVN5FFLq0okNunlQc0OI6CrAOhzIM9Gjcg4,14759
|
@@ -1105,8 +1104,12 @@ tests/transforms/intensity/test_rand_histogram_shiftd.py,sha256=0TFQO5qz5GVFyH95
|
|
1105
1104
|
tests/transforms/intensity/test_scale_intensity_range_percentiles.py,sha256=y5O_BLLVxDqUmF3BvqNHgt5dg04FrwSK9qHEXI8IzXY,3986
|
1106
1105
|
tests/transforms/intensity/test_scale_intensity_range_percentilesd.py,sha256=ZHSiTTJn5Lp1OaJVff7eq94OAHCc7gJZLSsTSuNFAhM,3980
|
1107
1106
|
tests/transforms/inverse/__init__.py,sha256=s9djSd6kvViPnFvMR11Dgd30Lv4oY6FaPJr4ZZJZLq0,573
|
1107
|
+
tests/transforms/inverse/test_inverse.py,sha256=vCj7yrm1L15HBEUEUdUgkXbfPckCnvslIltHncee7dk,18982
|
1108
1108
|
tests/transforms/inverse/test_inverse_array.py,sha256=KIFCHhkQSbVbirheLk4c9Kz0Uqpf9m5Do4LkD026ISk,2769
|
1109
|
-
tests/transforms/inverse/
|
1109
|
+
tests/transforms/inverse/test_inverse_dict.py,sha256=Sd84lHWCr-IpWp8DUNspfSfRyfooBNa301hOS6pKJNE,4817
|
1110
|
+
tests/transforms/inverse/test_invert.py,sha256=gBJgAaVs4WKTkaYS8JNXH3qeKM8DBAZvF_t3GoPmVPU,4292
|
1111
|
+
tests/transforms/inverse/test_invertd.py,sha256=J7e_unCTiD1fXzAXLvCCKkVCGqNMJcIPentoOjmwxq4,6094
|
1112
|
+
tests/transforms/inverse/test_traceable_transform.py,sha256=1NEw_C755p_3Hr96qFYAYqSZQk1sRfuWiOK8N9z8hDs,1887
|
1110
1113
|
tests/transforms/post/__init__.py,sha256=s9djSd6kvViPnFvMR11Dgd30Lv4oY6FaPJr4ZZJZLq0,573
|
1111
1114
|
tests/transforms/post/test_label_filterd.py,sha256=OMIoSlbTu8owWE2WqcRfobUDRZE96mpLaE1fuWpdWdY,2597
|
1112
1115
|
tests/transforms/post/test_probnms.py,sha256=-HMnXkVLqXiMkvgJeD7jDoLkm74gNwE57iigi63waKo,2866
|
@@ -1186,7 +1189,7 @@ tests/visualize/test_vis_gradcam.py,sha256=WpA-pvTB75eZs7JoIc5qyvOV9PwgkzWI8-Vow
|
|
1186
1189
|
tests/visualize/utils/__init__.py,sha256=s9djSd6kvViPnFvMR11Dgd30Lv4oY6FaPJr4ZZJZLq0,573
|
1187
1190
|
tests/visualize/utils/test_blend_images.py,sha256=RVs2p_8RWQDfhLHDNNtZaMig27v8o0km7XxNa-zWjKE,2274
|
1188
1191
|
tests/visualize/utils/test_matshow3d.py,sha256=wXYj77L5Jvnp0f6DvL1rsi_-YlCxS0HJ9hiPmrbpuP8,5021
|
1189
|
-
monai_weekly-1.5.
|
1190
|
-
monai_weekly-1.5.
|
1191
|
-
monai_weekly-1.5.
|
1192
|
-
monai_weekly-1.5.
|
1192
|
+
monai_weekly-1.5.dev2515.dist-info/METADATA,sha256=0PRAJeakVM2LmWoAgV6ooMkk8Z-btuFbcstMou9dQaA,12104
|
1193
|
+
monai_weekly-1.5.dev2515.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
1194
|
+
monai_weekly-1.5.dev2515.dist-info/top_level.txt,sha256=hn2Y6P9xBf2R8faMeVMHhPMvrdDKxMsIOwMDYI0yTjs,12
|
1195
|
+
monai_weekly-1.5.dev2515.dist-info/RECORD,,
|
@@ -63,6 +63,8 @@ TEST_CASE_5 = [
|
|
63
63
|
|
64
64
|
TEST_CASE_6 = [["models/model.pt", "configs/train.json"], "renalStructures_CECT_segmentation", "0.1.0"]
|
65
65
|
|
66
|
+
TEST_CASE_6_HF = [["models/model.pt", "configs/train.yaml"], "mednist_ddpm", "1.0.1"]
|
67
|
+
|
66
68
|
TEST_CASE_7 = [
|
67
69
|
["model.pt", "model.ts", "network.json", "test_output.pt", "test_input.pt"],
|
68
70
|
"test_bundle",
|
@@ -193,6 +195,7 @@ class TestDownload(unittest.TestCase):
|
|
193
195
|
|
194
196
|
@parameterized.expand([TEST_CASE_6])
|
195
197
|
@skip_if_quick
|
198
|
+
@skipUnless(has_huggingface_hub, "Requires `huggingface_hub`.")
|
196
199
|
def test_monaihosting_source_download_bundle(self, bundle_files, bundle_name, version):
|
197
200
|
with skip_if_downloading_fails():
|
198
201
|
# download a single file from url, also use `args_file`
|
@@ -239,6 +242,7 @@ class TestDownload(unittest.TestCase):
|
|
239
242
|
self.assertEqual(_list_latest_versions(data), ["1.1", "1.0"])
|
240
243
|
|
241
244
|
@skip_if_quick
|
245
|
+
@skipUnless(has_huggingface_hub, "Requires `huggingface_hub`.")
|
242
246
|
@patch("monai.bundle.scripts.get_versions", return_value={"version": "1.2"})
|
243
247
|
def test_download_monaihosting(self, mock_get_versions):
|
244
248
|
"""Test checking MONAI version from a metadata file."""
|
@@ -333,6 +337,7 @@ class TestLoad(unittest.TestCase):
|
|
333
337
|
|
334
338
|
@parameterized.expand([TEST_CASE_8])
|
335
339
|
@skip_if_quick
|
340
|
+
@skipUnless(has_huggingface_hub, "Requires `huggingface_hub`.")
|
336
341
|
def test_load_weights_with_net_override(self, bundle_name, device, net_override):
|
337
342
|
with skip_if_downloading_fails():
|
338
343
|
# download bundle, and load weights from the downloaded path
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# Copyright (c) MONAI Consortium
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
3
|
+
# you may not use this file except in compliance with the License.
|
4
|
+
# You may obtain a copy of the License at
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
# Unless required by applicable law or agreed to in writing, software
|
7
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
8
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
9
|
+
# See the License for the specific language governing permissions and
|
10
|
+
# limitations under the License.
|
11
|
+
|
12
|
+
from __future__ import annotations
|
13
|
+
|
14
|
+
import os
|
15
|
+
import tempfile
|
16
|
+
import unittest
|
17
|
+
from pathlib import Path
|
18
|
+
|
19
|
+
import numpy as np
|
20
|
+
|
21
|
+
from monai.apps.nnunet import nnUNetV2Runner
|
22
|
+
from monai.apps.nnunet.nnunet_bundle import (
|
23
|
+
convert_nnunet_to_monai_bundle,
|
24
|
+
get_nnunet_monai_predictor,
|
25
|
+
get_nnunet_trainer,
|
26
|
+
)
|
27
|
+
from monai.bundle.config_parser import ConfigParser
|
28
|
+
from monai.data import DataLoader, Dataset, create_test_image_3d
|
29
|
+
from monai.transforms import Compose, Decollated, EnsureChannelFirstd, LoadImaged, SaveImaged
|
30
|
+
from monai.utils import optional_import
|
31
|
+
from tests.test_utils import SkipIfBeforePyTorchVersion, skip_if_downloading_fails, skip_if_no_cuda, skip_if_quick
|
32
|
+
|
33
|
+
_, has_tb = optional_import("torch.utils.tensorboard", name="SummaryWriter")
|
34
|
+
_, has_nnunet = optional_import("nnunetv2")
|
35
|
+
|
36
|
+
sim_datalist: dict[str, list[dict]] = {
|
37
|
+
"testing": [{"image": "val_001.fake.nii.gz"}, {"image": "val_002.fake.nii.gz"}],
|
38
|
+
"training": [
|
39
|
+
{"fold": 0, "image": "tr_image_001.fake.nii.gz", "label": "tr_label_001.fake.nii.gz"},
|
40
|
+
{"fold": 0, "image": "tr_image_002.fake.nii.gz", "label": "tr_label_002.fake.nii.gz"},
|
41
|
+
{"fold": 1, "image": "tr_image_003.fake.nii.gz", "label": "tr_label_003.fake.nii.gz"},
|
42
|
+
{"fold": 1, "image": "tr_image_004.fake.nii.gz", "label": "tr_label_004.fake.nii.gz"},
|
43
|
+
{"fold": 2, "image": "tr_image_005.fake.nii.gz", "label": "tr_label_005.fake.nii.gz"},
|
44
|
+
{"fold": 2, "image": "tr_image_006.fake.nii.gz", "label": "tr_label_006.fake.nii.gz"},
|
45
|
+
{"fold": 3, "image": "tr_image_007.fake.nii.gz", "label": "tr_label_007.fake.nii.gz"},
|
46
|
+
{"fold": 3, "image": "tr_image_008.fake.nii.gz", "label": "tr_label_008.fake.nii.gz"},
|
47
|
+
{"fold": 4, "image": "tr_image_009.fake.nii.gz", "label": "tr_label_009.fake.nii.gz"},
|
48
|
+
{"fold": 4, "image": "tr_image_010.fake.nii.gz", "label": "tr_label_010.fake.nii.gz"},
|
49
|
+
],
|
50
|
+
}
|
51
|
+
|
52
|
+
|
53
|
+
@skip_if_quick
|
54
|
+
@SkipIfBeforePyTorchVersion((1, 13, 0))
|
55
|
+
@unittest.skipIf(not has_tb, "no tensorboard summary writer")
|
56
|
+
@unittest.skipIf(not has_nnunet, "no nnunetv2")
|
57
|
+
class TestnnUNetBundle(unittest.TestCase):
|
58
|
+
|
59
|
+
def setUp(self) -> None:
|
60
|
+
|
61
|
+
import nibabel as nib
|
62
|
+
|
63
|
+
self.test_dir = tempfile.TemporaryDirectory()
|
64
|
+
test_path = self.test_dir.name
|
65
|
+
|
66
|
+
sim_dataroot = os.path.join(test_path, "dataroot")
|
67
|
+
if not os.path.isdir(sim_dataroot):
|
68
|
+
os.makedirs(sim_dataroot)
|
69
|
+
|
70
|
+
self.sim_dataroot = sim_dataroot
|
71
|
+
# Generate a fake dataset
|
72
|
+
for d in sim_datalist["testing"] + sim_datalist["training"]:
|
73
|
+
im, seg = create_test_image_3d(24, 24, 24, rad_max=10, num_seg_classes=2)
|
74
|
+
nib_image = nib.Nifti1Image(im, affine=np.eye(4))
|
75
|
+
image_fpath = os.path.join(sim_dataroot, d["image"])
|
76
|
+
nib.save(nib_image, image_fpath)
|
77
|
+
|
78
|
+
if "label" in d:
|
79
|
+
nib_image = nib.Nifti1Image(seg, affine=np.eye(4))
|
80
|
+
label_fpath = os.path.join(sim_dataroot, d["label"])
|
81
|
+
nib.save(nib_image, label_fpath)
|
82
|
+
|
83
|
+
sim_json_datalist = os.path.join(sim_dataroot, "sim_input.json")
|
84
|
+
ConfigParser.export_config_file(sim_datalist, sim_json_datalist)
|
85
|
+
|
86
|
+
data_src_cfg = os.path.join(test_path, "data_src_cfg.yaml")
|
87
|
+
data_src = {"modality": "CT", "datalist": sim_json_datalist, "dataroot": sim_dataroot}
|
88
|
+
|
89
|
+
ConfigParser.export_config_file(data_src, data_src_cfg)
|
90
|
+
self.data_src_cfg = data_src_cfg
|
91
|
+
self.test_path = test_path
|
92
|
+
|
93
|
+
@skip_if_no_cuda
|
94
|
+
def test_nnunet_bundle(self) -> None:
|
95
|
+
runner = nnUNetV2Runner(
|
96
|
+
input_config=self.data_src_cfg, trainer_class_name="nnUNetTrainer_1epoch", work_dir=self.test_path
|
97
|
+
)
|
98
|
+
with skip_if_downloading_fails():
|
99
|
+
runner.run(run_train=False, run_find_best_configuration=False, run_predict_ensemble_postprocessing=False)
|
100
|
+
|
101
|
+
nnunet_trainer = get_nnunet_trainer(
|
102
|
+
dataset_name_or_id=runner.dataset_name, fold=0, configuration="3d_fullres"
|
103
|
+
)
|
104
|
+
|
105
|
+
print("Max Epochs: ", nnunet_trainer.num_epochs)
|
106
|
+
print("Num Iterations: ", nnunet_trainer.num_iterations_per_epoch)
|
107
|
+
print("Train Batch dims: ", next(nnunet_trainer.dataloader_train.generator)["data"].shape)
|
108
|
+
print("Val Batch dims: ", next(nnunet_trainer.dataloader_val.generator)["data"].shape)
|
109
|
+
print("Network: ", nnunet_trainer.network)
|
110
|
+
print("Optimizer: ", nnunet_trainer.optimizer)
|
111
|
+
print("Loss Function: ", nnunet_trainer.loss)
|
112
|
+
print("LR Scheduler: ", nnunet_trainer.lr_scheduler)
|
113
|
+
print("Device: ", nnunet_trainer.device)
|
114
|
+
runner.train_single_model("3d_fullres", fold=0)
|
115
|
+
|
116
|
+
nnunet_config = {"dataset_name_or_id": "001", "nnunet_trainer": "nnUNetTrainer_1epoch"}
|
117
|
+
self.bundle_root = os.path.join("bundle_root")
|
118
|
+
|
119
|
+
Path(self.bundle_root).joinpath("models").mkdir(parents=True, exist_ok=True)
|
120
|
+
convert_nnunet_to_monai_bundle(nnunet_config, self.bundle_root, 0)
|
121
|
+
|
122
|
+
data_transforms = Compose([LoadImaged(keys="image"), EnsureChannelFirstd(keys="image")])
|
123
|
+
dataset = Dataset(
|
124
|
+
data=[{"image": os.path.join(self.test_path, "dataroot", "val_001.fake.nii.gz")}], transform=data_transforms
|
125
|
+
)
|
126
|
+
data_loader = DataLoader(dataset, batch_size=1)
|
127
|
+
input = next(iter(data_loader))
|
128
|
+
|
129
|
+
predictor = get_nnunet_monai_predictor(Path(self.bundle_root).joinpath("models", "fold_0"))
|
130
|
+
pred_batch = predictor(input["image"])
|
131
|
+
Path(self.sim_dataroot).joinpath("predictions").mkdir(parents=True, exist_ok=True)
|
132
|
+
|
133
|
+
post_processing_transforms = Compose(
|
134
|
+
[
|
135
|
+
Decollated(keys=None, detach=True),
|
136
|
+
# Not needed after reading the data directly from the MONAI LoadImaged Transform
|
137
|
+
# Transposed(keys="pred", indices=[0, 3, 2, 1]),
|
138
|
+
SaveImaged(
|
139
|
+
keys="pred", output_dir=Path(self.sim_dataroot).joinpath("predictions"), output_postfix="pred"
|
140
|
+
),
|
141
|
+
]
|
142
|
+
)
|
143
|
+
post_processing_transforms({"pred": pred_batch})
|
144
|
+
|
145
|
+
def tearDown(self) -> None:
|
146
|
+
self.test_dir.cleanup()
|
147
|
+
|
148
|
+
|
149
|
+
if __name__ == "__main__":
|
150
|
+
unittest.main()
|
@@ -18,7 +18,7 @@ from parameterized import parameterized
|
|
18
18
|
|
19
19
|
from monai.networks import eval_mode
|
20
20
|
from monai.networks.nets.transchex import Transchex
|
21
|
-
from tests.test_utils import skip_if_quick
|
21
|
+
from tests.test_utils import skip_if_downloading_fails, skip_if_quick
|
22
22
|
|
23
23
|
TEST_CASE_TRANSCHEX = []
|
24
24
|
for drop_out in [0.4]:
|
@@ -49,7 +49,8 @@ for drop_out in [0.4]:
|
|
49
49
|
class TestTranschex(unittest.TestCase):
|
50
50
|
@parameterized.expand(TEST_CASE_TRANSCHEX)
|
51
51
|
def test_shape(self, input_param, expected_shape):
|
52
|
-
|
52
|
+
with skip_if_downloading_fails():
|
53
|
+
net = Transchex(**input_param)
|
53
54
|
with eval_mode(net):
|
54
55
|
result = net(torch.randint(2, (2, 512)), torch.randint(2, (2, 512)), torch.randn((2, 3, 224, 224)))
|
55
56
|
self.assertEqual(result.shape, expected_shape)
|