Spaces:
Runtime error
Runtime error
Daniel Nouri
commited on
Commit
•
5620f04
1
Parent(s):
de01502
Initial commit: Hugging Face Hub MONAI integration demo
Browse files- app.py +226 -0
- requirements.txt +9 -0
app.py
ADDED
@@ -0,0 +1,226 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from glob import glob
|
2 |
+
import logging
|
3 |
+
from matplotlib import pyplot as plt
|
4 |
+
import os
|
5 |
+
|
6 |
+
from monai.bundle.scripts import upload_zoo_bundle_to_hf
|
7 |
+
import streamlit as st
|
8 |
+
import torch
|
9 |
+
|
10 |
+
|
11 |
+
def main():
|
12 |
+
st.title("MONAI 🤗 Hugging Face Integration")
|
13 |
+
|
14 |
+
st.write("""\
|
15 |
+
Here's a demo of a prototype integration between
|
16 |
+
[MONAI](https://monai.io/) and the [Hugging Face
|
17 |
+
Hub](https://huggingface.co/docs/hub/index), which allows for
|
18 |
+
uploading models to the Hub and downloading them. The integration
|
19 |
+
itself is implemented in [this
|
20 |
+
branch](https://github.com/dnouri/MONAI/tree/dnouri/huggingface-support)
|
21 |
+
of MONAI.
|
22 |
+
""")
|
23 |
+
|
24 |
+
st.write("""\
|
25 |
+
## Uploading models to the Hub ⬆
|
26 |
+
|
27 |
+
The new `upload_zoo_bundle_to_hf` command allows us to upload models
|
28 |
+
from the existing [MONAI Model
|
29 |
+
Zoo](https://github.com/Project-MONAI/model-zoo) on Github directly
|
30 |
+
onto the Hugging Face Hub.
|
31 |
+
|
32 |
+
The `--name` option specifies the [filename of an existing
|
33 |
+
model](https://github.com/Project-MONAI/model-zoo/releases/tag/hosting_storage_v1)
|
34 |
+
in the MONAI Model Zoo, while the `--hf_organization` specifies the
|
35 |
+
name of the organization to upload to, in the Hugging Face Hub,
|
36 |
+
whereas `--hf_token` is the [HF user access
|
37 |
+
token](https://huggingface.co/docs/hub/security-tokens).
|
38 |
+
|
39 |
+
An additional `--hf_card_data` option allows us to specify [model card
|
40 |
+
metadata](https://huggingface.co/docs/hub/models-cards#model-card-metadata)
|
41 |
+
to be added to the Hugging Face model card.
|
42 |
+
|
43 |
+
An example call to the `upload_zoo_bundle_to_hf` script looks like
|
44 |
+
this:
|
45 |
+
|
46 |
+
```bash
|
47 |
+
python -m monai.bundle upload_zoo_bundle_to_hf \\
|
48 |
+
--name spleen_ct_segmentation_v0.1.0 \\
|
49 |
+
--hf_organization dnouri --hf_token mytoken \\
|
50 |
+
--hf_card_data '{"lang": "en"}'
|
51 |
+
```
|
52 |
+
|
53 |
+
An example of a thus automatically uploaded model can be found
|
54 |
+
[here](https://huggingface.co/dnouri/spleen_ct_segmentation).
|
55 |
+
|
56 |
+
### Try it out!
|
57 |
+
|
58 |
+
To try out uploading your own model, please provide the information below:
|
59 |
+
""")
|
60 |
+
filename = st.text_input("Filename of MONAI Model Zoo model "
|
61 |
+
"(e.g. ventricular_short_axis_3label_v0.1.0.zip)")
|
62 |
+
username = st.text_input("Hub organization or user name (e.g. dnouri)")
|
63 |
+
card_data = st.text_input("Optional model card metadata",
|
64 |
+
value='{"tags": ["MONAI"]}')
|
65 |
+
token = st.text_input("Hugging Face user access token")
|
66 |
+
|
67 |
+
if filename and username and token:
|
68 |
+
st.write("Please wait...")
|
69 |
+
upload_zoo_bundle_to_hf(
|
70 |
+
name=filename,
|
71 |
+
hf_organization=username,
|
72 |
+
hf_token=token,
|
73 |
+
hf_card_data=card_data or None,
|
74 |
+
)
|
75 |
+
st.write(f"""\
|
76 |
+
Done! You should be able to find the [result here](https://huggingface.co/{username}/{filename.rsplit("_", 1)[0]}).
|
77 |
+
""")
|
78 |
+
|
79 |
+
st.write("""\
|
80 |
+
## Downloading models from the Hub ⬇
|
81 |
+
|
82 |
+
Uploading isn't much fun if you can't also download the models from
|
83 |
+
the Hub! To help with that, we've added support for the Hugging Face
|
84 |
+
Hub to the existing MONAI bundle `download` command.
|
85 |
+
|
86 |
+
The `download` command's default `--source` is `github`. We'll choose
|
87 |
+
`huggingface` instead to download from the Hub.
|
88 |
+
|
89 |
+
The `--name` of the model is the name of your model on the Hub,
|
90 |
+
e.g. `ventricular_short_axis_3label`. Note that as per MONAI
|
91 |
+
convention, we do not specify the version name here. (Future versions of
|
92 |
+
this command might allow for downloading specific versions, or tags.)
|
93 |
+
|
94 |
+
The `--repo` normally points to the MONAI Model Zoo's ['hosting
|
95 |
+
storage' release page on
|
96 |
+
Github](https://github.com/Project-MONAI/model-zoo/releases/tag/hosting_storage_v1).
|
97 |
+
When we call `download` with the `huggingface` source, we'll require
|
98 |
+
the `--repo` argument to point to the organization or user name that
|
99 |
+
hosts the model, e.g. `dnouri`. (While this choice is a bit
|
100 |
+
confusing, it also reflects an attempt to pragmatically blend concepts
|
101 |
+
from both MONAI bundles and the Hub. Future versions might improve on
|
102 |
+
this.)
|
103 |
+
|
104 |
+
An example call to the `upload_zoo_bundle_to_hf` script that perhaps
|
105 |
+
downloads the model that we uploaded previously, looks like this:
|
106 |
+
|
107 |
+
```bash
|
108 |
+
python -m monai.bundle download \\
|
109 |
+
--name spleen_ct_segmentation \\
|
110 |
+
--source huggingface --repo dnouri
|
111 |
+
```
|
112 |
+
""")
|
113 |
+
|
114 |
+
st.write("""\
|
115 |
+
## Use model for inference 🧠
|
116 |
+
|
117 |
+
To use the `spleen_ct_segmentation` pretrained model to do inference,
|
118 |
+
we'll first load it into memory (as a TorchScript module) using the
|
119 |
+
`load` function below. This will download the model from the Hugging
|
120 |
+
Face Hub, as `load` uses the aforementioned `download` under the hood:
|
121 |
+
""")
|
122 |
+
# The next line is a workaround against a buggy interaction
|
123 |
+
# between how streamlit sets up stderr and how tqdm uses it:
|
124 |
+
logging.getLogger().setLevel(logging.NOTSET)
|
125 |
+
|
126 |
+
with st.echo():
|
127 |
+
from monai.bundle.scripts import load
|
128 |
+
|
129 |
+
model, metadata, extra = load(
|
130 |
+
name="spleen_ct_segmentation",
|
131 |
+
source="huggingface",
|
132 |
+
repo="dnouri",
|
133 |
+
load_ts_module=True,
|
134 |
+
progress=False,
|
135 |
+
)
|
136 |
+
|
137 |
+
st.write("""\
|
138 |
+
This will produce a model, but we'll also need the corresponding
|
139 |
+
transforms. These are defined in the MONAI bundle configuration
|
140 |
+
files. There's unfortunately not a convenient way to do this using a
|
141 |
+
MONAI bundle script function, so we'll have to reach into the MONAI
|
142 |
+
bowels for a bit:
|
143 |
+
""")
|
144 |
+
with st.echo():
|
145 |
+
from monai.bundle.config_parser import ConfigParser
|
146 |
+
from monai.bundle.scripts import _process_bundle_dir
|
147 |
+
|
148 |
+
model_dir = _process_bundle_dir() / "spleen_ct_segmentation"
|
149 |
+
config_paths = [
|
150 |
+
model_dir / "configs" / "train.json",
|
151 |
+
model_dir / "configs" / "evaluate.json",
|
152 |
+
]
|
153 |
+
config = ConfigParser(
|
154 |
+
ConfigParser.load_config_files(config_paths),
|
155 |
+
)
|
156 |
+
preprocess = config.get_parsed_content("validate#preprocessing")
|
157 |
+
|
158 |
+
st.write("""\
|
159 |
+
We'll borrow code from the MONAI [Spleen 3D segmentation with MONAI
|
160 |
+
tutorial](https://github.com/Project-MONAI/tutorials/blob/main/3d_segmentation/spleen_segmentation_3d.ipynb)
|
161 |
+
to download the data that our `spleen_ct_segmentation` model was
|
162 |
+
trained with:
|
163 |
+
""")
|
164 |
+
|
165 |
+
with st.echo():
|
166 |
+
from monai.apps import download_and_extract
|
167 |
+
|
168 |
+
root_dir = os.environ.get(
|
169 |
+
"MONAI_DATA_DIRECTORY",
|
170 |
+
os.path.expanduser("~/.cache/monai_data_directory")
|
171 |
+
)
|
172 |
+
os.makedirs(root_dir, exist_ok=True)
|
173 |
+
resource = "https://msd-for-monai.s3-us-west-2.amazonaws.com/Task09_Spleen.tar"
|
174 |
+
md5 = "410d4a301da4e5b2f6f86ec3ddba524e"
|
175 |
+
compressed_file = os.path.join(root_dir, "Task09_Spleen.tar")
|
176 |
+
data_dir = os.path.join(root_dir, "Task09_Spleen")
|
177 |
+
if not os.path.exists(data_dir):
|
178 |
+
download_and_extract(resource, compressed_file, root_dir, md5)
|
179 |
+
|
180 |
+
train_images = sorted(
|
181 |
+
glob(os.path.join(data_dir, "imagesTr", "*.nii.gz")))
|
182 |
+
train_labels = sorted(
|
183 |
+
glob(os.path.join(data_dir, "labelsTr", "*.nii.gz")))
|
184 |
+
data_dicts = [
|
185 |
+
{"image": image_name, "label": label_name}
|
186 |
+
for image_name, label_name in zip(train_images, train_labels)
|
187 |
+
]
|
188 |
+
files = data_dicts
|
189 |
+
st.write(f"Downloaded {len(files)} files.")
|
190 |
+
|
191 |
+
st.write("""\
|
192 |
+
Finally, we can run inference and plot some results: 🥳
|
193 |
+
""")
|
194 |
+
image_idx = st.slider("Image number", 0, len(files))
|
195 |
+
|
196 |
+
with st.echo():
|
197 |
+
from monai.inferers import sliding_window_inference
|
198 |
+
|
199 |
+
data = preprocess(files[image_idx])
|
200 |
+
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
|
201 |
+
output = sliding_window_inference(
|
202 |
+
inputs=data["image"].to(device)[None, ...],
|
203 |
+
roi_size=(160, 160, 160),
|
204 |
+
sw_batch_size=4,
|
205 |
+
predictor=model.eval(),
|
206 |
+
)
|
207 |
+
|
208 |
+
fig, (ax1, ax2, ax3) = plt.subplots(1, 3)
|
209 |
+
ax1.set_title("Image")
|
210 |
+
ax2.set_title("Label")
|
211 |
+
ax3.set_title("Output")
|
212 |
+
ax1.imshow(data["image"][0, :, :, 80], cmap="gray")
|
213 |
+
ax2.imshow(data["label"][0, :, :, 80], cmap="gray")
|
214 |
+
output_img = (
|
215 |
+
torch.argmax(output, dim=1)[0, :, :, 80]
|
216 |
+
.cpu().detach().numpy()
|
217 |
+
)
|
218 |
+
ax3.imshow(
|
219 |
+
output_img,
|
220 |
+
cmap="gray",
|
221 |
+
)
|
222 |
+
st.pyplot(fig)
|
223 |
+
|
224 |
+
|
225 |
+
if __name__ == "__main__":
|
226 |
+
main()
|
requirements.txt
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
git+https://github.com/dnouri/MONAI.git@dnouri/huggingface-support
|
2 |
+
dicom
|
3 |
+
itk
|
4 |
+
huggingface_hub
|
5 |
+
modelcards
|
6 |
+
nibabel
|
7 |
+
pynrrd
|
8 |
+
streamlit
|
9 |
+
# trigger rebuild of cached layer with some random message :-)
|