2
votes

Python / numpy: Supprimer la bordure vide (zéros) du tableau 3D

J'ai un tableau numpy 3D. Cela peut être considéré comme une image (pour être exact, ce sont les valeurs des points de champ). Je souhaite supprimer la bordure (valeurs 0, notez qu'il existe des valeurs négatives possibles) dans toutes les dimensions. La restriction est que la dimension reste la même pour toutes les molécules, par exemple. Je veux seulement supprimer la bordure dans la mesure où la "plus grande" entrée dans cette dimension est toujours à l'intérieur de la frontière. Il faut donc prendre en compte l'ensemble de l'ensemble de données (petit, sa taille n'est pas un problème).

Exemple en 2D:

1  0  0
1  1  0
0  0  0
0  0  0

0  0  0
0  1  0
0  0  1
0  0  1

Voici la ligne du haut, et la plupart des colonnes de gauche et de droite doivent être supprimées. Sur l'ensemble de l'ensemble de données, ils ne contiennent que 0 valeur.

Le résultat serait ci-dessous:

0  0  0  0  0
0  1  0  0  0
0  1  1  0  0
0  0  0  0  0
0  0  0  0  0

0  0  0  0  0
0  0  0  0  0
0  0  1  0  0
0  0  0  1  0
0  0  0  1  0

Puisque je ne suis pas un expert numpy, je ' J'ai du mal à définir un algorithme pour répondre à mes besoins. Je devrai trouver l'index min et max dans chaque dimension qui n'est pas 0, puis l'utiliser pour découper le tableau.

Similaire à ceci mais en 3D et le recadrage doit prendre en compte l'ensemble des données.

Comment puis-je y parvenir?

MISE À JOUR 13 février 2019:

J'ai donc essayé 3 réponses ici (une qui semble avoir été supprimée et qui utilisait zip), Martins et norok2s répondent . Les dimensions de sortie sont les mêmes, donc je suppose qu'elles fonctionnent toutes.

J'ai choisi la solution Martins car je peux facilement extraire la boîte englobante pour l'appliquer à l'ensemble de test.

MISE À JOUR 25 février :

Si quelqu'un observe encore cela, j'aimerais avoir d'autres commentaires. Comme dit, ce ne sont pas réellement des images mais des "valeurs de champ" signifiant float et non des images en niveaux de gris (uint8) ce qui signifie que je dois utiliser au moins float16 et cela nécessite simplement trop de mémoire. (J'ai 48 Go disponibles, mais ce n'est pas suffisant même pour 50% de l'ensemble d'entraînement).


5 commentaires

Où les plus petits tableaux doivent-ils être placés par rapport au plus grand? Ce que je veux dire, c'est, en 1D, en supposant que le plus grand objet est par ex. [1, 0, 1, 1] et un plus petit (réduit) étant [1, 1] s'il devenait [0, 0, 1, 1 ] (fin), [0, 1, 1, 0] (milieu) ou [1, 1, 0, 0] (début)?


Tout initial a la même taille. Dans le résultat final, les coordonnées «relatives» pour chaque valeur / pixels restants doivent rester les mêmes.


@beginner_ Vérifiez ma dernière modification. Cela doit fonctionner comme vous le souhaitiez maintenant


@beginner_ Avez-vous répondu à votre question?


@Martin c'est occupé ici. Je n'ai pas encore eu l'occasion de vérifier quelle réponse fonctionne le mieux


3 Réponses :


4
votes

Essayez ceci: - c'est un algorithme principal. Je ne comprends pas exactement quels côtés vous voulez extraire de vos exemples, mais l'algorithme ci-dessous devrait être très facile à modifier en fonction de vos besoins

Remarque: Cet algorithme extrait CUBE où toutes les bordures de valeur zéro sont «supprimées». Donc de chaque côté du cube se trouve une valeur! = 0

import numpy as np


class ImageContainer(object):
    def __init__(self,first_image):
        self.container =  np.uint8(np.expand_dims(np.array(first_image), axis=0))

    def add_image(self,image):
        #print(image.shape)
        temp = np.uint8(np.expand_dims(np.array(image), axis=0))
        #print(temp.shape)
        self.container  = np.concatenate((self.container,temp),axis = 0)
        print('container shape',self.container.shape)

# Create image container storage

image = np.zeros(shape = [5,5,3]) # some image
image[2,2,1]=1 # put something random in it
container = ImageContainer(image)
image = np.zeros(shape = [5,5,3]) # some image
image[2,2,2]=1
container.add_image(image)
image = np.zeros(shape = [5,5,3]) # some image
image[2,3,0]=1    # if we set [2,2,0] = 1, we can expect all images will have just 1x1 pixel size
container.add_image(image)
image = np.zeros(shape = [5,5,3]) # some image
image[2,2,1]=1
container.add_image(image)
>>> container.container.shape
('container shape', (4, 5, 5, 3)) # 4 images, size 5x5, 3 channels


# remove borders to all images at once
xs,ys,zs,zzs = np.where(container.container!=0) 
# for 4D object

# extract cube with extreme limits of where are the values != 0
result = container.container[min(xs):max(xs)+1,min(ys):max(ys)+1,min(zs):max(zs)+1,min(zzs):max(zzs)+1]

>>> print('Final shape:',result.shape) 


('Final shape', (4, 1, 2, 3)) # 4 images, size: 1x2, 3 channels

Cas 1:

d = np.zeros(shape = [5,5,5]) # no values except zeros
>>> result.shape


Traceback (most recent call last):
  File "C:\Users\zzz\Desktop\py.py", line 7, in <module>
    result = d[min(xs):max(xs)+1,min(ys):max(ys)+1,min(zs):max(zs)+1]
ValueError: min() arg is an empty sequence

Cas 2: # erreur case - seulement des zéros - la 3D résultante n'a pas de dimensions -> erreur

d = np.zeros(shape = [5,5,5])

d[3,2,1]=1
# ...  just one value

>>> result.shape # works

(1,1,1)

EDIT: Parce que ma solution n'a pas reçu assez d'amour et de compréhension, je vais donner un exemple à la 4ème dimension body, où 3 dimensions sont libres pour l'image et la 4ème dimension est l'endroit où les images sont stockées

import numpy as np

# testing dataset
d = np.zeros(shape = [5,5,5]) 

# fill some values
d[3,2,1]=1
d[3,3,1]=1
d[1,3,1]=1
d[1,3,4]=1

# find indexes in all axis
xs,ys,zs = np.where(d!=0) 
# for 4D object
# xs,ys,zs,as = np.where(d!=0) 

# extract cube with extreme limits of where are the values != 0
result = d[min(xs):max(xs)+1,min(ys):max(ys)+1,min(zs):max(zs)+1] 
# for 4D object
# result = d[min(xs):max(xs)+1,min(ys):max(ys)+1,min(zs):max(zs)+1,min(as):max(as)+1]

>>> result.shape
(3, 2, 4)


11 commentaires

La bordure inférieure ne doit pas être supprimée car la deuxième image a un 1 dans la rangée du bas. Le recadrage doit prendre en compte toutes les images et les coordonnées «relatives» de chaque pixel doivent rester les mêmes.


alors ok. Je me suis confus. Maintenant, cela devrait être correct. Mettez-y un corps 3D, et le tableau dans 'result' devrait être un cube minimal où les valeurs extrêmes! = 0 touchent le côté


Soit il me manque quelque chose, soit cela ne fonctionne pas pour l'ensemble de données. Si j'ai 10 images 3D, je devrai trouver la boîte englobante qui comprendra toutes les valeurs non nulles sur les 10 images.


Si vous avez 10 images 3D, vous allez simplement mettre ce tableau dans mon script et cela devrait fonctionner


Je pensais être clair avec le test de l'ensemble de données dans mon script


Tout ce que vous avez à faire est de passer un tableau avec une forme 3D = [x, y, z]. Rien d'autre


Vous auriez besoin d'écrire un algorithme 4D pour 10 images 3D, vous ne pouvez pas simplement appliquer la version 3D 10 fois car en général cela conduira à 10 formes de sortie différentes, ce que @beginner_ essaie d'éviter.


@ norok2 Mon algorithme est pour n'importe quel nombre de dimensions ... Il suffit d'étendre les index, mais il vaut mieux empiler les images 3D et les conserver en 3D. Aussi je ne vois aucune mention dans OP sur le corps de 4ème dimension


@ norok2 Si OP a une pile d'images en niveaux de gris pour créer un corps 3D, ma réponse est exactement ce que OP veut


@Martin A part que je crois que OP a dit juste plus tôt ici qu'il aurait 10 images 3D. Mon point est que si vous voulez résoudre des tableaux M N-dimensionnels, vous ne pouvez pas résoudre chacun des tableaux M séparément, car cela vous donnera des formes incohérentes pour les résultats. Vous pouvez toujours résoudre séparément, mais vous avez besoin d'une logique supplémentaire pour correspondre à la forme de sortie. ALTERNATIVEMENT, vous pouvez résoudre le même problème pour le tableau (N + 1) dimensionnel obtenu en empilant les entrées ensemble, puis en divisant la sortie. Cela garantira une production cohérente et préservera les positions relatives.


A N qui acceptera ma réponse. Le PO a dit que ma réponse ne fonctionnait pas pour lui. Donc je ne comprends pas ce qui se passe.



1
votes

Mise à jour:

Basé sur la solution de Martin utilisant min / max et np.where, mais en la généralisant à n'importe quelle dimension, vous pouvez le faire de cette manière:

def bounds_per_dimension(ndarray):
    return map(
        lambda e: range(e.min(), e.max() + 1),
        np.where(ndarray != 0)
    )

def zero_trim_ndarray(ndarray):
    return ndarray[np.ix_(*bounds_per_dimension(ndarray))]

d = np.array([[
    [0, 0, 0, 0, 0],
    [0, 1, 0, 0, 0],
    [0, 1, 1, 0, 0],
    [0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0],
], [
    [0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0],
    [0, 0, 1, 0, 0],
    [0, 0, 0, 1, 0],
    [0, 0, 0, 1, 0],
]])

zero_trim_ndarray(d)

p>


1 commentaires

Utiliser range () et np.ix_ () pour cela va être inutilement lent. Si vous chronométriez ce code par rapport à l'approche slice () / arr [] (telle qu'utilisée dans ma réponse), vous obtiendriez, même pour cet exemple simple, en utilisant d comme entrée, ~ 2x différence de vitesse.



1
votes

Vous pourriez voir votre problème comme un ajustement pour une boîte englobante spécifique sur le tableau formé en rassemblant toutes les formes que vous avez dans un seul tableau.

Par conséquent, si vous avez une fonction d'ajustement à n dimensions, la solution est simplement appliquez-le.

Une façon de mettre en œuvre ceci serait:

trimmed_list = np.split(trimmed_arr, arr.shape[-1], -1)

Une solution légèrement plus flexible (où vous pourriez indiquer sur quel axe agir) est disponible dans FlyingCircus ( Clause de non-responsabilité : je suis l'auteur principal du package) .

Donc, si vous avez votre liste de tableaux n-dim (en arrs ), vous pouvez d'abord les empiler en utilisant np.stack () puis découpez le résultat:

import numpy as np

arr = np.stack(arrs, -1)
trimmed_arr = trim(arr, arr != 0)

qui pourrait ensuite être séparé en utilisant np.split () , par exemple:

import numpy as np

def trim(arr, mask):
    bounding_box = tuple(
        slice(np.min(indexes), np.max(indexes) + 1)
        for indexes in np.where(mask))
    return arr[bounding_box]

EDIT:

Je viens de réaliser que cela utilise beaucoup la même approche que les autres réponses, sauf que cela me semble beaucoup plus propre.


0 commentaires