Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature suggestion: Function that places a structuring element in an array around a given index. #6122

Open
kolibril13 opened this issue Dec 12, 2021 · 2 comments

Comments

@kolibril13
Copy link
Contributor

kolibril13 commented Dec 12, 2021

The variety of structuring elements in scikit-image ​is quite amazing:
https://scikit-image.org/docs/dev/auto_examples/numpy_operations/plot_structuring_elements.html#generate-structuring-elements

However, when one wants to place them into a numpy array at a certain position, one has to do something like this, which is really cumbersome code:

import numpy as np
from skimage.morphology import  ball
x=np.full((20,20,20),0)
x[10-3:10+2,10-3:10+2,6-3:6+2]=ball(2)

and one (at least me 😉 ) gets lots of error messages like
ValueError: could not broadcast input array from shape (5,5,5) into shape (4,4,4)
This could be solved by a function like place_structuring_element.

Here is a proof of concept:

import numpy as np
import matplotlib.pyplot as plt
from skimage.morphology import  ball

voxelarray=np.full((20,20,20),0)

def place_structuring_element_in_array(array,structure, x,y,z):
    array[x:x+structure.shape[0],y:y+structure.shape[1],z:z+structure.shape[2]]= structure
    return array
voxelarray = place_structuring_element(voxelarray,ball(2), 1,1,1 )
voxelarray = place_structuring_element(voxelarray,ball(4), 6,6,6 )

fig = plt.figure(figsize=(8, 8))
ax = fig.add_subplot(1, 1, 1, projection="3d")
ax.view_init(15, 190)
ax.voxels(voxelarray, edgecolor='k')
plt.show()

image

The proof of concept is really only a quick draft, and one could add things like if the alignment to this anchor index should be at the CENTER, TOP, RIGHT, UPLEFT etc.
I just wanted to share this idea and ask if scikit-image might be interested in a function like this.

@jni
Copy link
Member

jni commented Dec 13, 2021

Hi @kolibril13, and thanks for your interest!

I see the utility of such a function, but I'm not sure about the API. I think this sort of belongs with skimage.draw, and disk is already there, for example. So for me it would be more about adding more such functions in order to match the structuring elements we already have.

@scikit-image scikit-image locked and limited conversation to collaborators Dec 13, 2021
@rfezzani rfezzani converted this issue into a discussion Dec 13, 2021
@scikit-image scikit-image unlocked this conversation Apr 8, 2022
@grlee77
Copy link
Contributor

grlee77 commented Apr 8, 2022

Prior conversations moved back to issues from Discussions

@rfezzani on Dec 13, 2021

I agree, this can take place in skimage.draw. The API can be something like

draw.structuring_element(footprint, *, position=None, shape=None, out=None):
    """Draw a structuring element.

    Parameters
    ----------
    footprint: ndarray
        A structuring element.
    position: array_like, optional
        The position of the structuring element in the output image. The center
        of the output image by default.
    shape: array_like, optional
        The shape of the output image. Same as footprint by default.
    out: ndarray, optional
        An array where footprint must be drawn.
    """

@kolibril13 on Dec 13, 2021
Nice to hear that this function might fit into skimage.draw.
I just wrote a more fleshed out version of this function, using a similar function signature that @rfezzani shared.
I did not understand the parameter "shape", that's why I left it out, and I think "array" and "structure" are more intuitive to use than "out" and "footprint" but I am open to change "array" and "structure" when there are arguments against these names.
Here is a new iteration of this idea:

import numpy as np
import matplotlib.pyplot as plt
from skimage.morphology import ball


def structuring_element(array, structure, position=None, alignment_to_index="center"):
    """Draw a structuring element.

    Parameters
    ----------
    array: ndarray
        An array where the structuring element will be placed.
    structure: ndarray
        A structuring element.
    position: array_like, optional
        The position of the structuring element in the output image. 
        When no position is given, the stucuting element will be drawn in the center of the array. 
        If one or more array axis haven an even count of total elements and therefore no integer as center index, 
        the center index is rounded up to the next integer.
    alignment_to_index: string, optional
        Can be either "center" or "corner".
    """
    if position == None:
        i, j, k = array.shape
        position = np.array([int(np.ceil(i / 2)), int(np.ceil(j / 2)), int(np.ceil(k / 2))])
    x = position[0]
    y = position[1]
    z = position[2]
    strulen_x = structure.shape[0]
    strulen_y = structure.shape[1]
    strulen_z = structure.shape[2]
    if alignment_to_index == "center":
        halfxA = int(np.ceil(strulen_x / 2))
        halfxB = int(np.floor(strulen_x / 2))
        halfyA = int(np.ceil(strulen_y / 2))
        halfyB = int(np.floor(strulen_y / 2))
        halfzA = int(np.ceil(strulen_z / 2))
        halfzB = int(np.floor(strulen_z / 2))
        array[
            x - halfxA : x + halfxB, y - halfyA : y + halfyB, z - halfzA : z + halfzB
        ] = structure
    if alignment_to_index == "corner":
        array[x : x + strulen_x, y : y + strulen_y, z : z + strulen_z] = structure

    return array


voxelarray = np.full((25, 25, 25), 0)
voxelarray = structuring_element(voxelarray, ball(7))
voxelarray = structuring_element(voxelarray, ball(2), position=[3, 20, 20], alignment_to_index="center")
voxelarray = structuring_element(voxelarray, ball(1), position=[0, 0, 0], alignment_to_index="corner")

fig = plt.figure(figsize=(8, 8))
ax = fig.add_subplot(1, 1, 1, projection="3d")
ax.view_init(10, 200)
ax.voxels(voxelarray, edgecolor="k")
plt.show()

image


@rezzani on Dec 15, 2021

@kolibril13

I did not understand the parameter "shape", that's why I left it out, and

The behavior that I though about is:

  • if shape is provided (not None), an array with this shape is created and the structuring element footprint is drawn in it,
  • if out is provided (not None), the structuring element footprint is drawn in it. It acts as array in your example.

I think "array" and "structure" are more intuitive to use than "out" and "footprint" but I am open to change "array" and "structure" when there are arguments against these names.

out and footprint are conventional argument names, already used in many places in scikit-image. We try to be consistent across scikit-image API wink


@kolibril13 on Dec 15, 2021
Thanks for clarifying!
That means that:

voxelarray = np.zeros((25, 25, 25))
voxelarray = structuring_element(footprint= ball(3), out= voxelarray)

would be the same as

voxelarray = structuring_element(footprint= ball(3), shape = (25, 25, 25))

?
And am I right: When out and shape are both given as parameters, that would cause an error?


@jni on Dec 15, 2021
Yes, you are right. Typically if both are provided, one takes precedence, in this case, out=


@kolibril13 on Dec 15, 2021
I've just made a notebook to test the function:
https://github.com/kolibril13/okapi/blob/main/gallery_3D_morphology.ipynb
Also, the new function is quite handy to build examples for morphological transformations
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants