How to build an image with every pixel's RGB value - python

I am using Python and the PIL library and I have the RGB information for every pixel of an image along with their location. Is there any way that I can build an image from this information?

Certainly. If your source data is sparse (ie. you don't have a value for every pixel location) you probably want to do the following:
create an empty image of the required dimensions
ensure your colour data is stored in raster order (ie. sort by y then x)
iterate through the data and store the pixel values in each location
So, assuming you have an array of tuples such as (x, y, (r,g,b)), you can do something like:
from pil import Image
WIDTH=640
HEIGHT=480
img = Image.new(WIDTH, HEIGHT, 'RGB')
# Define this based on your data
my_raster_sort(my_image_data)
img_data = img.load()
for x, y, color in my_image_data:
img_data[x,y] = color
If your source image data is complete (ie. you have a colour value for every position) then it would probably be faster to transform your data into a buffer formatted with the desired memory layout, and then create an image in one step using Image.frombuffer.

Yes. That's what Image.putpixel() (and possibly putdata() depending on how you have the data) are for. Find the documentation for these methods here.
Hard to advise you further since you provided no code or even a sample of the data.

Related

(Live) viewing of large images using pyvips

I recently discovered the awesome pyvips package and would like to use it to analyze data that was taken on a homebuilt slide scanner (not built by me). I scan about 4000 tiles of 1024x1024 pixels each along the edges of a square-shaped sample (the center part of the sample is not recorded). All tiles are saved as a single binary file. I have written a python class that returns a desired tile as a numpy array from the binary file and which also gives the (x, y) coordinates of the specific tile. Unfortunately, the tiles are not arranged on a grid.
I first determine the total width and height of the full image and initialize a black image of the correct size and subsequently place the tiles at the correct locations using the insert function. The composite image is about 120k x 120k pixels, but most of the image is empty. Finally, I plot the resulting image using matplotlib.
import pyvips
import numpy as np
import matplotlib.pyplot as plt
# class to read tiles from data file
sr = TileReader("path_to_scan_file")
# some stuff to determine the width and height of the total image...
# create empty image for inserting tiles
im = pyvips.Image.black(width, height)
# loop over all tiles and place tile at correct position
for i in range(sr.num_tiles()):
frame, coord = sr.ReadFrame(i)
tile = pyvips.Image.new_from_array(frame)
im = im.insert(tile, coord[0], coord[1])
# plot result
plt.imshow(im.numpy())
plt.show()
# save file
im.write_to_file('full_image.tiff')
Generating the full image in the loop seems to be very fast. However, plotting or saving the data is not. (Obviously,) the plotting only works for a small number of tiles (~10). I also tried saving the data to a pyramidal tiff. However, writing the image took several hours and the generated file seems to be corrupted or too large to be opened. Unfortunately I could not get nip2 installed without admin rights.
I would like to be able to manually select regions of interest of the composite image that I can use for further processing. What is the best/fastest way to interact with the generated image to enable this?
You can use crop to cut out a chunk of the image and pass that on to something else. It won't make the whole thing, it'll just render the bit you need, so it'll be quick.
Something like:
# loop over all tiles and place at correct position
# do this once on startup
for i in range(sr.num_tiles()):
frame, coord = sr.ReadFrame(i)
tile = pyvips.Image.new_from_array(frame)
im = im.insert(tile, coord[0], coord[1])
# left, top, width, height
# hook these numbers up to eg. a scrollbar
# do the crop again for each scrollbar movement
tile = im.crop(0, 0, 1000, 1000)
# plot result
plt.imshow(tile.numpy())
plt.show()
If you want to get fancy, the best solution is probably vips_sink_screen():
https://www.libvips.org/API/current/libvips-generate.html#vips-sink-screen
That'll let you generate pixels from any pipeline asynchronously as you pan and zoom, but it needs C, sadly. There's an example image viewer using this API here:
https://github.com/jcupitt/vipsdisp
That's running vips_sink_screen() in the background to generate GPU textures at various scales, then using that set of textures to paint the screen at 60 fps (ish) as you pan and zoom around. It can display huge dynamically computed images very quickly.

Display specific part of tiff image using rasterio without having to load the entire file

I have a large tiff file (around 2GB) containing a map. I have been able to successfully read the data and even display it using the following python code:
import rasterio
from rasterio.plot import show
with rasterio.open("image.tif") as img:
show(img)
data = img.read()
This works just fine. However, I need to be able to display specific parts of this map without having to load the entire file into memory (as it takes up too much of the RAM and is not doable on many other PCs). I tried using the Window class of rasterio in order to that, but when I tried to display the map the outcome was different from how the full map is displayed (as if it caused data loss):
import rasterio
from rasterio.plot import show
from rasterio.windows import Window
with rasterio.open("image.tif") as img:
data = img.read(window=Window(0, 0, 100000, 100000))
show(data)
So my question is, how can I display a part of the map without having to load into memory the entire file, while also making it look as if it had been cropped from the full map image?
thanks in advance :)
The reason that it displays nicely in the first case, but not in the second, is that in the first case you pass an instance of rasterio.DatasetReader to show (show(img)), but in the second case you pass in a numpy array (show(data)). The DatasetReader contains additional information, in particular an affine transformation and color interpretation, which show uses.
The additional things show does in the first case (for RGB data) can be recreated for the windowed case like so:
import rasterio
from rasterio.enums import ColorInterp
from rasterio.plot import show
from rasterio.windows import Window
with rasterio.open("image.tif") as img:
window = Window(0, 0, 100000, 100000)
# Lookup table for the color space in the source file
source_colorinterp = dict(zip(img.colorinterp, img.indexes))
# Read the image in the proper order so the numpy array will have the colors in the
# order expected by matplotlib (RGB)
rgb_indexes = [
source_colorinterp[ci]
for ci in (ColorInterp.red, ColorInterp.green, ColorInterp.blue)
]
data = img.read(rgb_indexes, window=window)
# Also pass in the affine transform corresponding to the window in order to
# display the correct coordinates and possibly orientation
show(data, transform=img.window_transform(window))
(I figured out what show does by looking at the source code here)
In case of data with a single channel, the underlying matplotlib library used for plotting scales the color range based on the min and max value of the data. To get exactly the same colors as before, you'll need to know the min and max of the whole image, or some values that come reasonably close.
Then you can explicitly tell matplotlib's imshow how to scale:
with rasterio.open("image.tif") as img:
window = Window(0, 0, 100000, 100000)
data = img.read(window=window, masked=True)
# adjust these
value_min = 0
value_max = 255
show(data, transform=img.window_transform(window), vmin=value_min, vmax=value_max)
Additional kwargs (like vmin and vmax here) will be passed on to matplotlib.axes.Axes.imshow, as documented here.
From the matplotlib documenation:
vmin, vmax: float, optional
When using scalar data and no explicit norm, vmin and vmax define the data range that the colormap covers. By default, the colormap covers the complete value range of the supplied data. It is deprecated to use vmin/vmax when norm is given. When using RGB(A) data, parameters vmin/vmax are ignored.
That way you could also change the colormap it uses etc.

How to convert a 1 channel image into a 3 channel with PIL?

I have an image that has one channel. I would like duplicate this one channel such that I can get a new image that has the same channel, just duplicated three times. Basically, making a quasi RBG image.
I see some info on how to do this with OpenCV, but not in PIL. It looks easy in Numpy, but again, PIL is different. I don't want to get into the habit of jumping from library to library all the time.
Here's one way without looking too hard at the docs..
fake image:
im = Image.new('P', (16,4), 127)
Get the (pixel) size of the single band image; create a new 3-band image of the same size; use zip to create pixel tuples from the original; put that into the new image..
w, h = im.size
ima = Image.new('RGB', (w,h))
data = zip(im.getdata(), im.getdata(), im.getdata())
ima.putdata(list(data))
Or even possibly
new = im.convert(mode='RGB')
just use:
image = Image.open(image_info.path).convert("RGB")
can convert both 1-channel and 4-channel to 3-channel

Viewing dicom image with Bokeh

I'm trying to set the graph background to a dicom image. I followed this example, but the image data given from dicom.pixel_array isn't RGBA. I'm not sure how to convert it, either. I'm also not sure what exactly bokeh is expecting. I've tried finding specifics in the documentation, but not such luck.
from bokeh.plotting import figure, show, output_file
import dicom
import numpy as np
path = "/pathToDicomImage.dcm"
data = dicom.read_file(path)
img = data.pixel_array
p = figure(x_range=(0,10), y_range=(0,10))
# must give a vector of images
p.image_rgba(image=[img], x=0, y=0, dw=10, dh=10)
output_file("image_rgba.html", title="image_rgba.py example")
show(p)
This code doesnt give me any errors, but it doesn't display anything. Maybe the pixel array doesn't have alpha data, so alpha defaults to 0? I'm not sure. Also, I can't quite figure out how to test it.
SOLVED
As was pointed out, I just needed to map the pixel data to rgba space. for this instance, it means duplicating the data to each channel, and setting alpha all the way.
def dicom_image_to_RGBA(image_data):
rows = len(image_data)
cols = rows
img = np.empty((rows,cols), dtype=np.uint32)
view = img.view(dtype=np.uint8).reshape((rows, cols, 4))
for i in range(0,rows):
for j in range(0,cols):
view[i][j][0] = image_data[i][j]
view[i][j][1] = image_data[i][j]
view[i][j][2] = image_data[i][j]
view[i][j][3] = 255
return img
Not being an expert in python, I have had a glance at pydicom's capabilities in handling pixel data. I figured out that pixel_array is the value of the pixel-data attribute of the DICOM dataset as is and pydicom does not offer any functionality to convert it into some standard format which can be handled uniformly. This means you will have to convert it to RGB in most cases which is a quite compilcated and error-prone task.
Things to consider in this:
The encoding (Big/Little Endian, various compression methods like JPEG, JPEG-LS, RLE, ZIP) - DICOM attribute (0002,0010) TransferSyntaxUID
The type of pixeldata (Grayscale, RGB, ...) - DICOM attribute (0028,0004) PhotometricInterpretation, (0028,0103) PixelRepresentation
In case of color images: are the values encoded colur by plane (RRRRR,.....GGGGG,.....BBBBB) or colour by pixel as you expect it to be (RGB RGB...)
The bit depth and which bits are used for actual pixel data values (0028,0100) BitsAllocated, (0028,0101) BitsStored, (0028,0102) Highbit.
are the pixel data values really the values to be displayed or are they indices to a colour/grayscale lookup table (0028,3000) ModalityLUTSequence, (0028,3002) LUTDescriptor, (0028,3003) LUTExplanation, (0028,3004) ModalityLUTType, (0028,3006) LUTData.
Scary, isn't it? For some modern image classes like Enhanced MR, there is even more than that.
However, if you constrain to a particular type of image (e.g. Computed Radiography). limitations to the above mentioned apply that make your life a bit easier.
If you would post a DICOM dump of the image header I could give you some hints how to display that particular image.
HTH
kritzel
What you need to do is map the pixel data returned from pixel_array to RGB space. Usually that is done using a look up table (LUT). Take a look at the functions GetImage and GetLUTValue in the dicomparser module in the dicompyler-core library.
In GetLUTValue it maps the data to an 8-bit greyscale image. If you want to use a different LUT, you would need to map the color space accordingly.

Python: Serial Transmission

I have an image stack of 500 images (jpeg) of 640x480. I intend to make 500 pixels (1st pixels of all images) as a list and then send that via COM1 to FPGA where I do my further processing.
I have a couple of questions here:
How do I import all the 500 images at a time into python and how do i store it?
How do I send the 500 pixel list via COM1 to FPGA?
I tried the following:
Converted the jpeg image to intensity values (each pixel is denoted by a number between 0 and 255) in MATLAB, saved the intensity values in a text file, read that file using readlines(). But it became too cumbersome to make the intensity value files for all the 500 images!
Used NumPy to put the read files in a matrix and then pick the first pixel of all images. But when I send it, its coming like: [56, 61, 78, ... ,71, 91].
Is there a way to eliminate the [ ] and , while sending the data serially?
Thanks in Advance! :)
Ad 1: Use a library like PIL for accessing the images. You can load an image using
from PIL import Image
im = Image.open("img000.jpg")
And access the first pixel value using
pixel = im.getpixel(0,0)
This returns a color tuple for the first pixel, you have to calculate the intensity from this.
Ad 2: This depends on how your FPGS expects the values? Since you mentioned eliminating [ , do you need a comma-separated ASCII string? Try
pixel_string = ','.join([str(x) for x in pixel_list])
If you need to send a series of bytes construct the byte string like
pixel_string = ''.join([chr(x) for x in pixel_list])
Both examples assume, that you have constructed you list of intensity values in pixel_list.

Categories

Resources