def from_image_file(self, formats=[], max_size_above_full=200): ''' Args: ident (str): The URI for the image. formats ([str]): The derivative formats the application can produce. ''' # Assumes that the image exists and the format is supported. Exceptions # should be raised by the resolver if that's not the case. self.tiles = [] self.sizes = None self.scaleFactors = None profile_description = { 'formats': formats, 'supports': OPTIONAL_FEATURES[:], } if (max_size_above_full == 0) or (max_size_above_full > 100): profile_description['supports'].append('sizeAboveFull') self.profile = Profile(compliance_uri=COMPLIANCE, description=profile_description) if self.src_format == 'jp2': self._from_jp2(self.src_img_fp) elif self.src_format in ('jpg', 'tif', 'png'): self._extract_with_pillow(self.src_img_fp) else: raise ImageInfoException( "Didn't get a source format, or at least one we recognize (%r)." % self.src_format) # in case of ii = ImageInfo().from_image_file() return self
def __init__(self, app=None, ident="", src_img_fp="", src_format="", extra={}): self.protocol = PROTOCOL self.ident = ident self.src_img_fp = src_img_fp self.src_format = src_format self.attribution = None self.logo = None self.license = None self.service = {} self.auth_rules = extra for (k, v) in extra.get('extraInfo', {}).items(): setattr(self, k, v) # If constructed from JSON, the pixel info will already be processed if app: try: formats = app.transformers[src_format].target_formats except KeyError: m = 'Didn\'t get a source format, or at least one we recognize ("%s")' % src_format raise ImageInfoException(500, m) # Finish setting up the info from the image file self.from_image_file(formats, app.max_size_above_full)
def from_image_file(self, formats=[], max_size_above_full=200): ''' Args: ident (str): The URI for the image. formats ([str]): The derivative formats the application can produce. ''' # Assumes that the image exists and the format is supported. Exceptions # should be raised by the resolver if that's not the case. self.tiles = [] self.sizes = None self.scaleFactors = None local_profile = {'formats': formats, 'supports': OPTIONAL_FEATURES[:]} if max_size_above_full == 0 or max_size_above_full > 100: local_profile['supports'].append('sizeAboveFull') self.profile = [COMPLIANCE, local_profile] if self.src_format == 'jp2': self._from_jp2(self.src_img_fp) elif self.src_format in ('jpg', 'tif', 'png'): self._extract_with_pillow(self.src_img_fp) else: m = 'Didn\'t get a source format, or at least one we recognize ("%s")' % self.src_format raise ImageInfoException(http_status=500, message=m) # in case of ii = ImageInfo().from_image_file() return self
def __init__(self, app=None, service=None, attribution=None, license=None, logo=None, src_img_fp="", src_format="", auth_rules=None): self.src_img_fp = src_img_fp self.src_format = src_format self.attribution = attribution self.logo = logo self.license = license self.service = service or {} self.auth_rules = auth_rules or {} # If constructed from JSON, the pixel info will already be processed if app: try: formats = app.transformers[src_format].target_formats except KeyError: raise ImageInfoException( "Didn't get a source format, or at least one we recognize (%r)." % src_format) # Finish setting up the info from the image file self.from_image_file(formats, app.max_size_above_full)
def _from_jp2(self, fp): '''Get info about a JP2. ''' logger.debug('Extracting info from JP2 file: %s' % fp) self.profile.description['qualities'] = ['default', 'bitonal'] with open(fp, 'rb') as jp2: try: self.extract_jp2(jp2) except JP2ExtractionError as err: logger.warning("Error extracting JP2 %s: %r", fp, str(err)) raise ImageInfoException("Invalid JP2 file")
def __init__(self, app=None, ident="", src_img_fp="", src_format="", extra={}): self.protocol = PROTOCOL self.ident = ident self.src_img_fp = src_img_fp self.src_format = src_format self.attribution = None self.logo = None self.license = None self.service = {} self.auth_rules = extra # The extraInfo parameter can be used to override specific attributes. # If there are extra attributes, drop an error. bad_attrs = [] for (k, v) in extra.get('extraInfo', {}).items(): try: setattr(self, k, v) except AttributeError: bad_attrs.append(k) if bad_attrs: raise ImageInfoException("Invalid parameters in extraInfo: %s." % ', '.join(bad_attrs)) # If constructed from JSON, the pixel info will already be processed if app: try: formats = app.transformers[src_format].target_formats except KeyError: raise ImageInfoException( "Didn't get a source format, or at least one we recognize (%r)." % src_format) # Finish setting up the info from the image file self.from_image_file(formats, app.max_size_above_full)
def _from_jp2(self, fp): '''Get info about a JP2. ''' logger.debug('Extracting info from JP2 file: %s' % fp) self.profile[1]['qualities'] = ['default', 'bitonal'] scaleFactors = [] with open(fp, 'rb') as jp2: #check that this is a jp2 file initial_bytes = jp2.read(24) if (not initial_bytes[:12] == '\x00\x00\x00\x0cjP \r\n\x87\n') or \ (not initial_bytes[16:] == 'ftypjp2 '): logger.warning('Invalid JP2 file at %s', fp) raise ImageInfoException('Invalid JP2 file') #grab width and height window = deque([], 4) b = jp2.read(1) while ''.join(window) != 'ihdr': b = jp2.read(1) c = struct.unpack('c', b)[0] window.append(c) self.height = int(struct.unpack(">I", jp2.read(4))[0]) # height (pg. 136) self.width = int(struct.unpack(">I", jp2.read(4))[0]) # width logger.debug("width: %s", self.width) logger.debug("height: %s", self.height) # Figure out color or grayscale. # Depending color profiles; there's probably a better way (or more than # one, anyway.) # see: JP2 I.5.3.3 Colour Specification box while ''.join(window) != 'colr': b = jp2.read(1) c = struct.unpack('c', b)[0] window.append(c) colr_meth = struct.unpack('B', jp2.read(1))[0] logger.debug('colr METH: %d', colr_meth) # PREC and APPROX, 1 byte each colr_prec = struct.unpack('b', jp2.read(1))[0] colr_approx = struct.unpack('B', jp2.read(1))[0] logger.debug('colr PREC: %d', colr_prec) logger.debug('colr APPROX: %d', colr_approx) if colr_meth == 1: # Enumerated Colourspace self.color_profile_bytes = None enum_cs = int(struct.unpack(">HH", jp2.read(4))[1]) logger.debug('Image contains an enumerated colourspace: %d', enum_cs) logger.debug('Enumerated colourspace: %d', enum_cs) if enum_cs == 16: # sRGB self.profile[1]['qualities'] += ['gray', 'color'] elif enum_cs == 17: # grayscale self.profile[1]['qualities'] += ['gray'] elif enum_cs == 18: # sYCC pass else: msg = 'Enumerated colourspace is neither "16", "17", or "18". ' msg += 'See jp2 spec pg. 139.' logger.warn(msg) elif colr_meth == 2: # (Restricted ICC profile). logger.debug('Image contains a restricted, embedded colour profile') # see http://www.color.org/icc-1_1998-09.pdf, page 18. self.assign_color_profile(jp2) else: logger.warn('colr METH is neither "1" or "2". See jp2 spec pg. 139.') # colr METH 3 = Any ICC method, colr METH 4 = Vendor Colour method # See jp2 spec pg. 182 - Table M.24 (Color spec box legal values) if colr_meth <= 4 and -128 <= colr_prec <= 127 and 1 <= colr_approx <= 4: self.assign_color_profile(jp2) logger.debug('qualities: %s', self.profile[1]['qualities']) window = deque(jp2.read(2), 2) while map(ord, window) != [0xFF, 0x4F]: # (SOC - required, see pg 14) window.append(jp2.read(1)) while map(ord, window) != [0xFF, 0x51]: # (SIZ - required, see pg 14) window.append(jp2.read(1)) jp2.read(20) # through Lsiz (16), Rsiz (16), Xsiz (32), Ysiz (32), XOsiz (32), YOsiz (32) tile_width = int(struct.unpack(">I", jp2.read(4))[0]) # XTsiz (32) tile_height = int(struct.unpack(">I", jp2.read(4))[0]) # YTsiz (32) logger.debug("tile width: %s", tile_width) logger.debug("tile height: %s", tile_height) self.tiles.append( { 'width' : tile_width } ) if tile_width != tile_height: self.tiles[0]['height'] = tile_height jp2.read(10) # XTOsiz (32), YTOsiz (32), Csiz (16) window = deque(jp2.read(2), 2) # while (ord(b) != 0xFF): b = jp2.read(1) # b = jp2.read(1) # 0x52: The COD marker segment while map(ord, window) != [0xFF, 0x52]: # (COD - required, see pg 14) window.append(jp2.read(1)) jp2.read(7) # through Lcod (16), Scod (8), SGcod (32) levels = int(struct.unpack(">B", jp2.read(1))[0]) logger.debug("levels: %s", levels) scaleFactors = [pow(2, l) for l in range(0,levels+1)] self.tiles[0]['scaleFactors'] = scaleFactors jp2.read(4) # through code block stuff # We may have precincts if Scod or Scoc = xxxx xxx0 # But we don't need to examine as this is the last variable in the # COD segment. Instead check if the next byte == 0xFF. If it is, # we don't have a Precint size parameter and we've moved on to either # the COC (optional, marker = 0xFF53) or the QCD (required, # marker = 0xFF5C) b = jp2.read(1) if ord(b) != 0xFF: if self.tiles[0]['width'] == self.width \ and self.tiles[0].get('height') in (self.height, None): # Clear what we got above in SIZ and prefer this. This could # technically break as it's possible to have precincts inside tiles. # Let's wait for that to come up.... self.tiles = [] for level in range(levels+1): i = int(bin(struct.unpack(">B", b)[0])[2:].zfill(8),2) x = i&15 y = i >> 4 w = 2**x h = 2**y b = jp2.read(1) try: entry = next((i for i in self.tiles if i['width'] == w)) entry['scaleFactors'].append(pow(2, level)) except StopIteration: self.tiles.append({'width':w, 'scaleFactors':[pow(2, level)]}) self.sizes = [ {'width': width, 'height': height} for width, height in self.sizes_for_scales(scaleFactors) ] self.sizes.sort(key=lambda size: max([size['width'], size['height']]))