From 5a783ae2e42fe84f2368867a54b81fd7b04cb5af Mon Sep 17 00:00:00 2001 From: Jan Lebert Date: Fri, 24 May 2024 14:36:23 -0700 Subject: [PATCH] Update IO tutorial --- docs/tutorials/io.ipynb | 160 +++++++++++++++++++++++++++++----------- optimap/image/_core.py | 4 +- 2 files changed, 118 insertions(+), 46 deletions(-) diff --git a/docs/tutorials/io.ipynb b/docs/tutorials/io.ipynb index 0af4d09..b4ea793 100644 --- a/docs/tutorials/io.ipynb +++ b/docs/tutorials/io.ipynb @@ -81,6 +81,7 @@ "* .dat (MultiRecorder)\n", "* .npy (NumPy array)\n", "* .mat (MATLAB), loads the first field in the file\n", + "* .mp4, .avi, .mov, ... (digital video files)\n", "\n", "Additional file formats will be added in the future (and upon request). All files can be imported using the same {func}`load_video` function, with which it is also possible to load only a specific number of frames or range of the data (e.g. from a specific frame to another), see below. \n", "\n", @@ -173,14 +174,14 @@ "```\n", ":::\n", "\n", - ":::{dropdown} Digital video (.mp4, .avi, ..)\n", - "optimap can import digital video files (.mp4, .avi, .mov, ...):\n", + ":::{dropdown} Digital video files (.mp4, .avi, .mov, ...)\n", + "optimap can import .mp4, .avi, .mov and other similar digital video files using the same {func}`load_video` function:\n", "\n", "```python\n", "video = om.load_video(\"example.mp4\")\n", "```\n", "\n", - "by default only the luminance channel of the input video is imported. To load the color video use\n", + "by default only the luminance channel of the input video is imported. To load the color video (RGB) use\n", "\n", "```python\n", "video = om.load_video(\"example.mp4\", as_grey=False)\n", @@ -255,26 +256,6 @@ "````" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Importing Images and Masks\n", - "\n", - "Individual images (.tif, .png, .jpg, .npy, ...) can be imported using {func}`load_image`:\n", - "\n", - "```python\n", - "image = om.load_image(\"example.tif\")\n", - "```\n", - "the `as_grey` argument can be used to convert the image to a grayscale image (if it is not already).\n", - "\n", - "Segmentation masks can be imported using {func}`load_mask`:\n", - "```python\n", - "mask = om.load_mask(\"mask.png\")\n", - "```\n", - "See [Tutorial 3: Masking / Segmentation](/tutorials/mask) for more details on how to load, create and use masks." - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -384,7 +365,7 @@ "source": [ "With the normalization (the subtraction of the minimum and the division by the maximum) in the code snippet above it is ensured that no values are below 0 or above 255 (or 65536, respectively).\n", "\n", - "### Video Export\n", + "### Exporting Videos\n", "\n", "The main purpose of exporting videos is to generate or render videos in a file format (.mp4) that can be played with an external video player application (e.g. Quicktime, VLC, Windows Media Player etc.) or be included in slideshows (e.g. Powerpoint). You can export videos in several ways: \n", "\n", @@ -473,7 +454,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Here, only every 2nd frame from frames 100-500 are exported to a video with a framerate of 30fps, `vmin` and `vmax` define the dynamic range of pixel values (0.1 is black and 0.9 white with the grayscale colormap) and the `magma` colormap.\n", + "Here, the video is exported with a framerate of 15fps, `vmin` and `vmax` are used define the dynamic range of pixel values. The `step` parameter can be used to only export every n-th frame.\n", "\n", "{func}`video.export_video_with_overlay` can be used to overlay two videos on top of each other. For instance, a pixel-wise normalized video which shows action potential or calcium waves, see [Tutorial 2](signal_extraction.ipynb), on top of the original grayscale video:" ] @@ -489,7 +470,7 @@ "outputs": [], "source": [ "overlay = om.video.normalize_pixelwise(video)\n", - "om.video.export_video_with_overlay(\"video.mp4\", video, overlay=overlay, fps=15)" + "om.export_video_with_overlay(\"video.mp4\", video, overlay=overlay, fps=15)" ] }, { @@ -515,7 +496,7 @@ "overlay_motion = om.video.temporal_difference(video, 3)\n", "overlay_motion = om.video.normalize_pixelwise(overlay_motion, ymin=1, ymax=-1)\n", "\n", - "om.video.export_video_with_overlay(\"video.mp4\", video_warped, overlay=overlay, alpha=alpha, fps=15)\n", + "om.export_video_with_overlay(\"video.mp4\", video_warped, overlay=overlay, alpha=alpha, fps=15)\n", "Video(filename='video.mp4', embed=True, html_attributes=\"controls autoplay loop\")" ] }, @@ -625,7 +606,31 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Saving / Exporting Images and Masks\n", + "## Images and Masks\n", + "\n", + "### Importing Images & Masks\n", + "\n", + "Individual images (.tif, .png, .jpg, .npy, ...) can be imported using {func}`load_image`:\n", + "\n", + "```python\n", + "image = om.load_image(\"example.tif\")\n", + "```\n", + "the `as_grey` argument can be used to convert the image to a grayscale image (if it is not already).\n", + "\n", + "Segmentation masks can be imported using {func}`load_mask`:\n", + "```python\n", + "mask = om.load_mask(\"mask.png\")\n", + "```\n", + "See [Tutorial 3: Masking / Segmentation](/tutorials/mask) for more details on how to load, create and use masks." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Saving or Exporting Images & Masks\n", + "\n", + "As for videos, we differentiate between saving and exporting. The save functions aim to prevent any loss of precision such that the resulting file can be loaded with {func}`load_image`. The export function on the other hand support colormaps and should be used when the resulting file will be used for presentation.\n", "\n", "Images can be saved using {func}`save_image`:\n", "\n", @@ -634,32 +639,33 @@ "```\n", "\n", "The following file formats and image data types are supported:\n", - "* PNG: .png, 8-bit or 16-bit unsigned images\n", - "* TIFF: .tif/.tiff, 8-bit unsigned, 16-bit unsigned, 32-bit float, or 64-bit float images\n", "* NumPy: .npy, all data types\n", + "* PNG: .png, 8-bit or 16-bit unsigned per image channel\n", + "* TIFF: .tif/.tiff, 8-bit unsigned, 16-bit unsigned, 32-bit float, or 64-bit float images\n", "* JPEG: .jpeg/.jpg, 8-bit unsigned\n", "* Windows bitmaps: .bmp, 8-bit unsigned\n", "\n", - ":::{admonition} Images appear black in viewer\n", - ":class: warning\n", - "The function {func}`save_image` aims to export the image in the same data format (e.g. 16-bit or floating point) to prevent loss of precision. This is sometimes unwanted, as it can make the images appear black in some image viewers. For the widest compatibility, convert images to 8-bit unsigned (uint8).\n", + "The image data is saved as it is, without any normalization or scaling.\n", + "\n", + ":::{admonition} Images appear black in another image viewer\n", + ":class: note\n", + "\n", + "Standard external image viewers can often not handle 16-bit unsigned or 32-bit float image data, which can make images saved with {func}`save_image` appear black. Use {func}`export_image` instead to save images in a format that can be viewed with standard image viewers if the image will not be further processed.\n", + ":::\n", + "\n", + "To export images use {func}`export_image`:\n", "\n", - "Setting the flag `compat` converts the input automatically to uint8 using {func}`image.normalize`\n", - "```python\n", - "om.save_image(\"example.png\", image, compat=True)\n", - "```\n", - "The option `compat=True` is equivalent to\n", "```python\n", - "image = om.image.normalize(image, dtype=\"uint8\")\n", - "om.save_image(\"example.png\", image)\n", + "om.export_image(\"example.png\", image)\n", "```\n", - "Note that {func}`image.normalize` uses the minimum and maximum value of the image by default for the normalization to uint8 (integer values 0-255). It is often advised to set the input data range manually using `vmin` and `vmax`:\n", + "\n", + "For grayscale images {func}`export_image` uses the minimum and maximum value of the image by default for the normalization to an 8-bit image (integer values 0-255). It is often advised to set the input data range manually using `vmin` and `vmax`:\n", + "\n", "```python\n", - "image = om.image.normalize(image, vmin=0, vmax=1, dtype=\"uint8\")\n", - "om.save_image(\"example.png\", image)\n", + "om.export_image(\"example.png\", image, vmin=0, vmax=1, cmap=\"gray\")\n", "```\n", - "This example here maps the value range [0, 1] to [0, 255]. Alternatively normalize the whole video at once using `video.normalize`.\n", - ":::" + "\n", + "In this example the value range [0, 1] is mapped to [0, 255]." ] }, { @@ -672,6 +678,70 @@ "om.save_mask(\"mask.png\", mask)\n", "```" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exporting Plots\n", + "\n", + "All plotting functions such as {func}`show_image`, {func}`show_mask`, {func}`show_traces` etc are based on matplotlib. To export the output click the save icon in the plot window, or use {func}`matplotlib.pyplot.savefig`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "skip-execution" + ] + }, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "om.show_image(image)\n", + "plt.savefig(\"image.png\", bbox_inches='tight')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "or equivalently:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ax = om.show_image(image)\n", + "ax.figure.savefig(\"image.png\", bbox_inches='tight')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "All plotting accept a {class}`matplotlib.axes.Axes` as input to create subplots figures:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "skip-execution" + ] + }, + "outputs": [], + "source": [ + "fig, axs = plt.subplots(2)\n", + "om.show_image(image, ax=axs[0])\n", + "om.show_traces(traces, ax=axs[1])\n", + "fig.savefig(\"image.png\", dpi=300)" + ] } ], "metadata": { diff --git a/optimap/image/_core.py b/optimap/image/_core.py index d49702e..72f140c 100644 --- a/optimap/image/_core.py +++ b/optimap/image/_core.py @@ -212,7 +212,7 @@ def save_mask(filename, mask, image=None, **kwargs): def save_image(filename, image: np.ndarray, compat=False, **kwargs): """Save an image to a file. Makes best effort to avoid data precision loss, use {func}`export_image` to export images for publications. - The file format is inferred from the filename extension. + The image data is saved as it is, without any normalization or scaling. The following file formats and image data types are supported: * NumPy: .npy, all data types @@ -221,6 +221,8 @@ def save_image(filename, image: np.ndarray, compat=False, **kwargs): * JPEG: .jpeg/.jpg, 8-bit unsigned * Windows bitmaps: .bmp, 8-bit unsigned + The file format is inferred from the filename extension. + Uses :func:`numpy.save` internally if the file extension is ``.npy`` and :func:`cv2.imwrite` otherwise. Parameters