Skip to content

Commit 33feab3

Browse files
mingxin-zhengpre-commit-ci[bot]wyli
authored
Add notebooks cell standards check (#1152)
Fixes #1143 . ### Description - Add checks for "Setup environment", "Setup imports", "print_config()" - Limit number of lines (100) in the output text - Fix redundant printing during notebook nightly test - Fix Auto3DSeg readme indentation. - Refractor the python code as suggested in a [previous comment](#1145 (comment)) ### Checks <!--- Put an `x` in all the boxes that apply, and remove the not applicable items --> - [x] Avoid including large-size files in the PR. - [x] Clean up long text outputs from code cells in the notebook. - [x] For security purposes, please check the contents and remove any sensitive info such as user names and private key. - [x] Ensure (1) hyperlinks and markdown anchors are working (2) use relative paths for tutorial repo files (3) put figure and graphs in the `./figure` folder - [x] Notebook runs automatically `./runner.sh -t <path to .ipynb file>` Signed-off-by: Mingxin Zheng <18563433+mingxin-zheng@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Wenqi Li <831580+wyli@users.noreply.github.com>
1 parent ba294b4 commit 33feab3

File tree

6 files changed

+266
-40
lines changed

6 files changed

+266
-40
lines changed

.github/contributing_templates/notebook/example_feature.ipynb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@
99
"Licensed under the Apache License, Version 2.0 (the \"License\"); \n",
1010
"you may not use this file except in compliance with the License. \n",
1111
"You may obtain a copy of the License at \n",
12-
" http://www.apache.org/licenses/LICENSE-2.0 \n",
12+
"&nbsp;&nbsp;&nbsp;&nbsp;http://www.apache.org/licenses/LICENSE-2.0 \n",
1313
"Unless required by applicable law or agreed to in writing, software \n",
1414
"distributed under the License is distributed on an \"AS IS\" BASIS, \n",
1515
"WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. \n",
1616
"See the License for the specific language governing permissions and \n",
17-
"limitations under the License. \n",
17+
"limitations under the License.\n",
1818
"\n",
1919
"# Your Tutorial Title"
2020
]
@@ -214,7 +214,7 @@
214214
],
215215
"metadata": {
216216
"kernelspec": {
217-
"display_name": "base",
217+
"display_name": "Python 3",
218218
"language": "python",
219219
"name": "python3"
220220
},
@@ -228,12 +228,12 @@
228228
"name": "python",
229229
"nbconvert_exporter": "python",
230230
"pygments_lexer": "ipython3",
231-
"version": "3.8.13"
231+
"version": "3.8.10 (default, Nov 14 2022, 12:59:47) \n[GCC 9.4.0]"
232232
},
233233
"orig_nbformat": 4,
234234
"vscode": {
235235
"interpreter": {
236-
"hash": "d4d1e4263499bec80672ea0156c357c1ee493ec2b1c70f0acce89fc37c4a6abe"
236+
"hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1"
237237
}
238238
}
239239
},

.github/workflows/guidelines.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: build
2+
3+
on:
4+
# quick tests for every pull request
5+
push:
6+
branches:
7+
- main
8+
pull_request:
9+
10+
jobs:
11+
# caching of these jobs:
12+
# - docker-20-03-py3-pip- (shared)
13+
# - ubuntu py37 pip-
14+
# - os-latest-pip- (shared)
15+
guidelines:
16+
runs-on: ubuntu-latest
17+
steps:
18+
- uses: actions/checkout@v3
19+
- name: Set up Python 3.8
20+
uses: actions/setup-python@v3
21+
with:
22+
python-version: 3.8
23+
- name: cache weekly timestamp
24+
id: pip-cache
25+
run: |
26+
echo "::set-output name=datew::$(date '+%Y-%V')"
27+
- name: cache for pip
28+
uses: actions/cache@v3
29+
id: cache
30+
with:
31+
path: ~/.cache/pip
32+
key: ${{ runner.os }}-pip-${{ steps.pip-cache.outputs.datew }}
33+
- name: Install dependencies
34+
run: |
35+
python -m pip install --upgrade pip wheel
36+
python -m pip install -r https://raw.githubusercontent.com/Project-MONAI/MONAI/dev/requirements-dev.txt
37+
python -m pip install -r requirements.txt
38+
- name: Guidelines notebook format check
39+
run: |
40+
$(pwd)/runner.sh --no-run --no-checks --cell-standard

CONTRIBUTING.md

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -59,23 +59,39 @@ Finally, the MONAI tutorial has a [README file](README.md) to communicate the im
5959

6060
All source code and notebook files should include copyright information at the top of the file.
6161

62-
NOTE: for Jupyter Notebook `.ipynb` files, the copyright information should appear at the top of the first markdown cell.
63-
There are extra two spaces at the end of each line to ensure no line auto-wrap in the markdown rendering in the display.
64-
65-
```markdown
66-
Copyright (c) MONAI Consortium
67-
Licensed under the Apache License, Version 2.0 (the "License");
68-
you may not use this file except in compliance with the License.
69-
You may obtain a copy of the License at
70-
http://www.apache.org/licenses/LICENSE-2.0
71-
Unless required by applicable law or agreed to in writing, software
72-
distributed under the License is distributed on an "AS IS" BASIS,
73-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
74-
See the License for the specific language governing permissions and
75-
limitations under the License.
76-
62+
NOTE: For script files (python, shell commands, and etc), please use the following header
63+
64+
```python
65+
# Copyright (c) MONAI Consortium
66+
# Licensed under the Apache License, Version 2.0 (the "License");
67+
# you may not use this file except in compliance with the License.
68+
# You may obtain a copy of the License at
69+
# http://www.apache.org/licenses/LICENSE-2.0
70+
# Unless required by applicable law or agreed to in writing, software
71+
# distributed under the License is distributed on an "AS IS" BASIS,
72+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
73+
# See the License for the specific language governing permissions and
74+
# limitations under the License.
7775
```
7876

77+
NOTE: For Jupyter notebook `.ipynb` files, the copyright information should appear at the top of the first markdown cell.
78+
There are *two extra spaces* at the end of each line to ensure no line auto-wrap in the markdown rendering in the display, e.g. the first line of the copyright should be `"Copyright (c) MONAI Consortium \n"` instead of `"Copyright (c) MONAI Consortium\n"` in the raw string format.
79+
Indentation in markdown is represented by consecutive 4 non-breaking space entities `&nbsp;` in the line.
80+
81+
Please copy the following header with formats and paste it in the first markdown cell of a Jupyter notebook:
82+
<pre>
83+
Copyright (c) MONAI Consortium <space>
84+
Licensed under the Apache License, Version 2.0 (the "License"); <space>
85+
you may not use this file except in compliance with the License. <space>
86+
You may obtain a copy of the License at <space>
87+
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;http://www.apache.org/licenses/LICENSE-2.0 <space>
88+
Unless required by applicable law or agreed to in writing, software <space>
89+
distributed under the License is distributed on an "AS IS" BASIS, <space>
90+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <space>
91+
See the License for the specific language governing permissions and <space>
92+
limitations under the License.
93+
</pre>
94+
7995
### Create a notebook
8096

8197
Jupyter Notebook is the preferred way of writing a new MONAI tutorial because we encourage contributors to visualize the outputs and keep the records with the code.

auto3dseg/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ We have demonstrated preliminary results of public datasets are described in the
4444
- [INSTANCE22](tasks/instance22/README.md)
4545
- [Beyond the Cranial Vault (BTCV) Abdomen Dataset](tasks/btcv/README.md)
4646
- Medical Segmentation Decathlon (MSD) Dataset
47-
- [Task04 Task04_Hippocampus](tasks/msd/Task04_Hippocampus/README.md)
47+
- [Task04 Task04_Hippocampus](tasks/msd/Task04_Hippocampus/README.md)
4848
- [Task05 Prostate](tasks/msd/Task05_Prostate/README.md)
4949
- [Task09 Spleen](tasks/msd/Task09_Spleen/README.md)
5050

ci/nbtest.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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+
import argparse
13+
import nbformat
14+
import re
15+
16+
def define_parser(parser):
17+
"""Define the parser for commands"""
18+
parser.add_argument("-f", "--filename", type=str, help="a .ipynb jupyter notebook", required=True)
19+
group = parser.add_mutually_exclusive_group()
20+
group.add_argument("-s", "--start", type=int, default=0, help="the starting index of target cells")
21+
group.add_argument("-i", "--index", type=int, help="the index of a target cell")
22+
parser.add_argument("-e", "--end", type=int, default=-1, help="the ending index of target cells")
23+
parser.add_argument("-k", "--keyword", type=str, default="", help="regex keyword to search in the target cell")
24+
parser.add_argument(
25+
"--type",
26+
type=str,
27+
default="markdown",
28+
help="the type of target cell (code/markdown). Default is markdown"
29+
)
30+
parser.add_argument(
31+
"--field",
32+
type=str,
33+
default="source",
34+
help="the type of target cell (code/markdown). Default is markdown"
35+
)
36+
37+
38+
def count_matches(filename, start, end, keyword, cell_type="markdown", field="source"):
39+
"""Count the number of keyword matches from start index to end index"""
40+
occurrences = []
41+
42+
with open(filename, "r") as f:
43+
notebook = nbformat.reads(f.read(), as_version=4)
44+
cells = notebook.cells[start:end]
45+
if not cells:
46+
raise ValueError(f"No cells extracted from index {start} to index {end}")
47+
for cell in cells:
48+
if cell.cell_type != cell_type:
49+
occurrences.append(0)
50+
else:
51+
# treat backslashes as literal characters
52+
keyword_rawstring = repr(keyword)[1:-1] # remove the single quotes before/after the word
53+
occurrences.append(len(re.findall(keyword_rawstring, str(cell[field])))) # value can be list of dict
54+
55+
return occurrences
56+
57+
def print_verification_bool(*args, **kwargs):
58+
"""Print true/false for whether target cells match the keyword and the cell_type to interface with bash"""
59+
60+
result = "true" if sum(count_matches(*args, **kwargs)) else "false"
61+
print(result)
62+
63+
def print_count_array(*args, **kwargs):
64+
"""Print number array of the matches to interface with bash"""
65+
66+
print(" ".join(map(str, count_matches(*args, **kwargs))))
67+
68+
def main():
69+
parser = argparse.ArgumentParser()
70+
# Create a "subparsers" object to hold the subcommands
71+
subparsers = parser.add_subparsers(title="subcommands", dest="subcommand")
72+
73+
# Create a parser for the "verify" subcommand
74+
parser_verify = subparsers.add_parser("verify")
75+
define_parser(parser_verify)
76+
# Create a parser for the "count" subcommand
77+
parser_count = subparsers.add_parser("count")
78+
define_parser(parser_count)
79+
80+
args = parser.parse_args()
81+
82+
if 'index' in args and args.index:
83+
args.start = args.index
84+
args.end = args.index + 1
85+
86+
if args.subcommand == "verify":
87+
print_verification_bool(args.filename, args.start, args.end, args.keyword, args.type, args.field)
88+
elif args.subcommand == "count":
89+
print_count_array(args.filename, args.start, args.end, args.keyword, args.type, args.field)
90+
else:
91+
print("No subcommand provided. Please use -h for help")
92+
93+
if __name__ == "__main__":
94+
main()

0 commit comments

Comments
 (0)