Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ before_install:
# install library dependencies
- sudo apt-get install libzbar0 make perl
# Install exiftool
- wget https://exiftool.org/Image-ExifTool-12.01.tar.gz
- tar -xzf Image-ExifTool-12.01.tar.gz
- pushd Image-ExifTool-12.01/
- wget https://cpan.metacpan.org/authors/id/E/EX/EXIFTOOL/Image-ExifTool-12.15.tar.gz
- tar -xzf Image-ExifTool-12.15.tar.gz
- pushd Image-ExifTool-12.15/
- perl Makefile.PL
- make test
- sudo make install
Expand Down
6 changes: 3 additions & 3 deletions MicaSense Image Processing Setup.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@
" \n",
"Next we installed [exiftool](https://exiftool.org/):\n",
"\n",
" wget https://exiftool.org/Image-ExifTool-10.98.tar.gz\n",
" tar -xvzf Image-ExifTool-10.98.tar.gz \n",
" cd Image-ExifTool-10.98/\n",
" wget https://cpan.metacpan.org/authors/id/E/EX/EXIFTOOL/Image-ExifTool-12.15.tar.gz\n",
" tar -xvzf Image-ExifTool-12.15.tar.gz \n",
" cd Image-ExifTool-12.15/\n",
" perl Makefile.PL \n",
" make test\n",
" sudo make install\n",
Expand Down
3 changes: 3 additions & 0 deletions data/0002SET/000/IMG_0000_1.tif
Git LFS file not shown
3 changes: 3 additions & 0 deletions data/0002SET/000/IMG_0000_2.tif
Git LFS file not shown
3 changes: 3 additions & 0 deletions data/0002SET/000/IMG_0000_3.tif
Git LFS file not shown
3 changes: 3 additions & 0 deletions data/0002SET/000/IMG_0000_4.tif
Git LFS file not shown
3 changes: 3 additions & 0 deletions data/0002SET/000/IMG_0000_5.tif
Git LFS file not shown
67 changes: 49 additions & 18 deletions micasense/panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@

class Panel(object):

def __init__(self, img,panelCorners=None):
def __init__(self, img,panelCorners=None,ignore_autocalibration=False):
# if we have panel images with QR metadata, panel detection is not called,
# so this can be forced here
if img is None:
raise IOError("Must provide an image")

Expand All @@ -45,7 +47,7 @@ def __init__(self, img,panelCorners=None):
self.gray8b = np.zeros(img.radiance().shape, dtype='uint8')
cv2.convertScaleAbs(img.undistorted(img.radiance()), self.gray8b, 256.0/scale, -1.0*scale*bias)

if self.image.auto_calibration_image:
if (self.image.auto_calibration_image) and ~ignore_autocalibration:
self.__panel_type = "auto" ## panels the camera found we call auto
if panelCorners is not None:
self.__panel_bounds = np.array(panelCorners)
Expand All @@ -59,7 +61,9 @@ def __init__(self, img,panelCorners=None):
self.saturated_panel_pixels_pct = None
self.panel_pixels_mean = None
self.panel_version = None

if re.search(r'RP\d{2}-(\d{7})-\D{2}', self.image.panel_serial):
self.serial = self.image.panel_serial
self.panel_version = int(self.image.panel_serial[2:4])
else:
self.__panel_type = "search" ## panels we search for we call search
self.serial = None
Expand Down Expand Up @@ -157,25 +161,52 @@ def panel_corners(self):
return None

if self.panel_version < 3:
reference_panel_pts = np.asarray([[894, 469], [868, 232], [630, 258], [656, 496]],
dtype=np.int32)
reference_qr_pts = np.asarray([[898, 748], [880, 567], [701, 584], [718, 762]],
dtype=np.int32)
elif self.panel_version >= 3:
reference_panel_pts = np.asarray([[557, 350], [550, 480], [695, 480], [700, 350]], dtype=np.int32)
reference_qr_pts = np.asarray([[821, 324], [819, 506], [996, 509], [999, 330]], dtype=np.int32)

# reference_panel_pts = np.asarray([[894, 469], [868, 232], [630, 258], [656, 496]],
# dtype=np.int32)
# reference_qr_pts = np.asarray([[898, 748], [880, 567], [701, 584], [718, 762]],
# dtype=np.int32)

# use the actual panel measures here - we use units of [mm]
# the panel is 154.4 x 152.4 mm , vs. the 84 x 84 mm for the QR code
# it is left 143.20 mm from the QR code
# use the inner 50% square of the panel
s = 76.2
p = 42
T = np.array([-143.2,0])

elif (self.panel_version >= 3) and (self.panel_version<6):
s = 50
p = 45
T = np.array([-145.8,0])
# reference_panel_pts = np.asarray([[557, 350], [550, 480], [695, 480], [700, 350]], dtype=np.int32)
# reference_qr_pts = np.asarray([[821, 324], [819, 506], [996, 509], [999, 330]], dtype=np.int32)
elif self.panel_version >= 6 :
# use the actual panel measures here - we use units of [mm]
# the panel is 100 x 100 mm , vs. the 91 x 91 mm for the QR code
# it is down 125.94 mm from the QR code
# use the inner 50% square of the panel
p = 41
s = 50
T = np.array([0,-130.84])


reference_panel_pts = np.asarray([[-s, s], [s, s], [s, -s], [-s, -s]], dtype=np.float32)*.5+T
reference_qr_pts = np.asarray([[-p, p], [p, p], [p, -p], [-p, -p]], dtype=np.float32)
bounds = []
costs = []
for rotation in range(0,4):
qr_points = np.roll(reference_qr_pts, rotation, axis=0)

src = np.asarray([tuple(row) for row in qr_points[:3]], np.float32)
dst = np.asarray([tuple(row) for row in self.qr_corners()[:3]], np.float32)
warp_matrix = cv2.getAffineTransform(src, dst)
src = np.asarray([tuple(row) for row in qr_points[:]], np.float32)
dst = np.asarray([tuple(row) for row in self.qr_corners()[:]], np.float32)

# we determine the homography from the 4 corner points
warp_matrix = cv2.getPerspectiveTransform(src,dst)

#warp_matrix = cv2.getAffineTransform(src, dst)

pts = np.asarray([reference_panel_pts], 'int32')
panel_bounds = cv2.convexHull(cv2.transform(pts, warp_matrix), clockwise=False)
pts = np.asarray([reference_panel_pts], 'float32')
panel_bounds = cv2.convexHull(cv2.perspectiveTransform(pts, warp_matrix), clockwise=False)
panel_bounds = np.squeeze(panel_bounds) # remove nested lists

bounds_in_image = True
Expand All @@ -184,7 +215,7 @@ def panel_corners(self):
bounds_in_image = False
if bounds_in_image:
mean, std, _, _ = self.region_stats(self.image.raw(),panel_bounds, sat_threshold=65000)
bounds.append(panel_bounds)
bounds.append(panel_bounds.astype(np.int32))
costs.append(std/mean)

idx = costs.index(min(costs))
Expand All @@ -195,7 +226,7 @@ def ordered_panel_coordinates(self):
"""
Return panel region coordinates in a predictable order. Panel region coordinates that are automatically
detected by the camera are ordered differently than coordinates detected by Panel.panel_corners().
:return: [ (lr), (ll), (ul), (ur) ] to mirror Image.panel_region attribute order
:return: [ (ur), (ul), (ll), (lr) ] to mirror Image.panel_region attribute order
"""
pc = self.panel_corners()
pc = sorted(pc, key=lambda x: x[0])
Expand Down
23 changes: 17 additions & 6 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ def ten_band_files_dir():

@pytest.fixture()
def panel_rededge_file_list(files_dir):
return glob.glob(os.path.join(files_dir, 'IMG_0000_*.tif'))
return sorted(glob.glob(os.path.join(files_dir, 'IMG_0000_*.tif')))

@pytest.fixture()
def non_panel_rededge_file_list(files_dir):
return glob.glob(os.path.join(files_dir, 'IMG_0001_*.tif'))
return sorted(glob.glob(os.path.join(files_dir, 'IMG_0001_*.tif')))

@pytest.fixture()
def bad_file_list(files_dir):
Expand All @@ -58,7 +58,7 @@ def bad_file_list(files_dir):

@pytest.fixture()
def panel_altum_file_list(altum_files_dir):
return glob.glob(os.path.join(altum_files_dir, 'IMG_0000_*.tif'))
return sorted(glob.glob(os.path.join(altum_files_dir, 'IMG_0000_*.tif')))

@pytest.fixture()
def panel_rededge_capture(panel_rededge_file_list):
Expand All @@ -70,11 +70,11 @@ def non_panel_rededge_capture(non_panel_rededge_file_list):

@pytest.fixture()
def panel_10band_rededge_file_list(ten_band_files_dir):
return glob.glob(os.path.join(ten_band_files_dir, 'IMG_0000_*.tif'))
return sorted(glob.glob(os.path.join(ten_band_files_dir, 'IMG_0000_*.tif')))

@pytest.fixture()
def flight_10band_rededge_file_list(ten_band_files_dir):
return glob.glob(os.path.join(ten_band_files_dir, 'IMG_0431_*.tif'))
return sorted(glob.glob(os.path.join(ten_band_files_dir, 'IMG_0431_*.tif')))

@pytest.fixture()
def panel_altum_capture(panel_altum_file_list):
Expand All @@ -83,7 +83,7 @@ def panel_altum_capture(panel_altum_file_list):

@pytest.fixture()
def non_panel_altum_file_list(altum_files_dir):
return glob.glob(os.path.join(altum_files_dir, 'IMG_0008_*.tif'))
return sorted(glob.glob(os.path.join(altum_files_dir, 'IMG_0008_*.tif')))

@pytest.fixture()
def non_panel_altum_capture(non_panel_altum_file_list):
Expand All @@ -101,6 +101,17 @@ def panel_image_name_red():
image_path = os.path.join('data', '0000SET', '000')
return os.path.join(image_path, 'IMG_0000_2.tif')

@pytest.fixture()
def panel_image_name_RP06_blue():
image_path = os.path.join('data', '0002SET', '000')
return os.path.join(image_path, 'IMG_0000_1.tif')

@pytest.fixture()
def panel_images_RP06():
image_path = os.path.join('data', '0002SET', '000')
return sorted(glob.glob(os.path.join(image_path, 'IMG*.tif')))


@pytest.fixture()
def flight_image_name():
image_path = os.path.join('data', '0000SET', '000')
Expand Down
6 changes: 3 additions & 3 deletions tests/test_capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def test_panel_radiance(panel_rededge_capture):
0.13081077851565506]
assert len(rad) == len(expected_rad)
for i,_ in enumerate(expected_rad):
assert rad[i] == pytest.approx(expected_rad[i], rel=0.001)
assert rad[i] == pytest.approx(expected_rad[i], rel=0.01)

def test_panel_raw(panel_rededge_capture):
raw = panel_rededge_capture.panel_raw()
Expand All @@ -137,15 +137,15 @@ def test_panel_raw(panel_rededge_capture):
54479.170371812339]
assert len(raw) == len(expected_raw)
for i,_ in enumerate(expected_raw):
assert raw[i] == pytest.approx(expected_raw[i], rel=0.001)
assert raw[i] == pytest.approx(expected_raw[i], rel=0.01)

def test_panel_irradiance(panel_rededge_capture):
panel_reflectance_by_band = [0.67, 0.69, 0.68, 0.61, 0.67]
rad = panel_rededge_capture.panel_irradiance(panel_reflectance_by_band)
expected_rad = [0.79845135523772681, 0.81681533164998943, 0.74944205649335915, 0.54833776619262586, 0.61336444894797537]
assert len(rad) == len(expected_rad)
for i,_ in enumerate(expected_rad):
assert rad[i] == pytest.approx(expected_rad[i], rel=0.001)
assert rad[i] == pytest.approx(expected_rad[i], rel=0.01)

def test_panel_albedo_not_preset(panel_rededge_capture):
assert panel_rededge_capture.panels_in_all_expected_images()
Expand Down
48 changes: 40 additions & 8 deletions tests/test_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,37 @@
import micasense.panel as panel
import operator

def test_RP06_panel_ID(panel_image_name_RP06_blue):
img = image.Image(panel_image_name_RP06_blue)
pan = panel.Panel(img)
qr_corners = pan.qr_corners()
assert pan.panel_version == 6

def test_RP06_panel_ID_autodetect(panel_image_name_RP06_blue):
img = image.Image(panel_image_name_RP06_blue)
pan = panel.Panel(img,ignore_autocalibration=True)
qr_corners = pan.qr_corners()

assert pan.panel_version == 6
def test_RP06_panel_raw(panel_images_RP06):
test_mean = [33082,34347,33971,34186,33371]
test_std = [474.7,582.6,476.3,464,658.9]
test_num = [3616,3552,3669,3612,3729]
test_sat = [0,0,0,0,0]
for i,m,s,n,sa in zip(panel_images_RP06,test_mean,test_std,test_num,test_sat):
img = image.Image(i)
pan = panel.Panel(img)
mean, std, num, sat = pan.raw()
assert pan.panel_detected()
print('mean {:f} std {:f} num {:f} sat {:f}'.format(mean,std,num,sat))
print('m {:f} s {:f} n {:f} sa {:f}'.format(m,s,n,sa))
assert mean == pytest.approx(m,rel=0.1)
assert std == pytest.approx(s,rel=0.1)
assert num == pytest.approx(n,rel=0.1)
assert sat == pytest.approx(sa,rel=0.1)



def test_qr_corners(panel_image_name):
img = image.Image(panel_image_name)
pan = panel.Panel(img)
Expand All @@ -47,7 +78,8 @@ def test_panel_corners(panel_image_name):
img = image.Image(panel_image_name)
pan = panel.Panel(img)
panel_pts = pan.panel_corners()
good_pts = [[809, 613], [648, 615], [646, 454], [808, 452]]
good_pts = [[785,594],[674,593],[673,483],[783,484]]

assert panel_pts is not None
assert len(panel_pts) == len(good_pts)
assert pan.serial == 'RP02-1603036-SC'
Expand Down Expand Up @@ -102,34 +134,34 @@ def test_raw_panel(panel_image_name):
pan = panel.Panel(img)
mean, std, num, sat = pan.raw()
assert mean == pytest.approx(45406.0, rel=0.01)
assert std == pytest.approx(738.0, rel=0.05)
assert num == pytest.approx(26005, rel=0.02)
assert std == pytest.approx(689.0, rel=0.05)
assert num == pytest.approx(12154, rel=0.02)
assert sat == pytest.approx(0, abs=2)

def test_intensity_panel(panel_image_name):
img = image.Image(panel_image_name)
pan = panel.Panel(img)
mean, std, num, sat = pan.intensity()
assert mean == pytest.approx(1162, rel=0.01)
assert std == pytest.approx(23, rel=0.03)
assert num == pytest.approx(26005, rel=0.02)
assert std == pytest.approx(20, rel=0.03)
assert num == pytest.approx(12154, rel=0.02)
assert sat == pytest.approx(0, abs=2)

def test_radiance_panel(panel_image_name):
img = image.Image(panel_image_name)
pan = panel.Panel(img)
mean, std, num, sat = pan.radiance()
assert mean == pytest.approx(0.170284, rel=0.01)
assert std == pytest.approx(0.0033872969661854742, rel=0.02)
assert num == pytest.approx(26005, rel=0.02)
assert std == pytest.approx(0.0029387953691472554, rel=0.02)
assert num == pytest.approx(12154, rel=0.02)
assert sat == pytest.approx(0, abs=2)

def test_irradiance_mean(panel_image_name):
img = image.Image(panel_image_name)
pan = panel.Panel(img)
panel_reflectance = 0.67
mean = pan.irradiance_mean(panel_reflectance)
assert mean == pytest.approx(0.7984, rel=0.001)
assert mean == pytest.approx(0.7984, rel=0.01)

def test_panel_detected(panel_image_name):
img = image.Image(panel_image_name)
Expand Down