diff --git a/.travis.yml b/.travis.yml index a3ffcfea..16059d0b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/MicaSense Image Processing Setup.ipynb b/MicaSense Image Processing Setup.ipynb index da170b91..bdf8d3ea 100755 --- a/MicaSense Image Processing Setup.ipynb +++ b/MicaSense Image Processing Setup.ipynb @@ -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", diff --git a/data/0002SET/000/IMG_0000_1.tif b/data/0002SET/000/IMG_0000_1.tif new file mode 100644 index 00000000..b2128f10 --- /dev/null +++ b/data/0002SET/000/IMG_0000_1.tif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e0ef38734a94aae45347f96512d1d084d470956e805c0907a8d7aa659d289ce6 +size 2466318 diff --git a/data/0002SET/000/IMG_0000_2.tif b/data/0002SET/000/IMG_0000_2.tif new file mode 100644 index 00000000..421c52e0 --- /dev/null +++ b/data/0002SET/000/IMG_0000_2.tif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa00bc26b2037d67f1de7c4618b9d3ac6fc1bea5e22851dcf13a3cdcdad8a400 +size 2466308 diff --git a/data/0002SET/000/IMG_0000_3.tif b/data/0002SET/000/IMG_0000_3.tif new file mode 100644 index 00000000..b4915698 --- /dev/null +++ b/data/0002SET/000/IMG_0000_3.tif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e41e98bea9994eff7959f4e93c134f0185f8287e2cda1bd19493528de3610b6f +size 2466300 diff --git a/data/0002SET/000/IMG_0000_4.tif b/data/0002SET/000/IMG_0000_4.tif new file mode 100644 index 00000000..0e2702e2 --- /dev/null +++ b/data/0002SET/000/IMG_0000_4.tif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:008b19f85db896219d662eb88434103e8393af09e43fbbe00f0be0fa40e848b8 +size 2466336 diff --git a/data/0002SET/000/IMG_0000_5.tif b/data/0002SET/000/IMG_0000_5.tif new file mode 100644 index 00000000..df248a6b --- /dev/null +++ b/data/0002SET/000/IMG_0000_5.tif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c149e3bc4b0edc16c351c46922aec0aa42c14a820e00de818edf7c9a6252169 +size 2466332 diff --git a/micasense/panel.py b/micasense/panel.py index 150ea982..2c314afe 100644 --- a/micasense/panel.py +++ b/micasense/panel.py @@ -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") @@ -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) @@ -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 @@ -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 @@ -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)) @@ -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]) diff --git a/tests/conftest.py b/tests/conftest.py index ea48195f..c5f297a9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -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): @@ -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): @@ -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): @@ -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): @@ -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') diff --git a/tests/test_capture.py b/tests/test_capture.py index 75a5632e..a08a6a4b 100644 --- a/tests/test_capture.py +++ b/tests/test_capture.py @@ -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() @@ -137,7 +137,7 @@ 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] @@ -145,7 +145,7 @@ def test_panel_irradiance(panel_rededge_capture): 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() diff --git a/tests/test_panel.py b/tests/test_panel.py index 35d69909..ca2d34fa 100644 --- a/tests/test_panel.py +++ b/tests/test_panel.py @@ -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) @@ -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' @@ -102,8 +134,8 @@ 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): @@ -111,8 +143,8 @@ def test_intensity_panel(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): @@ -120,8 +152,8 @@ def test_radiance_panel(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): @@ -129,7 +161,7 @@ def test_irradiance_mean(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)