-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathxrf_opencv.py
More file actions
150 lines (128 loc) · 6.25 KB
/
xrf_opencv.py
File metadata and controls
150 lines (128 loc) · 6.25 KB
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
# LacCore/CSDCO
# xrf.py
#
# Prepare a raw XRF radiograph TIFF by doing the following:
# 1. Rotate image 180 degrees.
# 2. Increase contrast by finding the range of colors in the source image,
# mapping all pixels <= minimum to black and >= maximum to white, and
# interpolating for pixels between min and max.
# 3. Add ruler image below XRF image.
# 4. Save resulting image in TIFF format.
import os
import cv2 # OpenCV
import numpy
from common import create_dirs, get_color_depth, get_component_count, remove_alpha_channel, UnexpectedColorDepthError, RulerTooShortError, UnexpectedComponentCountError
ProgressListener = None
def setProgressListener(pl):
global ProgressListener
ProgressListener = pl
ProgressListener.clear()
def reportProgress(value, text):
global ProgressListener
if ProgressListener:
ProgressListener.setValueAndText(value, text)
class ContrastAdjuster:
def __init__(self, minv, maxv, gamma, pixeldepth):
self.minv = minv
self.maxv = maxv
self._interval = self.maxv - self.minv
self._invgamma = 1.0/gamma
self.pixeldepth = pixeldepth
def new_level(self, value):
if value <= self.minv:
return 0.0
if value >= self.maxv:
return 1.0
return ((value - self.minv) / float(self._interval)) ** self._invgamma
def adjust(self, val):
# print("val = {}".format(val))
return int(self.pixeldepth * self.new_level(val))
# Return min and max pixel value in img.
def get_pixel_range(img):
pmin = None
pmax = None
for row_idx in range(len(img)):
rmin = min(img[row_idx])
rmax = max(img[row_idx])
if pmin is None or rmin < pmin:
pmin = rmin
if pmax is None or rmax > pmax:
pmax = rmax
return pmin, pmax
# Count unique pixels in image. Slow.
def count_unique_pixels(img):
pixs = []
for row_idx in range(len(img)):
for p in img[row_idx]:
if p not in pixs:
pixs.append(p)
return len(pixs)
# returns a copy of img with contrast maximized
def adjust_contrast(img, colorDepth, gamma):
pmin, pmax = get_pixel_range(img)
# print("pixel range = {} to {}".format(pmin, pmax))
# print("unique pixel count = {}".format(count_unique_pixels(img)))
adjuster = ContrastAdjuster(pmin, pmax, gamma, (2 ** colorDepth) - 1)
# vectorize to apply adjuster.adjust() to every pixel in img
adjust_func = numpy.vectorize(adjuster.adjust)
adj_img = adjust_func(img)
return adj_img.astype('uint16') # adjust_func yields int64 array, convert to uint16
def load_ruler_image(rulerPath, colorDepth):
ruler_img = cv2.imread(rulerPath, -1) # -1 to preserve file's color depth
rulerDepth = get_color_depth(ruler_img)
if colorDepth is None:
raise UnexpectedColorDepthError("Ruler image {} has an unrecognized color depth. Only 16-bit and 8-bit are accepted.".format(rulerPath))
rulerComponents = get_component_count(ruler_img)
if rulerComponents in [3,4]: # convert RGB(A) to grayscale
if rulerComponents == 4:
ruler_img = remove_alpha_channel(ruler_img)
ruler_img = cv2.cvtColor(ruler_img, cv2.COLOR_BGR2GRAY)
elif rulerComponents != 1:
raise UnexpectedComponentCountError("Ruler image {} has an unexpected number of color components ({}). Only RGB and grayscale are accepted.".format(rulerPath, rulerComponents))
# 5/10/2019 no need to convert to RGB as we require grayscale input and always output in grayscale
# if rulerComponents == 1: # grayscale
# print("Converting grayscale ruler to RGB")
# ruler_img = grayscale_to_rgb(ruler_img)
if rulerDepth == 8 and colorDepth == 16:
print("Converting 8-bit ruler to 16-bit to match core image")
# ruler_img *= 256 alone doesn't work, must explicitly change array dtype
# from uint8 to uint16 first
ruler_img = ruler_img.astype('uint16')
ruler_img *= 256
elif rulerDepth == 16 and colorDepth == 8:
print("Converting 16-bit ruler to 8-bit to match core image")
ruler8bit_img = ruler_img * 1/256.0 # float for Python 2, where 1/256 = 0
ruler_img = ruler8bit_img.astype('uint8')
return ruler_img
# rotate, adjust contrast, add ruler, save to destDir
def prepare_xrf(imgPath, rulerPath, gamma, outputBaseName, destDir):
baseProgStr = "Processing {}...".format(imgPath)
reportProgress(0, baseProgStr)
radiographDir = 'radiograph'
create_dirs(destDir, [radiographDir])
img = cv2.imread(imgPath, -1)
img = numpy.rot90(img, k=2) # rotate 180 degrees so core top is at image left
imgWidth = img.shape[1]
colorDepth = get_color_depth(img)
component_count = get_component_count(img)
if component_count in [3,4]:
raise UnexpectedComponentCountError("Image {} appears to be {}. Only grayscale images are accepted.".format(imgPath, "RGBA" if component_count == 4 else 'RGB'))
elif component_count != 1:
raise UnexpectedComponentCountError("Image {} has an unexpected number of color components ({}). Only grayscale images are accepted.".format(imgPath, component_count))
ruler_img = load_ruler_image(rulerPath, colorDepth)
rulerWidth = ruler_img.shape[1]
if rulerWidth < imgWidth:
raise RulerTooShortError("Ruler image {} is too short for core image {}".format(rulerPath, imgPath))
reportProgress(25, baseProgStr + "adjusting levels")
adj_img = adjust_contrast(img, colorDepth, gamma)
# print("adjusted image shape = {}, dtype = {}".format(adj_img.shape, adj_img.dtype))
# print("adj_img = {}".format(adj_img))
# Trim end of ruler so its width matches trimmed core image width, then
# add to bottom of core image. Image widths must be the same to stack vertically
# with numpy.concatenate().
ruler_img = numpy.delete(ruler_img, range(imgWidth, rulerWidth), axis=1)
tiff_img = numpy.concatenate((adj_img, ruler_img), axis=0)
cv2.imwrite(os.path.join(destDir, radiographDir, outputBaseName + ".tif"), tiff_img)
if __name__ == "__main__":
for rg in ["DNA-MUB17-1A-2L-1_XR", "DNA-MUB17-1A-3L-1_XR", "DNA-MUB17-1A-4L-1_XR"]:
prepare_xrf("../data/ImageProcessing/XRF Images/{}/radiograph.tif".format(rg), "rulers/Geotek20ppmmRulerGrayscale_XRF.tif", 1.4, "{}_16bit".format(rg), "testdata")