def parse_exif_values(self, _path_file, _force_focal, _force_ccd): # Disable exifread log logging.getLogger('exifread').setLevel(logging.CRITICAL) with open(_path_file, 'rb') as f: tags = exifread.process_file(f, details=False) try: if 'Image Make' in tags: self.camera_make = tags['Image Make'].values.encode('utf8') if 'Image Model' in tags: self.camera_model = tags['Image Model'].values.encode( 'utf8') if 'EXIF FocalLength' in tags: self.focal_length = self.float_values( tags['EXIF FocalLength'])[0] if 'GPS GPSAltitude' in tags: self.altitude = self.float_values( tags['GPS GPSAltitude'])[0] if 'GPS GPSAltitudeRef' in tags and self.int_values( tags['GPS GPSAltitudeRef'])[0] > 0: self.altitude *= -1 if 'GPS GPSLatitude' in tags and 'GPS GPSLatitudeRef' in tags: self.latitude = self.dms_to_decimal( tags['GPS GPSLatitude'], tags['GPS GPSLatitudeRef']) if 'GPS GPSLongitude' in tags and 'GPS GPSLongitudeRef' in tags: self.longitude = self.dms_to_decimal( tags['GPS GPSLongitude'], tags['GPS GPSLongitudeRef']) except IndexError as e: log.ODM_WARNING("Cannot read EXIF tags for %s: %s" % (_path_file, e.message)) if self.camera_make and self.camera_model: self.make_model = sensor_string(self.camera_make, self.camera_model) # needed to do that since sometimes metadata contains wrong data img = cv2.imread(_path_file) self.width = img.shape[1] self.height = img.shape[0] # force focal and ccd_width with user parameter if _force_focal: self.focal_length = _force_focal if _force_ccd: self.ccd_width = _force_ccd # find ccd_width from file if needed if self.ccd_width is None and self.camera_model is not None: # load ccd_widths from file ccd_widths = system.get_ccd_widths() # search ccd by camera model key = [x for x in ccd_widths.keys() if self.make_model in x] # convert to float if found if key: self.ccd_width = float(ccd_widths[key[0]]) else: log.ODM_WARNING( 'Could not find ccd_width in file. Use --force-ccd or edit the sensor_data.json ' 'file to manually input ccd width')
def georeference_with_gcp(self, gcp_file, output_coords_file, output_gcp_file, rerun=False): if not io.file_exists(output_coords_file) or not io.file_exists( output_gcp_file) or rerun: gcp = GCPFile(gcp_file) if gcp.exists(): # Create coords file, we'll be using this later # during georeferencing with open(output_coords_file, 'w') as f: coords_header = gcp.wgs84_utm_zone() f.write(coords_header + "\n") log.ODM_DEBUG("Generated coords file from GCP: %s" % coords_header) # Convert GCP file to a UTM projection since the rest of the pipeline # does not handle other SRS well. rejected_entries = [] utm_gcp = GCPFile( gcp.create_utm_copy( output_gcp_file, filenames=[p.filename for p in self.photos], rejected_entries=rejected_entries, include_extras=False)) if not utm_gcp.exists(): raise RuntimeError( "Could not project GCP file to UTM. Please double check your GCP file for mistakes." ) for re in rejected_entries: log.ODM_WARNING("GCP line ignored (image not found): %s" % str(re)) if utm_gcp.entries_count() > 0: log.ODM_INFO( "%s GCP points will be used for georeferencing" % utm_gcp.entries_count()) else: raise RuntimeError( "A GCP file was provided, but no valid GCP entries could be used. Note that the GCP file is case sensitive (\".JPG\" is not the same as \".jpg\")." ) self.gcp = utm_gcp else: log.ODM_WARNING("GCP file does not exist: %s" % gcp_file) return else: log.ODM_INFO("Coordinates file already exist: %s" % output_coords_file) log.ODM_INFO("GCP file already exist: %s" % output_gcp_file) self.gcp = GCPFile(output_gcp_file) self.georef = ODM_GeoRef.FromCoordsFile(output_coords_file) return self.georef
def parse_exif_values(self, _path_file): # Disable exifread log logging.getLogger('exifread').setLevel(logging.CRITICAL) with open(_path_file, 'rb') as f: tags = exifread.process_file(f, details=False) try: if 'Image Make' in tags: self.camera_make = tags['Image Make'].values.encode('utf8') if 'Image Model' in tags: self.camera_model = tags['Image Model'].values.encode('utf8') if 'GPS GPSAltitude' in tags: self.altitude = self.float_values(tags['GPS GPSAltitude'])[0] if 'GPS GPSAltitudeRef' in tags and self.int_values(tags['GPS GPSAltitudeRef'])[0] > 0: self.altitude *= -1 if 'GPS GPSLatitude' in tags and 'GPS GPSLatitudeRef' in tags: self.latitude = self.dms_to_decimal(tags['GPS GPSLatitude'], tags['GPS GPSLatitudeRef']) if 'GPS GPSLongitude' in tags and 'GPS GPSLongitudeRef' in tags: self.longitude = self.dms_to_decimal(tags['GPS GPSLongitude'], tags['GPS GPSLongitudeRef']) except IndexError as e: log.ODM_WARNING("Cannot read EXIF tags for %s: %s" % (_path_file, e.message)) if self.camera_make and self.camera_model: self.make_model = sensor_string(self.camera_make, self.camera_model) # needed to do that since sometimes metadata contains wrong data try: self.width, self.height = get_image_size.get_image_size(_path_file) except get_image_size.UnknownImageFormat: # Fallback to slower cv2 img = cv2.imread(_path_file) self.width = img.shape[1] self.height = img.shape[0]
def parse_exif_values(self, _path_file): # Disable exifread log logging.getLogger('exifread').setLevel(logging.CRITICAL) with open(_path_file, 'rb') as f: tags = exifread.process_file(f, details=False) try: if 'Image Make' in tags: self.camera_make = tags['Image Make'].values.encode('utf8') if 'Image Model' in tags: self.camera_model = tags['Image Model'].values.encode( 'utf8') if 'GPS GPSAltitude' in tags: self.altitude = self.float_values( tags['GPS GPSAltitude'])[0] if 'GPS GPSAltitudeRef' in tags and self.int_values( tags['GPS GPSAltitudeRef'])[0] > 0: self.altitude *= -1 if 'GPS GPSLatitude' in tags and 'GPS GPSLatitudeRef' in tags: self.latitude = self.dms_to_decimal( tags['GPS GPSLatitude'], tags['GPS GPSLatitudeRef']) if 'GPS GPSLongitude' in tags and 'GPS GPSLongitudeRef' in tags: self.longitude = self.dms_to_decimal( tags['GPS GPSLongitude'], tags['GPS GPSLongitudeRef']) except IndexError as e: log.ODM_WARNING("Cannot read EXIF tags for %s: %s" % (_path_file, e.message)) if self.camera_make and self.camera_model: self.make_model = sensor_string(self.camera_make, self.camera_model) self.width, self.height = get_image_size.get_image_size(_path_file)
def parse_pyexiv2_values(self, _path_file, _force_focal, _force_ccd): # read image metadata metadata = pyexiv2.ImageMetadata(_path_file) metadata.read() # loop over image tags for key in metadata: # try/catch tag value due to weird bug in pyexiv2 # ValueError: invalid literal for int() with base 10: '' GPS = 'Exif.GPSInfo.GPS' try: # parse tag names if key == 'Exif.Image.Make': self.camera_make = metadata[key].value elif key == 'Exif.Image.Model': self.camera_model = metadata[key].value elif key == 'Exif.Photo.FocalLength': self.focal_length = float(metadata[key].value) elif key == GPS + 'Latitude': self.latitude = self.dms_to_decimal(*metadata[key].value + [metadata[GPS + 'LatitudeRef'].value]) elif key == GPS + 'Longitude': self.longitude = self.dms_to_decimal(*metadata[key].value + [metadata[GPS + 'LongitudeRef'].value]) elif key == GPS + 'Altitude': self.altitude = float(metadata[key].value) if metadata[GPS + 'AltitudeRef'] and int(metadata[GPS + 'AltitudeRef'].value) > 0: self.altitude *= -1. except (pyexiv2.ExifValueError, ValueError) as e: pass except KeyError as e: log.ODM_DEBUG('Tag not set') except NotImplementedError as e: pass if self.camera_make and self.camera_model: self.make_model = sensor_string(self.camera_make, self.camera_model) # needed to do that since sometimes metadata contains wrong data img = cv2.imread(_path_file) self.width = img.shape[1] self.height = img.shape[0] # force focal and ccd_width with user parameter if _force_focal: self.focal_length = _force_focal if _force_ccd: self.ccd_width = _force_ccd # find ccd_width from file if needed if self.ccd_width is None and self.camera_model is not None: # load ccd_widths from file ccd_widths = system.get_ccd_widths() # search ccd by camera model key = [x for x in ccd_widths.keys() if self.make_model in x] # convert to float if found if key: self.ccd_width = float(ccd_widths[key[0]]) else: log.ODM_WARNING('Could not find ccd_width in file. Use --force-ccd or edit the sensor_data.json ' 'file to manually input ccd width')
def parse_coordinate_system(self, _file): """Write attributes to jobOptions from coord file""" # check for coordinate file existence if not io.file_exists(_file): log.ODM_WARNING('Could not find file %s' % _file) return with open(_file) as f: # extract reference system and utm zone from first line. # We will assume the following format: # 'WGS84 UTM 17N' or 'WGS84 UTM 17N \n' line = f.readline().rstrip() log.ODM_DEBUG('Line: %s' % line) ref = line.split(' ') # match_wgs_utm = re.search('WGS84 UTM (\d{1,2})(N|S)', line, re.I) if ref[0] == 'WGS84' and ref[1] == 'UTM': # match_wgs_utm: datum = ref[0] utm_pole = ref[2][len(ref[2]) - 1] utm_zone = int(ref[2][:len(ref[2]) - 1]) return Proj(proj="utm", zone=utm_zone, datum=datum, no_defs=True) elif '+proj' in line: return Proj(line.strip('\'')) elif 'epsg' in line.lower(): return Proj(init=line) else: raise log.ODM_ERROR('Could not parse coordinates. Bad CRS supplied: %s' % line)
def georeference_with_gps(self, images_path, output_coords_file, rerun=False): try: if not io.file_exists(output_coords_file) or rerun: location.extract_utm_coords(self.photos, images_path, output_coords_file) else: log.ODM_INFO("Coordinates file already exist: %s" % output_coords_file) self.georef = ODM_GeoRef.FromCoordsFile(output_coords_file) except: log.ODM_WARNING('Could not generate coordinates file. The orthophoto will not be georeferenced.') self.gcp = GCPFile(None) return self.georef
def parse_coordinate_system(self, _file): """Write attributes to jobOptions from coord file""" # check for coordinate file existence if not io.file_exists(_file): log.ODM_WARNING('Could not find file %s' % _file) return with open(_file) as f: # extract reference system and utm zone from first line. # We will assume the following format: # 'WGS84 UTM 17N' or 'WGS84 UTM 17N \n' line = f.readline().rstrip() log.ODM_DEBUG('Line: %s' % line) ref = line.split(' ') # match_wgs_utm = re.search('WGS84 UTM (\d{1,2})(N|S)', line, re.I) try: if ref[0] == 'WGS84' and ref[1] == 'UTM': # match_wgs_utm: datum = ref[0] utm_pole = (ref[2][len(ref[2]) - 1]).upper() utm_zone = int(ref[2][:len(ref[2]) - 1]) proj_args = { 'proj': "utm", 'zone': utm_zone, 'datum': datum, 'no_defs': True } if utm_pole == 'S': proj_args['south'] = True return Proj(**proj_args) elif '+proj' in line: return Proj(line.strip('\'')) elif 'epsg' in line.lower(): return Proj(init=line) else: log.ODM_ERROR( 'Could not parse coordinates. Bad CRS supplied: %s' % line) except RuntimeError as e: log.ODM_ERROR( 'Uh oh! There seems to be a problem with your GCP file.\n\n' 'The line: %s\n\n' 'Is not valid. Projections that are valid include:\n' ' - EPSG:*****\n' ' - WGS84 UTM **(N|S)\n' ' - Any valid proj4 string (for example, +proj=utm +zone=32 +north +ellps=WGS84 +datum=WGS84 +units=m +no_defs)\n\n' 'Modify your GCP file and try again.' % line) raise RuntimeError(e)
def FromCoordsFile(coords_file): # check for coordinate file existence if not io.file_exists(coords_file): log.ODM_WARNING('Could not find file %s' % coords_file) return srs = None with open(coords_file) as f: # extract reference system and utm zone from first line. # We will assume the following format: # 'WGS84 UTM 17N' or 'WGS84 UTM 17N \n' line = f.readline().rstrip() srs = location.parse_srs_header(line) return ODM_GeoRef(srs)
def get_xmp(self, file): img_str = str(file.read()) xmp_start = img_str.find('<x:xmpmeta') xmp_end = img_str.find('</x:xmpmeta') if xmp_start < xmp_end: xmp_str = img_str[xmp_start:xmp_end + 12] try: xdict = x2d.parse(xmp_str) except ExpatError: from bs4 import BeautifulSoup xmp_str = str(BeautifulSoup(xmp_str, 'xml')) xdict = x2d.parse(xmp_str) log.ODM_WARNING("%s has malformed XMP XML (but we fixed it)" % self.filename) xdict = xdict.get('x:xmpmeta', {}) xdict = xdict.get('rdf:RDF', {}) xdict = xdict.get('rdf:Description', {}) if isinstance(xdict, list): return xdict else: return [xdict] else: return []
def parse_exif_values(self, _path_file): # Disable exifread log logging.getLogger('exifread').setLevel(logging.CRITICAL) with open(_path_file, 'rb') as f: tags = exifread.process_file(f, details=False) try: if 'Image Make' in tags: self.camera_make = tags['Image Make'].values.encode('utf8') if 'Image Model' in tags: self.camera_model = tags['Image Model'].values.encode( 'utf8') if 'GPS GPSAltitude' in tags: self.altitude = self.float_value(tags['GPS GPSAltitude']) if 'GPS GPSAltitudeRef' in tags and self.int_value( tags['GPS GPSAltitudeRef']) > 0: self.altitude *= -1 if 'GPS GPSLatitude' in tags and 'GPS GPSLatitudeRef' in tags: self.latitude = self.dms_to_decimal( tags['GPS GPSLatitude'], tags['GPS GPSLatitudeRef']) if 'GPS GPSLongitude' in tags and 'GPS GPSLongitudeRef' in tags: self.longitude = self.dms_to_decimal( tags['GPS GPSLongitude'], tags['GPS GPSLongitudeRef']) except IndexError as e: log.ODM_WARNING("Cannot read basic EXIF tags for %s: %s" % (_path_file, e.message)) try: if 'Image Tag 0xC61A' in tags: self.black_level = self.list_values( tags['Image Tag 0xC61A']) elif 'BlackLevel' in tags: self.black_level = self.list_values(tags['BlackLevel']) if 'EXIF ExposureTime' in tags: self.exposure_time = self.float_value( tags['EXIF ExposureTime']) if 'EXIF FNumber' in tags: self.fnumber = self.float_value(tags['EXIF FNumber']) if 'EXIF ISOSpeed' in tags: self.iso_speed = self.int_value(tags['EXIF ISOSpeed']) elif 'EXIF PhotographicSensitivity' in tags: self.iso_speed = self.int_value( tags['EXIF PhotographicSensitivity']) elif 'EXIF ISOSpeedRatings' in tags: self.iso_speed = self.int_value( tags['EXIF ISOSpeedRatings']) if 'Image BitsPerSample' in tags: self.bits_per_sample = self.int_value( tags['Image BitsPerSample']) if 'EXIF DateTimeOriginal' in tags: str_time = tags['EXIF DateTimeOriginal'].values.encode( 'utf8') utc_time = datetime.strptime(str_time, "%Y:%m:%d %H:%M:%S") subsec = 0 if 'EXIF SubSecTime' in tags: subsec = self.int_value(tags['EXIF SubSecTime']) negative = 1.0 if subsec < 0: negative = -1.0 subsec *= -1.0 subsec = float('0.{}'.format(int(subsec))) subsec *= negative ms = subsec * 1e3 utc_time += timedelta(milliseconds=ms) timezone = pytz.timezone('UTC') epoch = timezone.localize(datetime.utcfromtimestamp(0)) self.utc_time = (timezone.localize(utc_time) - epoch).total_seconds() * 1000.0 except Exception as e: log.ODM_WARNING("Cannot read extended EXIF tags for %s: %s" % (_path_file, e.message)) # Extract XMP tags f.seek(0) xmp = self.get_xmp(f) for tags in xmp: try: band_name = self.get_xmp_tag( tags, ['Camera:BandName', '@Camera:BandName']) if band_name is not None: self.band_name = band_name.replace(" ", "") self.set_attr_from_xmp_tag( 'band_index', tags, [ 'DLS:SensorId', # Micasense RedEdge '@Camera:RigCameraIndex', # Parrot Sequoia, Sentera 21244-00_3.2MP-GS-0001 'Camera:RigCameraIndex', # MicaSense Altum ]) self.set_attr_from_xmp_tag( 'radiometric_calibration', tags, [ 'MicaSense:RadiometricCalibration', ]) self.set_attr_from_xmp_tag('vignetting_center', tags, [ 'Camera:VignettingCenter', 'Sentera:VignettingCenter', ]) self.set_attr_from_xmp_tag('vignetting_polynomial', tags, [ 'Camera:VignettingPolynomial', 'Sentera:VignettingPolynomial', ]) self.set_attr_from_xmp_tag('horizontal_irradiance', tags, ['Camera:HorizontalIrradiance'], float) self.set_attr_from_xmp_tag( 'irradiance_scale_to_si', tags, ['Camera:IrradianceScaleToSIUnits'], float) self.set_attr_from_xmp_tag('sun_sensor', tags, [ 'Camera:SunSensor', ], float) self.set_attr_from_xmp_tag('spectral_irradiance', tags, [ 'Camera:SpectralIrradiance', 'Camera:Irradiance', ], float) if 'DLS:Yaw' in tags: self.set_attr_from_xmp_tag('dls_yaw', tags, ['DLS:Yaw'], float) self.set_attr_from_xmp_tag('dls_pitch', tags, ['DLS:Pitch'], float) self.set_attr_from_xmp_tag('dls_roll', tags, ['DLS:Roll'], float) except Exception as e: log.ODM_WARNING("Cannot read XMP tags for %s: %s" % (_path_file, e.message)) # self.set_attr_from_xmp_tag('center_wavelength', tags, [ # 'Camera:CentralWavelength' # ], float) # self.set_attr_from_xmp_tag('bandwidth', tags, [ # 'Camera:WavelengthFWHM' # ], float) self.width, self.height = get_image_size.get_image_size(_path_file) # Sanitize band name since we use it in folder paths self.band_name = re.sub('[^A-Za-z0-9]+', '', self.band_name)
def parse_exif_values(self, _path_file): # Disable exifread log logging.getLogger('exifread').setLevel(logging.CRITICAL) with open(_path_file, 'rb') as f: tags = exifread.process_file(f, details=False) try: if 'Image Make' in tags: self.camera_make = tags['Image Make'].values.encode('utf8') if 'Image Model' in tags: self.camera_model = tags['Image Model'].values.encode( 'utf8') if 'GPS GPSAltitude' in tags: self.altitude = self.float_values( tags['GPS GPSAltitude'])[0] if 'GPS GPSAltitudeRef' in tags and self.int_values( tags['GPS GPSAltitudeRef'])[0] > 0: self.altitude *= -1 if 'GPS GPSLatitude' in tags and 'GPS GPSLatitudeRef' in tags: self.latitude = self.dms_to_decimal( tags['GPS GPSLatitude'], tags['GPS GPSLatitudeRef']) if 'GPS GPSLongitude' in tags and 'GPS GPSLongitudeRef' in tags: self.longitude = self.dms_to_decimal( tags['GPS GPSLongitude'], tags['GPS GPSLongitudeRef']) except IndexError as e: log.ODM_WARNING("Cannot read EXIF tags for %s: %s" % (_path_file, e.message)) # Extract XMP tags f.seek(0) xmp = self.get_xmp(f) # Find band name and camera index (if available) camera_index_tags = [ 'DLS:SensorId', # Micasense RedEdge '@Camera:RigCameraIndex', # Parrot Sequoia 'Camera:RigCameraIndex', # MicaSense Altum ] for tags in xmp: if 'Camera:BandName' in tags: cbt = tags['Camera:BandName'] band_name = None if isinstance(cbt, string_types): band_name = str(tags['Camera:BandName']) elif isinstance(cbt, dict): items = cbt.get('rdf:Seq', {}).get('rdf:li', {}) if items: band_name = " ".join(items) if band_name is not None: self.band_name = band_name.replace(" ", "") else: log.ODM_WARNING( "Camera:BandName tag found in XMP, but we couldn't parse it. Multispectral bands might be improperly classified." ) for cit in camera_index_tags: if cit in tags: self.band_index = int(tags[cit]) self.width, self.height = get_image_size.get_image_size(_path_file) # Sanitize band name since we use it in folder paths self.band_name = re.sub('[^A-Za-z0-9]+', '', self.band_name)