-
Notifications
You must be signed in to change notification settings - Fork 13
/
image_conversion.py
343 lines (286 loc) · 15.8 KB
/
image_conversion.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#--------------------------------------------------------------------------------------------------
# Program Name: gamera-rodan
# Program Description: Job wrappers that allows some Gamrea functionality to work in Rodan.
#
# Filename: gamera-rodan/wrappers/plugins/image_conversion.py
# Purpose: Wrapper for image conversion plugins.
#
# Copyright (C) 2016 DDMAL
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#--------------------------------------------------------------------------------------------------
# gamera4 works in python3 so we can use the same functions as used in the former gamera versions
try:
import gamera
from gamera.core import load_image
from gamera.plugins import image_conversion
except ImportError:
pass
from rodan.jobs.base import RodanTask
import logging
logger = logging.getLogger('rodan')
class gamera_to_rgb(RodanTask):
name = 'Convert to RGB PNG'
author = 'Ryan Bannon'
description = """
Converts the given image to an RGB image according to the following rules:
- for ONEBIT images, 0 is mapped to (255,255,255) and everything else to (0,0,0)
- for GREYSCALE and GREY16 images, R=G=B
- for FLOAT images, the range [min,max] is linearly mapped to the 256 grey values
Note, converting an image to one of the same type performs a copy operation.
"""
settings ={'job_queue': 'Python3'}
enabled = True
category = 'Gamera - Image Conversion'
interactive = False
input_port_types = [{
'name': 'PNG image',
'resource_types': ['image/rgb+png', 'image/onebit+png', 'image/greyscale+png', 'image/grey16+png'],
'minimum': 1,
'maximum': 1
}]
output_port_types = [{
'name': 'RGB PNG image',
'resource_types': ['image/rgb+png'],
'minimum': 1,
'maximum': 2
}]
def run_my_task(self, inputs, settings, outputs):
image_source = load_image(inputs['PNG image'][0]['resource_path'])
image_result = image_source.to_rgb()
image_result.save_PNG(outputs['RGB PNG image'][0]['resource_path'])
for i in range(len(outputs['RGB PNG image'])):
image_result.save_PNG(outputs['RGB PNG image'][i]['resource_path'])
return True
def test_my_task(self, testcase):
import cv2
import numpy as np
input_rgb_png_path = "/code/Rodan/rodan/test/files/lenna.png"
input_onebit_png_path = "/code/Rodan/rodan/test/files/lenna_one-bit-png_output.png"
input_greyscale_png_path = "/code/Rodan/rodan/test/files/lenna_greyscale-png_output.png"
input_grey16_png_path = "/code/Rodan/rodan/test/files/lenna_greyscale-16-png_output.png"
output_rgb_png_path = testcase.new_available_path()
output_onebit_png_path = testcase.new_available_path()
output_greyscale_png_path = testcase.new_available_path()
output_grey16_png_path = testcase.new_available_path()
gt_rgb_png_path = input_rgb_png_path
gt_onebit_png_path = "/code/Rodan/rodan/test/files/lenna_convert2Rgb-png_one-bit-png_output.png"
gt_greyscale_png_path = "/code/Rodan/rodan/test/files/lenna_convert2Rgb-png_greyscale-png_output.png"
gt_grey16_png_path = "/code/Rodan/rodan/test/files/lenna_convert2Rgb-png_greyscale-16-png_output.png"
for input_path, out_path, gt_path in zip([input_rgb_png_path, input_onebit_png_path, input_greyscale_png_path, input_grey16_png_path],
[output_rgb_png_path, output_onebit_png_path, output_greyscale_png_path, output_grey16_png_path],
[gt_rgb_png_path, gt_onebit_png_path, gt_greyscale_png_path, gt_grey16_png_path]):
inputs = {
"PNG image": [{"resource_path":input_path}]
}
outputs = {
"RGB PNG image": [{"resource_path":out_path}]
}
self.run_my_task(inputs=inputs, outputs=outputs, settings={})
# The predicted result and gt result should be identical to each other
# The gt result is from running this job (niblack threshold) on production
gt_output = cv2.imread(gt_path, cv2.IMREAD_UNCHANGED)
pred_output = cv2.imread(out_path, cv2.IMREAD_UNCHANGED)
np.testing.assert_array_equal(gt_output, pred_output, "Failed: {}".format(input_path))
class gamera_to_greyscale(RodanTask):
name = 'Convert to greyscale PNG'
author = 'Ryan Bannon'
description = """
Converts the given image to a GREYSCALE image according to the following rules:
- for ONEBIT images, 0 is mapped to 255 and everything else to 0.
- for FLOAT images, the range [min,max] is linearly scaled to [0,255]
- for GREY16 images, the range [0,max] is linearly scaled to [0,255]
- for RGB images, the luminance is used, which is defined in VIGRA as 0.3*R + 0.59*G + 0.11*B
Converting an image to one of the same type performs a copy operation.
"""
settings ={'job_queue': 'Python3'}
enabled = True
category = 'Gamera - Image Conversion'
interactive = False
input_port_types = [{
'name': 'PNG image',
'resource_types': ['image/rgb+png', 'image/onebit+png', 'image/greyscale+png', 'image/grey16+png'],
'minimum': 1,
'maximum': 1
}]
output_port_types = [{
'name': 'Greyscale PNG image',
'resource_types': ['image/greyscale+png'],
'minimum': 1,
'maximum': 2
}]
def run_my_task(self, inputs, settings, outputs):
image_source = load_image(inputs['PNG image'][0]['resource_path'])
image_result = image_source.to_greyscale()
for i in range(len(outputs['Greyscale PNG image'])):
image_result.save_PNG(outputs['Greyscale PNG image'][i]['resource_path'])
return True
def test_my_task(self, testcase):
import cv2
import numpy as np
input_rgb_png_path = "/code/Rodan/rodan/test/files/lenna.png"
input_onebit_png_path = "/code/Rodan/rodan/test/files/lenna_one-bit-png_output.png"
input_greyscale_png_path = "/code/Rodan/rodan/test/files/lenna_greyscale-png_output.png"
input_grey16_png_path = "/code/Rodan/rodan/test/files/lenna_greyscale-16-png_output.png"
output_rgb_png_path = testcase.new_available_path()
output_onebit_png_path = testcase.new_available_path()
output_greyscale_png_path = testcase.new_available_path()
output_grey16_png_path = testcase.new_available_path()
gt_rgb_png_path = "/code/Rodan/rodan/test/files/lenna_convert2greyscale-png_lenna_output.png"
gt_onebit_png_path = "/code/Rodan/rodan/test/files/lenna_convert2greyscale-png_one-bit-png_output.png"
gt_greyscale_png_path = "/code/Rodan/rodan/test/files/lenna_convert2greyscale-png_greyscale-png_output.png"
gt_grey16_png_path = "/code/Rodan/rodan/test/files/lenna_convert2greyscale-png_greyscale-16-png_output.png"
for input_path, out_path, gt_path in zip([input_rgb_png_path, input_onebit_png_path, input_greyscale_png_path, input_grey16_png_path],
[output_rgb_png_path, output_onebit_png_path, output_greyscale_png_path, output_grey16_png_path],
[gt_rgb_png_path, gt_onebit_png_path, gt_greyscale_png_path, gt_grey16_png_path]):
inputs = {
"PNG image": [{"resource_path":input_path}]
}
outputs = {
"Greyscale PNG image": [{"resource_path":out_path}]
}
self.run_my_task(inputs=inputs, outputs=outputs, settings={})
# The predicted result and gt result should be identical to each other
# The gt result is from running this job (niblack threshold) on production
gt_output = cv2.imread(gt_path, cv2.IMREAD_UNCHANGED)
pred_output = cv2.imread(out_path, cv2.IMREAD_UNCHANGED)
np.testing.assert_array_equal(gt_output, pred_output, "Failed: {}".format(input_path))
class gamera_to_grey16(RodanTask):
name = 'Convert to greyscale 16 PNG'
author = 'Ryan Bannon'
settings ={'job_queue': 'Python3'}
description = """
Converts the given image to a GREY16 image according to the following rules:
- for ONEBIT images, 0 is mapped to 65535 and everything else to 0.
- for FLOAT images, the range [min,max] is linearly scaled to [0,65535]
- for GREYSCALE images, pixel values are copied unchanged
- for RGB images, the luminance is used, which is defined in VIGRA as 0.3*R + 0.59*G + 0.11*B. This results only in a value range [0,255]
Converting an image to one of the same type performs a copy operation.
"""
enabled = True
category = 'Gamera - Image Conversion'
interactive = False
input_port_types = [{
'name': 'PNG image',
'resource_types': ['image/rgb+png', 'image/onebit+png', 'image/greyscale+png', 'image/grey16+png'],
'minimum': 1,
'maximum': 1
}]
output_port_types = [{
'name': 'Greyscale 16 PNG image',
'resource_types': ['image/grey16+png'],
'minimum': 1,
'maximum': 2
}]
def run_my_task(self, inputs, settings, outputs):
image_source = load_image(inputs['PNG image'][0]['resource_path'])
image_result = image_source.to_grey16()
for i in range(len(outputs['Greyscale 16 PNG image'])):
image_result.save_PNG(outputs['Greyscale 16 PNG image'][i]['resource_path'])
return True
def test_my_task(self, testcase):
import cv2
import numpy as np
input_rgb_png_path = "/code/Rodan/rodan/test/files/lenna.png"
input_onebit_png_path = "/code/Rodan/rodan/test/files/lenna_one-bit-png_output.png"
input_greyscale_png_path = "/code/Rodan/rodan/test/files/lenna_greyscale-png_output.png"
input_grey16_png_path = "/code/Rodan/rodan/test/files/lenna_greyscale-16-png_output.png"
output_rgb_png_path = testcase.new_available_path()
output_onebit_png_path = testcase.new_available_path()
output_greyscale_png_path = testcase.new_available_path()
output_grey16_png_path = testcase.new_available_path()
gt_rgb_png_path = "/code/Rodan/rodan/test/files/lenna_convert2greyscale16-png_lenna_output.png"
gt_onebit_png_path = "/code/Rodan/rodan/test/files/lenna_convert2greyscale16-png_one-bit-png_output.png"
gt_greyscale_png_path = "/code/Rodan/rodan/test/files/lenna_convert2greyscale16-png_greyscale-png_output.png"
gt_grey16_png_path = "/code/Rodan/rodan/test/files/lenna_convert2greyscale16-png_greyscale-16-png_output.png"
for input_path, out_path, gt_path in zip([input_rgb_png_path, input_onebit_png_path, input_greyscale_png_path, input_grey16_png_path],
[output_rgb_png_path, output_onebit_png_path, output_greyscale_png_path, output_grey16_png_path],
[gt_rgb_png_path, gt_onebit_png_path, gt_greyscale_png_path, gt_grey16_png_path]):
inputs = {
"PNG image": [{"resource_path":input_path}]
}
outputs = {
"Greyscale 16 PNG image": [{"resource_path":out_path}]
}
self.run_my_task(inputs=inputs, outputs=outputs, settings={})
# The predicted result and gt result should be identical to each other
# The gt result is from running this job (niblack threshold) on production
gt_output = cv2.imread(gt_path, cv2.IMREAD_UNCHANGED)
pred_output = cv2.imread(out_path, cv2.IMREAD_UNCHANGED)
np.testing.assert_array_equal(gt_output, pred_output, "Failed: {}".format(input_path))
class gamera_to_onebit(RodanTask):
name = 'Convert to one-bit (black and white) PNG'
author = 'Ryan Bannon'
description = """
Converts the given image to a ONEBIT image. First the image is converted
and then the otsu_threshold_ algorithm is applied.
For other ways to convert to ONEBIT images, see the Binarization_ category.
Converting an image to one of the same type performs a copy operation.
.. _otsu_threshold: binarization.html#otsu-threshold
.. _Binarization: binarization.html
"""
settings ={'job_queue': 'Python3'}
enabled = True
category = 'Gamera - Image Conversion'
interactive = False
input_port_types = [{
'name': 'PNG image',
'resource_types': ['image/rgb+png', 'image/onebit+png', 'image/greyscale+png', 'image/grey16+png'],
'minimum': 1,
'maximum': 1
}]
output_port_types = [{
'name': 'One-bit PNG image',
'resource_types': ['image/onebit+png'],
'minimum': 1,
'maximum': 2
}]
def run_my_task(self, inputs, settings, outputs):
image_source = load_image(inputs['PNG image'][0]['resource_path'])
image_result = image_source.to_onebit()
for i in range(len(outputs['One-bit PNG image'])):
image_result.save_PNG(outputs['One-bit PNG image'][i]['resource_path'])
return True
def test_my_task(self, testcase):
import cv2
import numpy as np
input_rgb_png_path = "/code/Rodan/rodan/test/files/lenna.png"
input_onebit_png_path = "/code/Rodan/rodan/test/files/lenna_one-bit-png_output.png"
input_greyscale_png_path = "/code/Rodan/rodan/test/files/lenna_greyscale-png_output.png"
input_grey16_png_path = "/code/Rodan/rodan/test/files/lenna_greyscale-16-png_output.png"
output_rgb_png_path = testcase.new_available_path()
output_onebit_png_path = testcase.new_available_path()
output_greyscale_png_path = testcase.new_available_path()
output_grey16_png_path = testcase.new_available_path()
gt_rgb_png_path = "/code/Rodan/rodan/test/files/lenna_convert2onebit-png_lenna_output.png"
gt_onebit_png_path = "/code/Rodan/rodan/test/files/lenna_convert2onebit-png_one-bit-png_output.png"
gt_greyscale_png_path = "/code/Rodan/rodan/test/files/lenna_convert2onebit-png_greyscale-png_output.png"
gt_grey16_png_path = "/code/Rodan/rodan/test/files/lenna_convert2onebit-png_greyscale-16-png_output.png"
for input_path, out_path, gt_path in zip([input_rgb_png_path, input_onebit_png_path, input_greyscale_png_path, input_grey16_png_path],
[output_rgb_png_path, output_onebit_png_path, output_greyscale_png_path, output_grey16_png_path],
[gt_rgb_png_path, gt_onebit_png_path, gt_greyscale_png_path, gt_grey16_png_path]):
inputs = {
"PNG image": [{"resource_path":input_path}]
}
outputs = {
"One-bit PNG image": [{"resource_path":out_path}]
}
self.run_my_task(inputs=inputs, outputs=outputs, settings={})
# The predicted result and gt result should be identical to each other
# The gt result is from running this job (niblack threshold) on production
gt_output = cv2.imread(gt_path, cv2.IMREAD_UNCHANGED)
pred_output = cv2.imread(out_path, cv2.IMREAD_UNCHANGED)
np.testing.assert_array_equal(gt_output, pred_output, "Failed: {}".format(input_path))