Closed EKami closed 5 years ago
Can I ask which version of Python you are using? Also can you also clarify exactly which line of the above is causing the error? Thanks
@Peter554 Sure, Python 3.6.6, the line causing the error is the last one:
img = stain_normalizer.transform(img)
I installed spams with conda
I also obtain:
RuntimeWarning: divide by zero encountered in true_divide
source_concentrations *= (self.maxC_target / maxC_source)
In some of my images
If I use staintools.StainNormalizer(method='macenko')
instead I get:
numpy.linalg.linalg.LinAlgError: Eigenvalues did not converge
If that can help. Getting an exception by using the StainNormalizer is kinda ok, at least I can catch it and continue the execution of my code. But if I get a Floating point exception like above there is nothing much I can do.
It seems you are getting zero for the max concentration on img
. I believe you load this from a numpy array img = np.load("out.npy")
. Can you please send a picture of this image? Is it in the correct format (uint8: unsigned integers in range 0-255)?
@Peter554 it's mostly a white image (RGB range 0-255 with dtype uint8
). I understand this is not the kind of images that's expected to fit to the normalizer but my point is that I shouldn't get a segfault if I use a valid image while this image doesn't have the right concentration. I should, at worse, get a python exception that I can catch and let my program continue.
The way I planned to use your tool is to stick it to my deep learning dataset pipeline so all kind of images can go through it, I eventually could check the image content to make sure I do not pass images that the normalizer do not expect but in that case I would have to rely on my own assumptions to create the condition which prevent my whole program from crashing.
Instead your lib could just ignore the stain normalization on a given image (which is what makes the most sense to me) if that same image is not what your lib expect or at least throw a python exception then I wouldn't have to create hacky conditions to avoid crashes.
The sample code I posted in that thread along with the saved numpy array are sufficient for you to be able to reproduce the crash. I would like to be able to submit a PR but I'm not sure I would make this right. Thanks :)
Yes I agree the current behaviour is poor. If you have a solution that amends this behaviour a PR would certainly be welcome.
Hey @Peter554 I just checked and it seems the segfault is actually happening in SPAMS. spams.trainDL()
in VahadaneStainExtractor
seems to be the culprit (the segfault happens right after the get_stain_matrix
returns so that's very weird as everything seems to happen smoothly then fails). You can reproduce by adding this test to your test suite:
import os
from pathlib import Path
import staintools
from staintools.utils.misc_utils import *
import numpy as np
def test_stain_normalizer():
# Ensure the test do not segfault
x = np.array([[255, 255, 250],
[255, 255, 255],
[255, 255, 255]], dtype=np.uint8)
x = np.repeat(x[:, :, np.newaxis], 3, axis=2) # Adds 3 channels
stain_normalizer = staintools.StainNormalizer(method='vahadane')
script_dir = Path(os.path.dirname(os.path.abspath(__file__)))
i1 = staintools.read_image(str(script_dir / ".." / "data" / "i1.png"))
stain_normalizer.fit(i1)
output = stain_normalizer.transform(x)
assert output.shape == x.shape
I really don't have any idea how to deal with SPAMS, do you have any recommendation of a precheck on my images that we talked about that I could add which will prevent me to end up with a segfault? Thanks a lot!
Hi, I am part of the SPAMS dev team. I found the issue. In both examples, resp.:
img = np.load("out.npy")
stain_normalizer = staintools.StainNormalizer(method='vahadane')
img = stain_normalizer.transform(img)
and
x = np.array([[255, 255, 250],
[255, 255, 255],
[255, 255, 255]], dtype=np.uint8)
x = np.repeat(x[:, :, np.newaxis], 3, axis=2) # Adds 3 channels
stain_normalizer = staintools.StainNormalizer(method='vahadane')
output = stain_normalizer.transform(x)
both images (img
and x
) are fully masked in the method VahadaneStainExtractor.get_stain_matrix
(see staintools/stain_extractors/vahadane_stain_extractor.py
), thus the decomposition from SPAMS is applied to an empty array, hence the segfault.
Here is a reproducible example:
import numpy as np
x = np.array([[255, 255, 250],
[255, 255, 255],
[255, 255, 255]], dtype=np.uint8)
x = np.repeat(x[:, :, np.newaxis], 3, axis=2) # Adds 3 channels
## see staintools/stain_extractors/vahadane_stain_extractor.py
from staintools.utils.misc_utils import convert_RGB_to_OD, normalize_rows, get_luminosity_mask
luminosity_threshold=0.8
dictionary_regularizer=0.1
I=x
tissue_mask = get_luminosity_mask(I, threshold=luminosity_threshold).reshape((-1,))
OD = convert_RGB_to_OD(I).reshape((-1, 3))
OD = OD[tissue_mask]
print(OD)
print(OD.shape)
import spams
dictionary = spams.trainDL(X=OD.T, K=2, lambda1=dictionary_regularizer, mode=2, modeD=0, posAlpha=True, posD=True, verbose=False).T # segfault: OD.T is an empty array
I will see how we can add som checks in SPAMS for such scenario. In the mean time it might probably be a quickier fix if there was a check before using spams in StainTools.
Hello, Thank you so much for taking the time to investigate on this issue! As I said on the github issue a python programmer don't expect to get a segfault when something goes wrong, he must, at best, get a python exception.
I will prob be able to submit a quick fix to the staintools repo thanks to your help but SPAMS would become more robust if it is bulletproof to these kind of failures :).
Again, thanks a lot for the help.
Passe une bonne journée.
On Tue, Nov 13th, 2018 at 1:8 PM, gdurif notifications@github.com wrote:
Hi, I am part of the SPAMS dev team. I found the issue. In both examples, resp.:
img = np.load("out.npy") stain_normalizer = staintools.StainNormalizer(method='vahadane') img = stain_normalizer.transform(img)
and
x = np.array([[255, 255, 250], [255, 255, 255], [255, 255, 255]], dtype=np.uint8) x = np.repeat(x[:, :, np.newaxis], 3, axis=2) # Adds 3 channels stain_normalizer = staintools.StainNormalizer(method='vahadane') output = stain_normalizer.transform(x)
both images ( img and x ) are fully masked in the method VahadaneStainExtractor.get_stain_matrix (see staintools/stain_extractors/vahadane_stain_extractor.py ), thus the decomposition from SPAMS is applied to an empty array, hence the segfault.
Here is a reproducible example:
import numpy as np x = np.array([[255, 255, 250], [255, 255, 255], [255, 255, 255]], dtype=np.uint8) x = np.repeat(x[:, :, np.newaxis], 3, axis=2)
Adds 3 channels ## see
staintools/stain_extractors/vahadane_stain_extractor.py from staintools.utils.misc_utils import convert_RGB_to_OD, normalize_rows, get_luminosity_mask luminosity_threshold=0.8 dictionary_regularizer=0.1 I=x tissue_mask = get_luminosity_mask(I, threshold=luminosity_threshold).reshape((-1,)) OD = convert_RGB_to_OD(I).reshape((-1, 3)) OD = OD[tissue_mask] print(OD) print(OD.shape) import spams dictionary = spams.trainDL(X=OD.T, K=2, lambda1=dictionary_regularizer, mode=2, modeD=0, posAlpha=True, posD=True, verbose=False).T # segfault: OD.T is an empty array
I will see how we can add check in SPAMS for such scenario. In the mean time it might probably be a quickier fix if there was a check before using spams in StainTools.
— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub ( https://github.com/Peter554/StainTools/issues/26#issuecomment-438243832 ) , or mute the thread ( https://github.com/notifications/unsubscribe-auth/AD2AopzMhL94ve5sHZ4XGWdhpX3Z-NB3ks5uurZEgaJpZM4X10ZJ ).
Thanks for the tip. I will try to look into it as soon as possible.
PR submitted here: https://github.com/Peter554/StainTools/pull/27 . Ignoring the stain normalization in the entire image may be a better solution than raising an exception, your call @Peter554
Should be fixed now. Thanks.
Hello @Peter554, I get a
Floating point exception (core dumped)
when I'm usingtransform(img)
fromstaintools.StainNormalizer(method='vahadane')
withstain_normalizer.fit(i1_standard)
previously applied andi1_standard
being the same image as given in the example. Here is a sample code to reproduce the error:Output:
Any idea why this happens? I've put the npy array file here. Thanks a lot for this amazing tool!