mirror of
https://github.com/qgis/QGIS.git
synced 2025-02-24 00:47:57 -05:00
358 lines
13 KiB
Python
Executable File
358 lines
13 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
****************************3***********************************************
|
|
parse_dash_results.py
|
|
---------------------
|
|
Date : October 2016
|
|
Copyright : (C) 2016 by Nyall Dawson
|
|
Email : nyall dot dawson at gmail dot com
|
|
***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************
|
|
"""
|
|
|
|
__author__ = 'Nyall Dawson'
|
|
__date__ = 'October 2016'
|
|
__copyright__ = '(C) 2016, Nyall Dawson'
|
|
# This will get replaced with a git SHA1 when you do a git archive
|
|
__revision__ = '$Format:%H$'
|
|
|
|
import os
|
|
import sys
|
|
import argparse
|
|
import urllib
|
|
import re
|
|
from bs4 import BeautifulSoup
|
|
from PyQt5.QtGui import (
|
|
QImage, QColor, qRed, qBlue, qGreen, qAlpha, qRgb, QPixmap)
|
|
from PyQt5.QtWidgets import (QDialog,
|
|
QApplication,
|
|
QLabel,
|
|
QVBoxLayout,
|
|
QHBoxLayout,
|
|
QGridLayout,
|
|
QPushButton,
|
|
QDoubleSpinBox,
|
|
QMessageBox)
|
|
import struct
|
|
import glob
|
|
|
|
dash_url = 'https://dash.orfeo-toolbox.org'
|
|
|
|
|
|
def error(msg):
|
|
print(msg)
|
|
sys.exit(1)
|
|
|
|
|
|
def colorDiff(c1, c2):
|
|
redDiff = abs(qRed(c1) - qRed(c2))
|
|
greenDiff = abs(qGreen(c1) - qGreen(c2))
|
|
blueDiff = abs(qBlue(c1) - qBlue(c2))
|
|
alphaDiff = abs(qAlpha(c1) - qAlpha(c2))
|
|
return max(redDiff, greenDiff, blueDiff, alphaDiff)
|
|
|
|
|
|
def imageFromPath(path):
|
|
if (path[:7] == 'https://' or path[:7] == 'file://'):
|
|
# fetch remote image
|
|
data = urllib.request.urlopen(path).read()
|
|
image = QImage()
|
|
image.loadFromData(data)
|
|
else:
|
|
image = QImage(path)
|
|
return image
|
|
|
|
|
|
class ResultHandler(QDialog):
|
|
|
|
def __init__(self, parent=None):
|
|
super(ResultHandler, self).__init__()
|
|
self.setWindowTitle('Dash results')
|
|
self.control_label = QLabel()
|
|
self.rendered_label = QLabel()
|
|
self.diff_label = QLabel()
|
|
|
|
self.mask_label = QLabel()
|
|
self.new_mask_label = QLabel()
|
|
|
|
grid = QGridLayout()
|
|
self.test_name_label = QLabel()
|
|
grid.addWidget(self.test_name_label, 0, 0)
|
|
grid.addWidget(QLabel('Control'), 1, 0)
|
|
grid.addWidget(QLabel('Rendered'), 1, 1)
|
|
grid.addWidget(QLabel('Difference'), 1, 2)
|
|
grid.addWidget(self.control_label, 2, 0)
|
|
grid.addWidget(self.rendered_label, 2, 1)
|
|
grid.addWidget(self.diff_label, 2, 2)
|
|
grid.addWidget(QLabel('Current Mask'), 3, 0)
|
|
grid.addWidget(QLabel('New Mask'), 3, 1)
|
|
grid.addWidget(self.mask_label, 4, 0)
|
|
grid.addWidget(self.new_mask_label, 4, 1)
|
|
|
|
v_layout = QVBoxLayout()
|
|
v_layout.addLayout(grid, 1)
|
|
|
|
next_image_button = QPushButton()
|
|
next_image_button.setText('Skip')
|
|
next_image_button.pressed.connect(self.load_next)
|
|
|
|
self.overload_spin = QDoubleSpinBox()
|
|
self.overload_spin.setMinimum(1)
|
|
self.overload_spin.setMaximum(255)
|
|
self.overload_spin.setValue(1)
|
|
|
|
preview_mask_button = QPushButton()
|
|
preview_mask_button.setText('Preview New Mask')
|
|
preview_mask_button.pressed.connect(self.preview_mask)
|
|
|
|
save_mask_button = QPushButton()
|
|
save_mask_button.setText('Save New Mask')
|
|
save_mask_button.pressed.connect(self.save_mask)
|
|
|
|
button_layout = QHBoxLayout()
|
|
button_layout.addWidget(next_image_button)
|
|
button_layout.addWidget(QLabel('Mask diff multiplier:'))
|
|
button_layout.addWidget(self.overload_spin)
|
|
button_layout.addWidget(preview_mask_button)
|
|
button_layout.addWidget(save_mask_button)
|
|
button_layout.addStretch()
|
|
v_layout.addLayout(button_layout)
|
|
self.setLayout(v_layout)
|
|
|
|
def closeEvent(self, event):
|
|
self.reject()
|
|
|
|
def parse_url(self, url):
|
|
print('Fetching dash results from: {}'.format(url))
|
|
page = urllib.request.urlopen(url)
|
|
soup = BeautifulSoup(page, "lxml")
|
|
|
|
# build up list of rendered images
|
|
measurement_img = [img for img in soup.find_all('img') if
|
|
img.get('alt') and img.get('alt').startswith('Rendered Image')]
|
|
|
|
images = {}
|
|
for img in measurement_img:
|
|
m = re.search('Rendered Image (.*?)\s', img.get('alt'))
|
|
test_name = m.group(1)
|
|
rendered_image = img.get('src')
|
|
images[test_name] = '{}/{}'.format(dash_url, rendered_image)
|
|
|
|
print('found images:\n{}'.format(images))
|
|
self.images = images
|
|
self.load_next()
|
|
|
|
def load_next(self):
|
|
if not self.images:
|
|
# all done
|
|
self.accept()
|
|
|
|
test_name, rendered_image = self.images.popitem()
|
|
self.test_name_label.setText(test_name)
|
|
control_image = self.get_control_image_path(test_name)
|
|
if not control_image:
|
|
self.load_next()
|
|
return
|
|
|
|
self.mask_image_path = control_image[:-4] + '_mask.png'
|
|
self.load_images(control_image, rendered_image, self.mask_image_path)
|
|
|
|
def load_images(self, control_image_path, rendered_image_path, mask_image_path):
|
|
self.control_image = imageFromPath(control_image_path)
|
|
if not self.control_image:
|
|
error('Could not read control image {}'.format(control_image_path))
|
|
|
|
self.rendered_image = imageFromPath(rendered_image_path)
|
|
if not self.rendered_image:
|
|
error(
|
|
'Could not read rendered image {}'.format(rendered_image_path))
|
|
if not self.rendered_image.width() == self.control_image.width() or not self.rendered_image.height() == self.control_image.height():
|
|
print(
|
|
'Size mismatch - control image is {}x{}, rendered image is {}x{}'.format(self.control_image.width(),
|
|
self.control_image.height(
|
|
),
|
|
self.rendered_image.width(
|
|
),
|
|
self.rendered_image.height()))
|
|
|
|
max_width = min(
|
|
self.rendered_image.width(), self.control_image.width())
|
|
max_height = min(
|
|
self.rendered_image.height(), self.control_image.height())
|
|
|
|
# read current mask, if it exist
|
|
self.mask_image = imageFromPath(mask_image_path)
|
|
if self.mask_image.isNull():
|
|
print(
|
|
'Mask image does not exist, creating {}'.format(mask_image_path))
|
|
self.mask_image = QImage(
|
|
self.control_image.width(), self.control_image.height(), QImage.Format_ARGB32)
|
|
self.mask_image.fill(QColor(0, 0, 0))
|
|
|
|
self.diff_image = self.create_diff_image(
|
|
self.control_image, self.rendered_image, self.mask_image)
|
|
if not self.diff_image:
|
|
self.load_next()
|
|
return
|
|
|
|
self.control_label.setPixmap(QPixmap.fromImage(self.control_image))
|
|
self.rendered_label.setPixmap(QPixmap.fromImage(self.rendered_image))
|
|
self.mask_label.setPixmap(QPixmap.fromImage(self.mask_image))
|
|
self.diff_label.setPixmap(QPixmap.fromImage(self.diff_image))
|
|
self.preview_mask()
|
|
|
|
def preview_mask(self):
|
|
self.new_mask_image = self.create_mask(
|
|
self.control_image, self.rendered_image, self.mask_image, self.overload_spin.value())
|
|
self.new_mask_label.setPixmap(QPixmap.fromImage(self.new_mask_image))
|
|
|
|
def save_mask(self):
|
|
self.new_mask_image.save(self.mask_image_path, "png")
|
|
self.load_next()
|
|
|
|
def create_mask(self, control_image, rendered_image, mask_image, overload=1):
|
|
max_width = min(rendered_image.width(), control_image.width())
|
|
max_height = min(rendered_image.height(), control_image.height())
|
|
|
|
new_mask_image = QImage(
|
|
control_image.width(), control_image.height(), QImage.Format_ARGB32)
|
|
new_mask_image.fill(QColor(0, 0, 0))
|
|
|
|
# loop through pixels in rendered image and compare
|
|
mismatch_count = 0
|
|
linebytes = max_width * 4
|
|
for y in range(max_height):
|
|
control_scanline = control_image.constScanLine(
|
|
y).asstring(linebytes)
|
|
rendered_scanline = rendered_image.constScanLine(
|
|
y).asstring(linebytes)
|
|
mask_scanline = mask_image.scanLine(y).asstring(linebytes)
|
|
|
|
for x in range(max_width):
|
|
currentTolerance = qRed(
|
|
struct.unpack('I', mask_scanline[x * 4:x * 4 + 4])[0])
|
|
|
|
if currentTolerance == 255:
|
|
# ignore pixel
|
|
new_mask_image.setPixel(
|
|
x, y, qRgb(currentTolerance, currentTolerance, currentTolerance))
|
|
continue
|
|
|
|
expected_rgb = struct.unpack(
|
|
'I', control_scanline[x * 4:x * 4 + 4])[0]
|
|
rendered_rgb = struct.unpack(
|
|
'I', rendered_scanline[x * 4:x * 4 + 4])[0]
|
|
difference = min(
|
|
255, colorDiff(expected_rgb, rendered_rgb) * overload)
|
|
|
|
if difference > currentTolerance:
|
|
# update mask image
|
|
new_mask_image.setPixel(
|
|
x, y, qRgb(difference, difference, difference))
|
|
mismatch_count += 1
|
|
else:
|
|
new_mask_image.setPixel(
|
|
x, y, qRgb(currentTolerance, currentTolerance, currentTolerance))
|
|
return new_mask_image
|
|
|
|
def get_control_image_path(self, test_name):
|
|
if os.path.isfile(test_name):
|
|
return path
|
|
|
|
# else try and find matching test image
|
|
script_folder = os.path.dirname(os.path.realpath(sys.argv[0]))
|
|
control_images_folder = os.path.join(
|
|
script_folder, '../tests/testdata/control_images')
|
|
|
|
matching_control_images = [x[0]
|
|
for x in os.walk(control_images_folder) if test_name in x[0]]
|
|
if len(matching_control_images) > 1:
|
|
QMessageBox.warning(
|
|
self, 'Result', 'Found multiple matching control images for {}'.format(test_name))
|
|
return None
|
|
elif len(matching_control_images) == 0:
|
|
QMessageBox.warning(
|
|
self, 'Result', 'No matching control images found for {}'.format(test_name))
|
|
return None
|
|
|
|
found_control_image_path = matching_control_images[0]
|
|
|
|
# check for a single matching expected image
|
|
images = glob.glob(os.path.join(found_control_image_path, '*.png'))
|
|
filtered_images = [i for i in images if not i[-9:] == '_mask.png']
|
|
if len(filtered_images) > 1:
|
|
error(
|
|
'Found multiple matching control images for {}'.format(test_name))
|
|
elif len(filtered_images) == 0:
|
|
error('No matching control images found for {}'.format(test_name))
|
|
|
|
found_image = filtered_images[0]
|
|
print('Found matching control image: {}'.format(found_image))
|
|
return found_image
|
|
|
|
def create_diff_image(self, control_image, rendered_image, mask_image):
|
|
# loop through pixels in rendered image and compare
|
|
mismatch_count = 0
|
|
max_width = min(rendered_image.width(), control_image.width())
|
|
max_height = min(rendered_image.height(), control_image.height())
|
|
linebytes = max_width * 4
|
|
|
|
diff_image = QImage(
|
|
control_image.width(), control_image.height(), QImage.Format_ARGB32)
|
|
diff_image.fill(QColor(152, 219, 249))
|
|
|
|
for y in range(max_height):
|
|
control_scanline = control_image.constScanLine(
|
|
y).asstring(linebytes)
|
|
rendered_scanline = rendered_image.constScanLine(
|
|
y).asstring(linebytes)
|
|
mask_scanline = mask_image.scanLine(y).asstring(linebytes)
|
|
|
|
for x in range(max_width):
|
|
currentTolerance = qRed(
|
|
struct.unpack('I', mask_scanline[x * 4:x * 4 + 4])[0])
|
|
|
|
if currentTolerance == 255:
|
|
# ignore pixel
|
|
continue
|
|
|
|
expected_rgb = struct.unpack(
|
|
'I', control_scanline[x * 4:x * 4 + 4])[0]
|
|
rendered_rgb = struct.unpack(
|
|
'I', rendered_scanline[x * 4:x * 4 + 4])[0]
|
|
difference = colorDiff(expected_rgb, rendered_rgb)
|
|
|
|
if difference > currentTolerance:
|
|
# update mask image
|
|
diff_image.setPixel(x, y, qRgb(255, 0, 0))
|
|
mismatch_count += 1
|
|
|
|
if mismatch_count:
|
|
return diff_image
|
|
else:
|
|
print('No mismatches')
|
|
return None
|
|
|
|
|
|
def main():
|
|
app = QApplication(sys.argv)
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('dash_url')
|
|
args = parser.parse_args()
|
|
|
|
w = ResultHandler()
|
|
w.parse_url(args.dash_url)
|
|
w.exec()
|
|
|
|
if __name__ == '__main__':
|
|
main()
|