Daniel Nouri commited on
Commit
5620f04
1 Parent(s): de01502

Initial commit: Hugging Face Hub MONAI integration demo

Browse files
Files changed (2) hide show
  1. app.py +226 -0
  2. 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 :-)