{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Tutorial about images" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the context of SMLM you also have to deal with pixelated images. For instance, raw data is recorded as movies from which localization data is generated. Or widefield microscopy images are recorded together with SMLM data.\n", "\n", "To deal with image data, various image processing libraries exist. Locan makes use of a simple image class that serves as container and adapter class for any image class from a third-party library." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import tempfile\n", "from dataclasses import dataclass\n", "from pathlib import Path\n", "\n", "%matplotlib inline\n", "\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import httpx\n", "import skimage as ski\n", "\n", "import locan as lc\n", "from locan.data.images import Image" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "lc.show_versions(system=False, dependencies=False, verbose=False)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "directory = Path(tempfile.mkdtemp())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Some image" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's get some image data:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "url = \"https://raw.githubusercontent.com/super-resolution/Locan/main/docs/_static/logo_plus.png\"" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "response = httpx.get(url)\n", "print(\"Response is ok: \", response.is_success)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "file_path = directory / Path(url).name\n", "\n", "with open(file_path, 'wb') as file:\n", " for chunk in response.iter_bytes(chunk_size=128):\n", " file.write(chunk)\n", " \n", "file_path" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "logo = ski.io.imread(file_path)\n", "type(logo), logo.shape" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plt.imshow(logo);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The Image class" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this example, the image is a plain numpy array. However, the image can be a more complex class with a specific interface, depending on the image library used.\n", "\n", "Locan provides an adapter class to wrap any image class. The adapter class `locan.Image` provides a minimal standard interface." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Image?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Objects should be instantiated through an appropriate constructor method." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "[method for method in dir(Image) if method.startswith(\"from\")]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Image attributes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Upon instantiation `locan.Image` wraps the original image class and provides an array as `image.data`.\n", "\n", "Any attribute request is forwarded first to image.data or - if not available - to the original images class referenced through image._image." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "image = Image.from_numpy(array=logo, is_rgb=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "image.is_rgb" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "image.shape" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "image.size" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "image.dtype" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "image.ndim" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The Image data attribute" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Typically, image.data should present the image as an array object that can be used with the array API standard.\n", "\n", "If this is not the case a numpy.NDArray is provided." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "image = Image.from_array(array=[[1, 2, 3], [1, 2, 3]])\n", "type(image.data), image.dtype" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "image = Image.from_array(array=logo, is_rgb=True)\n", "type(image.data), image.dtype" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "plt.imshow(image.data);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You might want to use methods according to the array API standard also with other libraries than np." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.sum(image.data)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Alternatively, use a different method implementation from array_api_compat:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "try:\n", " from array_api_compat import array_namespace\n", " xp = array_namespace(image.data)\n", " logo_as_float = xp.astype(image.data, np.float32)\n", " print(type(logo_as_float), logo_as_float.dtype)\n", "except ImportError as e:\n", " print(\"Exception:\", e)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The Image bins attribute" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To keep pixel coordinates with the image use the bins attribute:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "image = Image.from_array(array=logo[:, :, 0], is_rgb=False)\n", "image.shape" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "image.bins = lc.Bins(n_bins=image.shape, bin_range=[[10, 20],[100, 200]])\n", "[edges[0:3] for edges in image.bins.bin_edges]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "image.bins" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The Image meta attribute" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Metadata that relates to some localization data should be kept under the meta attribute:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "image = Image.from_array(array=logo[:, :, 0], is_rgb=False, meta={\"comment\": \"Free text can go here...\"})\n", "image.meta" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Additional metadata should be added to the meta.map attribute or in a separate attribute:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "exif = {\"Model\": \"Some Camera\", \"Information\": \"details\"}\n", "image.exif = exif\n", "image.exif" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "image.meta.map.update(exif)\n", "image.meta" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Modify the Image class" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To create an Image object from any other library object\n", "modify the initialization of self.data and other attributes accordingly. \n", " \n", "Here is a contrived example:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "@dataclass\n", "class ThirdPartyImage:\n", " img = [[1, 2, 3], [1, 2, 3],]\n", " is_rgb = False\n", " extra_metadata = {\"Information\": \"details\"}\n", "\n", "third_party_image = ThirdPartyImage()\n", "third_party_image.img, third_party_image.is_rgb, third_party_image.extra_metadata" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class MyImage(Image):\n", "\n", " @classmethod\n", " def from_third_party(cls, image, meta=None):\n", " new_image = cls(\n", " image=image,\n", " data=np.asarray(image.img),\n", " is_rgb=image.is_rgb,\n", " meta=meta\n", " )\n", " new_image.meta.map.update(image.extra_metadata)\n", " return new_image" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "image = MyImage.from_third_party(image=third_party_image)\n", "image.meta" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "image.data" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.12.6" }, "widgets": { "application/vnd.jupyter.widget-state+json": { "state": {}, "version_major": 2, "version_minor": 0 } } }, "nbformat": 4, "nbformat_minor": 4 }