# Terser way to get the intensity of one pixel

I want a function to return the intensity of one pixel notwithstanding the Color Space of the source image.

I wrote:

getIntensity[i_Image, pos_List] :=
ColorConvert[Flatten@{PixelValue[i, pos]}, ImageColorSpace[i] -> "Grayscale"]


but it looks clumsy and there is probably a better way that I'm overlooking.

Test cases:

i = ExampleData[{"TestImage", "Lena"}];
ig = ColorConvert[i, "Grayscale"];
getIntensity[i, {1, 1}]
getIntensity[ig, {1, 1}]

(*
{0.172275}
{0.172549}
*)


Note: Not very important right now, but it would be nice if the position could be specified as a rectangular region {a ;; b, c ;; d}

• {a ;; b, c ;; d} will return the the intensity of every pixel or a mean value?
– yode
Mar 18 '16 at 6:34
• @yode A list (matrix) with the intensities for every pixel Mar 18 '16 at 6:47
• Incredible, how unterse it is to transform a Span specification to a Take specification! Working on this right now. Mar 18 '16 at 9:46
• Here's a function. Should work in v9, though ColorConvert got updated in v10. f[i_Image, c_List] := ImageTake[i, Sequence @@ (Replace[ c, {h___, n_Integer, t___} :> {h, {n}, t}] /. Span -> List)] // ColorConvert[#, "Grayscale"] & // ImageData I'm afraid, taking a span specification forces me to use a very clumsy replacement rule. Mar 18 '16 at 9:49
• The golfed down version that takes a Take kind of part specification looks like this: f[i_, c__] := ImageData@ColorConvert[ImageTake[i, c], "Grayscale"] Mar 18 '16 at 9:51

Reposting my comment as an answer.

This has the same caveat, as @yode mentioned: pixel coordinates (from top to bottom, then from left to right) vs "normal coordinates" (left to right, then upwards).

Perhaps this is terser, although it always returns a 2-d array, even if only of one element. The golfed down version doesn't take a Span:

f[i_, c__] := ImageData@ColorConvert[ImageTake[i, c], "Grayscale"]


Usage:

f[image, {row1, row2, step(optional)}, {col1, col2, step(optional)}]


the part specification works exactly like ImageTake.

The ungolfed version accepts Belisarius' {a ;; b, c ;; d} desire (also with the step size, if need be):

f[i_Image, c_List] := ImageTake[i,
Sequence @@ (Replace[ c, {h___, n_Integer, t___} :> {h, {n}, t}] /. Span -> List)] //
ColorConvert[#, "Grayscale"] & // ImageData


The replacement rule to accept Span is rather clumsy and can probably be improved. An integer specification will be equivalent to n ;; n ;; 1.

If we extend the code as

f[i_, c__] := ImageData@ColorConvert[ImageTake[
ImageRotate[i, -Pi/2], c], "Grayscale"]


(or similarly in the ungolfed version), we'll get the "normal" coordinates. In this case the part specification will be first the x-range, then the y-range from bottom to top. It might be counterintuitive though, because the returned array will be essentially the image rotated clockwise.

Adding in the discussion in the comments: the image standard coordinates know nothing about the negative specifications, so I can probably assume that the Span-like argument will take only positive values. Because ImageRotate is horribly inefficient to simply get a coordinate transformation, and @Belisarius hinted at the use of ImageTrim, there's a very simple workaround available (no 3-argument Span specification though, sorry).

f[i_, c:{(_Span|_Integer)..}] := ImageData@ColorConvert[ImageTrim[i, Thread[List@@@c]-.5], "Grayscale"]


I have no idea, why Belisarius' two approaches (convert to grayscale, then get pixel value or vice versa) give different results. Mine is consistent with getIntensity[ig, {1, 1}].

• On the same line ImageData@ColorConvert[ImageTrim[i, {c - .5, c - .5}], "Grayscale"] uses image standard coordinates Mar 18 '16 at 13:56
• Yes, that is vastly better. As @nikie alerted me over here, ImageRotate is a horribly inefficient way to do the coordinate transformation, although very straighforward. Mar 18 '16 at 14:10
• @Dr.belisarius by the way, seeing as we're dealing with image standard coordinates, are negative specifications to refer to "{x,y} from last" at all relevant here? Because if not, there's a lot less corner cases to deal with, when implementing the expected behavior of Span. Mar 18 '16 at 14:13
• I suspected the span thing could be cumbersome, so the final note on the question. I like your answer, let the span rest in peace. Mar 18 '16 at 14:20
• @Dr.belisarius just did, though the answer is a bit cluttered now. No time to tidy up though, maybe some other time. Mar 18 '16 at 14:29
fun[i_Image, rect_List] :=
ImageMeasurements[i, "MeanIntensity",
Masking -> SparseArray[{# -> 1}, ImageDimensions[i]]] & /@
Catenate@CoordinateBoundingBoxArray[rect]


Usage:

i = ExampleData[{"TestImage", "Lena"}];
fun[i, {{2, 3}, {4, 5}}]


{0.635294, 0.631373, 0.635294, 0.635294, 0.631373, 0.635294, 0.635294, 0.631373, 0.635294}

Update: I'm very sorry I make a mistake in the Image Coordinate Systems,And the code above serve a Index Coordinates.So we cann't use SparseArray as a mask unless you have a need in Index Coordinates.But this time let's face up to a Pixel-aligned Image Coordinates

When your object is a list of pixel:

fun1[i_Image, pos_List] :=
ImageMeasurements[i, "MeanIntensity",
ReplacePixelValue[
ConstantImage[0, ImageDimensions[i]], {# -> 1}]] & /@ pos


Usage:

fun1[i, {{1, 1}(*your test case*), {5, 6}, {10, 10}}]
fun1[ig, {{1, 1}(*your test case*), {5, 6}, {10, 10}}]


{0.172549, 0.211765, 0.247059}

{0.172549, 0.211765, 0.247059}

If your object is a region of pixel rectange:

fun2[i_Image, rect_List] :=
ImageMeasurements[i, "MeanIntensity",
ReplacePixelValue[
ConstantImage[0, ImageDimensions[i]], {# -> 1}]] & /@
Catenate@CoordinateBoundingBoxArray[rect]


Usage:

fun2[i, {{1, 1}, {5, 8}}]


{0.172549, 0.172549, 0.168627, 0.180392, 0.211765, 0.188235, 0.184314, 0.188235, 0.172549, 0.172549, 0.168627, 0.180392, 0.211765, 0.188235, 0.184314, 0.188235, 0.215686, 0.215686, 0.196078, 0.196078, 0.188235, 0.223529, 0.215686, 0.207843, 0.2, 0.2, 0.184314, 0.196078, 0.184314, 0.203922, 0.207843, 0.192157, 0.211765, 0.211765, 0.211765, 0.203922, 0.207843, 0.211765, 0.196078, 0.207843}

• I meant "terser". Like in more "brief, short, to the point, concise, succinct, crisp" :) Mar 18 '16 at 7:05
• Only these option/function have more letters.but is brief actually I think.aha~
– yode
Mar 18 '16 at 7:09
• In any case, I upvoted without testing it (it is a bad practice!). But I'm in v9 and you're using v10 features, so no way to check. Could you include the results for my two test cases? Mar 18 '16 at 7:17