From 70d1eeeee96ce618faf85adf198d8f134bdd5106 Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Thu, 18 Oct 2018 17:41:47 +1300 Subject: [PATCH] :white_check_mark: Using a jupyter notebook to doctest jupyter notebooks Closes #10. Created a handy jupyter notebook with scripts to parse jupyter notebooks and execute doctests within the function calls! Added a few unit tests as a start to some (but not all) functions in data_prep.ipynb and srgan_train.ipynb. Fits with ease of use by allowing debugging in a jupyter notebook, but not (yet) compatible with code coverage solutions showing you % coverage of your code. Also note that matplotlib inline magic was removed, as plots still get plotted, and it doesn't work so well (sometimes) when you parse the .ipynb to a .py file with such magics. Created a download_to_path function in data_prep.ipynb as it's hard to create a test for the other functions requiring a big data file as input. --- Pipfile | 2 + Pipfile.lock | 287 +++++++++++++++++++++++++++++++++++++++++++++- README.md | 3 +- data_prep.ipynb | 43 ++++--- srgan_train.ipynb | 16 ++- test_ipynb.ipynb | 166 +++++++++++++++++++++++++++ 6 files changed, 495 insertions(+), 22 deletions(-) create mode 100644 test_ipynb.ipynb diff --git a/Pipfile b/Pipfile index 3afbd77..60a5942 100644 --- a/Pipfile +++ b/Pipfile @@ -23,6 +23,8 @@ toolz = "==0.9.0" tqdm = "==4.27.0" [dev-packages] +nbval = "==0.9.1" +pytest = "==3.9.1" [requires] python_version = "3.6" diff --git a/Pipfile.lock b/Pipfile.lock index f4ba764..d4db3e2 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "00136968eefab5059ce4a6f9a9260073579d42bc40158a38064f440ba4ae57be" + "sha256": "642f5d9917b6971688514ab4a8cca0dbc0cd6a95eecebc6c5e6c6bcc74f9ae33" }, "pipfile-spec": 6, "requires": { @@ -1039,5 +1039,288 @@ "version": "==1.1.0" } }, - "develop": {} + "develop": { + "atomicwrites": { + "hashes": [ + "sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0", + "sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee" + ], + "version": "==1.2.1" + }, + "attrs": { + "hashes": [ + "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", + "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb" + ], + "version": "==18.2.0" + }, + "backcall": { + "hashes": [ + "sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4", + "sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2" + ], + "version": "==0.1.0" + }, + "coverage": { + "hashes": [ + "sha256:043d55226aec1d2baf4b2fcab5c204561ccf184a388096f41e396c1c092aff38", + "sha256:10bfd0b80b01d0684f968abbe1186bc19962e07b4b7601bb43b175b617cf689d", + "sha256:17e59864f19b3233032edb0566f26c25cc7f599503fb34d2645b5ce1fd6c2c3c", + "sha256:2105ee183c51fed27e2b6801029b3903f5c2774c78e3f53bd920ca468d0f5679", + "sha256:236505d15af6c7b7bfe2a9485db4b2bdea21d9239351483326184314418c79a8", + "sha256:237284425271db4f30d458b355decf388ab20b05278bdf8dc9a65de0973726c6", + "sha256:26d8eea4c840b73c61a1081d68bceb57b21a2d4f7afda6cac8ac38cb05226b00", + "sha256:39a3740f7721155f4269aedf67b211101c07bd2111b334dfd69b807156ab15d9", + "sha256:4bd0c42db8efc8a60965769796d43a5570906a870bc819f7388860aa72779d1b", + "sha256:4dcddadea47ac30b696956bd18365cd3a86724821656601151e263b86d34798f", + "sha256:51ea341289ac4456db946a25bd644f5635e5ae3793df262813cde875887d25c8", + "sha256:5415cafb082dad78935b3045c2e5d8907f436d15ad24c3fdb8e1839e084e4961", + "sha256:5631f1983074b33c35dbb84607f337b9d7e9808116d7f0f2cb7b9d6d4381d50e", + "sha256:5e9249bc361cd22565fd98590a53fd25a3dd666b74791ed7237fa99de938bbed", + "sha256:6a48746154f1331f28ef9e889c625b5b15a36cb86dd8021b4bdd1180a2186aa5", + "sha256:71d376dbac64855ed693bc1ca121794570fe603e8783cdfa304ec6825d4e768f", + "sha256:749ebd8a615337747592bd1523dfc4af7199b2bf6403b55f96c728668aeff91f", + "sha256:8ec528b585b95234e9c0c31dcd0a89152d8ed82b4567aa62dbcb3e9a0600deee", + "sha256:a1a9ccd879811437ca0307c914f136d6edb85bd0470e6d4966c6397927bcabd9", + "sha256:abd956c334752776230b779537d911a5a12fcb69d8fd3fe332ae63a140301ae6", + "sha256:ad18f836017f2e8881145795f483636564807aaed54223459915a0d4735300cf", + "sha256:b07ac0b1533298ddbc54c9bf3464664895f22899fec027b8d6c8d3ac59023283", + "sha256:d9385f1445e30e8e42b75a36a7899ea1fd0f5784233a626625d70f9b087de404", + "sha256:db2d1fcd32dbeeb914b2660af1838e9c178b75173f95fd221b1f9410b5d3ef1d", + "sha256:e1dec211147f1fd7cb7a0f9a96aeeca467a5af02d38911307b3b8c2324f9917e", + "sha256:e96dffc1fa57bb8c1c238f3d989341a97302492d09cb11f77df031112621c35c", + "sha256:ed4d97eb0ecdee29d0748acd84e6380729f78ce5ba0c7fe3401801634c25a1c5" + ], + "version": "==5.0a3" + }, + "decorator": { + "hashes": [ + "sha256:2c51dff8ef3c447388fe5e4453d24a2bf128d3a4c32af3fabef1f01c6851ab82", + "sha256:c39efa13fbdeb4506c476c9b3babf6a718da943dab7811c206005a4a956c080c" + ], + "version": "==4.3.0" + }, + "ipykernel": { + "hashes": [ + "sha256:0aeb7ec277ac42cc2b59ae3d08b10909b2ec161dc6908096210527162b53675d", + "sha256:0fc0bf97920d454102168ec2008620066878848fcfca06c22b669696212e292f" + ], + "version": "==5.1.0" + }, + "ipython": { + "hashes": [ + "sha256:47b17ea874454a5c2eacc2732b04a750d260b01ba479323155ac8a39031f5535", + "sha256:9fed506c3772c875a3048bc134a25e6f5e997b1569b2636f6a5d891f34cbfd46" + ], + "index": "pypi", + "version": "==7.0.1" + }, + "ipython-genutils": { + "hashes": [ + "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", + "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8" + ], + "version": "==0.2.0" + }, + "jedi": { + "hashes": [ + "sha256:0191c447165f798e6a730285f2eee783fff81b0d3df261945ecb80983b5c3ca7", + "sha256:b7493f73a2febe0dc33d51c99b474547f7f6c0b2c8fb2b21f453eef204c12148" + ], + "version": "==0.13.1" + }, + "jsonschema": { + "hashes": [ + "sha256:3ae8afd6f4ca6417f14bf43ef61341311598f14234cdb4174fe43d42b236a3c8", + "sha256:dfd8426040892c8d0ef6da574085f282569f189cb24b70091a66c21c12d6705e" + ], + "version": "==3.0.0a3" + }, + "jupyter-client": { + "hashes": [ + "sha256:27befcf0446b01e29853014d6a902dd101ad7d7f94e2252b1adca17c3466b761", + "sha256:59e6d791e22a8002ad0e80b78c6fd6deecab4f9e1b1aa1a22f4213de271b29ea" + ], + "version": "==5.2.3" + }, + "jupyter-core": { + "hashes": [ + "sha256:927d713ffa616ea11972534411544589976b2493fc7e09ad946e010aa7eb9970", + "sha256:ba70754aa680300306c699790128f6fbd8c306ee5927976cbe48adacf240c0b7" + ], + "version": "==4.4.0" + }, + "more-itertools": { + "hashes": [ + "sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092", + "sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e", + "sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d" + ], + "version": "==4.3.0" + }, + "nbformat": { + "hashes": [ + "sha256:b9a0dbdbd45bb034f4f8893cafd6f652ea08c8c1674ba83f2dc55d3955743b0b", + "sha256:f7494ef0df60766b7cabe0a3651556345a963b74dbc16bc7c18479041170d402" + ], + "version": "==4.4.0" + }, + "nbval": { + "hashes": [ + "sha256:3f18b87af4e94ccd073263dd58cd3eebabe9f5e4d6ab535b39d3af64811c7eda", + "sha256:74ff5e2c90a50b1ddf7edd02978c4e43221b1ee252dc14fcaa4230aae4492eda" + ], + "index": "pypi", + "version": "==0.9.1" + }, + "parso": { + "hashes": [ + "sha256:35704a43a3c113cce4de228ddb39aab374b8004f4f2407d070b6a2ca784ce8a2", + "sha256:895c63e93b94ac1e1690f5fdd40b65f07c8171e3e53cbd7793b5b96c0e0a7f24" + ], + "version": "==0.3.1" + }, + "pexpect": { + "hashes": [ + "sha256:2a8e88259839571d1251d278476f3eec5db26deb73a70be5ed5dc5435e418aba", + "sha256:3fbd41d4caf27fa4a377bfd16fef87271099463e6fa73e92a52f92dfee5d425b" + ], + "markers": "sys_platform != 'win32'", + "version": "==4.6.0" + }, + "pickleshare": { + "hashes": [ + "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca", + "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56" + ], + "version": "==0.7.5" + }, + "pluggy": { + "hashes": [ + "sha256:447ba94990e8014ee25ec853339faf7b0fc8050cdc3289d4d71f7f410fb90095", + "sha256:bde19360a8ec4dfd8a20dcb811780a30998101f078fc7ded6162f0076f50508f" + ], + "version": "==0.8.0" + }, + "prompt-toolkit": { + "hashes": [ + "sha256:646b3401b3b0bb7752100bc9b7aeecb36cb09cdfc63652b5856708b5ba8db7da", + "sha256:82766ffd7397e6661465e20bd1390db0781ca4fbbab4cf6c2578cacdd8b09754", + "sha256:ccad8461b5d912782726af17122113e196085e7e11d57cf0c9b982bf1ab2c7be" + ], + "version": "==2.0.6" + }, + "ptyprocess": { + "hashes": [ + "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0", + "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f" + ], + "markers": "os_name != 'nt'", + "version": "==0.6.0" + }, + "py": { + "hashes": [ + "sha256:bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694", + "sha256:e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6" + ], + "version": "==1.7.0" + }, + "pygments": { + "hashes": [ + "sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d", + "sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc" + ], + "version": "==2.2.0" + }, + "pyrsistent": { + "hashes": [ + "sha256:f64dd1b706c31f7aa24495a7da58c0407c072981289b675331e2a16364355102" + ], + "version": "==0.14.5" + }, + "pytest": { + "hashes": [ + "sha256:10e59f84267370ab20cec9305bafe7505ba4d6b93ecbf66a1cce86193ed511d5", + "sha256:8c827e7d4816dfe13e9329c8226aef8e6e75d65b939bc74fda894143b6d1df59" + ], + "index": "pypi", + "version": "==3.9.1" + }, + "python-dateutil": { + "hashes": [ + "sha256:1adb80e7a782c12e52ef9a8182bebeb73f1d7e24e374397af06fb4956c8dc5c0", + "sha256:e27001de32f627c22380a688bcc43ce83504a7bc5da472209b4c70f02829f0b8" + ], + "version": "==2.7.3" + }, + "pyzmq": { + "hashes": [ + "sha256:25a0715c8f69cf72f67cfe5a68a3f3ed391c67c063d2257bec0fe7fc2c7f08f8", + "sha256:2bab63759632c6b9e0d5bf19cc63c3b01df267d660e0abcf230cf0afaa966349", + "sha256:30ab49d99b24bf0908ebe1cdfa421720bfab6f93174e4883075b7ff38cc555ba", + "sha256:32c7ca9fc547a91e3c26fc6080b6982e46e79819e706eb414dd78f635a65d946", + "sha256:41219ae72b3cc86d97557fe5b1ef5d1adc1057292ec597b50050874a970a39cf", + "sha256:4b8c48a9a13cea8f1f16622f9bd46127108af14cd26150461e3eab71e0de3e46", + "sha256:55724997b4a929c0d01b43c95051318e26ddbae23565018e138ae2dc60187e59", + "sha256:65f0a4afae59d4fc0aad54a917ab599162613a761b760ba167d66cc646ac3786", + "sha256:6f88591a8b246f5c285ee6ce5c1bf4f6bd8464b7f090b1333a446b6240a68d40", + "sha256:75022a4c60dcd8765bb9ca32f6de75a0ec83b0d96e0309dc479f4c7b21f26cb7", + "sha256:76ea493bfab18dcb090d825f3662b5612e2def73dffc196d51a5194b0294a81d", + "sha256:7b60c045b80709e4e3c085bab9b691e71761b44c2b42dbb047b8b498e7bc16b3", + "sha256:8e6af2f736734aef8ed6f278f9f552ec7f37b1a6b98e59b887484a840757f67d", + "sha256:9ac2298e486524331e26390eac14e4627effd3f8e001d4266ed9d8f1d2d31cce", + "sha256:9ba650f493a9bc1f24feca1d90fce0e5dd41088a252ac9840131dfbdbf3815ca", + "sha256:a02a4a385e394e46012dc83d2e8fd6523f039bb52997c1c34a2e0dd49ed839c1", + "sha256:a3ceee84114d9f5711fa0f4db9c652af0e4636c89eabc9b7f03a3882569dd1ed", + "sha256:a72b82ac1910f2cf61a49139f4974f994984475f771b0faa730839607eeedddf", + "sha256:ab136ac51027e7c484c53138a0fab4a8a51e80d05162eb7b1585583bcfdbad27", + "sha256:c095b224300bcac61e6c445e27f9046981b1ac20d891b2f1714da89d34c637c8", + "sha256:c5cc52d16c06dc2521340d69adda78a8e1031705924e103c0eb8fc8af861d810", + "sha256:d612e9833a89e8177f8c1dc68d7b4ff98d3186cd331acd616b01bbdab67d3a7b", + "sha256:e828376a23c66c6fe90dcea24b4b72cd774f555a6ee94081670872918df87a19", + "sha256:e9767c7ab2eb552796440168d5c6e23a99ecaade08dda16266d43ad461730192", + "sha256:ebf8b800d42d217e4710d1582b0c8bff20cdcb4faad7c7213e52644034300924" + ], + "version": "==17.1.2" + }, + "simplegeneric": { + "hashes": [ + "sha256:dc972e06094b9af5b855b3df4a646395e43d1c9d0d39ed345b7393560d0b9173" + ], + "version": "==0.8.1" + }, + "six": { + "hashes": [ + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + ], + "version": "==1.11.0" + }, + "tornado": { + "hashes": [ + "sha256:0662d28b1ca9f67108c7e3b77afabfb9c7e87bde174fbda78186ecedc2499a9d", + "sha256:4e5158d97583502a7e2739951553cbd88a72076f152b4b11b64b9a10c4c49409", + "sha256:732e836008c708de2e89a31cb2fa6c0e5a70cb60492bee6f1ea1047500feaf7f", + "sha256:8154ec22c450df4e06b35f131adc4f2f3a12ec85981a203301d310abf580500f", + "sha256:8e9d728c4579682e837c92fdd98036bd5cdefa1da2aaf6acf26947e6dd0c01c5", + "sha256:d4b3e5329f572f055b587efc57d29bd051589fb5a43ec8898c77a47ec2fa2bbb", + "sha256:e5f2585afccbff22390cddac29849df463b252b711aa2ce7c5f3f342a5b3b444" + ], + "version": "==5.1.1" + }, + "traitlets": { + "hashes": [ + "sha256:9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835", + "sha256:c6cb5e6f57c5a9bdaa40fa71ce7b4af30298fbab9ece9815b5d995ab6217c7d9" + ], + "version": "==4.3.2" + }, + "wcwidth": { + "hashes": [ + "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", + "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" + ], + "version": "==0.1.7" + } + } } diff --git a/README.md b/README.md index 26fbaa5..1d182d3 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,8 @@ Also a convenient [flat file](https://en.wikipedia.org/wiki/Flat-file_database) ├── README.md (the markdown file you're reading now) ├── data_prep.ipynb (jupyter notebook that prepares the data) ├── environment.yml (conda packages to install, used by binder) - └── srgan_train.ipynb (jupyter notebook that trains the Super Resolution Generative Adversarial Network model) + ├── srgan_train.ipynb (jupyter notebook that trains the Super Resolution Generative Adversarial Network model) + └── test_ipynb.ipynb (jupyter notebook that runs doctests in the other jupyter notebooks!) ``` diff --git a/data_prep.ipynb b/data_prep.ipynb index 68c3d5c..26be5dd 100644 --- a/data_prep.ipynb +++ b/data_prep.ipynb @@ -50,7 +50,6 @@ "import rasterio.plot\n", "import shapely.geometry\n", "import skimage.util.shape\n", - "%matplotlib inline\n", "\n", "print('Python :', sys.version.split('\\n')[0])\n", "print('Numpy :', np.__version__)\n", @@ -77,20 +76,38 @@ "execution_count": 2, "metadata": {}, "outputs": [], + "source": [ + "def download_to_path(path:str, url:str):\n", + " \"\"\"\n", + " Download from a url to a path\n", + " \n", + " >>> download_to_path(path=\"highres/2017_Antarctica_Basler.csv\", url=\"https://data.cresis.ku.edu/data/rds/2017_Antarctica_Basler/csv_good/2017_Antarctica_Basler.csv\").headers['Content-Length']\n", + " '64'\n", + " \"\"\"\n", + " #if not os.path.exists(path=path):\n", + " r = requests.get(url=url, stream=True)\n", + " with open(file=path, mode='wb') as fd:\n", + " for chunk in r.iter_content(chunk_size=1024):\n", + " fd.write(chunk)\n", + " return r" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], "source": [ "url = \"http://data.pgc.umn.edu/elev/dem/bedmap2/bedmap2_bed.tif\" #link to BEDMAP2 Bed Elevation GeoTiff\n", "path = \"lowres/bedmap2_bed.tif\" #path to download the file to\n", "\n", "if not os.path.exists(path=path):\n", - " r = requests.get(url=url, stream=True)\n", - " with open(file=path, mode='wb') as fd:\n", - " for chunk in r.iter_content(chunk_size=1024):\n", - " fd.write(chunk)" + " download_to_path(path=path, url=url)" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -120,7 +137,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -141,10 +158,7 @@ "\n", "for path, url in highresDict.items():\n", " if not os.path.exists(path=path):\n", - " r = requests.get(url=url, stream=True)\n", - " with open(file=path, mode='wb') as fd:\n", - " for chunk in r.iter_content(chunk_size=1024):\n", - " fd.write(chunk)" + " download_to_path(path=path, url=url)" ] }, { @@ -156,7 +170,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -166,10 +180,7 @@ "\n", "for path, url in miscDict.items():\n", " if not os.path.exists(path=path):\n", - " r = requests.get(url=url, stream=True)\n", - " with open(file=path, mode='wb') as fd:\n", - " for chunk in r.iter_content(chunk_size=1024):\n", - " fd.write(chunk)" + " download_to_path(path=path, url=url)" ] }, { diff --git a/srgan_train.ipynb b/srgan_train.ipynb index e5cc7a0..c739e3b 100644 --- a/srgan_train.ipynb +++ b/srgan_train.ipynb @@ -62,7 +62,6 @@ "import numpy as np\n", "import quilt\n", "import skimage.transform\n", - "%matplotlib inline\n", "\n", "import keras\n", "from keras import backend as K\n", @@ -203,7 +202,12 @@ " \n", " Example:\n", " An input_shape of (8,8,1) passing through 16 residual blocks with a scaling of 4\n", - " and output_channels 1 will result in an image of shape (32,32,1) \n", + " and output_channels 1 will result in an image of shape (32,32,1)\n", + " \n", + " >>> generator_model().input_shape\n", + " [(None, 8, 8, 1), (None, 40, 40, 1), (None, 16, 16, 1)]\n", + " >>> generator_model().output_shape\n", + " (None, 32, 32, 1)\n", " \"\"\"\n", " \n", " assert(num_residual_blocks>=1) #ensure that we have 1 or more residual blocks\n", @@ -288,8 +292,14 @@ "metadata": {}, "outputs": [], "source": [ - "# Peak Signal-Noise Ratio (PSNR) metric https://en.wikipedia.org/wiki/Peak_signal-to-noise_ratio#Definition\n", "def psnr(y_true, y_pred):\n", + " \"\"\"\n", + " Peak Signal-Noise Ratio (PSNR) metric.\n", + " See https://en.wikipedia.org/wiki/Peak_signal-to-noise_ratio#Definition\n", + " \n", + " >>> K.eval(psnr(y_true=np.ones(shape=(3,3)), y_pred=np.full(shape=(3,3), fill_value=2)))\n", + " array([221.80709678, 221.80709678, 221.80709678])\n", + " \"\"\"\n", " mse = K.mean(K.square(K.np.subtract(y_pred, y_true)), axis=-1) + K.epsilon() #add epsilon to prevent zero division\n", " return K.np.multiply(20, K.log(2**16/K.sqrt(mse))) #setting MAX_I as 2^16, i.e. max for int16" ] diff --git a/test_ipynb.ipynb b/test_ipynb.ipynb new file mode 100644 index 0000000..af4f2c5 --- /dev/null +++ b/test_ipynb.ipynb @@ -0,0 +1,166 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Doctests for jupyter notebook\n", + "\n", + "Handy way to process the doctests in jupyter notebooks (.ipynb) containing Python functions.\n", + "The script converts an .ipynb to a string format (basically a .py file), loads them as modules, and runs doctest on them.\n", + "To run it in the console, do:\n", + "\n", + " python -m pytest --verbose --disable-warnings --nbval test_ipynb.ipynb\n", + "\n", + "The script should tell you which ipynb file's doctests has failed (e.g. srgan_train.ipynb).\n", + "You can then open up this very jupyter notebook to debug and inspect the situation further." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import ast\n", + "import doctest\n", + "import nbconvert\n", + "import nbformat\n", + "import os\n", + "import sys\n", + "import types\n", + "\n", + "def _doctest_ipynb(path:str):\n", + " \"\"\"\n", + " First converts .ipynb to a temporary .py file\n", + " Then it runs doctest on the temp .py file\n", + " \"\"\"\n", + " assert(path.endswith('.ipynb'))\n", + " basename, _ = os.path.splitext(path)\n", + " \n", + " #read ipynb file and get the text\n", + " with open(path) as ipynb_file:\n", + " nb = nbformat.reads(s=ipynb_file.read(), as_version=nbformat.NO_CONVERT)\n", + " assert(isinstance(nb, nbformat.notebooknode.NotebookNode))\n", + " \n", + " #convert the .ipynb text to a string .py format\n", + " pyexporter = nbconvert.PythonExporter()\n", + " source, meta = pyexporter.from_notebook_node(nb=nb)\n", + " assert(isinstance(source, str))\n", + " \n", + " #parse the .py string to pick out only 'import' and 'def function's\n", + " parsed_code = ast.parse(source=source)\n", + " for node in parsed_code.body[:]:\n", + " if node.__class__ not in [ast.FunctionDef, ast.Import, ast.ImportFrom]:\n", + " parsed_code.body.remove(node)\n", + " assert(len(parsed_code.body) > 0)\n", + " \n", + " #import modules from the parsed .py string\n", + " module = types.ModuleType(basename)\n", + " code = compile(source=parsed_code, filename=f'{basename}.py', mode='exec')\n", + " exec(code, module.__dict__)\n", + " \n", + " num_failures, num_attempted = doctest.testmod(m=module, verbose=True)\n", + " if num_failures > 0:\n", + " sys.exit(num_failures)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Trying:\n", + " download_to_path(path=\"highres/2017_Antarctica_Basler.csv\", url=\"https://data.cresis.ku.edu/data/rds/2017_Antarctica_Basler/csv_good/2017_Antarctica_Basler.csv\").headers['Content-Length']\n", + "Expecting:\n", + " '64'\n", + "ok\n", + "3 items had no tests:\n", + " data_prep\n", + " data_prep.get_window_bounds\n", + " data_prep.selective_tile\n", + "1 items passed all tests:\n", + " 1 tests in data_prep.download_to_path\n", + "1 tests in 4 items.\n", + "1 passed and 0 failed.\n", + "Test passed.\n" + ] + } + ], + "source": [ + "_doctest_ipynb('data_prep.ipynb')" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Using TensorFlow backend.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Trying:\n", + " generator_model().input_shape\n", + "Expecting:\n", + " [(None, 8, 8, 1), (None, 40, 40, 1), (None, 16, 16, 1)]\n", + "ok\n", + "Trying:\n", + " generator_model().output_shape\n", + "Expecting:\n", + " (None, 32, 32, 1)\n", + "ok\n", + "Trying:\n", + " K.eval(psnr(y_true=np.ones(shape=(3,3)), y_pred=np.full(shape=(3,3), fill_value=2)))\n", + "Expecting:\n", + " array([221.80709678, 221.80709678, 221.80709678])\n", + "ok\n", + "1 items had no tests:\n", + " srgan_train\n", + "2 items passed all tests:\n", + " 2 tests in srgan_train.generator_model\n", + " 1 tests in srgan_train.psnr\n", + "3 tests in 3 items.\n", + "3 passed and 0 failed.\n", + "Test passed.\n" + ] + } + ], + "source": [ + "_doctest_ipynb('srgan_train.ipynb')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "deepbedmap", + "language": "python", + "name": "deepbedmap" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}