Source code for imm.imagedata

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
The imagedata module handles reading images, perhaps converting images, and writing image data to a text file.
Typically a text file that is a Python module.

"""

__author__  = 'E.R. Uber'
__email__   = 'eruber@gmail.com'
__license__ = 'ISCL'
__version__ = '2.1.0'

#----------------------------------------------------------------------------------------
import os
import sys
import logging
from io import BytesIO
import re
import random
import string

#----------------------------------------------------------------------------------------
from logging import NullHandler
from datetime import datetime as dt

#----------------------------------------------------------------------------------------
from PIL import Image, ImageTk

#----------------------------------------------------------------------------------------
APPEND_MODE = 'APPEND'
WRITE_MODE  = 'WRITE'

WRITE_MODE_NAMES = ( WRITE_MODE, APPEND_MODE)

WRITE_MODES = ('wb', 'ab')

WRITE_MODE_MAP = { 
                    WRITE_MODE_NAMES[0] : WRITE_MODES[0],
                    WRITE_MODE_NAMES[1] : WRITE_MODES[1]
                  }

NEW_LINE = '\n'

#----------------------------------------------------------------------------------------
[docs]def make_string_valid_python_identifier(s): """ Modify the input string, s, until we can return a valid Python identifier. The process is as follows: 1. Replace all spaces and dashes with underscores 2. Remove any invalid Python identifier characters (any char that is not 0-9, a-z, A-Z, or underscore) 3. If an empty identifier remains, generate a random identifier 4. If the identifier begins with a digit, prefix an underscore 5. If the identifier begins with an underscore, prefix the string 'image' """ # Replace all spaces and dashes with underscores variableName = s.replace(' ', '_') variableName = s.replace('-', '_') # Remove non-python identifier characters # See: http://stackoverflow.com/questions/3303312/how-do-i-convert-a-string-to-a-valid-variable-name-in-python # Remove invalid characters -- any character that is not a number, letter, or underscore variableName = re.sub('[^0-9a-zA-Z_]', '', variableName) if len(variableName) == 0: # create a random name variableName = '_' + id_generator() if variableName[0].isdigit(): variableName = '_' + variableName if variableName.startswith('_'): variableName = 'image' + variableName return(variableName)
#----------------------------------------------------------------------------------------
[docs]def id_generator(size=7, chars=string.ascii_lowercase + string.digits): """ Returns a random python identifier that is size characters in length and composed of characters from the set of chars. """ # See: http://pythontips.com/2013/07/28/generating-a-random-string/ # See: http://stackoverflow.com/questions/2257441/random-string-generation-with-upper-case-letters-and-digits-in-python id = ''.join(random.choice(chars) for x in range(size)) return(id)
#---------------------------------------------------------------------------------------- class IllegalFileIOWriteModeError(Exception): pass #----------------------------------------------------------------------------------------
[docs]class Generator(object): """ This class implements a context manager for opening, or using a previouisly opened, Python module text file and writing image data to it. :param output: Can be a string naming the output text file to be created or it can be an already opened output file object. If not specificed, a string is assumed and the default value is "gfxmodule.py". :param writemode: A string defining the write mode. Legal values are 'WRITE' and 'APPEND', the default is 'WRITE'. :param encoding: A string defining the write encoding of the output. Defaults to 'utf-8', Note that if the output parameter represents an already opened output file object, then this context manager does not own the output file object resource and will therefore not close it upon exiting the context manager's with statement code block. """ def __init__(self, output='gfxmodule.py', writemode=WRITE_MODE_NAMES[0], encoding='utf-8'): # Get a logger descended from the root logger, which is found by logger.getLogger() self._logr = logging.getLogger().getChild(__name__) # This library is using a null handler so that if the user does not have logging configured, # the default behavior of WARNING or above being printed to sys.stderr does not happen. # See: https://docs.python.org/3/howto/logging.html#configuring-logging-for-a-library self._logr.addHandler(logging.NullHandler()) if hasattr(output, "read"): self._logr.info("Output already opened by user, not owned by this context") # output is an already opened file object self._output_file_stream = output self._output_file = "<ALREADY OPENED BY USER>" # This context does NOT own the output file stream so # it should not close it self._close_on_context_exit = False else: self._logr.info("Output will be owned by this context") # output is a string naming a file to be opened self._output_file_stream = None self._output_file = output self._close_on_context_exit = True if writemode in WRITE_MODES: self._write_mode = writemode elif writemode in WRITE_MODE_NAMES: self._write_mode = WRITE_MODE_MAP[writemode] else: raise IllegalFileIOWriteModeError("Input parameter 'writemode' should be one of %s, but is %s" % (WRITE_MODE_NAMES, writemode)) self._image_file = None self._image_var_name = None self._encoding = encoding def __enter__(self): """ The enter method to make this class a context manager. """ return(self) def __exit__(self, exc_type, exc_val, exc_tb): """ The exit method to make this class a context manager. """ if self._close_on_context_exit: # This context manager owns this context's resource, so we can close it self._logr.debug("Attempting to close the output stream") self.close()
[docs] def write(self, imagefile, imagevarname=None): """ This method writes the image data read from imagefile to the output Python module text file. :param imagefile: A string specifying the image file name to read. :param imagevarname: A string specifying the image data's variable name. If imagefile is NOT a .png file, then it will be converted in memory to a PNG image before being written to the output Python module text file. If imagevarname is NOT specified, then a legal Python variable name will be derived from the imagefile name. If no legal Python identifier can be derived from the image file name, the a random identifer will be generated. """ self._image_file = os.path.abspath(imagefile) self._image_var_name = imagevarname if self._image_var_name is None: # If no image variable name is specified, we do our best to derive one from the # image file name path, filename_with_ext = os.path.split(self._image_file) filename_with_no_ext, ext = os.path.splitext(filename_with_ext) self._image_var_name = make_string_valid_python_identifier(filename_with_no_ext) if self._output_file_stream is None: try: self._logr.debug("Opening output file '%s' write stream in mode '%s'" % (self._output_file, self._write_mode)) self._output_file_stream = open(self._output_file, self._write_mode) except (OSError, IOError) as e: self._logr.exception(e) raise imageBuf = BytesIO() try: self._logr.debug("Reading image file '%s' with PIL.Image.open()" % self._image_file) img = Image.open(self._image_file) # Save the opened image to an in memory bytes buffer as a PNG image self._logr.debug("Saving image object in PNG format to a bytes buffer in memory.") img.save(imageBuf, 'PNG') # image data to write to module text file self._image_data = imageBuf.getvalue() self._image_var_name = "%s_data" % self._image_var_name.lower() self._logr.info("Writing image data as variable '%s' to output file '%s'" % (self._image_var_name, self._output_file)) dataRef = "%s = " % self._image_var_name self._output_file_stream.write(bytes(dataRef.encode(self._encoding))) self._output_file_stream.write(bytes(repr(self._image_data).encode(self._encoding))) self._output_file_stream.write(bytes(NEW_LINE.encode(self._encoding))) except Exception as e: self._logr.exception(e) raise finally: imageBuf.close()
[docs] def close(self): """ Close the output write stream. If imm.imagedata.Generator() is used has a context manager, this close() method will be called automatically if necessary upon exit of the context's with block. """ if self._output_file_stream: self._output_file_stream.close() self._output_file_stream = None self._logr.debug("Closed output file '%s' write stream" % self._output_file) else: self._logr.debug("The output file '%s' write stream is ALREADY CLOSED" % self._output_file)
if __name__ == "__main__": pass