-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
# Description Added image plugin from TEM demo. Improved the test, added cropping and an option for interpolation order when resizing. Closes #685 ## Type of change - [ ] Bug fix & code cleanup - [x] New feature - [ ] Documentation update - [ ] Test update ## Checklist for the reviewer This checklist should be used as a help for the reviewer. - [ ] Is the change limited to one issue? - [ ] Does this PR close the issue? - [ ] Is the code easy to read and understand? - [ ] Do all new feature have an accompanying new test? - [ ] Has the documentation been updated as necessary?
- Loading branch information
Showing
7 changed files
with
204 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{ | ||
"uri": "http://onto-ns.com/meta/0.1/Image", | ||
"description": "Metadata for an image.", | ||
"dimensions": { | ||
"height": "Image hight", | ||
"width": "Image width.", | ||
"channels": "Number of channels in each pixel (1 for gray-scale, 3 for RGB and 4 for RGBA)." | ||
}, | ||
"properties": { | ||
"filename": { | ||
"type": "string", | ||
"description": "File name." | ||
}, | ||
"data": { | ||
"type": "float32", | ||
"shape": ["height", "width", "channels"], | ||
"description": "Image data." | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
"""DLite storage plugin for images.""" | ||
import numpy as np | ||
|
||
from skimage.io import imread, imsave | ||
from skimage.exposure import equalize_hist | ||
from skimage.transform import resize | ||
|
||
import dlite | ||
from dlite.options import Options | ||
|
||
|
||
class image(dlite.DLiteStorageBase): | ||
"""DLite storage plugin for images. | ||
Arguments: | ||
location: Path to YAML file. | ||
options: Supported options: | ||
- `plugin`: Name of scikit image io plugin to use for loading | ||
the image. By default, the different plugins are tried | ||
(starting with imageio) until a suitable candidate is found. | ||
If not given and fname is a tiff file, the 'tifffile' plugin | ||
will be used. | ||
- `crop`: Crop out a part of the image. Applied before other | ||
operations. Specified as comma-separated set of ranges | ||
to crop out for each dimension. Example: for a 2D image will | ||
":,50:150" keep the hight, but reduce the width to 100 pixes | ||
starting from x=50. | ||
- `as_gray`: Whether to convert color images to gray-scale | ||
when loading. Default is false. | ||
- `equalize`: Whether to equalize histogram before saving. | ||
Default is false. | ||
- `resize`: Required image size when saving. Should be given | ||
as `HEIGHTxWIDTH` (ex: "256x128"). | ||
- `order`: Order of spline interpolation for resize. Default | ||
to zero for binary images and 1 otherwise. Should be in the | ||
range 0-5. | ||
""" | ||
meta = "http://onto-ns.com/meta/0.1/Image" | ||
|
||
def open(self, location, options=None): | ||
"""Loads an image from `location`. No options are supported.""" | ||
self.location = location | ||
self.options = Options( | ||
options, defaults="as_gray=false;equalize=false" | ||
) | ||
|
||
def load(self, id=None): | ||
"""Returns TEMImage instance.""" | ||
as_gray = dlite.asbool(self.options.as_gray) | ||
data = imread( | ||
self.location, as_gray=as_gray, plugin=self.options.get("plugin"), | ||
) | ||
|
||
crop = self.options.get("crop") | ||
if crop: | ||
# We call __getitem__() explicitly, since the preferred syntax | ||
# `data[*toindex(crop)]` is not supported by Python 3.7 | ||
data = data.__getitem__(*toindex(crop)) | ||
|
||
# Infer dimensions | ||
shape = [1]*3 | ||
shape[3 - len(data.shape):] = data.shape | ||
dimnames = "height", "width", "channels" | ||
dimensions = dict(zip(dimnames, shape)) | ||
|
||
# Create and populate DLite instance | ||
Image = dlite.get_instance(self.meta) | ||
image = Image(dimensions=dimensions) | ||
image.filename = self.location | ||
image.data = np.array(data, ndmin=3) | ||
|
||
return image | ||
|
||
def save(self, inst): | ||
"""Stores DLite instance `inst` to storage.""" | ||
if dlite.asbool(self.options.equalize): | ||
data = equalize_hist(inst.data) | ||
else: | ||
data = inst.data | ||
|
||
if data.shape[0] == 1: | ||
data = data[0,:,:] | ||
|
||
crop = self.options.get("crop") | ||
if crop: | ||
# We call __getitem__() explicitly, since the preferred syntax | ||
# `data[*toindex(crop)]` is not supported by Python 3.7 | ||
data = data.__getitem__(toindex(crop)) | ||
|
||
if self.options.get("resize"): | ||
size = [int(s) for s in self.options.resize.split("x")] | ||
shape = list(data.shape) | ||
shape[:len(size)] = size | ||
kw = {} | ||
if self.options.get("order"): | ||
kw["order"] = int(self.options.order) | ||
data = resize(data, output_shape=shape, **kw) | ||
|
||
hi, lo = data.max(), data.min() | ||
scaled = np.uint8((data - lo)/(hi - lo + 1e-3)*256) | ||
imsave( | ||
self.location, scaled, plugin=self.options.get("plugin") | ||
) | ||
|
||
|
||
def toindex(crop_string): | ||
"""Convert a crop string to a valid numpy index. | ||
Example usage: `arr[*toindex(":,50:150")]` | ||
""" | ||
ranges = [] | ||
for dim in crop_string.split(","): | ||
if dim == ":": | ||
ranges.append(slice(None)) | ||
else: | ||
ranges.append(slice(*[int(d) for d in dim.split(":")])) | ||
return tuple(ranges) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
*.json | ||
*.yaml | ||
*.bson | ||
*.csv | ||
*.xlsx | ||
*.rdf | ||
*.ttl | ||
*.txt | ||
*.png |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
from pathlib import Path | ||
|
||
import dlite | ||
|
||
|
||
thisdir = Path(__file__).absolute().parent | ||
indir = thisdir / 'input' | ||
outdir = thisdir / 'output' | ||
#entitydir = thisdir.parent / "python-storage-plugins" | ||
#dlite.storage_path.append(entitydir) | ||
|
||
# Test save | ||
image = dlite.Instance.from_location("image", indir / "image.png") | ||
image.save("image", outdir / "image.png") | ||
image.save("image", outdir / "image-crop.png", "crop=60:120,60:120") | ||
image.save("image", outdir / "image-cropy.png", "crop=60:120") | ||
image.save("image", outdir / "image-eq.png", "equalize=true") | ||
image.save("image", outdir / "image-resize.png", "resize=128x128") | ||
image.save("image", outdir / "image-resize2.png", "resize=512x512") | ||
image.save("image", outdir / "image-resize3.png", "resize=512x512;order=3") | ||
|
||
assert image.filename == str(indir / "image.png") | ||
assert image.data.shape == (256, 256, 4) | ||
|
||
# Test load | ||
im = dlite.Instance.from_location("image", outdir / "image.png") | ||
assert im.data.shape == image.data.shape | ||
assert np.all(im.data == image.data) | ||
assert Path(im.filename).name == Path(image.filename).name | ||
|
||
im = dlite.Instance.from_location("image", outdir / "image-crop.png") | ||
assert im.data.shape == (60, 60, 4) | ||
|
||
im = dlite.Instance.from_location("image", outdir / "image-cropy.png") | ||
assert im.data.shape == (60, 256, 4) | ||
|
||
im = dlite.Instance.from_location("image", outdir / "image-eq.png") | ||
assert im.data.shape == (256, 256, 4) | ||
|
||
im = dlite.Instance.from_location("image", outdir / "image-resize.png") | ||
assert im.data.shape == (128, 128, 4) | ||
|
||
im = dlite.Instance.from_location("image", outdir / "image-resize2.png") | ||
assert im.data.shape == (512, 512, 4) | ||
|
||
im = dlite.Instance.from_location("image", outdir / "image-resize3.png") | ||
assert im.data.shape == (512, 512, 4) |