Creating ImageIO Plugins¶
Imagio is plugin-based. Every supported format is provided with a plugin. You can write your own plugins to make imageio support additional formats. And we would be interested in adding such code to the imageio codebase!
What is a plugin¶
In imageio, a plugin provides one or more Format
objects, and
corresponding Reader
and Writer
classes.
Each Format object represents an implementation to read/write a
particular file format. Its Reader and Writer classes do the actual
reading/saving.
The reader and writer objects have a request
attribute that can be
used to obtain information about the read or write Request
, such as
user-provided keyword arguments, as well get access to the raw image
data.
Registering¶
Strictly speaking a format can be used stand alone. However, to allow
imageio to automatically select it for a specific file, the format must
be registered using imageio.formats.add_format()
.
Note that a plugin is not required to be part of the imageio package; as long as a format is registered, imageio can use it. This makes imageio very easy to extend.
What methods to implement¶
Imageio is designed such that plugins only need to implement a few private methods. The public API is implemented by the base classes. In effect, the public methods can be given a decent docstring which does not have to be repeated at the plugins.
For the Format class, the following needs to be implemented/specified:
The format needs a short name, a description, and a list of file extensions that are common for the file-format in question. These are set when instantiation the Format object.
Use a docstring to provide more detailed information about the format/plugin, such as parameters for reading and saving that the user can supply via keyword arguments.
Implement
_can_read(request)
, return a bool. See also theRequest
class.Implement
_can_write(request)
, ditto.
For the Format.Reader class:
Implement
_open(**kwargs)
to initialize the reader. Deal with the user-provided keyword arguments here.Implement
_close()
to clean up.Implement
_get_length()
to provide a suitable length based on what the user expects. Can beinf
for streaming data.Implement
_get_data(index)
to return an array and a meta-data dict.Implement
_get_meta_data(index)
to return a meta-data dict. If index is None, it should return the ‘global’ meta-data.
For the Format.Writer class:
Implement
_open(**kwargs)
to initialize the writer. Deal with the user-provided keyword arguments here.Implement
_close()
to clean up.Implement
_append_data(im, meta)
to add data (and meta-data).Implement
_set_meta_data(meta)
to set the global meta-data.
Example / template plugin¶
1# -*- coding: utf-8 -*-
2# imageio is distributed under the terms of the (new) BSD License.
3
4""" Example plugin. You can use this as a template for your own plugin.
5"""
6
7import numpy as np
8
9from .. import formats
10from ..core import Format
11
12
13class DummyFormat(Format):
14 """The dummy format is an example format that does nothing.
15 It will never indicate that it can read or write a file. When
16 explicitly asked to read, it will simply read the bytes. When
17 explicitly asked to write, it will raise an error.
18
19 This documentation is shown when the user does ``help('thisformat')``.
20
21 Parameters for reading
22 ----------------------
23 Specify arguments in numpy doc style here.
24
25 Parameters for saving
26 ---------------------
27 Specify arguments in numpy doc style here.
28
29 """
30
31 def _can_read(self, request):
32 # This method is called when the format manager is searching
33 # for a format to read a certain image. Return True if this format
34 # can do it.
35 #
36 # The format manager is aware of the extensions and the modes
37 # that each format can handle. It will first ask all formats
38 # that *seem* to be able to read it whether they can. If none
39 # can, it will ask the remaining formats if they can: the
40 # extension might be missing, and this allows formats to provide
41 # functionality for certain extensions, while giving preference
42 # to other plugins.
43 #
44 # If a format says it can, it should live up to it. The format
45 # would ideally check the request.firstbytes and look for a
46 # header of some kind.
47 #
48 # The request object has:
49 # request.filename: a representation of the source (only for reporting)
50 # request.firstbytes: the first 256 bytes of the file.
51 # request.mode[0]: read or write mode
52 # request.mode[1]: what kind of data the user expects: one of 'iIvV?'
53
54 if request.mode[1] in (self.modes + "?"):
55 if request.extension in self.extensions:
56 return True
57
58 def _can_write(self, request):
59 # This method is called when the format manager is searching
60 # for a format to write a certain image. It will first ask all
61 # formats that *seem* to be able to write it whether they can.
62 # If none can, it will ask the remaining formats if they can.
63 #
64 # Return True if the format can do it.
65
66 # In most cases, this code does suffice:
67 if request.mode[1] in (self.modes + "?"):
68 if request.extension in self.extensions:
69 return True
70
71 # -- reader
72
73 class Reader(Format.Reader):
74 def _open(self, some_option=False, length=1):
75 # Specify kwargs here. Optionally, the user-specified kwargs
76 # can also be accessed via the request.kwargs object.
77 #
78 # The request object provides two ways to get access to the
79 # data. Use just one:
80 # - Use request.get_file() for a file object (preferred)
81 # - Use request.get_local_filename() for a file on the system
82 self._fp = self.request.get_file()
83 self._length = length # passed as an arg in this case for testing
84 self._data = None
85
86 def _close(self):
87 # Close the reader.
88 # Note that the request object will close self._fp
89 pass
90
91 def _get_length(self):
92 # Return the number of images. Can be np.inf
93 return self._length
94
95 def _get_data(self, index):
96 # Return the data and meta data for the given index
97 if index >= self._length:
98 raise IndexError("Image index %i > %i" % (index, self._length))
99 # Read all bytes
100 if self._data is None:
101 self._data = self._fp.read()
102 # Put in a numpy array
103 im = np.frombuffer(self._data, "uint8")
104 im.shape = len(im), 1
105 # Return array and dummy meta data
106 return im, {}
107
108 def _get_meta_data(self, index):
109 # Get the meta data for the given index. If index is None, it
110 # should return the global meta data.
111 return {} # This format does not support meta data
112
113 # -- writer
114
115 class Writer(Format.Writer):
116 def _open(self, flags=0):
117 # Specify kwargs here. Optionally, the user-specified kwargs
118 # can also be accessed via the request.kwargs object.
119 #
120 # The request object provides two ways to write the data.
121 # Use just one:
122 # - Use request.get_file() for a file object (preferred)
123 # - Use request.get_local_filename() for a file on the system
124 self._fp = self.request.get_file()
125
126 def _close(self):
127 # Close the reader.
128 # Note that the request object will close self._fp
129 pass
130
131 def _append_data(self, im, meta):
132 # Process the given data and meta data.
133 raise RuntimeError("The dummy format cannot write image data.")
134
135 def set_meta_data(self, meta):
136 # Process the given meta data (global for all images)
137 # It is not mandatory to support this.
138 raise RuntimeError("The dummy format cannot write meta data.")
139
140
141# Register. You register an *instance* of a Format class. Here specify:
142format = DummyFormat(
143 "dummy", # short name
144 "An example format that does nothing.", # one line descr.
145 ".foobar .nonexistentext", # list of extensions
146 "iI", # modes, characters in iIvV
147)
148formats.add_format(format)