Skip to content

Commit

Permalink
add behave tests
Browse files Browse the repository at this point in the history
(commit edited and squashed to exclude binary files)

change path

change satellite_data path to mounted ext_data

change results path to mounted ext_data

timezone correction

Delete large images
  • Loading branch information
KatrinPoepp authored and gerritholl committed Nov 19, 2024
1 parent a19bf71 commit 72d04ce
Show file tree
Hide file tree
Showing 7 changed files with 370 additions and 0 deletions.
28 changes: 28 additions & 0 deletions satpy/tests/behave/create_reference.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from dask.diagnostics import ProgressBar
from satpy import Scene
from glob import glob
import os
import warnings
import dask

os.environ['OMP_NUM_THREADS'] = os.environ['MKL_NUM_THREADS'] = '2'
os.environ['PYTROLL_CHUNK_SIZE'] = '1024'
warnings.simplefilter('ignore')
dask.config.set(scheduler='threads', num_workers=4)

# Get the list of satellite files to open
satellite = "GOES16"
filenames = glob(f'./satellite_data/{satellite}/*.nc')

scn = Scene(reader='abi_l1b', filenames=filenames)

# what composites Satpy knows how to make and that it has the inputs for?
print(scn.available_composite_names())

composite = 'airmass'
scn.load([composite])
with ProgressBar():
scn.save_datasets(writer='simple_image', filename=f'./features/data/reference/reference_image_{satellite}_{composite}.png')



106 changes: 106 additions & 0 deletions satpy/tests/behave/download_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#!/usr/bin/env python
"""Download test data and ancillary data for running this tutorial."""

import os
import math
import requests
from zipfile import ZipFile
from tqdm import tqdm

TUTORIAL_ROOT = os.path.dirname(os.path.abspath(__file__))


def download_pyspectral_luts():
print("Downloading lookup tables used by pyspectral...")
from pyspectral.utils import download_luts, download_rsr
download_luts()
download_rsr()
return True


def _download_data_zip(url, output_filename):
if os.path.isfile(output_filename):
print("Data zip file already exists, won't re-download: {}".format(output_filename))
return True

print("Downloading {}".format(url))
r = requests.get(url, stream=True)

# Total size in bytes.
total_size = int(r.headers.get('content-length', 0))
block_size = 1024
wrote = 0
with open(output_filename, 'wb') as f:
for data in tqdm(r.iter_content(block_size), total=math.ceil(total_size//block_size), unit='KB', unit_scale=True):
wrote += len(data)
f.write(data)
if total_size != 0 and wrote != total_size:
print("ERROR: something went wrong downloading {}".format(url))
return False
return True


def _unzip(filename, output_dir):
print("Extracting {}".format(filename))
try:
with ZipFile(filename, 'r') as zip_obj:
zip_obj.extractall(output_dir)
except (IOError, OSError):
print("FAIL: Could not extract {}".format(filename))
return False
return True


def _download_and_unzip(url, output_dir):
filename = os.path.basename(url)
if _download_data_zip(url, filename):
return _unzip(filename, output_dir)
return False


def download_test_data():
cwd = os.getcwd()
os.chdir(TUTORIAL_ROOT)

ret = _download_and_unzip(
'https://bin.ssec.wisc.edu/pub/davidh/20180511_texas_fire_abi_l1b_conus.zip',
os.path.join('data', 'abi_l1b')
)
ret &= _download_and_unzip(
'https://bin.ssec.wisc.edu/pub/davidh/20180511_texas_fire_abi_l1b_meso.zip',
os.path.join('data', 'abi_l1b')
)
ret &= _download_and_unzip(
'https://bin.ssec.wisc.edu/pub/davidh/20180511_texas_fire_viirs_sdr.zip',
os.path.join('data', 'viirs_sdr')
)
os.chdir(cwd)
return ret


def main():
import argparse
parser = argparse.ArgumentParser(description="Download data necessary for the Satpy tutorial")
parser.add_argument('--luts-only', action='store_true',
help="Only download LUTs for pyspectral operation")
parser.add_argument('--data-only', action='store_true',
help="Only download test data")
args = parser.parse_args()

ret = True
if not args.data_only:
ret &= download_pyspectral_luts()
if not args.luts_only:
ret &= download_test_data()

if ret:
print("Downloaded `.zip` files can now be deleted.")
print("SUCCESS")
else:
print("FAIL")
return int(not ret)


if __name__ == "__main__":
import sys
sys.exit(main())
11 changes: 11 additions & 0 deletions satpy/tests/behave/features/image_comparison.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Feature: Image Comparison

Scenario Outline: Compare generated image with reference image
Given I have a <composite> reference image file from <satellite>
When I generate a new <composite> image file from <satellite>
Then the generated image should be the same as the reference image

Examples:
|satellite |composite |
|GOES17 |airmass |
|GOES16 |airmass |
101 changes: 101 additions & 0 deletions satpy/tests/behave/features/steps/image_comparison.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import os
import warnings
from glob import glob
from PIL import Image
import cv2
import dask
import numpy as np
from behave import given, when, then
from satpy import Scene
from datetime import datetime
import pytz

ext_data_path = "/app/ext_data"

# Define a before_all hook to create the timestamp and test results directory
def before_all(context):
berlin_time = datetime.now(pytz.timezone('Europe/Berlin'))
context.timestamp = berlin_time.strftime("%Y-%m-%d-%H-%M-%S")
context.test_results_dir = f"{ext_data_path}/test_results/image_comparison/{context.timestamp}"
os.makedirs(os.path.join(context.test_results_dir, 'generated'), exist_ok=True)
os.makedirs(os.path.join(context.test_results_dir, 'difference'), exist_ok=True)

# Write the timestamp to test_results.txt
results_file = os.path.join(context.test_results_dir, 'test_results.txt')
with open(results_file, 'a') as f:
f.write(f"Test executed at {context.timestamp}.\n\n")

# Register the before_all hook
def setup_hooks():
from behave import use_fixture
from behave.runner import Context

use_fixture(before_all, Context)

setup_hooks()
@given('I have a {composite} reference image file from {satellite}')
def step_given_reference_image(context, composite, satellite):
reference_image = f"reference_image_{satellite}_{composite}.png"
context.reference_image = cv2.imread(f"./features/data/reference/{reference_image}")
context.reference_different_image = cv2.imread(f"./features/data/reference_different/{reference_image}")
context.satellite = satellite
context.composite = composite


@when('I generate a new {composite} image file from {satellite}')
def step_when_generate_image(context, composite, satellite):
os.environ['OMP_NUM_THREADS'] = os.environ['MKL_NUM_THREADS'] = '2'
os.environ['PYTROLL_CHUNK_SIZE'] = '1024'
warnings.simplefilter('ignore')
dask.config.set(scheduler='threads', num_workers=4)

# Get the list of satellite files to open
filenames = glob(f'{ext_data_path}/satellite_data/{satellite}/*.nc')

scn = Scene(reader='abi_l1b', filenames=filenames)

scn.load([composite])

# Save the generated image in the generated folder
generated_image_path = os.path.join(context.test_results_dir, 'generated',
f'generated_{context.satellite}_{context.composite}.png')
scn.save_datasets(writer='simple_image', filename=generated_image_path)

# Save the generated image in the context
context.generated_image = cv2.imread(generated_image_path)


@then('the generated image should be the same as the reference image')
def step_then_compare_images(context):
threshold = 2000
# Load the images
imageA = cv2.cvtColor(context.reference_different_image, cv2.COLOR_BGR2GRAY)
imageB = cv2.cvtColor(context.generated_image, cv2.COLOR_BGR2GRAY)
# Ensure both images have the same dimensions
if imageA.shape != imageB.shape:
raise ValueError("Both images must have the same dimensions")
array1 = np.array(imageA)
array2 = np.array(imageB)
# Perform pixel-wise comparison
result_matrix = (array1 != array2).astype(np.uint8) * 255

# Save the resulting numpy array as an image in the difference folder
diff_image_path = os.path.join(context.test_results_dir, 'difference',
f'diff_{context.satellite}_{context.composite}.png')
cv2.imwrite(diff_image_path, result_matrix)

# Count non-zero pixels in the result matrix
non_zero_count = np.count_nonzero(result_matrix)

# Write the results to a file in the test results directory
results_file = os.path.join(context.test_results_dir, 'test_results.txt')
with open(results_file, 'a') as f:
f.write(f"Test for {context.satellite} - {context.composite}\n")
f.write(f"Non-zero pixel differences: {non_zero_count}\n")
if non_zero_count < threshold:
f.write(f"Result: Passed - {non_zero_count} pixel differences.\n\n")
else:
f.write(f"Result: Failed - {non_zero_count} pixel differences exceed the threshold of {threshold}.\n\n")

# Assert that the number of differences is below the threshold
assert non_zero_count < threshold, f"Images are not similar enough. {non_zero_count} pixel differences exceed the threshold of {threshold}."
27 changes: 27 additions & 0 deletions satpy/tests/behave/modify_image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from PIL import Image, ImageDraw, ImageFont


def add_text_to_image(input_path, output_path, text, position=(800, 2200), font_size=700, font_color=(255, 255, 255)):
# Open the image
image = Image.open(input_path)

# Create a drawing object
draw = ImageDraw.Draw(image)

# Load a font
font = ImageFont.load_default()

# Specify font size and color
font = ImageFont.truetype("arial.ttf", font_size)
draw.text(position, text, font=font, fill=font_color)

# Save the modified image
image.save(output_path)

Check warning on line 19 in satpy/tests/behave/modify_image.py

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (main)

❌ New issue: Excess Number of Function Arguments

add_text_to_image has 6 arguments, threshold = 4. This function has too many arguments, indicating a lack of encapsulation. Avoid adding more arguments.


# Example usage
input_image_path = 'C:/Users/sennlaub/IdeaProjects/DWD_Pytroll/img/reference.png'
output_image_path = 'C:/Users/sennlaub/IdeaProjects/DWD_Pytroll/img/reference_different.png'
text_to_add = 'Hello, World!'

add_text_to_image(input_image_path, output_image_path, text_to_add)
89 changes: 89 additions & 0 deletions satpy/tests/behave/test_install.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/usr/bin/env python
"""Test that the installation steps for this tutorial were successful.
1. Check that Satpy features are available and all dependencies are importable.
2. Check that data has been downloaded.
"""

import io
import os
from contextlib import redirect_stdout

try:
from satpy.utils import check_satpy
except ImportError:
print("FAIL: Satpy is not importable")
raise


TUTORIAL_ROOT = os.path.dirname(os.path.abspath(__file__))


def check_satpy_features():
print("Checking Satpy features...\n")
readers = ['abi_l1b', 'viirs_sdr']
writers = ['cf', 'geotiff', 'simple_image']
extras = ['cartopy', 'geoviews']
out = io.StringIO()
with redirect_stdout(out):
check_satpy(readers=readers, writers=writers, extras=extras)
out_str = out.getvalue()
print(out_str)

for feature in readers + writers + extras:
if feature + ": ok" not in out_str:
print("FAIL: Missing or corrupt Satpy dependency (see above for details).")
return False
return True


def check_data_download():
print("Checking data directories...\n")

# base_dirs
abi_dir = os.path.join(TUTORIAL_ROOT, 'data', 'abi_l1b')
viirs_dir = os.path.join(TUTORIAL_ROOT, 'data', 'viirs_sdr')

# data case dirs
conus_dir = os.path.join(abi_dir, '20180511_texas_fire_abi_l1b_conus')
meso_dir = os.path.join(abi_dir, '20180511_texas_fire_abi_l1b_meso')
viirs_dir = os.path.join(viirs_dir, '20180511_texas_fire_viirs_sdr')
if not os.path.isdir(conus_dir):
print("FAIL: Missing ABI L1B CONUS data: {}".format(conus_dir))
return False
if not os.path.isdir(meso_dir):
print("FAIL: Missing ABI L1B Mesoscale data: {}".format(meso_dir))
return False
if not os.path.isdir(viirs_dir):
print("FAIL: Missing VIIRS SDR data: {}".format(viirs_dir))
return False

# number of files
if len(os.listdir(conus_dir)) != 16:
print("FAIL: Expected 16 files in {}".format(conus_dir))
return False
if len(os.listdir(meso_dir)) != 1440:
print("FAIL: Expected 1440 files in {}".format(meso_dir))
return False
if len(os.listdir(viirs_dir)) != 21:
print("FAIL: Expected 21 files in {}".format(viirs_dir))
return False

return True


def main():
ret = True
ret &= check_satpy_features()
ret &= check_data_download()
if ret:
print("SUCCESS")
else:
print("FAIL")
return ret


if __name__ == "__main__":
import sys
sys.exit(main())
8 changes: 8 additions & 0 deletions satpy/tests/behave/test_results.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Test for GOES17 - airmass
Non-zero pixel differences: 6607692
Result: Failed - 6607692 pixel differences exceed the threshold of 100.

Test for GOES16 - airmass
Non-zero pixel differences: 1590
Result: Failed - 1590 pixel differences exceed the threshold of 100.

0 comments on commit 72d04ce

Please sign in to comment.