<commit_before>
Getting started: Skeleton analysis with Skan
============================================

Skeleton analysis allows us to measure fine structure in samples. In
this case, we are going to measure the network formed by a protein
called *spectrin* inside the membrane of a red blood cell, either
infected by the malaria parasite *Plasmodium falciparum*, or uninfected.

0. Extracting a skeleton from an image
--------------------------------------

Skan’s primary purpose is to analyse skeletons. (It primarily mimics the
`“Analyze Skeleton” Fiji
plugin <https://imagej.net/AnalyzeSkeleton>`__.) In this section, we
will run some simple preprocessing on our source images to extract a
skeleton, provided by Skan and
`scikit-image <https://scikit-image.org>`__, but this is far from the
optimal way of extracting a skeleton from an image. Skip to section 1
for Skan’s primary functionality.

First, we load the images. These images were produced by an FEI scanning
electron microscope. `ImageIO <https://imageio.github.io>`__ provides a
method to read these images, including metadata.

.. nbplot::

    >>> from glob import glob
    >>> import imageio as iio
    >>>
    >>> files = glob('example-data/*.tif')
    >>> image0 = iio.imread(files[0], format='fei')

We display the images with `Matplotlib <http://matplotlib.org>`__.

.. mpl-interactive::

.. nbplot::

    >>>
    >>> fig, ax = plt.subplots()
    >>> ax.imshow(image0, cmap='gray');




As an aside, we can extract the pixel spacing in meters from the
``.meta`` attribute of the ImageIO image:

.. nbplot::

    >>> spacing = image0.meta['Scan']['PixelHeight']
    >>> spacing_nm = spacing * 1e9  # nm per pixel
    >>> dim_nm = np.array(image0.shape) / spacing_nm
    >>>
    >>> fig, ax = plt.subplots()
    >>> ax.imshow(image0, cmap='gray',
    ...           extent=[0, dim_nm[1], dim_nm[0], 0]);
    >>> ax.set_xlabel('x (nm)')
    >>> ax.set_ylabel('y (nm)');




This is an image of the inside of the membrane of a red blood cell. We
want to measure the network of bright regions in this image, which is
made up of a protein called *spectrin*. Our strategy will be to:

-  smooth the image with Gaussian smoothing
-  threshold the image with Sauvola thresholding (Otsu thresholding also
   works)
-  skeletonise the resulting binary image

These steps can be performed by a single function in Skan,
``skan.pre.threshold``. We use parameters derived from the metadata, so
that our analysis is portable to images taken at slightly different
physical resolutions.

.. nbplot::

    >>> from skan.pre import threshold
    >>>
    >>> smooth_radius = 5 / spacing_nm  # float OK
    >>> threshold_radius = int(np.ceil(50 / spacing_nm))
    >>> binary0 = threshold(image0, sigma=smooth_radius,
    ...                     radius=threshold_radius)
    ...
    >>> fig, ax = plt.subplots()
    >>> ax.imshow(binary0);




(There are some thresholding artifacts around the left edge, due to the
dark border caused by microscope imaging drift and alignment. We will
ignore this in this document, but the Skan GUI allows for a “crop”
parameter to filter out these regions.)

Finally, we skeletonise this binary image:

.. nbplot::

    >>> from skimage import morphology
    >>>
    >>> skeleton0 = morphology.skeletonize(binary0)

Skan has functions for drawing skeletons in 2D:

.. nbplot::

    >>> from skan import draw
    >>>
    >>> fig, ax = plt.subplots()
    >>> draw.overlay_skeleton_2d(image0, skeleton0, dilate=1, axes=ax);




1. Measuring the length of skeleton branches
--------------------------------------------

Now that we have a skeleton, we can use Skan’s primary functions:
producing a network of skeleton pixels, and measuring the properties of
branches along that network.

.. nbplot::

    >>> from skan import csr
    >>>
    >>> pixel_graph, coordinates, degrees = csr.skeleton_to_csgraph(skeleton0)

The pixel graph is a SciPy `CSR matrix <>`__ in which entry
:math:`(i, j)` is 0 if pixels :math:`i` and :math:`j` are not connected,
and otherwise is equal to the distance between pixels :math:`i` and
:math:`j` in the skeleton. This will normally be 1 between adjacent
pixels and :math:`\sqrt{2}` between diagonally adjacent pixels, but in
this can be scaled by a ``spacing=`` keyword argument that sets the
scale (and this scale can be different for each image axis). In our
case, we know the spacing between pixels, so we can measure our network<commit_msg>Add correct link to scipy CSR<commit_after>
Getting started: Skeleton analysis with Skan
============================================

Skeleton analysis allows us to measure fine structure in samples. In
this case, we are going to measure the network formed by a protein
called *spectrin* inside the membrane of a red blood cell, either
infected by the malaria parasite *Plasmodium falciparum*, or uninfected.

0. Extracting a skeleton from an image
--------------------------------------

Skan’s primary purpose is to analyse skeletons. (It primarily mimics the
`“Analyze Skeleton” Fiji
plugin <https://imagej.net/AnalyzeSkeleton>`__.) In this section, we
will run some simple preprocessing on our source images to extract a
skeleton, provided by Skan and
`scikit-image <https://scikit-image.org>`__, but this is far from the
optimal way of extracting a skeleton from an image. Skip to section 1
for Skan’s primary functionality.

First, we load the images. These images were produced by an FEI scanning
electron microscope. `ImageIO <https://imageio.github.io>`__ provides a
method to read these images, including metadata.

.. nbplot::

    >>> from glob import glob
    >>> import imageio as iio
    >>>
    >>> files = glob('example-data/*.tif')
    >>> image0 = iio.imread(files[0], format='fei')

We display the images with `Matplotlib <http://matplotlib.org>`__.

.. mpl-interactive::

.. nbplot::

    >>>
    >>> fig, ax = plt.subplots()
    >>> ax.imshow(image0, cmap='gray');




As an aside, we can extract the pixel spacing in meters from the
``.meta`` attribute of the ImageIO image:

.. nbplot::

    >>> spacing = image0.meta['Scan']['PixelHeight']
    >>> spacing_nm = spacing * 1e9  # nm per pixel
    >>> dim_nm = np.array(image0.shape) / spacing_nm
    >>>
    >>> fig, ax = plt.subplots()
    >>> ax.imshow(image0, cmap='gray',
    ...           extent=[0, dim_nm[1], dim_nm[0], 0]);
    >>> ax.set_xlabel('x (nm)')
    >>> ax.set_ylabel('y (nm)');




This is an image of the inside of the membrane of a red blood cell. We
want to measure the network of bright regions in this image, which is
made up of a protein called *spectrin*. Our strategy will be to:

-  smooth the image with Gaussian smoothing
-  threshold the image with Sauvola thresholding (Otsu thresholding also
   works)
-  skeletonise the resulting binary image

These steps can be performed by a single function in Skan,
``skan.pre.threshold``. We use parameters derived from the metadata, so
that our analysis is portable to images taken at slightly different
physical resolutions.

.. nbplot::

    >>> from skan.pre import threshold
    >>>
    >>> smooth_radius = 5 / spacing_nm  # float OK
    >>> threshold_radius = int(np.ceil(50 / spacing_nm))
    >>> binary0 = threshold(image0, sigma=smooth_radius,
    ...                     radius=threshold_radius)
    ...
    >>> fig, ax = plt.subplots()
    >>> ax.imshow(binary0);




(There are some thresholding artifacts around the left edge, due to the
dark border caused by microscope imaging drift and alignment. We will
ignore this in this document, but the Skan GUI allows for a “crop”
parameter to filter out these regions.)

Finally, we skeletonise this binary image:

.. nbplot::

    >>> from skimage import morphology
    >>>
    >>> skeleton0 = morphology.skeletonize(binary0)

Skan has functions for drawing skeletons in 2D:

.. nbplot::

    >>> from skan import draw
    >>>
    >>> fig, ax = plt.subplots()
    >>> draw.overlay_skeleton_2d(image0, skeleton0, dilate=1, axes=ax);




1. Measuring the length of skeleton branches
--------------------------------------------

Now that we have a skeleton, we can use Skan’s primary functions:
producing a network of skeleton pixels, and measuring the properties of
branches along that network.

.. nbplot::

    >>> from skan import csr
    >>>
    >>> pixel_graph, coordinates, degrees = csr.skeleton_to_csgraph(skeleton0)

The pixel graph is a SciPy `CSR
matrix <https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.csr_matrix.html>`__
in which entry :math:`(i, j)` is 0 if pixels :math:`i` and :math:`j` are
not connected, and otherwise is equal to the distance between pixels
:math:`i` and :math:`j` in the skeleton. This will normally be 1 between
adjacent pixels and :math:`\sqrt{2}` between diagonally adjacent pixels,
but in this can be scaled by a ``spacing=`` keyword argument that sets
the scale (and this scale can be different for each image axis). In our
case, we know the spacing between pixels, so we can measure our network
{'fraction_non_alphanumeric': 0.1276194700077614, 'fraction_numerical': 0.007096130391395942, 'mean_word_length': 3.4086021505376345, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 211, 'https://': 7, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4476230', 'n_tokens_mistral': 2723, 'n_tokens_neox': 2532, 'n_words': 1093}