{ "cells": [ { "cell_type": "markdown", "id": "silent-webmaster", "metadata": {}, "source": [ "# Load Image with labels from IDR, analyze using StarDist and compare results\n", "\n", "The notebook shows how to load an IDR image with labels.\n", "\n", "The image is referenced in the paper \"NesSys: a novel method for accurate nuclear segmentation in 3D\" published August 2019 in PLOS Biology: https://doi.org/10.1371/journal.pbio.3000388 and can be viewed online in the [Image Data Resource](https://idr.openmicroscopy.org/webclient/?show=image-6001247).\n", "\n", "\n", "In this notebook, the image is loaded together with the labels and analyzed using [StarDist](https://github.com/stardist/stardist). The StarDist analysis produces a segmentation, which is then viewed side-by-side with the original segmentations produced by the authors of the paper obtained via the loaded labels.\n", "\n", "If you wish to run the notebook locally or run the corresponding [Python script](../scripts/idr0062_prediction.py), please read instruction in [README](https://github.com/ome/omero-guide-python/blob/master/README.md)." ] }, { "cell_type": "markdown", "id": "chronic-surgeon", "metadata": {}, "source": [ "### Install dependencies if required\n", "\n", "The cell below will install dependencies if you choose to run the notebook in [Google Colab](https://colab.research.google.com/notebooks/intro.ipynb#recent=true). " ] }, { "cell_type": "code", "execution_count": 2, "id": "hydraulic-security", "metadata": { "scrolled": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Requirement already satisfied: omero-py in /srv/conda/envs/notebook/lib/python3.9/site-packages (5.11.1)\n", "Requirement already satisfied: stardist in /srv/conda/envs/notebook/lib/python3.9/site-packages (0.8.1)\n", "Requirement already satisfied: geojson in /srv/conda/envs/notebook/lib/python3.9/site-packages (2.5.0)\n", "Requirement already satisfied: omero-cli-zarr in /srv/conda/envs/notebook/lib/python3.9/site-packages (0.3.0)\n", "Requirement already satisfied: keras==2.6 in /srv/conda/envs/notebook/lib/python3.9/site-packages (2.6.0)\n", "Requirement already satisfied: ome-zarr>=0.3.0 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from omero-cli-zarr) (0.4.1)\n", "Requirement already satisfied: requests in /srv/conda/envs/notebook/lib/python3.9/site-packages (from omero-py) (2.25.1)\n", "Requirement already satisfied: numpy in /srv/conda/envs/notebook/lib/python3.9/site-packages (from omero-py) (1.21.6)\n", "Requirement already satisfied: PyYAML in /srv/conda/envs/notebook/lib/python3.9/site-packages (from omero-py) (6.0)\n", "Requirement already satisfied: zeroc-ice<3.7,>=3.6.4 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from omero-py) (3.6.5)\n", "Requirement already satisfied: future in /srv/conda/envs/notebook/lib/python3.9/site-packages (from omero-py) (0.18.2)\n", "Requirement already satisfied: appdirs in /srv/conda/envs/notebook/lib/python3.9/site-packages (from omero-py) (1.4.4)\n", "Requirement already satisfied: Pillow in /srv/conda/envs/notebook/lib/python3.9/site-packages (from omero-py) (9.0.1)\n", "Requirement already satisfied: scikit-image in /srv/conda/envs/notebook/lib/python3.9/site-packages (from ome-zarr>=0.3.0->omero-cli-zarr) (0.19.2)\n", "Requirement already satisfied: dask in /srv/conda/envs/notebook/lib/python3.9/site-packages (from ome-zarr>=0.3.0->omero-cli-zarr) (2022.4.1)\n", "Requirement already satisfied: toolz in /srv/conda/envs/notebook/lib/python3.9/site-packages (from ome-zarr>=0.3.0->omero-cli-zarr) (0.11.2)\n", "Requirement already satisfied: fsspec[s3]!=2021.07.0 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from ome-zarr>=0.3.0->omero-cli-zarr) (2022.3.0)\n", "Requirement already satisfied: zarr>=2.8.1 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from ome-zarr>=0.3.0->omero-cli-zarr) (2.11.3)\n", "Requirement already satisfied: aiohttp<4 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from ome-zarr>=0.3.0->omero-cli-zarr) (3.8.1)\n", "Requirement already satisfied: yarl<2.0,>=1.0 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from aiohttp<4->ome-zarr>=0.3.0->omero-cli-zarr) (1.7.2)\n", "Requirement already satisfied: charset-normalizer<3.0,>=2.0 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from aiohttp<4->ome-zarr>=0.3.0->omero-cli-zarr) (2.0.12)\n", "Requirement already satisfied: async-timeout<5.0,>=4.0.0a3 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from aiohttp<4->ome-zarr>=0.3.0->omero-cli-zarr) (4.0.2)\n", "Requirement already satisfied: multidict<7.0,>=4.5 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from aiohttp<4->ome-zarr>=0.3.0->omero-cli-zarr) (6.0.2)\n", "Requirement already satisfied: frozenlist>=1.1.1 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from aiohttp<4->ome-zarr>=0.3.0->omero-cli-zarr) (1.3.0)\n", "Requirement already satisfied: attrs>=17.3.0 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from aiohttp<4->ome-zarr>=0.3.0->omero-cli-zarr) (20.3.0)\n", "Requirement already satisfied: aiosignal>=1.1.2 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from aiohttp<4->ome-zarr>=0.3.0->omero-cli-zarr) (1.2.0)\n", "Requirement already satisfied: s3fs in /srv/conda/envs/notebook/lib/python3.9/site-packages (from fsspec[s3]!=2021.07.0->ome-zarr>=0.3.0->omero-cli-zarr) (2022.3.0)\n", "Requirement already satisfied: idna>=2.0 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from yarl<2.0,>=1.0->aiohttp<4->ome-zarr>=0.3.0->omero-cli-zarr) (2.10)\n", "Requirement already satisfied: numcodecs>=0.6.4 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from zarr>=2.8.1->ome-zarr>=0.3.0->omero-cli-zarr) (0.9.1)\n", "Requirement already satisfied: asciitree in /srv/conda/envs/notebook/lib/python3.9/site-packages (from zarr>=2.8.1->ome-zarr>=0.3.0->omero-cli-zarr) (0.3.3)\n", "Requirement already satisfied: fasteners in /srv/conda/envs/notebook/lib/python3.9/site-packages (from zarr>=2.8.1->ome-zarr>=0.3.0->omero-cli-zarr) (0.17.3)\n", "Requirement already satisfied: numba in /srv/conda/envs/notebook/lib/python3.9/site-packages (from stardist) (0.55.1)\n", "Requirement already satisfied: imageio in /srv/conda/envs/notebook/lib/python3.9/site-packages (from stardist) (2.18.0)\n", "Requirement already satisfied: csbdeep>=0.6.3 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from stardist) (0.6.3)\n", "Requirement already satisfied: six in /srv/conda/envs/notebook/lib/python3.9/site-packages (from csbdeep>=0.6.3->stardist) (1.15.0)\n", "Requirement already satisfied: h5py>=3 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from csbdeep>=0.6.3->stardist) (3.6.0)\n", "Requirement already satisfied: tqdm in /srv/conda/envs/notebook/lib/python3.9/site-packages (from csbdeep>=0.6.3->stardist) (4.64.0)\n", "Requirement already satisfied: matplotlib in /srv/conda/envs/notebook/lib/python3.9/site-packages (from csbdeep>=0.6.3->stardist) (3.5.1)\n", "Requirement already satisfied: tifffile in /srv/conda/envs/notebook/lib/python3.9/site-packages (from csbdeep>=0.6.3->stardist) (2022.4.26)\n", "Requirement already satisfied: scipy in /srv/conda/envs/notebook/lib/python3.9/site-packages (from csbdeep>=0.6.3->stardist) (1.8.0)\n", "Requirement already satisfied: partd>=0.3.10 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from dask->ome-zarr>=0.3.0->omero-cli-zarr) (1.2.0)\n", "Requirement already satisfied: cloudpickle>=1.1.1 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from dask->ome-zarr>=0.3.0->omero-cli-zarr) (2.0.0)\n", "Requirement already satisfied: packaging>=20.0 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from dask->ome-zarr>=0.3.0->omero-cli-zarr) (20.9)\n", "Requirement already satisfied: pyparsing>=2.0.2 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from packaging>=20.0->dask->ome-zarr>=0.3.0->omero-cli-zarr) (2.4.7)\n", "Requirement already satisfied: locket in /srv/conda/envs/notebook/lib/python3.9/site-packages (from partd>=0.3.10->dask->ome-zarr>=0.3.0->omero-cli-zarr) (1.0.0)\n", "Requirement already satisfied: cycler>=0.10 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from matplotlib->csbdeep>=0.6.3->stardist) (0.11.0)\n", "Requirement already satisfied: fonttools>=4.22.0 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from matplotlib->csbdeep>=0.6.3->stardist) (4.33.3)\n", "Requirement already satisfied: python-dateutil>=2.7 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from matplotlib->csbdeep>=0.6.3->stardist) (2.8.1)\n", "Requirement already satisfied: kiwisolver>=1.0.1 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from matplotlib->csbdeep>=0.6.3->stardist) (1.4.2)\n", "Requirement already satisfied: setuptools in /srv/conda/envs/notebook/lib/python3.9/site-packages (from numba->stardist) (49.6.0.post20210108)\n", "Requirement already satisfied: llvmlite<0.39,>=0.38.0rc1 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from numba->stardist) (0.38.0)\n", "Requirement already satisfied: certifi>=2017.4.17 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from requests->omero-py) (2020.12.5)\n", "Requirement already satisfied: urllib3<1.27,>=1.21.1 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from requests->omero-py) (1.26.3)\n", "Requirement already satisfied: chardet<5,>=3.0.2 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from requests->omero-py) (4.0.0)\n", "Requirement already satisfied: aiobotocore~=2.2.0 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from s3fs->fsspec[s3]!=2021.07.0->ome-zarr>=0.3.0->omero-cli-zarr) (2.2.0)\n", "Requirement already satisfied: botocore<1.24.22,>=1.24.21 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from aiobotocore~=2.2.0->s3fs->fsspec[s3]!=2021.07.0->ome-zarr>=0.3.0->omero-cli-zarr) (1.24.21)\n", "Requirement already satisfied: aioitertools>=0.5.1 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from aiobotocore~=2.2.0->s3fs->fsspec[s3]!=2021.07.0->ome-zarr>=0.3.0->omero-cli-zarr) (0.10.0)\n", "Requirement already satisfied: wrapt>=1.10.10 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from aiobotocore~=2.2.0->s3fs->fsspec[s3]!=2021.07.0->ome-zarr>=0.3.0->omero-cli-zarr) (1.14.0)\n", "Requirement already satisfied: typing_extensions>=4.0 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from aioitertools>=0.5.1->aiobotocore~=2.2.0->s3fs->fsspec[s3]!=2021.07.0->ome-zarr>=0.3.0->omero-cli-zarr) (4.2.0)\n", "Requirement already satisfied: jmespath<2.0.0,>=0.7.1 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from botocore<1.24.22,>=1.24.21->aiobotocore~=2.2.0->s3fs->fsspec[s3]!=2021.07.0->ome-zarr>=0.3.0->omero-cli-zarr) (1.0.0)\n", "Requirement already satisfied: networkx>=2.2 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from scikit-image->ome-zarr>=0.3.0->omero-cli-zarr) (2.8)\n", "Requirement already satisfied: PyWavelets>=1.1.1 in /srv/conda/envs/notebook/lib/python3.9/site-packages (from scikit-image->ome-zarr>=0.3.0->omero-cli-zarr) (1.3.0)\n", "Note: you may need to restart the kernel to use updated packages.\n" ] } ], "source": [ "%pip install omero-py stardist==0.8.1 geojson omero-cli-zarr" ] }, { "cell_type": "markdown", "id": "vocational-spending", "metadata": {}, "source": [ "### Import " ] }, { "cell_type": "code", "execution_count": 3, "id": "intermediate-portland", "metadata": {}, "outputs": [], "source": [ "from omero.gateway import BlitzGateway\n", "\n", "import matplotlib.pyplot as plt\n", "%matplotlib inline\n", "import numpy" ] }, { "cell_type": "markdown", "id": "seventh-conditioning", "metadata": {}, "source": [ "### Create a connection to IDR" ] }, { "cell_type": "code", "execution_count": 4, "id": "stupid-captain", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "True\n" ] } ], "source": [ "HOST = 'ws://idr.openmicroscopy.org/omero-ws'\n", "conn = BlitzGateway('public', 'public',\n", " host=HOST, secure=True)\n", "print(conn.connect())\n", "conn.c.enableKeepAlive(60)" ] }, { "cell_type": "markdown", "id": "resident-cambodia", "metadata": {}, "source": [ "### IDR image to analyze" ] }, { "cell_type": "code", "execution_count": 5, "id": "urban-refund", "metadata": {}, "outputs": [], "source": [ "image_id = 6001247" ] }, { "cell_type": "code", "execution_count": 6, "id": "secondary-surname", "metadata": {}, "outputs": [], "source": [ "image = conn.getObject(\"Image\", image_id)" ] }, { "cell_type": "markdown", "id": "stretch-importance", "metadata": {}, "source": [ "### Helper method to load the 5D image\n", "\n", "The image is loaded a TCZYX numpy array." ] }, { "cell_type": "code", "execution_count": 7, "id": "massive-semester", "metadata": {}, "outputs": [], "source": [ "def load_numpy_array(image):\n", " pixels = image.getPrimaryPixels()\n", " size_z = image.getSizeZ()\n", " size_c = image.getSizeC()\n", " size_t = image.getSizeT()\n", " size_y = image.getSizeY()\n", " size_x = image.getSizeX()\n", " z, t, c = 0, 0, 0 # first plane of the image\n", "\n", " zct_list = []\n", " for t in range(size_t):\n", " for c in range(size_c): # all channels\n", " for z in range(size_z): # get the Z-stack\n", " zct_list.append((z, c, t))\n", "\n", " values = []\n", " # Load all the planes as YX numpy array\n", " planes = pixels.getPlanes(zct_list)\n", " s = \"t:%s c:%s z:%s y:%s x:%s\" % (size_t, size_c, size_z, size_y, size_x)\n", " print(s)\n", " print(\"Downloading image %s\" % image.getName())\n", " all_planes = numpy.stack(list(planes))\n", " shape = (size_t, size_c, size_z, size_y, size_x)\n", " return numpy.reshape(all_planes, newshape=shape)" ] }, { "cell_type": "markdown", "id": "taken-butler", "metadata": {}, "source": [ "### Load the binary data\n", "Load the binary data as a numpy array" ] }, { "cell_type": "code", "execution_count": 8, "id": "focused-smith", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "t:1 c:2 z:257 y:210 x:253\n", "Downloading image B4_C3.tif\n" ] } ], "source": [ "data = load_numpy_array(image)" ] }, { "cell_type": "markdown", "id": "certain-dating", "metadata": {}, "source": [ "## Load StarDist trained model " ] }, { "cell_type": "code", "execution_count": 9, "id": "headed-approach", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Found model '2D_demo' for 'StarDist2D'.\n", "Downloading data from https://github.com/stardist/stardist-models/releases/download/v0.1/python_2D_demo.zip\n", "5300224/5298331 [==============================] - 1s 0us/step\n", "5308416/5298331 [==============================] - 1s 0us/step\n", "Loading network weights from 'weights_best.h5'.\n", "Loading thresholds from 'thresholds.json'.\n", "Using default values: prob_thresh=0.486166, nms_thresh=0.5.\n" ] } ], "source": [ "from stardist.models import StarDist2D\n", "model_versatile = StarDist2D.from_pretrained('2D_demo')" ] }, { "cell_type": "markdown", "id": "reported-output", "metadata": {}, "source": [ "## Prediction based on a default StarDist model\n", "Normalize the input image\n", "\n", "``model_versatile.predict_instances`` will\n", "\n", " * predict object probabilities and star-convex polygon distances (see model.predict if you want those)\n", " * perform non-maximum suppression (with overlap threshold nms_thresh) for polygons above object probability threshold prob_thresh.\n", " * render all remaining polygon instances in a label image\n", " * return the label instances image and also the details (coordinates, etc.) of all remaining polygons" ] }, { "cell_type": "code", "execution_count": 10, "id": "cultural-sacrifice", "metadata": {}, "outputs": [], "source": [ "from csbdeep.utils import normalize\n", "axis_norm = (0,1)\n", "c = 1\n", "img = normalize(data[0, c, :, :, :], 1,99.8, axis=axis_norm)\n", "results = []\n", "results_details = []\n", "for i in range(len(img)):\n", " new_labels, details = model_versatile.predict_instances(img[i])\n", " results_details.append(details)\n", " results.append(new_labels)\n", "\n", "label_slices = numpy.array(results)" ] }, { "cell_type": "markdown", "id": "5ddfa3f8", "metadata": {}, "source": [ "## Load the original labels\n", "Load the original labels in order to compare them with the StarDist ones\n", "Original labels have been saved as mask." ] }, { "cell_type": "code", "execution_count": 11, "id": "f5510a66", "metadata": {}, "outputs": [], "source": [ "from omero_zarr import masks" ] }, { "cell_type": "code", "execution_count": 12, "id": "f65541a2", "metadata": {}, "outputs": [], "source": [ "roi_service = conn.getRoiService()\n", "result = roi_service.findByImage(image_id, None)\n", "\n", "dims = (image.getSizeT(), image.getSizeC(), image.getSizeZ(), image.getSizeY(), image.getSizeX())\n", "shapes = []\n", "for roi in result.rois:\n", " shapes.append(roi.copyShapes())\n", "\n", "saver = masks.MaskSaver(None, image, numpy.int64)\n", "labels, fillColors, properties = saver.masks_to_labels(shapes, mask_shape=dims)" ] }, { "cell_type": "code", "execution_count": 13, "id": "d4eb85b0", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(1, 2, 257, 210, 253)\n" ] } ], "source": [ "print(labels.shape)" ] }, { "cell_type": "markdown", "id": "requested-parking", "metadata": {}, "source": [ "## Compare labels\n", "Display the original labels and the labels based on StarDist prediction side-by-side" ] }, { "cell_type": "code", "execution_count": 14, "id": "cooked-puppy", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "52aebbafa3b641d0b6d557420ae6bc7b", "version_major": 2, "version_minor": 0 }, "text/plain": [ "interactive(children=(IntSlider(value=1, continuous_update=False, description='Select Z', max=256), Output()),…" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from ipywidgets import *\n", "\n", "def update(z=0):\n", " c = 1\n", " fig = plt.figure(figsize=(10, 10))\n", " plt.subplot(121)\n", " plt.imshow(data[0, c, z, :, :], cmap='jet')\n", " try:\n", " plt.imshow(labels[0, c, z, :, :], cmap='gray', alpha=0.5)\n", " except Exception:\n", " print(z)\n", " plt.subplot(122)\n", " plt.imshow(data[0, c, z, :, :], cmap='gray')\n", " plt.imshow(label_slices[z, :, :], cmap='jet', alpha=0.5)\n", " plt.tight_layout()\n", " fig.canvas.flush_events()\n", "\n", "interact(update, z= widgets.IntSlider(value=1, min=0, max=data.shape[2]-1, step=1, description=\"Select Z\", continuous_update=False))" ] }, { "cell_type": "markdown", "id": "fundamental-raise", "metadata": {}, "source": [ "### Close the connection " ] }, { "cell_type": "code", "execution_count": 15, "id": "interior-summit", "metadata": {}, "outputs": [], "source": [ "conn.close()" ] }, { "cell_type": "markdown", "id": "4d4fac89", "metadata": {}, "source": [ "## Save the StarDist labels\n", "\n", "StarDist offers method to save the labels into ImageJ rois using ``export_imagej_rois``. This is outside the scope of this notebook. \n", "\n", "Below we show how to save the segmentation represented as polygon details locally in a machine- and human-readable format: **geojson**.\n", "\n", "* Convert the StarDist polygon coordinates into geojson Polygons\n", "* Save the output in the `notebooks` folder in a `.geojson` file." ] }, { "cell_type": "code", "execution_count": 16, "id": "fc918964", "metadata": {}, "outputs": [], "source": [ "# Convert into Polygon and add to Geometry Collection\n", "from geojson import Feature, FeatureCollection, Polygon\n", "c = 1\n", "shapes = []\n", "for i in range(len(results_details)):\n", " details = results_details[i]\n", " for obj_id, region in enumerate(details['coord']):\n", " coordinates = []\n", " x = region[1]\n", " y = region[0]\n", " for j in range(len(x)):\n", " coordinates.append((float(x[j]), float(y[j])))\n", " # append the first coordinate to close the polygon\n", " coordinates.append(coordinates[0])\n", " shape = Polygon(coordinates)\n", " properties = {\n", " \"stroke-width\": 1,\n", " \"z\": i,\n", " \"c\": c,\n", " }\n", " shapes.append(Feature(geometry=shape, properties=properties)) \n", "\n", "gc = FeatureCollection(shapes)" ] }, { "cell_type": "code", "execution_count": 17, "id": "fcd89454", "metadata": {}, "outputs": [], "source": [ "# Save the shapes as geojson\n", "import geojson\n", "geojson_file = \"stardist_shapes_%s.geojson\" % image_id\n", "geojson_dump = geojson.dumps(gc, sort_keys=True)\n", "with open(geojson_file, 'w') as out:\n", " out.write(geojson_dump)" ] }, { "cell_type": "markdown", "id": "d2b111fe", "metadata": {}, "source": [ "## Exercises\n", "\n", "**Exercise 1:**\n", " - Using the json library, read the geojson file into a variable in this notebook\n", " - Display the shapes on a selected z-section e.g. z = 5.\n", "\n", "**Exercise 2:**\n", " - Convert the StarDist labels into OMERO polygons.\n", " - Save the converted labels to an OMERO.server if possible.\n", " \n", " \n", "See [Solutions](Solution_Exercises.ipynb)" ] }, { "cell_type": "markdown", "id": "computational-brain", "metadata": {}, "source": [ "### License (BSD 2-Clause)\n", "Copyright (C) 2022 University of Dundee. All Rights Reserved.\n", "\n", "Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n", "\n", "Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\n", "Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\n", "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. " ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "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.9.2" } }, "nbformat": 4, "nbformat_minor": 5 }