update docs
the examples page was using the old python binding
This commit is contained in:
parent
4ea743f5e6
commit
c01afbc559
321
doc/Examples.md
321
doc/Examples.md
@ -9,11 +9,11 @@
|
|||||||
<refpurpose>A few example Python programs using libvips</refpurpose>
|
<refpurpose>A few example Python programs using libvips</refpurpose>
|
||||||
</refnamediv>
|
</refnamediv>
|
||||||
|
|
||||||
This page shows a few libvips examples using Python. They will work with small syntax
|
This page shows a few libvips examples using Python. They will work with
|
||||||
changes in any language with a libvips binding.
|
small syntax changes in any language with a libvips binding.
|
||||||
|
|
||||||
The libvips test suite is written in Python and exercises every operation in the API.
|
The libvips test suite is written in Python and exercises every operation
|
||||||
It's also a useful source of examples.
|
in the API. It's also a useful source of examples.
|
||||||
|
|
||||||
# Average a region of interest box on an image
|
# Average a region of interest box on an image
|
||||||
|
|
||||||
@ -21,177 +21,108 @@ It's also a useful source of examples.
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import gi
|
import pyvips
|
||||||
gi.require_version('Vips', '8.0')
|
|
||||||
from gi.repository import Vips
|
|
||||||
|
|
||||||
left = 10
|
left = 10
|
||||||
top = 10
|
top = 10
|
||||||
width = 64
|
width = 64
|
||||||
height = 64
|
height = 64
|
||||||
|
|
||||||
image = Vips.Image.new_from_file(sys.argv[1])
|
image = pyvips.Image.new_from_file(sys.argv[1])
|
||||||
roi = image.crop(left, top, width, height)
|
roi = image.crop(left, top, width, height)
|
||||||
print 'average:', roi.avg()
|
print 'average:', roi.avg()
|
||||||
```
|
```
|
||||||
|
|
||||||
# libvips and numpy
|
# libvips and numpy
|
||||||
|
|
||||||
You can use `Vips.Image.new_from_memory_copy()` to make a vips image from an area of
|
You can use `pyvips.Image.new_from_memory()` to make a vips image from
|
||||||
memory. The memory array needs to be laid out band-interleaved, as a set of scanlines,
|
an area of memory. The memory array needs to be laid out band-interleaved,
|
||||||
with no padding between lines.
|
as a set of scanlines, with no padding between lines.
|
||||||
|
|
||||||
This example moves an image from numpy to vips, but it's simple to move the other way
|
|
||||||
(use `Vips.Image.write_to_memory()`) to to move images into or out of PIL.
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
#!/usr/bin/python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import numpy
|
|
||||||
import scipy.ndimage
|
|
||||||
import gi
|
|
||||||
gi.require_version('Vips', '8.0')
|
|
||||||
from gi.repository import Vips
|
|
||||||
|
|
||||||
def np_dtype_to_vips_format(np_dtype):
|
|
||||||
'''
|
|
||||||
Map numpy data types to VIPS data formats.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
np_dtype: numpy.dtype
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
gi.overrides.Vips.BandFormat
|
|
||||||
'''
|
|
||||||
lookup = {
|
|
||||||
numpy.dtype('int8'): Vips.BandFormat.CHAR,
|
|
||||||
numpy.dtype('uint8'): Vips.BandFormat.UCHAR,
|
|
||||||
numpy.dtype('int16'): Vips.BandFormat.SHORT,
|
|
||||||
numpy.dtype('uint16'): Vips.BandFormat.USHORT,
|
|
||||||
numpy.dtype('int32'): Vips.BandFormat.INT,
|
|
||||||
numpy.dtype('float32'): Vips.BandFormat.FLOAT,
|
|
||||||
numpy.dtype('float64'): Vips.BandFormat.DOUBLE
|
|
||||||
}
|
|
||||||
return lookup[np_dtype]
|
|
||||||
|
|
||||||
def np_array_to_vips_image(array):
|
|
||||||
'''
|
|
||||||
Convert a `numpy` array to a `Vips` image object.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
nparray: numpy.ndarray
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
gi.overrides.Vips.image
|
|
||||||
'''
|
|
||||||
# Look up what VIPS format corresponds to the type of this np array
|
|
||||||
vips_format = np_dtype_to_vips_format(array.dtype)
|
|
||||||
dims = array.shape
|
|
||||||
height = dims[0]
|
|
||||||
width = 1
|
|
||||||
bands = 1
|
|
||||||
if len(dims) > 1:
|
|
||||||
width = dims[1]
|
|
||||||
if len(dims) > 2:
|
|
||||||
bands = dims[2]
|
|
||||||
img = Vips.Image.new_from_memory_copy(array.data,
|
|
||||||
width, height, bands, vips_format)
|
|
||||||
|
|
||||||
return img
|
|
||||||
|
|
||||||
array = numpy.random.random((10,10))
|
|
||||||
vips_image = np_array_to_vips_image(array)
|
|
||||||
print 'avg =', vips_image.avg()
|
|
||||||
|
|
||||||
array = scipy.ndimage.imread("test.jpg")
|
|
||||||
vips_image = np_array_to_vips_image(array)
|
|
||||||
print 'avg =', vips_image.avg()
|
|
||||||
vips_image.write_to_file("test2.jpg")
|
|
||||||
```
|
|
||||||
|
|
||||||
# Watermarking
|
|
||||||
|
|
||||||
This example renders a simple watermark on an image. Use it like this:
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
./watermark.py somefile.png output.jpg "hello <i>world</i>"
|
|
||||||
```
|
|
||||||
|
|
||||||
The text is rendered in transparent red pixels all over the image. It knows about
|
|
||||||
transparency, CMYK, and 16-bit images.
|
|
||||||
|
|
||||||
```python
|
|
||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import gi
|
import time
|
||||||
gi.require_version('Vips', '8.0')
|
|
||||||
from gi.repository import Vips
|
|
||||||
|
|
||||||
im = Vips.Image.new_from_file(sys.argv[1], access = Vips.Access.SEQUENTIAL)
|
|
||||||
|
|
||||||
text = Vips.Image.text(sys.argv[3], width = 500, dpi = 300)
|
|
||||||
text = (text * 0.3).cast("uchar")
|
|
||||||
text = text.embed(100, 100, text.width + 200, text.width + 200)
|
|
||||||
text = text.replicate(1 + im.width / text.width, 1 + im.height / text.height)
|
|
||||||
text = text.crop(0, 0, im.width, im.height)
|
|
||||||
|
|
||||||
# we want to blend into the visible part of the image and leave any alpha
|
import pyvips
|
||||||
# channels untouched ... we need to split im into two parts
|
from PIL import Image
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
# 16-bit images have 65535 as white
|
if len(sys.argv) != 3:
|
||||||
if im.format == Vips.BandFormat.USHORT:
|
print('usage: {0} input-filename output-filename'.format(sys.argv[0]))
|
||||||
white = 65535
|
sys.exit(-1)
|
||||||
else:
|
|
||||||
white = 255
|
|
||||||
|
|
||||||
# guess how many bands from the start of im contain visible colour information
|
# map vips formats to np dtypes
|
||||||
if im.bands >= 4 and im.interpretation == Vips.Interpretation.CMYK:
|
format_to_dtype = {
|
||||||
# cmyk image ... put the white into the magenta channel
|
'uchar': np.uint8,
|
||||||
n_visible_bands = 4
|
'char': np.int8,
|
||||||
text_colour = [0, white, 0, 0]
|
'ushort': np.uint16,
|
||||||
elif im.bands >= 3:
|
'short': np.int16,
|
||||||
# colour image ... put the white into the red channel
|
'uint': np.uint32,
|
||||||
n_visible_bands = 3
|
'int': np.int32,
|
||||||
text_colour = [white, 0, 0]
|
'float': np.float32,
|
||||||
else:
|
'double': np.float64,
|
||||||
# mono image
|
'complex': np.complex64,
|
||||||
n_visible_bands = 1
|
'dpcomplex': np.complex128,
|
||||||
text_colour = white
|
}
|
||||||
|
|
||||||
# split into image and alpha
|
# map np dtypes to vips
|
||||||
if im.bands - n_visible_bands > 0:
|
dtype_to_format = {
|
||||||
alpha = im.extract_band(n_visible_bands, n = im.bands - n_visible_bands)
|
'uint8': 'uchar',
|
||||||
im = im.extract_band(0, n = n_visible_bands)
|
'int8': 'char',
|
||||||
else:
|
'uint16': 'ushort',
|
||||||
alpha = None
|
'int16': 'short',
|
||||||
|
'uint32': 'uint',
|
||||||
|
'int32': 'int',
|
||||||
|
'float32': 'float',
|
||||||
|
'float64': 'double',
|
||||||
|
'complex64': 'complex',
|
||||||
|
'complex128': 'dpcomplex',
|
||||||
|
}
|
||||||
|
|
||||||
# blend means do a smooth fade using the 0 - 255 values in the condition channel
|
# load with PIL
|
||||||
# (test in this case) ... this will render the anit-aliasing
|
start_pillow = time.time()
|
||||||
im = text.ifthenelse(text_colour, im, blend = True)
|
pillow_img = np.asarray(Image.open(sys.argv[1]))
|
||||||
|
print('Pillow Time:', time.time()-start_pillow)
|
||||||
|
print('original shape', pillow_img.shape)
|
||||||
|
|
||||||
# reattach alpha
|
# load with vips to a memory array
|
||||||
if alpha:
|
start_vips = time.time()
|
||||||
im = im.bandjoin(alpha)
|
img = pyvips.Image.new_from_file(sys.argv[1], access='sequential')
|
||||||
|
mem_img = img.write_to_memory()
|
||||||
im.write_to_file(sys.argv[2])
|
|
||||||
|
|
||||||
|
# then make a numpy array from that buffer object
|
||||||
|
np_3d = np.ndarray(buffer=mem_img,
|
||||||
|
dtype=format_to_dtype[img.format],
|
||||||
|
shape=[img.height, img.width, img.bands])
|
||||||
|
|
||||||
|
print('Vips Time:', time.time()-start_vips)
|
||||||
|
print('final shape', np_3d.shape)
|
||||||
|
|
||||||
|
# verify we have the same result
|
||||||
|
print('Sum of the Differences:', np.sum(np_3d-pillow_img))
|
||||||
|
|
||||||
|
# make a vips image from the numpy array
|
||||||
|
height, width, bands = np_3d.shape
|
||||||
|
linear = np_3d.reshape(width * height * bands)
|
||||||
|
vi = pyvips.Image.new_from_memory(linear.data, width, height, bands,
|
||||||
|
dtype_to_format[str(np_3d.dtype)])
|
||||||
|
|
||||||
|
# and write back to disc for checking
|
||||||
|
vi.write_to_file(sys.argv[2])
|
||||||
```
|
```
|
||||||
|
|
||||||
# Build huge image mosaic
|
# Build huge image mosaic
|
||||||
|
|
||||||
This makes a 100,000 x 100,000 black image, then inserts all the images you pass on the
|
This makes a 100,000 x 100,000 black image, then inserts all the images you
|
||||||
command-line into it at random positions. libvips is able to run this program in
|
pass on the command-line into it at random positions. libvips is able to run
|
||||||
sequential mode: it'll open all the input images at the same time, and stream pixels from
|
this program in sequential mode: it'll open all the input images at the same
|
||||||
them as it needs them to generate the output.
|
time, and stream pixels from them as it needs them to generate the output.
|
||||||
|
|
||||||
To test it, first make a large 1-bit image. This command will take the green channel and
|
To test it, first make a large 1-bit image. This command will take the
|
||||||
write as a 1-bit fax image. `wtc.jpg` is a test 10,000 x 10,000 jpeg:
|
green channel and write as a 1-bit fax image. `wtc.jpg` is a test 10,000
|
||||||
|
x 10,000 jpeg:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ vips extract_band wtc.jpg x.tif[squash,compression=ccittfax4,strip] 1
|
$ vips extract_band wtc.jpg x.tif[squash,compression=ccittfax4,strip] 1
|
||||||
@ -217,110 +148,26 @@ It completes in just under two minutes on this laptop, and needs about
|
|||||||
7gb of RAM to run. It would need about the same amount of memory for a
|
7gb of RAM to run. It would need about the same amount of memory for a
|
||||||
full-colour RGB image, I was just keen to keep disc usage down.
|
full-colour RGB image, I was just keen to keep disc usage down.
|
||||||
|
|
||||||
If you wanted to handle transparency, or if you wanted mixed CMYK and RGB images, you'd
|
If you wanted to handle transparency, or if you wanted mixed CMYK and RGB
|
||||||
need to do some more work to convert them all into the same colourspace before
|
images, you'd need to do some more work to convert them all into the same
|
||||||
inserting them.
|
colourspace before inserting them.
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import random
|
import random
|
||||||
|
import pyvips
|
||||||
import gi
|
|
||||||
gi.require_version('Vips', '8.0')
|
|
||||||
from gi.repository import Vips
|
|
||||||
|
|
||||||
# turn on progress reporting
|
|
||||||
Vips.progress_set(True)
|
|
||||||
|
|
||||||
# this makes a 8-bit, mono image of 100,000 x 100,000 pixels, each pixel zero
|
# this makes a 8-bit, mono image of 100,000 x 100,000 pixels, each pixel zero
|
||||||
im = Vips.Image.black(100000, 100000)
|
im = pyvips.Image.black(100000, 100000)
|
||||||
|
|
||||||
for filename in sys.argv[2:]:
|
for filename in sys.argv[2:]:
|
||||||
tile = Vips.Image.new_from_file(filename, access = Vips.Access.SEQUENTIAL)
|
tile = pyvips.Image.new_from_file(filename, access='sequential')
|
||||||
|
|
||||||
im = im.insert(tile,
|
im = im.insert(tile,
|
||||||
random.randint(0, im.width - tile.width),
|
random.randint(0, im.width - tile.width),
|
||||||
random.randint(0, im.height - tile.height))
|
random.randint(0, im.height - tile.height))
|
||||||
|
|
||||||
im.write_to_file(sys.argv[1])
|
im.write_to_file(sys.argv[1])
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
# Rename DICOM images using header fields
|
|
||||||
|
|
||||||
DICOM images commonly come in an awful directory hierarchy named as something
|
|
||||||
like `images/a/b/e/z04`. There can be thousands of files and it can be very
|
|
||||||
hard to find the one you want.
|
|
||||||
|
|
||||||
This utility copies files to a single flat directory, naming them using
|
|
||||||
fields from the DICOM header. You can actually find stuff! Useful.
|
|
||||||
|
|
||||||
```python
|
|
||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import re
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
import gi
|
|
||||||
gi.require_version('Vips', '8.0')
|
|
||||||
from gi.repository import Vips
|
|
||||||
|
|
||||||
if len(sys.argv) != 3:
|
|
||||||
print 'rename DICOM files using tags from the header'
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
srcdir = sys.argv[1]
|
|
||||||
destdir = sys.argv[2]
|
|
||||||
|
|
||||||
if not os.access(destdir, os.F_OK | os.R_OK | os.W_OK | os.X_OK):
|
|
||||||
os.mkdir(destdir)
|
|
||||||
|
|
||||||
def get_field(vim, field):
|
|
||||||
result = vim.get_value(field)
|
|
||||||
|
|
||||||
# remove any \n etc.
|
|
||||||
result = re.sub("\n", "", result)
|
|
||||||
|
|
||||||
# remove any leading or trailing spaces
|
|
||||||
result = re.sub(" $", "", result)
|
|
||||||
result = re.sub("^ ", "", result)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
modality_name = "magick-dcm:Modality"
|
|
||||||
series_name = "magick-dcm:SeriesNumber"
|
|
||||||
instance_name = "magick-dcm:Instance(formerlyImage)Number"
|
|
||||||
date_name = "magick-dcm:ImageDate"
|
|
||||||
|
|
||||||
for(dirpath, dirnames, filenames) in os.walk(srcdir):
|
|
||||||
for file in filenames:
|
|
||||||
path = os.path.join(dirpath, file)
|
|
||||||
|
|
||||||
try:
|
|
||||||
vim = Vips.Image.new_from_file(path)
|
|
||||||
except Vips.Error, e:
|
|
||||||
print 'unable to open', path
|
|
||||||
print e
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
modality = get_field(vim, modality_name)
|
|
||||||
series = get_field(vim, series_name)
|
|
||||||
instance = get_field(vim, instance_name)
|
|
||||||
date = get_field(vim, date_name)
|
|
||||||
except Vips.Error, e:
|
|
||||||
print 'unable to get fields from header', path
|
|
||||||
print e
|
|
||||||
continue
|
|
||||||
|
|
||||||
match = re.match("(\d\d\d\d)(\d\d)(\d\d)", date)
|
|
||||||
date = match.group(1) + "." + match.group(2) + "." + match.group(3)
|
|
||||||
|
|
||||||
newname = "lan." + modality + "." + instance + "." + date + ".IMA"
|
|
||||||
|
|
||||||
shutil.copyfile(path, os.path.join(destdir, newname))
|
|
||||||
```
|
```
|
||||||
|
Loading…
Reference in New Issue
Block a user