Issues with scaling images for canny edge detection
From what I'd read in the documentation, the function img_as_float would
Hi, *Summary: *I'm fairly new to skimage, and I'm trying to replicate some work I've done in IDL using the Canny edge detector. I've imported the same image into skimage and tried running the Canny function with the same parameters, but I either get a blank image, or very different results to IDL which don't change regardless of the parameters I use. I suspect my problems may be related to how I am scaling my image to make it between 0 and 1, as the documentation for the skimage Canny function requires. *More details:* The input image I used in both IDL and skimage is available at https://www.dropbox.com/s/xaiq9kitrf1b4cf/HOT_sub.tif. In IDL I called the CANNY function (documentation available at http://www.exelisvis.com/docs/CANNY.html) as follows: result = CANNY(image, HIGH=0.95, LOW=0.3, SIGMA=2) and got the following image: https://lh3.googleusercontent.com/-xwuyuH_mxEA/Ucm2Y2m2EyI/AAAAAAAAEnw/46OmI... I loaded the image into skimage as follows: hot = skimage.io.imread("HOT_sub.tif") And removed all negative values by adding the absolute value of the minimum: abs_hot = hot + abs(np.min(hot)) then scale this between 0 and 1 in a sensible way, but it gave an error: C:\Python27\lib\site-packages\skimage\util\dtype.pyc in convert(image, dtype) 73 if kind_in == 'f': 74 if np.min(image) < 0 or np.max(image) > 1: ---> 75 raise ValueError("Images of type float must be between 0 and 1") 76 if kind == 'f': 77 # floating point -> floating point ValueError: Images of type float must be between 0 and 1 So I did it myself by simply dividing by the maximum value: im_hot = img_as_float(abs_hot/np.max(abs_hot) However, running the Canny edge detector on this image produces an entirely blank edge image: edges = canny(im_hot, sigma=2, low_threshold=0.3, high_threshold=0.90) np.sum(edges) # Gives 0 showing there are no edges found Regardless how I play with the parameters, I can't seem to get it to give me any edges. Interestingly, if I ignore the instructions to make sure that my input image is between 0 and 1, and just use the raw image: edges = canny(hot, sigma=2, low_threshold=0.3, high_threshold=0.90) I get a more sensible result (well, at least it isn't blank!): https://lh5.googleusercontent.com/-Ta33pkF-pIY/Ucm4abti3lI/AAAAAAAAEoA/iyX6F... But this is very different to the result given by IDL - and furthermore, adjusting the parameters doesn't seem to change the output at all. What am I doing wrong here? I suspect it is something to do with the image scaling, but I'm not sure - it could be a conceptual problem with my image processing knowledge, or I could be using skimage improperly. Does anyone have any ideas or suggestions as to where to go from here? If I manage to solve this I will, of course, write up the solution on my blog so that others can benefit too. Best regards, Robin University of Southampton, UK
From the operation you listed, abs_hot = hot + abs(np.min(hot)), it seems
I can‘t duplicate this, but I may know what's going on. img_as_float converts non-float datatypes into floating-point images on the range [0, 1]. The traceback you note shows that a floating point array was passed to img_as_float, but the image had values outside [0, 1]. Try checking hot.dtype before running img_as_float; if it's an (unsigned) integer, everything should work fine. like hot should still be an integer, but that traceback code path is only active for inputs where arr.dtype.kind == 'f' so abs_hot got converted to float at some point. Check abs_hot.dtype right prior to running img_as_float; is it an integer or a float? My intuition for a blank canny result is that your division operation abs_hot / np.max(abs_hot) may have been between two integer types, resulting in a boolean array which would have almost no edges. Try assigning normhot = abs_hot / np.max(abs_hot) and checking the dtype; if it's boolean, cast one or both to float before the division and re-run. The other possibility is the canny parameters are pretty far off. I‘m not sure what’s going on with the raw result, but check the above and get back with us! Hopefully that helps to get things moving. Josh On Tuesday, June 25, 2013 10:37:20 AM UTC-5, Robin Wilson wrote: Hi,
*Summary: *I'm fairly new to skimage, and I'm trying to replicate some work I've done in IDL using the Canny edge detector. I've imported the same image into skimage and tried running the Canny function with the same parameters, but I either get a blank image, or very different results to IDL which don't change regardless of the parameters I use. I suspect my problems may be related to how I am scaling my image to make it between 0 and 1, as the documentation for the skimage Canny function requires.
*More details:* The input image I used in both IDL and skimage is available at https://www.dropbox.com/s/xaiq9kitrf1b4cf/HOT_sub.tif.
In IDL I called the CANNY function (documentation available at http://www.exelisvis.com/docs/CANNY.html) as follows:
result = CANNY(image, HIGH=0.95, LOW=0.3, SIGMA=2)
and got the following image:
https://lh3.googleusercontent.com/-xwuyuH_mxEA/Ucm2Y2m2EyI/AAAAAAAAEnw/46OmI... I loaded the image into skimage as follows:
hot = skimage.io.imread("HOT_sub.tif")
And removed all negative values by adding the absolute value of the minimum:
abs_hot = hot + abs(np.min(hot))
From what I'd read in the documentation, the function img_as_float would then scale this between 0 and 1 in a sensible way, but it gave an error:
C:\Python27\lib\site-packages\skimage\util\dtype.pyc in convert(image, dtype) 73 if kind_in == 'f': 74 if np.min(image) < 0 or np.max(image) > 1: ---> 75 raise ValueError("Images of type float must be between 0 and 1") 76 if kind == 'f': 77 # floating point -> floating point
ValueError: Images of type float must be between 0 and 1
So I did it myself by simply dividing by the maximum value:
im_hot = img_as_float(abs_hot/np.max(abs_hot)
However, running the Canny edge detector on this image produces an entirely blank edge image:
edges = canny(im_hot, sigma=2, low_threshold=0.3, high_threshold=0.90) np.sum(edges) # Gives 0 showing there are no edges found
Regardless how I play with the parameters, I can't seem to get it to give me any edges.
Interestingly, if I ignore the instructions to make sure that my input image is between 0 and 1, and just use the raw image:
edges = canny(hot, sigma=2, low_threshold=0.3, high_threshold=0.90)
I get a more sensible result (well, at least it isn't blank!):
https://lh5.googleusercontent.com/-Ta33pkF-pIY/Ucm4abti3lI/AAAAAAAAEoA/iyX6F... But this is very different to the result given by IDL - and furthermore, adjusting the parameters doesn't seem to change the output at all.
What am I doing wrong here? I suspect it is something to do with the image scaling, but I'm not sure - it could be a conceptual problem with my image processing knowledge, or I could be using skimage improperly. Does anyone have any ideas or suggestions as to where to go from here? If I manage to solve this I will, of course, write up the solution on my blog so that others can benefit too.
Best regards,
Robin University of Southampton, UK
Argh, correction: if normhot = abs_hot / np.max(abs_hot) were conducting truncating division the result wouldn't be a boolean array but an integer array, with all values 0 except the previous maximum value(s), which would be 1. Functionally boolean, but the dtype would be integer. On Tuesday, June 25, 2013 10:55:31 PM UTC-5, Josh Warner wrote:
I can‘t duplicate this, but I may know what's going on.
img_as_float converts non-float datatypes into floating-point images on the range [0, 1]. The traceback you note shows that a floating point array was passed to img_as_float, but the image had values outside [0, 1]. Try checking hot.dtype before running img_as_float; if it's an (unsigned) integer, everything should work fine.
From the operation you listed, abs_hot = hot + abs(np.min(hot)), it seems like hot should still be an integer, but that traceback code path is only active for inputs where arr.dtype.kind == 'f' so abs_hot got converted to float at some point. Check abs_hot.dtype right prior to running img_as_float; is it an integer or a float?
My intuition for a blank canny result is that your division operation abs_hot / np.max(abs_hot) may have been between two integer types, resulting in a boolean array which would have almost no edges. Try assigning normhot = abs_hot / np.max(abs_hot) and checking the dtype; if it's boolean, cast one or both to float before the division and re-run. The other possibility is the canny parameters are pretty far off.
I‘m not sure what’s going on with the raw result, but check the above and get back with us! Hopefully that helps to get things moving.
Josh
On Tuesday, June 25, 2013 10:37:20 AM UTC-5, Robin Wilson wrote:
Hi,
*Summary: *I'm fairly new to skimage, and I'm trying to replicate some work I've done in IDL using the Canny edge detector. I've imported the same image into skimage and tried running the Canny function with the same parameters, but I either get a blank image, or very different results to IDL which don't change regardless of the parameters I use. I suspect my problems may be related to how I am scaling my image to make it between 0 and 1, as the documentation for the skimage Canny function requires.
*More details:* The input image I used in both IDL and skimage is available at https://www.dropbox.com/s/xaiq9kitrf1b4cf/HOT_sub.tif.
In IDL I called the CANNY function (documentation available at http://www.exelisvis.com/docs/CANNY.html) as follows:
result = CANNY(image, HIGH=0.95, LOW=0.3, SIGMA=2)
and got the following image:
https://lh3.googleusercontent.com/-xwuyuH_mxEA/Ucm2Y2m2EyI/AAAAAAAAEnw/46OmI... I loaded the image into skimage as follows:
hot = skimage.io.imread("HOT_sub.tif")
And removed all negative values by adding the absolute value of the minimum:
abs_hot = hot + abs(np.min(hot))
From what I'd read in the documentation, the function img_as_float would then scale this between 0 and 1 in a sensible way, but it gave an error:
C:\Python27\lib\site-packages\skimage\util\dtype.pyc in convert(image, dtype) 73 if kind_in == 'f': 74 if np.min(image) < 0 or np.max(image) > 1: ---> 75 raise ValueError("Images of type float must be between 0 and 1") 76 if kind == 'f': 77 # floating point -> floating point
ValueError: Images of type float must be between 0 and 1
So I did it myself by simply dividing by the maximum value:
im_hot = img_as_float(abs_hot/np.max(abs_hot)
However, running the Canny edge detector on this image produces an entirely blank edge image:
edges = canny(im_hot, sigma=2, low_threshold=0.3, high_threshold=0.90) np.sum(edges) # Gives 0 showing there are no edges found
Regardless how I play with the parameters, I can't seem to get it to give me any edges.
Interestingly, if I ignore the instructions to make sure that my input image is between 0 and 1, and just use the raw image:
edges = canny(hot, sigma=2, low_threshold=0.3, high_threshold=0.90)
I get a more sensible result (well, at least it isn't blank!):
https://lh5.googleusercontent.com/-Ta33pkF-pIY/Ucm4abti3lI/AAAAAAAAEoA/iyX6F... But this is very different to the result given by IDL - and furthermore, adjusting the parameters doesn't seem to change the output at all.
What am I doing wrong here? I suspect it is something to do with the image scaling, but I'm not sure - it could be a conceptual problem with my image processing knowledge, or I could be using skimage improperly. Does anyone have any ideas or suggestions as to where to go from here? If I manage to solve this I will, of course, write up the solution on my blog so that others can benefit too.
Best regards,
Robin University of Southampton, UK
Hi Josh, Thanks for the responses - I think I've managed to sort out what is going on a bit more clearly now. In terms of the data types issue, the original image is a float array, with both positive and negative values. I didn't realise that img_as_float wouldn't take a floating point array as input - I thought if it got given a float it would just do the rescaling as appropriate. Given that the original image is a float, all of the derived arrays are floats too - including the result of the division operation. Anyway, I think your final comment about the canny parameters possibly being far off is the real problem. I've now managed to get a sensible result with both the scaled image, and the original image (which is interesting, as the documentation specifically states that the input image should be normalised to be in the range 0.0-1.0). I managed to do this by using radically different canny parameters to the ones I was using before: # For the scaled image edges = canny(normhot, sigma=2.0, low_threshold=0.0000000000000001, high_threshold=0.01) # For the original image edges = canny(hot, sigma=2.0, low_threshold=10, high_threshold=150) (for comparison, the parameters I was using earlier - which worked fine in IDL - were 0.3 and 0.9) I've worked out why those parameters worked in IDL but didn't give useful results when using skimage: the IDL documentation ( http://www.exelisvis.com/docs/CANNY.html) describes the parameters as: High: The high value used to calculate the high threshold during edge detection, given as a factor of the histogram of the magnitude array. Low: The low value used to calculate the low threshold during edge detection, given as a factor of the HIGHhttp://www.exelisvis.com/docs/CANNY.html#C_854643309_1362362 value. Thus, the IDL code expects parameters in the range 0-1, which are then used to find a percentile of the histogram of magnitude values, whereas the skimage code does thresholding on the raw magnitude values, and that is why the same parameters were giving such different results in IDL and skimage. I'm now engaged in looking into the IDL code to see exactly how their thresholding works, as my naive modification of the skimage canny code to try and replicate the IDL results doesn't give me quite the same answers. I think I've managed to solve most of my problems, but I have one question: the canny routine seems to work fine for my original image, without rescaling it from 0-1. Is there a reason that it should only work for images between 0-1, or am I safe to use my original images? Cheers, Robin On Wednesday, 26 June 2013 04:59:09 UTC+1, Josh Warner wrote:
Argh, correction: if normhot = abs_hot / np.max(abs_hot) were conducting truncating division the result wouldn't be a boolean array but an integer array, with all values 0 except the previous maximum value(s), which would be 1. Functionally boolean, but the dtype would be integer.
On Tuesday, June 25, 2013 10:55:31 PM UTC-5, Josh Warner wrote:
I can‘t duplicate this, but I may know what's going on.
img_as_float converts non-float datatypes into floating-point images on the range [0, 1]. The traceback you note shows that a floating point array was passed to img_as_float, but the image had values outside [0, 1]. Try checking hot.dtype before running img_as_float; if it's an (unsigned) integer, everything should work fine.
From the operation you listed, abs_hot = hot + abs(np.min(hot)), it seems like hot should still be an integer, but that traceback code path is only active for inputs where arr.dtype.kind == 'f' so abs_hot got converted to float at some point. Check abs_hot.dtype right prior to running img_as_float; is it an integer or a float?
My intuition for a blank canny result is that your division operation abs_hot / np.max(abs_hot) may have been between two integer types, resulting in a boolean array which would have almost no edges. Try assigning normhot = abs_hot / np.max(abs_hot) and checking the dtype; if it's boolean, cast one or both to float before the division and re-run. The other possibility is the canny parameters are pretty far off.
I‘m not sure what’s going on with the raw result, but check the above and get back with us! Hopefully that helps to get things moving.
Josh
On Tuesday, June 25, 2013 10:37:20 AM UTC-5, Robin Wilson wrote:
Hi,
*Summary: *I'm fairly new to skimage, and I'm trying to replicate some work I've done in IDL using the Canny edge detector. I've imported the same image into skimage and tried running the Canny function with the same parameters, but I either get a blank image, or very different results to IDL which don't change regardless of the parameters I use. I suspect my problems may be related to how I am scaling my image to make it between 0 and 1, as the documentation for the skimage Canny function requires.
*More details:* The input image I used in both IDL and skimage is available at https://www.dropbox.com/s/xaiq9kitrf1b4cf/HOT_sub.tif.
In IDL I called the CANNY function (documentation available at http://www.exelisvis.com/docs/CANNY.html) as follows:
result = CANNY(image, HIGH=0.95, LOW=0.3, SIGMA=2)
and got the following image:
https://lh3.googleusercontent.com/-xwuyuH_mxEA/Ucm2Y2m2EyI/AAAAAAAAEnw/46OmI... I loaded the image into skimage as follows:
hot = skimage.io.imread("HOT_sub.tif")
And removed all negative values by adding the absolute value of the minimum:
abs_hot = hot + abs(np.min(hot))
From what I'd read in the documentation, the function img_as_float would then scale this between 0 and 1 in a sensible way, but it gave an error:
C:\Python27\lib\site-packages\skimage\util\dtype.pyc in convert(image, dtype) 73 if kind_in == 'f': 74 if np.min(image) < 0 or np.max(image) > 1: ---> 75 raise ValueError("Images of type float must be between 0 and 1") 76 if kind == 'f': 77 # floating point -> floating point
ValueError: Images of type float must be between 0 and 1
So I did it myself by simply dividing by the maximum value:
im_hot = img_as_float(abs_hot/np.max(abs_hot)
However, running the Canny edge detector on this image produces an entirely blank edge image:
edges = canny(im_hot, sigma=2, low_threshold=0.3, high_threshold=0.90) np.sum(edges) # Gives 0 showing there are no edges found
Regardless how I play with the parameters, I can't seem to get it to give me any edges.
Interestingly, if I ignore the instructions to make sure that my input image is between 0 and 1, and just use the raw image:
edges = canny(hot, sigma=2, low_threshold=0.3, high_threshold=0.90)
I get a more sensible result (well, at least it isn't blank!):
https://lh5.googleusercontent.com/-Ta33pkF-pIY/Ucm4abti3lI/AAAAAAAAEoA/iyX6F... But this is very different to the result given by IDL - and furthermore, adjusting the parameters doesn't seem to change the output at all.
What am I doing wrong here? I suspect it is something to do with the image scaling, but I'm not sure - it could be a conceptual problem with my image processing knowledge, or I could be using skimage improperly. Does anyone have any ideas or suggestions as to where to go from here? If I manage to solve this I will, of course, write up the solution on my blog so that others can benefit too.
Best regards,
Robin University of Southampton, UK
On Wed, Jun 26, 2013 at 6:08 AM, Robin Wilson wrote: <snip> I think I've managed to solve most of my problems, but I have one
question: the canny routine seems to work fine for my original image,
without rescaling it from 0-1. Is there a reason that it should only work
for images between 0-1, or am I safe to use my original images? Hi Robin,
It may be OK to use the scaling in the original images for canny, but it's
best to stick to (0, 1). For details, see:
http://scikit-image.org/docs/dev/user_guide/data_types.html
The easiest way to handle rescaling to the correct float range is using
rescale_intensity: from skimage import exposure
exposure.rescale_intensity(hot) By default, this takes the minimum and maximum values in the image
(-6548.7, 4123.2) and rescales the image such that those values map to the
data types' min/max values; (0, 1) or (-1, 1) for float images (depends on
whether the input has negative values). Since your image has negative
values, the rescaled result will be (-1, 1).
Usually it's preferable to stick to (0, 1), so you might want to force the
output range: exposure.rescale_intensity(hot, out_range=(0, 1)) Note that most of your data are the middle ranges. If you plot the image,
it'll just appear gray. To fix that, you may want to clip the input range: exposure.rescale_intensity(hot, in_range=(-100, 100), out_range=(0, 1)) Now everything at or below -100 in the original gets mapped to 0;
everything at or above 100 gets mapped to 1; and everything in between -100
and 100 is linearly rescaled between 0 and 1.
Hope that helps!
-Tony
participants (3)
-
Josh Warner
-
Robin Wilson
-
Tony Yu