-
Notifications
You must be signed in to change notification settings - Fork 3
/
micasense_calibration.py
284 lines (217 loc) · 9.17 KB
/
micasense_calibration.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
"""
Compute radiance-reflectance relationship for flight
Based on https://github.com/micasense/imageprocessing
Andrew Tedstone ([email protected]), November 2017
"""
import cv2
import matplotlib.pyplot as plt
import numpy as np
import os, glob
import math
import pandas as pd
import datetime as dt
import sys
import configparser
try:
import tkinter as tk
from tkinter import filedialog
except ImportError:
print('WARNING: tkinter not present on your system. You will have to \
supply a file name manually if calling radrefl_factor.')
import micasense.plotutils as plotutils
import micasense.metadata as metadata
import micasense.utils as msutils
# Check location of exiftool
exiftoolPath = None
if os.name == 'nt': # MS Windows
exiftoolPath = 'C:/exiftool/exiftool.exe'
def radrefl_factor(panel_cal, image_name=None, plot_steps=False,
cal_model_fn=None, return_date=False):
""" Compute radiance-reflectance factor for a RedEdge band from panel.
Uses photo of calibrated reflectance panel acquired in band of interest to
calculate a flight-specific radiance-reflectance factor which can be
applied to image collected while in flight.
:param panel_cal: Panel reflectance factors for the panel used in the
image. (use load_panel_factors to open).
:type panel_cal: pd.Series
:param image_name: filepath to the image of the reflectance panel. If None
you will be prompted to choose a file using a pop-up file dialog.
:type image_name: str or None
:param plot_steps: If True, plot panel image at every step of correction.
:type plot_steps: bool
:param cal_model_fn: filename of calibration model parameters for camera
(only for images acquired with RedEdge firmware < 2.1.0).
:type cal_model_fn: str
:param return_date: If True, return a datetime representation of image
acquisition date and time.
:type return_date: bool
:returns: radiance (, return_date)
:rtype: float or tuple
"""
# Load file
if image_name == None:
# Ask for filename of refl image
# https://stackoverflow.com/questions/9319317/quick-and-easy-file-dialog-in-python
root = tk.Tk()
root.withdraw()
image_name = filedialog.askopenfilename()
# Read raw image DN values
# reads 16 bit tif - this will likely not work for 12 bit images
imageRaw = plt.imread(image_name)
# Display the image
if plot_steps:
fig, ax = plt.subplots(figsize=(8,6))
ax.imshow(imageRaw, cmap='gray')
plt.show()
fig = plotutils.plotwithcolorbar(imageRaw, title='Raw image values with colorbar')
# Load metadata
meta = load_metadata(image_name)
cameraMake = meta.get_item('EXIF:Make')
cameraModel = meta.get_item('EXIF:Model')
bandName = meta.get_item('XMP:BandName')
# Prevent processing from continuing if firmware version too low and
# no user-provided calibration data.
if not check_firmware_version(meta):
if cal_model_fn == None:
print('Firmware version < 2.1.0 and no calibration model data provided.')
raise ValueError
# Add calibration data to metadata if required.
if cal_model_fn != None:
print('WARNING: Using user-provided calibration model data.')
meta = add_cal_metadata(meta, cal_model_fn)
else:
print('Using in-camera calibration model data.')
# Correct the image of refl panel
radianceImage, L, V, R = msutils.raw_image_to_radiance(meta, imageRaw)
if plot_steps:
plotutils.plotwithcolorbar(V,'Vignette Factor')
plotutils.plotwithcolorbar(R,'Row Gradient Factor')
plotutils.plotwithcolorbar(V*R,'Combined Corrections')
plotutils.plotwithcolorbar(L,'Vignette and row gradient corrected raw values')
# Display the corrected refl panel image
plotutils.plotwithcolorbar(radianceImage,'All factors applied and scaled to radiance')
# Ask user to select rectangle points of refl panel area
print('Click the top-left then bottom-right corners of the reflectance panel ...')
points = plt.ginput(n=2, show_clicks=True)
plt.close()
# Mark points on image
markedImg = radianceImage.copy()
ulx = int(points[0][0]) # upper left column (x coordinate) of panel area
uly = int(points[0][1]) # upper left row (y coordinate) of panel area
lrx = int(points[1][0]) # lower right column (x coordinate) of panel area
lry = int(points[1][1]) # lower right row (y coordinate) of panel area
if plot_steps:
# Plot rectangle on image
cv2.rectangle(markedImg,(ulx,uly),(lrx,lry),(0,255,0),3)
plotutils.plotwithcolorbar(markedImg, 'Panel region in radiance image')
# Select panel region from radiance image
panelRegion = radianceImage[uly:lry, ulx:lrx]
meanRadiance = panelRegion.mean()
print('Mean Radiance in panel region: {:1.3f} W/m^2/nm/sr'.format(meanRadiance))
panelReflectance = panel_cal.loc[bandName].factor
radianceToReflectance = panelReflectance / meanRadiance
print('Radiance to reflectance conversion factor: {:1.3f}'.format(radianceToReflectance))
# Create reflectance image
reflectanceImage = radianceImage * radianceToReflectance
if plot_steps:
plotutils.plotwithcolorbar(reflectanceImage, 'Converted Reflectance Image')
# Blur the panel to check for trends - we want a consistent reflectance.
panelRegionRefl = reflectanceImage[uly:lry, ulx:lrx]
panelRegionReflBlur = cv2.GaussianBlur(panelRegionRefl,(55,55),5)
plotutils.plotwithcolorbar(panelRegionReflBlur, 'Smoothed panel region in reflectance image')
print('Min Reflectance in panel region: {:1.2f}'.format(panelRegionRefl.min()))
print('Max Reflectance in panel region: {:1.2f}'.format(panelRegionRefl.max()))
print('Mean Reflectance in panel region: {:1.2f}'.format(panelRegionRefl.mean()))
print('Standard deviation in region: {:1.4f}'.format(panelRegionRefl.std()))
if return_date:
# Get time of image acquisition
create_date = dt.datetime.strptime(meta.get_item('EXIF:CreateDate'), '%Y:%m:%d %H:%M:%S')
return radianceToReflectance, create_date
else:
return radianceToReflectance
def load_metadata(image_name):
""" Wrapper to return metadata from exiftool """
meta = metadata.Metadata(image_name, exiftoolPath=exiftoolPath)
return meta
def load_panel_factors(panel_cal_fn):
"""
Load reflectance panel calibration data.
Expects a CSV file with columns 'band_number', 'band_name' and 'factor', and rows named using
the RedEdge camera bands written into image metadata.
:param panel_cal_fn: File path and name of CSV file to load.
:type panel_cal_fn: str
:returns: Series representing CSV file.
:rtype: pd.Series
"""
panel_cal = pd.read_csv(panel_cal_fn, index_col='band_name').squeeze()
panel_cal.name = panel_cal_fn
return panel_cal
def check_firmware_version(meta, return_values=False, verbose=False):
""" Check RedEdge firmware version - returns True if >= 2.1.0.
:param meta: metadata for image to check
:type meta: metadata.Metadata
:param return_values: If True, return major/minor/point values of firmware
version as a list in location [1] of returned tuple.
:type return_values: bool
:param verbose: If True print firmware version
:type verbose: bool
:returns: True if >= 2.1.0, otherwise False. If return_values=True then
returns tuple (True/False, firmware_version)
:rtype: bool, tuple
"""
firmwareVersion = meta.get_item('EXIF:Software')
if verbose:
print('{0} {1} firmware version: {2}'.format(cameraMake,
cameraModel,
firmwareVersion))
# Extract major [0], minor [1] and point [2] versions
ver = [i for i in firmwareVersion[1:].split('.')]
if (int(ver[0]) <= 2) and (int(ver[1]) < 1):
val = False
else:
val = True
if return_values:
return (val, ver)
else:
return val
def add_cal_metadata(image_meta, camera_model_fn):
""" Add Camera Radiometric Calibration Model to images without it.
Adds relevant model parameters to the in-memory representation of an
image's EXIF metadata, so that it can be used by micasense toolkit.
:param image_meta: a metadata object to add parameters to.
:type image_meta: metadata.Metadata
:param camera_model_fn: filename of configuration file containing parameters
which correspond to those for the camera which acquired the image.
:type camera:model_fn: str
:returns: metadata object with parameters added.
:rtype: metadata.Metadata
"""
params = configparser.ConfigParser()
params.optionxform = str
params.read_file(open(camera_model_fn))
for item in params.items('Model'):
split_items = item[1].split(',')
if len(split_items) > 1:
param = [float(i.strip()) for i in split_items]
else:
param = item[1]
image_meta.exif['XMP:'+item[0]] = param
params = None
return image_meta
def calibrate_correct_image(raw_image, meta, rad2refl_factor):
""" Calibrate and correct a raw image to reflectance.
This function converts raw values to radiance, then applies radiance
to reflectance conversion factor, and finally corrects for lens distortion.
:param raw_image: the raw image to correct
:type raw_image: np.array
:param meta: metadata corresponding with image to correct
:type meta: metadata.Metadata
:param rad2refl_factor: conversion/scaling factor radiance->reflectance
:type rad2refl_factor: float
:returns: corrected image
:rtype: np.array
"""
fl_im_rad, _, _, _ = msutils.raw_image_to_radiance(meta, raw_image)
fl_im_refl = fl_im_rad * rad2refl_factor
#fl_im_refl_cor = msutils.correct_lens_distortion(meta, fl_im_refl)
return fl_im_refl