Logic behind int16, uint8 etc. images - python

There is something that I probably misunderstand about data types in images. Lets say we have an uint8 image. Since uint8 is between 0 and 255, 0 is the darkest and 255 is the brightest intensity value.
Same logic would make -32768 the darkest and 32767 the brightest intensity value for an int16 image. However, I have an int16 image(it is originally a dicom) where the darkest pixel is -1024 and the brightest is 4095. I say int16 because pixels are saved in an int16 type numpy array.
In addition, when I concatenate two int16 numpy arrays where one of them is a = np.ones((100,100), dtype=np.int16) * 32767 and the other is b = np.ones((100,100), dtype=np.int16) * 32766 , It results in a binary image where 32767s is white and 32766s are black.
Can someone help me about what I am getting wrong?

Short answer
Nothing is wrong, this is how DICOM works.
Long answer
In DICOM standard, pixel value is not directly related to its color (gray level). These values should correspond to physical properties of the acquired item (e.g. in Computed Tomography pixel values are measured in Hounsfield Units. *(unless they are linearly rescaled, see below)).
Gray level of the pixel image is displayed dynamically based on arbitrary chosen minimal and maximal values, which are set by the user. Every pixel value less or equal minimum is black, every pixel greater or equal maximum is white, the others are linearly interpolated gray levels.
So it is perfectly fine that in binary image black minimum is equal to 32766 and white maximum is equal to 32767.
If you use DICOM viewer, you will have possibility to change dynamically these minimal and maximal values, so you will change total contrast and brightness of the image. It is necessary for radiologists, to diagnose e.g. lungs and bones in different ranges. And if you export DICOM to other file format, you should choose, what is the color mapping. Normally it is full range (the lowest value gets black, the brightest gets white).
There are two other values, which are often used instead of minimum and maximum: "window width" (ww) and "window level" (wl). ww = max-min, wl=(max+min)/2.
You should look at these questions and answers:
Window width and center calculation of DICOM image
*you should also consider tags "rescale intercept" (0028,1052), and "rescale slope" (0028,1053), that lineary rescale value of pixel array to the final value, but normally it is implemented in dicom toolkit.
FinalPixelValue = (RawPixelValue * RescaleSlope) + RescaleIntercept

Related

Statistical meaning of pre-probability formula used in Python computer vision code? (matplotlib.image.imread)

import numpy as np
import matplotlib.image as img;
C = 1.0-np.mean(img.imread('circle.png'),axis=2);
C /= np.sum(C);
The image has 256 X 256 pixels. The resulting array C appears to be a 256 X 256 array containing probabilities of each pixel (color, depth, I don't know what the probability is of due to my lack of knowledge of what matplotlib.image.imread does.
I understand the 4th line of code to mean probability values (real data normalized by their sum, so that they sum to 1), but what is the meaning of the formula on the 3rd line of the code, in and outside of the image context, i.e. statistical intuition?
Ultimately, I would like to apply the above probability transformation to a single time series of real data, so I'm wondering if the formula 1.0-np.mean() would still apply for my (non-computer vision, but distributional) application
an image contains pixels.
an image can have one color channel (grayscale) or multiple (red-green-blue).
"depth" is a term describing the gradations of the pixel values. 8 bits are common and that means 2^8 = 256 different levels per channel, or 256^3 = 16.7 million different colors. 1 bit would be black and white. advanced cameras and scanners may have 10, 12, or more bits of depth.
I see nothing involving probabilities here.
img.imread('circle.png') read the image. you will get a numpy array of shape (height, width, 3) because the image is likely in color. the third dimension (dimension 2) expresses the channels of color per pixel. I am guessing that this routine loads images as floating point values with a range of 0.0 to 1.0.
np.mean(..., axis=2) takes the average of all color channels for every pixel. it does an average/mean calculation along axis 2 (the third one), which contains the color values of every pixel. the shape of this array will be (height, width) and represents the input image as grayscale. the weighting of colors is a little questionable. usually the green channel gets more weight (brightest) and the blue channel gets less weight (darkest).
C = 1.0- ... inverts the picture.
np.sum(C) simply sums up all the grayscale pixels of C. you will get a measure of the overall brightness of the picture.
C /= np.sum(C) divides by that brightness measure. you will get a picture with normalized brightness. there is a factor for the image's size (width*height) missing so these values will be very small (dim/dark).
Use this instead (mean instead of sum) to adjust the intensity values to be 0.5 (gray) on average.
C /= np.mean(C) # now 1.0 on average
C *= 0.5
# or just C *= 0.5 / np.mean(C)

Make brightness of two images same

I have two images:
How to make intensities/brightness of one image equal to other?
You can calculate the pictures brightness by following this procedure:
Calculate the brightness for each pixel by:
a. Converting them to grayscale, each pixel will hold a value between 0 and 255
b. Convert you image from RGB to HSV, where V refers to the brightness
Calculate overall brighness (sum of each pixel value divided by number of pixels)
Once calculated the brightness for both pictures, you can adjust it for one to be equal to the second picture.
EDIT
I strongly recommend using option 1b, as you will not loose color information, so you can convert back and forth. Using the grayscale variant, you will have to work on a copy of your image data.

How to get border pixels of an image in python?

I have an image, using steganography I want to save the data in border pixels only.
In other words, I want to save data only in the least significant bits(LSB) of border pixels of an image.
Is there any way to get border pixels to store data( max 15 characters text) in the border pixels?
Plz, help me out...
OBTAINING BORDER PIXELS:
Masking operations are one of many ways to obtain the border pixels of an image. The code would be as follows:
a= cv2.imread('cal1.jpg')
bw = 20 //width of border required
mask = np.ones(a.shape[:2], dtype = "uint8")
cv2.rectangle(mask, (bw,bw),(a.shape[1]-bw,a.shape[0]-bw), 0, -1)
output = cv2.bitwise_and(a, a, mask = mask)
cv2.imshow('out', output)
cv2.waitKey(5000)
After I get an array of ones with the same dimension as the input image, I use cv2.rectangle function to draw a rectangle of zeros. The first argument is the image you want to draw on, second argument is start (x,y) point and the third argument is the end (x,y) point. Fourth argument is the color and '-1' represents the thickness of rectangle drawn (-1 fills the rectangle). You can find the documentation for the function here.
Now that we have our mask, you can use 'cv2.bitwise_and' (documentation) function to perform AND operation on the pixels. Basically what happens is, the pixels that are AND with '1' pixels in the mask, retain their pixel values. Pixels that are AND with '0' pixels in the mask are made 0. This way you will have the output as follows:
.
The input image was :
You have the border pixels now!
Using LSB planes to store your info is not a good idea. It makes sense when you think about it. A simple lossy compression would affect most of your hidden data. Saving your image as JPEG would result in loss of info or severe affected info. If you want to still try LSB, look into bit-plane slicing. Through bit-plane slicing, you basically obtain bit planes (from MSB to LSB) of the image. (image from researchgate.net)
I have done it in Matlab and not quite sure about doing it in python. In Matlab,
the function, 'bitget(image, 1)', returns the LSB of the image. I found a question on bit-plane slicing using python here. Though unanswered, you might want to look into the posted code.
To access border pixel and enter data into it.
A shape of an image is accessed by t= img.shape. It returns a tuple of the number of rows, columns, and channels.A component is RGB which 1,2,3 respectively.int(r[0]) is variable in which a value is stored.
import cv2
img = cv2.imread('xyz.png')
t = img.shape
print(t)
component = 2
img.itemset((0,0,component),int(r[0]))
img.itemset((0,t[1]-1,component),int(r[1]))
img.itemset((t[0]-1,0,component),int(r[2]))
img.itemset((t[0]-1,t[1]-1,component),int(r[3]))
print(img.item(0,0,component))
print(img.item(0,t[1]-1,component))
print(img.item(t[0]-1,0,component))
print(img.item(t[0]-1,t[1]-1,component))
cv2.imwrite('output.png',img)

Python Pillow: how to overlay one binary image on top of another to produce a composite?

I am doing some image processing, and I need to check if a binary image is identical to another.
Processing speed isn't an issue, and the simple thing I thought to do was count the white pixels remaining after adding the inverse of image A to image B (these images are very nearly identical, but not quite--some sort of distance metric is the goal).
Note: take the logarithm to linearize the distance
However, in order to create the composite image, I need to include a "mask" that is the same size as the two images.
I am having trouble finding an example of creating the mask online and using it for the Image.composite function.
Here is my code:
compA = ImageOps.invert(imgA)
imgAB = Image.composite(compA,imgB,??? mask)
Right now, I have created a mask of all zeros--however, the composite image does not appear correctly (both A and B are exactly the same images; a mask of all zeros--or all ones for that matter--does not work).
mask = Image.fromarray(np.zeros(imgA.size,dtype=int),mode='L')
imgAB = Image.composite(compA,imgB,mask)
How do I just add these two binary images on top of eachother?
Clearly you're using numpy, so why not just work with numpy arrays and explicitly do whatever arithmetic you want to do in that domain—such as subtracting one image from the other:
arrayA = numpy.asarray( imgA, dtype=int )
arrayB = numpy.asarray( imgB, dtype=int )
arrayDelta = arrayA - arrayB
print( (arrayDelta !=0 ).sum() ) # print the number of non-identical pixels (why count them by hand?)
# NB: this number may be inflated by a factor of 3 if there are 3 identical channels R, G, B
imgDelta = Image.fromarray((numpy.sign(arrayDelta)*127+127).astype('uint8')) # display this image if you want to visualize where the differences are
You could do this even more simply, e.g.
print((numpy.asarray(imgA) != numpy.asarray(imgB)).sum())
but I thought casting to a signed integer type first and then subtracting would allow you to visualize more information (A white and B black -> white pixel in delta; A black and B white -> black pixel in delta)

Transform a PyGame black and white display to a 7x5 bit Matrix

I have a Pygame black display on which I will draw a letter with white color, as shown in the image below. The size of the display can be anything above 100x100 pixels.
I know I can use something like this to get the surface 2d array:
miSuface = pygame.display.get_surface()
miCoso = pygame.surfarray.array2d(miSuface)
However, I would like to somehow translate this array to a 7x5 bit matrix, on which 0 will correspond to a black pixel and 1 to a white pixel. My final intent is to use the matrix to train a neural network and create a simple OCR. Is there any way I can achieve this? Or is there a better approach to get the 7x5 matrix?
I don't know of any way offhand to compress your array2d into either a smaller array or one with 1-bit of color information. But, you can do the following:
Iterate through the array. If the color is less or equal to 888888, change it to 000000. If it's greater, change it to FFFFFF.
Create a new [7][5] array.
Iterate through again. Add the values of each pixel (black = 0, white = 1) in any given 35th of the array. The sample size will depend entirely on the size of your original array2d. If the average for that block is greater than or equal to 17.5, add a white element to your matrix. If it's less than 17.5, add a black element to your matrix.
I'm not explicitly familiar with the call to pygame.surfarray.array2d(). However, since you're going from a binary color layout to a smaller binary color matrix, you can subdivide the original image using your new proportions in order to properly color the resulting square. I'll give an example.
Say your initial image is 14x10 and you wish to have a 7x5 matrix. Your initial image looks like this:
[[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,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,0],
[0,0,0,0,0,0,1,1,1,0,0,0,0,0],
[0,0,0,0,0,1,1,0,1,1,0,0,0,0],
[0,0,0,0,1,1,1,1,1,1,1,0,0,0],
[0,0,0,1,1,1,1,1,1,1,1,1,0,0],
[0,0,1,1,0,0,0,0,0,0,0,1,1,0],
[0,1,1,0,0,0,0,0,0,0,0,0,1,1]]
What you need to do is divide x-wise by 7, and y-wise by 5. Since I picked nice numbers, the slices of the large image you'll be looking at will be 2x2. Take the top left 2x2 block, for example:
[[0,0],
[0,0]] -> [0]
This mini-matrix maps to a single pixel of your 7x5 image. Obviously, in this case it will be 0. Let's look at the bottom right:
[[1,0],
[1,1]] -> [1]
This will map to a value of 1 in your 7x5 image. As you can see, the tricky case in this example is when you have equal 1s and 0s. This will not be a huge issue, fortunately, as your initial image is always at least 100x100.
Applying this method to my example, the shrunk 7x5 image looks like this:
[[0,0,0,0,0,0,0],
[0,0,0,0,0,0,0],
[0,0,0,1,1,0,0],
[0,0,1,1,1,1,0],
[0,1,0,0,0,0,1]]
Psuedocode steps:
Find the size of the mini-matrices (divide by 5 and 7). This will work with an image of any size larger than 7x5.
For each mini-matrix, count the black and white spaces (0 and 1).
Decide whether the space in your final 7x5 matrix should be black or white. In my example, I say that the final space should be black if (number of white squares >= number of black squares). I'm worried that using this will cause problems for you because your pen size is relatively thin compared to the size of your 7x5 divisions. If this is a problem, try something like if (number of white squares * 2 >= number of black squares). This effectively weights the white squares more.
I'm happy to elaborate on this psuedocode. Just let me know.
Finally, if you are still having issues, I might try using a size larger than 7x5. It will give you more precision at a cost to your OCR algorithm. Good luck.

Categories