|
- import os
- import argparse
- import lxml.etree as ET
- import subprocess
- import flirimageextractor
- import cv2
- import numpy as np
- from pathlib import Path
- from selenium import webdriver
- import rasterio
- # import shapefile
-
- arg_parser = argparse.ArgumentParser(description='Export SVG composition of FLIR images as TIFF with thermo layer')
-
- arg_parser.add_argument('Input',
- metavar='input_svg',
- type=str,
- help='Path to the input SVG file cotaining xlinks to FLIR images')
-
- arg_parser.add_argument('Output',
- metavar='output_tiff',
- type=str,
- help='Output filename')
-
-
- args = arg_parser.parse_args()
- dirname = os.path.dirname(__file__)
- INPUT_PATH = os.path.join(dirname, args.Input)
- INPUT_DIR = os.path.split(INPUT_PATH)[0]
-
- TEMP_MAP_THERMALPNG_SVG_PATH = os.path.join(INPUT_DIR, 'map_thermalpng.svg')
- TEMP_MAP_THERMALPNG_PATH = os.path.join(INPUT_DIR, 'map_thermalpng.png')
- TEMP_MAP_PREVIEW_PATH = os.path.join(INPUT_DIR, 'map_preview.png')
- TEMP_MAP_UNGROUPED_SVG_PATH = os.path.join(INPUT_DIR, 'map_ungrouped.svg')
-
- TEMP_MAP_ALIGNMENTPROOF_PATH = os.path.join(INPUT_DIR, 'map_thermalpng_proof.png')
-
-
- THERMALPNG_DIR = 'thermalpngs'
-
- # VECTOR_SHAPEFILE_PATH = os.path.join(INPUT_DIR, 'shapefile')
-
-
- OUTPUT_PATH = os.path.join(dirname, args.Output)
-
-
-
- def make_thermalpng_tiles():
- """
- Extract thermal infomration as greyscale PNG-16
- (temp * 1000 to retain some decimals)
- and save the png tiles.
- Assuming, that the data will always have a positive value.
- """
- print('Building PNG tiles representing the thermal data ...')
- Path(os.path.join(INPUT_DIR, THERMALPNG_DIR)).mkdir(parents=True, exist_ok=True)
- png_output_dir = os.path.join(INPUT_DIR, THERMALPNG_DIR)
-
- for root_path, directories, file in os.walk(os.path.join(dirname, INPUT_DIR)):
- for file in file:
- if(file.endswith(".jpg")):
- print(' Processing ' + file)
- full_filepath = os.path.join(root_path, file)
- flir = flirimageextractor.FlirImageExtractor()
- flir.process_image(full_filepath)
- thermal_img_np = flir.thermal_image_np
- multiplied_image = cv2.multiply(thermal_img_np, 1000)
- output_file_path = os.path.join(png_output_dir, file + '.thermal.png')
- cv2.imwrite(output_file_path, multiplied_image.astype(np.uint16))
-
-
-
-
- def make_thermalpng_svg():
- """
- replaces the image paths with the thermal pngs
- and creates new SVG file
- """
- print('Replacing the images inside the SVG with the PNG tiles ...')
- # print("svg_file")
- # print(dir(svg_file))
- tree = ET.parse(INPUT_PATH)
- root = tree.getroot()
- # print(ET.tostring(root))
- # tile_rows = root.xpath('//image', namespaces={'n': "http://www.w3.org/2000/svg"})
- # print(dir(root))
- tile_elements = root.xpath('//*[@class="thermal_image"]')
- linkattrib ='{http://www.w3.org/1999/xlink}href'
- for tile in tile_elements:
- tile.attrib[linkattrib] = os.path.join(THERMALPNG_DIR, tile.attrib[linkattrib] + '.thermal.png')
- # Post Production
- tile.attrib["mask"] = 'url(#tilefademask)'
- tile.attrib["style"] = 'opacity:.7'
-
-
- # newxml = ET.tostring(tree, encoding="unicode")
- # print(newxml)
- # return newxml
-
-
-
-
- with open(TEMP_MAP_THERMALPNG_SVG_PATH, 'wb') as f:
- tree.write(f, encoding='utf-8')
-
- return tree
-
-
-
- def make_thermalpng():
- """
- exports the SVG canvas as Gray_16 PNG
- """
- print('Creating a big 16-bit grayscale PNG image representing the thermal data out of the SVG file ...')
- command = [
- '/snap/bin/inkscape',
- '--pipe',
- '--export-type=png',
- '--export-png-color-mode=Gray_16'
- ],
- input_file = open(TEMP_MAP_THERMALPNG_SVG_PATH, "rb")
- output_file = open(TEMP_MAP_THERMALPNG_PATH, "wb")
- completed = subprocess.run(
- *command,
- cwd=INPUT_DIR, # needed for reative image links
- stdin=input_file,
- stdout=output_file
- )
- return completed
-
- # def make_thermalpreview():
- # """
- # exports the preview image
- # """
- # command = [
- # '/snap/bin/inkscape',
- # '--pipe',
- # '--export-type=png',
- # '--export-png-color-mode=Gray_8'
- # ],
- # input_file = open(TEMP_MAP_THERMALPNG_SVG_PATH, "rb")
- # output_file = open(TEMP_MAP_PREVIEW_PATH, "wb")
- # completed = subprocess.run(
- # *command,
- # cwd=INPUT_DIR, # needed for reative image links
- # stdin=input_file,
- # stdout=output_file
- # )
- # return completed
-
-
-
- def get_thermal_numpy_array():
- print('Converting the PNG into NumPy Array and normalize temperature values ...')
- image = cv2.imread(TEMP_MAP_THERMALPNG_PATH, cv2.IMREAD_ANYDEPTH)
- image_float = image.astype(np.float32)
- image_float_normalized = cv2.divide(image_float, 1000)
- # print(image_float_normalized[1000][905]) # looking what's the value of some pixel
- return image_float_normalized
-
- def get_used_tiles_relpaths():
- """
- outputs an array of all used tile filenames in the input SVG
- (relative filepaths like they appear in the svg.)
- """
- images = []
- tree = ET.parse(INPUT_PATH)
- root = tree.getroot()
- tile_elements = root.xpath('//*[@class="thermal_image"]')
- linkattrib ='{http://www.w3.org/1999/xlink}href'
- for tile in tile_elements:
- images.append(tile.attrib[linkattrib])
- return images
-
-
-
- # def deg_coordinates_to_decimal(coordStr):
- # coordArr = coordStr.split(', ')
- # calculatedCoordArray = []
- # for calculation in coordArr:
- # calculationArr = calculation.split('/')
- # calculatedCoordArray.append(int(calculationArr[0]) / int(calculationArr[1]))
- # degrees = calculatedCoordArray[0]
- # minutes = calculatedCoordArray[1]
- # seconds = calculatedCoordArray[2]
- # decimal = (degrees + (minutes * 1/60) + (seconds * 1/60 * 1/60))
- # # print(decimal)
- # return decimal
-
-
- # def read_coordinates_from_tile(filename):
- # full_filepath = os.path.join(INPUT_DIR, filename)
- # with Image(filename=full_filepath) as image:
- # for key, value in image.metadata.items():
- # if key == 'exif:GPSLatitude':
- # # print('latstr', value)
- # lat = deg_coordinates_to_decimal(value) # lat -> Y vertical
- # if key == 'exif:GPSLongitude':
- # # print('lonstr', value)
- # lon = deg_coordinates_to_decimal(value) # lon -> X horizontal
-
- # return [lat, lon]
-
-
- # def get_coordinate_boundaries():
- # image_names = get_used_tiles_relpaths()
- # coordinates = {
- # 'lat': [],
- # 'lon': []
- # }
- # for filename in image_names:
- # tile_coordinates = read_coordinates_from_tile(filename)
- # coordinates['lat'].append(tile_coordinates[0])
- # coordinates['lon'].append(tile_coordinates[1])
-
- # boundaries = {
- # 'xmin': min(coordinates['lon']),
- # 'xmax': max(coordinates['lon']),
- # 'ymin': min(coordinates['lat']),
- # 'ymax': max(coordinates['lat']),
- # }
- # return boundaries
-
-
- # def create_ungrouped_svg():
- # """
- # exports the SVG without any grouped elements
- # (the quick and dirty way)
- # """
- # print('Create an SVG without groups ...')
- # inkscape_actions = "select-all:groups; SelectionUnGroup; select-all:groups; SelectionUnGroup; select-all:groups; SelectionUnGroup; select-all:groups; SelectionUnGroup; select-all:groups; SelectionUnGroup; select-all:groups; SelectionUnGroup; select-all:groups; SelectionUnGroup; export-filename: {}; export-plain-svg; export-do;".format(TEMP_MAP_UNGROUPED_SVG_PATH)
- # command = [
- # '/snap/bin/inkscape',
- # '--pipe',
- # '--actions={}'.format(inkscape_actions),
- # ],
- # input_file = open(INPUT_PATH, "rb")
- # completed = subprocess.run(
- # *command,
- # cwd=INPUT_DIR, # needed for reative image links
- # stdin=input_file,
- # )
- # print('completed', completed)
- # return completed
-
-
- def get_ground_control_points():
- """
- Using selenium with firefox for rendering the SVG and
- getting the positional matching between GPS
- and x/y coords of SVG-image.
- image tags need to have the gps data attached as data attributes.
- """
- print('Getting ground control points ...')
- options = webdriver.firefox.options.Options()
- options.headless = True
- driver = webdriver.Firefox(options=options)
- driver.get("file://{}".format(INPUT_PATH))
- images = driver.find_elements_by_class_name("thermal_image")
-
- gcps = []
- for image in images:
- location = image.location
- size = image.size
- raster_y = float(location['y'] + size['height']/2)
- raster_x = float(location['x'] + size['width']/2)
- reference_lon = float(image.get_attribute('data-lon'))
- reference_lat = float(image.get_attribute('data-lat'))
- imageMapping = rasterio.control.GroundControlPoint(row=raster_y, col=raster_x, x=reference_lon, y=reference_lat)
- gcps.append(imageMapping)
-
- driver.quit()
- return gcps
-
-
- # def make_vector_shapefile():
- # w = shapefile.Writer(VECTOR_SHAPEFILE_PATH)
- # w.field('name', 'C')
-
- # tree = ET.parse(INPUT_PATH)
- # root = tree.getroot()
- # tiles = root.xpath('//*[@class="thermal_image"]')
- # for index, tile in enumerate(tiles):
- # w.point(float(tile.attrib['data-lon']), float(tile.attrib['data-lat']))
- # w.record('point{}'.format(index))
- # w.close()
- # return True
-
- def verify_coordinate_matching():
- """
- During development, i want to proof
- that the point/coordinate matching is right.
- Producing a png file with red dots for visual proof.
- """
- img = cv2.imread(TEMP_MAP_THERMALPNG_PATH, cv2.IMREAD_GRAYSCALE)
- img = cv2.cvtColor(img,cv2.COLOR_GRAY2RGB)
-
- options = webdriver.firefox.options.Options()
- # options.headless = True
- driver = webdriver.Firefox(options=options)
- driver.get("file://{}".format(INPUT_PATH))
- images = driver.find_elements_by_class_name("thermal_image")
-
- gcps = []
- for image in images:
- location = image.location
- size = image.size
- raster_y = int(location['y'] + size['height']/2)
- raster_x = int(location['x'] + size['width']/2)
- reference_lon = float(image.get_attribute('data-lon'))
- reference_lat = float(image.get_attribute('data-lat'))
- print(raster_x, raster_y)
- img = cv2.circle(img, (raster_x, raster_y), radius=10, color=(0, 0, 255), thickness=-1)
- driver.quit()
- cv2.imwrite(TEMP_MAP_ALIGNMENTPROOF_PATH, img)
-
-
-
-
- def make_geotiff_image():
-
- thermal_numpy_array = get_thermal_numpy_array()
- thermal_numpy_array[thermal_numpy_array == 0] = np.NaN # zeros to NaN
-
- # # coordinates of all tiles
- # geo_bound = get_coordinate_boundaries()
- # print('boundaries', geo_bound)
-
- np_shape = thermal_numpy_array.shape
- image_size = (np_shape[0], np_shape[1])
-
- gcps = get_ground_control_points()
- print('Applying affine transform ...')
- gcp_transform = rasterio.transform.from_gcps(gcps)
- print(gcp_transform)
-
- print('Generating the GeoTiff ...')
-
- raster_io_dataset = rasterio.open(
- OUTPUT_PATH,
- 'w',
- driver='GTiff',
- height=thermal_numpy_array.shape[0],
- width=thermal_numpy_array.shape[1],
- count=1,
- dtype=thermal_numpy_array.dtype,
- transform=gcp_transform,
- crs='+proj=latlong',
- )
- raster_io_dataset.write(thermal_numpy_array, 1)
-
- # # try to get rid of the black frame
- # src = rasterio.open(OUTPUT_PATH)
- # src[src == 0] = np.NaN # zeros to NaN
- # raster_io_dataset2 = rasterio.open(
- # OUTPUT_PATH,
- # 'w',
- # driver='GTiff',
- # height=src.shape[0],
- # width=src.shape[1],
- # count=1,
- # dtype=src.dtype,
- # crs='+proj=latlong',
- # )
- # raster_io_dataset2.write(src, 1)
-
- print('Saved to ', OUTPUT_PATH)
-
-
-
- # make_thermalpng_tiles()
- make_thermalpng_svg()
- make_thermalpng()
- make_geotiff_image()
-
- # # Helpers for debugging
- # verify_coordinate_matching()
- # make_vector_shapefile()
|