# ISC License
#
# Copyright (c) 2016, Autonomous Vehicle Systems Lab, University of Colorado at Boulder
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
# Unit Test Script
# Module Name: Camera
# Author: Lucas Webb and Hanspeter Schaub
# Creation Date: April 30, 2020
import colorsys
import inspect
import math
import os
import numpy as np
import pytest
filename = inspect.getframeinfo(inspect.currentframe()).filename
path = os.path.dirname(os.path.abspath(filename))
bskName = 'Basilisk'
splitPath = path.split(bskName)
# Import all of the modules that we are going to be called in this simulation
importErr = False
reasonErr = ""
try:
from PIL import Image, ImageDraw
except ImportError:
importErr = True
reasonErr = "python Pillow package not installed---can't test Cameras module"
# Import all of the modules that we are going to be called in this simulation
from Basilisk.architecture import messaging
from Basilisk.utilities import macros
from Basilisk.utilities import SimulationBaseClass
try:
from Basilisk.simulation import camera
except ImportError:
importErr = True
reasonErr += "\nCamera not built---check OpenCV option"
# Uncomment this line is this test is to be skipped in the global unit test run, adjust message as needed.
# @pytest.mark.skipif(conditionstring)
# Uncomment this line if this test has an expected failure, adjust message as needed.
# @pytest.mark.xfail(conditionstring)
# Provide a unique test method name, starting with 'test_'.
[docs]
@pytest.mark.skipif(importErr, reason= reasonErr)
@pytest.mark.parametrize("HSV", [
[0, 0, 0]
, [1.0, 20.0, -30.0]
, [-1.0, 20.0, -30.0]
, [3.14159, 100, -100]
])
@pytest.mark.parametrize("BGR", [
[0, 0, 0]
, [10, 20, 30]
, [-10, -30, 50]
, [-100, 200, 20]
])
# update "module" in this function name to reflect the module name
def test_module(show_plots, HSV, BGR):
"""
**Validation Test Description**
This module tests the color shifting capability of the camera module. Multiple HSV and BGR color
adjustments are tests on a TV test image.
**Description of Variables Being Tested**
Multiple points on the test images are adjusted in python and compared to the BSK camera module
saved image. The HSV and BGR color corrections are applied on a series of points in the test image.
The integer red, green and blue color values are checked to be identical.
"""
# each test method requires a single assert method to be called
image = "tv_test.png"
[testResults, testMessage] = cameraColorTest(image, HSV, BGR)
# Clean up
imagePath = path + '/' + image
savedImage = '/'.join(imagePath.split('/')[:-1]) + '/hsv' + str(HSV) + 'bgr' + str(BGR) + '0.000000.png'
try:
os.remove(savedImage)
except FileNotFoundError:
pass
assert testResults < 1, testMessage
[docs]
def cameraColorTest(image, HSV, BGR):
"""
Test method to apply the HSV and BGR image adjustments.
:param image: image name to load from local folder
:param HSV: 3d vector of HSV adjustments
:param BGR: 3d vector of BGR adjustments
"""
if importErr:
print(reasonErr)
exit()
# Truth values from python
imagePath = path + '/' + image
input_image = Image.open(imagePath)
input_image.load()
#################################################
testFailCount = 0 # zero unit test result counter
testMessages = [] # create empty array to store test log messages
unitTaskName = "unitTask" # arbitrary name (don't change)
unitProcessName = "TestProcess" # arbitrary name (don't change)
# Create a sim module as an empty container
unitTestSim = SimulationBaseClass.SimBaseClass()
# Create test thread
testProcessRate = macros.sec2nano(0.5) # update process rate update time
testProc = unitTestSim.CreateNewProcess(unitProcessName)
testProc.addTask(unitTestSim.CreateNewTask(unitTaskName, testProcessRate))
# Construct algorithm and associated C++ container
module = camera.Camera()
module.ModelTag = "cameras"
# Add test module to runtime call list
unitTestSim.AddModelToTask(unitTaskName, module)
module.filename = imagePath
module.saveImages = True
# make each image saved have a unique name for this test case
module.saveDir = '/'.join(imagePath.split('/')[:-1]) + '/hsv' + str(HSV) + 'bgr' + str(BGR)
# Create input message and size it because the regular creator of that message
# is not part of the test.
inputMessageData = messaging.CameraImageMsgPayload()
inputMessageData.timeTag = int(1E9)
inputMessageData.cameraID = 1
inCamMsg = messaging.CameraImageMsg().write(inputMessageData)
module.imageInMsg.subscribeTo(inCamMsg)
module.cameraIsOn = 1
module.sigma_CB = [0, 0, 1]
# Noise parameters
module.bgrPercent = np.array(BGR)
module.hsv = np.array(HSV)
# Setup logging on the test module output message so that we get all the writes to it
dataLog = module.cameraConfigOutMsg.recorder()
unitTestSim.AddModelToTask(unitTaskName, dataLog)
unitTestSim.InitializeSimulation()
unitTestSim.TotalSim.SingleStepProcesses()
corruptedPath = module.saveDir + '0.000000.png'
# print out error message if test failed
if not trueColorAdjust(imagePath, corruptedPath, HSV, BGR):
testFailCount += 1
testMessages.append("Test failed color adjustment " + image)
# each test method requires a single assert method to be called
# this check below just makes sure no sub-test failures were found
return [testFailCount, ''.join(testMessages)]
def rgb_to_hsv(rgb):
hsv = colorsys.rgb_to_hsv(rgb[0], rgb[1], rgb[2])
return [hsv[0] * 180., hsv[1] * 255., hsv[2]]
def hsv_to_rgb(hsv):
rgb = colorsys.hsv_to_rgb(hsv[0]/180., hsv[1]/255., hsv[2]/255.)
return [rgb[0] * 255, rgb[1] * 255, rgb[2] * 255]
def trueColorAdjust(image, corrupted, HSV, BGR):
input_rgb = Image.open(image).load()
output = Image.open(corrupted).load()
# these points correspond to the included 'tv_test.png'
testPoints = [(100, 300), (250, 300), (450, 300), (600, 300), (700, 300), (950, 300), (1100, 300), (300, 800),
(880, 780)]
for point in testPoints:
px = point[0]
py = point[1]
# do HSV adjustment
hsv = rgb_to_hsv(input_rgb[px, py])
expected = [0, 0, 0]
input_degrees = math.degrees(HSV[0])
h_360 = (hsv[0] * 2) + input_degrees
h_360 -= 360. * math.floor(h_360 * (1. / 360.))
h_360 = int(h_360 / 2)
if h_360 == 180:
h_360 = 0
expected[0] = int(h_360)
for i in range(2):
expected[i+1] = int(hsv[i+1] * (HSV[i+1]/100. + 1.))
if expected[i+1] < 0:
expected[i+1] = 0
if expected[i+1] > 255:
expected[i+1] = 255
expectedAfterHSV = hsv_to_rgb(expected)
expectedAfterHSV = [int(i) for i in expectedAfterHSV]
# do BGR adjustment
for i in range(3):
expected[i] = int((BGR[2 - i] / 100. + 1.) * expectedAfterHSV[i])
if expected[i] > 255:
expected[i] = 255
if expected[i] < 0:
expected[i] = 0
for i in range(3):
if abs(int(output[px, py][i]) - expected[i]) > 3:
print("Failed HSV at point: px=" + str(px) + " py= + " + str(py))
return False
print("Passed Color Check")
return True
#
# This statement below ensures that the unitTestScript can be run as a
# stand-along python script
#
if __name__ == "__main__":
hsvAdjust = [1.0, 20.0, -30.0]
bgrAdjust = [-100, 0, 0]
cameraColorTest("tv_test.png", hsvAdjust, bgrAdjust)