import imageio
import numpy as np
from typing import List
import json
from json import JSONEncoder
import geojson
from PIL import Image
from io import BytesIO
import base64
from skimage import img_as_float, img_as_ubyte
from PIL import Image
from io import BytesIO
import base64
from skimage import img_as_float, img_as_ubyte
[docs]class SimpleDTO(object):
"""Base class used to convert objects
to JSON notation facilitating the
communication between front
and back ends.
"""
def __init__(self):
pass
[docs] def toJSON(self, compact=True):
"""
Rewrites the instance as a JSON object
Parameters
----------
compact=True : boolean
If true line-breaks and whitespaces
will not be included.
Returns
-------
str representation of the object
"""
ret = None
if compact:
ret = json.dumps(self, default=lambda o: o.__dict__,
sort_keys=True, separators=(',', ':'))
else:
ret = json.dumps(self, default=lambda o: o.__dict__,
sort_keys=True, indent=4)
print(ret)
return ret
[docs]class CustomJSONEncoder(JSONEncoder):
"""
Convenience class used to return JSON
responses in the views.
"""
[docs] def default(self, o):
return o.__dict__
[docs]class ProcessedImageData():
"""
Represents the result object derived from
some subclass of :py:class:`django_website.MapMiners.MapMiner.MapMiner`.
fields:
- id=None : str
Representation of the geoImage used
to referentiate it at the backend.
- imageData=None : str
A base64 encoded sting or an url
to the actual image. Depends on
the property imageDataType.
- imageDataType=None : str
data:image/jpeg;base64 if the imageData is base64 encoded
URL if the imageData is a url to the actual image.
- filterId=None: str
The identification (filterId property) of the
:py:class:`django_website.ImageFilters.ImageFilter.ImageFilter`
subclass used to generate this ProcessedImageData.
- density=-1 : float
This property can be used as a quantitative metric
about the processed image (e.g. ammount of greenery
in the processed image).
- isPresent=None : boolean
This property can be used as a flag indicating
the presence of some feature found in the
processed image.
"""
def __init__(self):
self.id = None
self.imageData = None
self.imageDataType = None
self.filterId = None
self.density = -1
self.isPresent = None
[docs]class GeoImage():
"""
Object responsible for keeping image and panorama's data
fields:
- id : str
Used to syncronize calls to the image filtering endpoint (see :class:`ImageFilter`).
- data : str
Used to hold either the image from this location, (encoded as a base64 string) or an url to it.
- dataType : str
Used to distinguish when data is a base64 string encoding an image or an url to it.
dataType will be 'URL' in the latter case and 'data:image/jpeg;base64' otherwise.
- location : <geojson.Point>
Represents the geographical location of this object.
A <geojson.Point> is represented by its 'coordinates' property which are in turn
a tuple with exactly 2 or 3 values.
- heading : float
Represents the horizontal angle of the image.
That is the azimuth, of some spherical coordinate system, of the camera
in the moment that the picture was taken.
- pitch : float
Represents the vertical angle of the image,
that is the altitude of a spherical coordinate system, of the camera
in the moment that the picture was taken.
- metadata : dict
Represents data not directly related to the GeoImage but usefull
for relating it to other GeoImages. For example, the metadata
may contain information about other GeoImages in the same
location, or perhaps the timestamp of when the picture
was taken.
- processedDataList : dict[str, :class:`ProcessedImageData`]
Contains the list of ProcessedImageData objects related
to this GeoImage. For example, after being processed
by the :class:`GreeneryFilter` class a GeoImage will
contain as one of its processedDataList members the
'greenery' key to a ProcessedImageData containing
the results of the greenery filter applied over
this GeoImage.
"""
def __init__(self):
self.id = None
self.data = None
self.dataType = None
self.location = geojson.Point()
self.heading = 0
self.pitch = 0
self.metadata = {} #Json Structured dict
self.processedDataList = {} #Dictionary containing ProcessedData objects as values and filterId's as keys
[docs] @classmethod
def fromJSON(cls, jsonData: dict):
"""
Helper method used to instantiate a GeoImage
from its JSON representation (encoded as a dict).
Parameters
----------
jsonData: dict
The dict representing the GeoImage to be
instantiated.
Returns
-------
A new instance of :class:`GeoImage`
"""
geoImage = cls()
geoImage.id = jsonData.get('id')
geoImage.data = jsonData.get('data')
geoImage.dataType = jsonData.get('dataType')
geoImage.location = jsonData.get('location', geojson.Point())
geoImage.heading = jsonData.get('heading', 0)
geoImage.pitch = jsonData.get('pitch', 0)
geoImage.metadata = jsonData.get('metadata', {}) or {}
geoImage.processedDataList = jsonData.get('processedDataList', {}) or {}
#if geoImage.dataType == 'URL':
# geoImage.setDataFromImage(imageio.imread(geoImage.data))
return geoImage
#def setDataFromImage(self, data: imageio.core.util.Image):
# self.data = data
# self.dataType = 'ndarray'
# if data.dtype == 'uint8':
# self.data = img_as_float(self.data)
# self.size = {'width': 0, 'height': 0, 'channels': 0}
# #self.size['channels'] = data.ndim
# #self.size['width'], self.size['height'], *_ = data.shape
# self.size['width'], self.size['height'], self.size['channels'] = data.shape
#def setDataFromBase64(self, data: str):
# image = np.array(Image.open(BytesIO(base64.b64decode(data))))
# self.data = data
# self.dataType = 'ndarray'
# if data.dtype == 'uint8':
# self.data = img_as_float(self.data)
# self.size = {'width': 0, 'height': 0, 'channels': 0}
# self.size['width'], self.size['height'], self.size['channels'] = data.shape
#def setDataToBase64(self):
# """Converts the 'data' property from 'ndarray' to a jpeg image encoded in a base64 string"""
# if self.dataType == 'ndarray':
# self.data = Image.fromarray(img_as_ubyte(self.data))
# elif self.dataType == 'data:image/jpeg;base64':
# return
# #buff = BytesIO()
## #self.data.save(buff, format="JPEG")
# #base64_image_string = base64.b64encode(buff.getvalue()).decode("utf-8")
# #self.data = base64_image_string
# self.data = GeoImage.imageToBase64JPEG(self.data)
# self.dataType = 'data:image/jpeg;base64'
[docs] @staticmethod
def imageToBase64JPEG(inputImage: Image):
"""
Used to encode a PIL Image into a base64 string.
Parameters
----------
inputImage: PIL.Image
The image to be encoded
Returns
-------
A base64 encoded string representing the input image.
"""
buff = BytesIO()
inputImage.save(buff, format="JPEG")
return base64.b64encode(buff.getvalue()).decode("utf-8")
[docs] def setProcessedData(self, filterId: str, type: str, imageData=None, density=-1, isPresent=None):
"""
Sets or updates a ProcessedData object (identified by its filterId) from the ProcessedDataDict
Parameters
----------
filterId : str
Identifies the ProcessedData object with the id
of the :class:`ImageFilter` subclass used.
type: str
Defines the image format ('ndarray' or None)
imageData=None: Any
Image's pixel data, defaults to None. But can be
a Numpy.ndarray or a base64 string
density=-1: float
Defines how much of a feature is present in an image (eg. greenery), defaults to -1
isPresent=None: boolean
Defines if a feature exists in the image (eg. Poles), defaults to None
"""
pImageData = ProcessedImageData()
if type == 'ndarray':
imageData = Image.fromarray(img_as_ubyte(imageData))
imageData = GeoImage.imageToBase64JPEG(imageData)
pImageData.imageData = f'data:image/jpeg;base64,{imageData}'
pImageData.filterId = filterId
pImageData.density = density
pImageData.isPresent = isPresent
self.processedDataList[filterId] = pImageData
# def getPNG(self):
# """Converts the image to a PNG file"""
# outdata = self.data.copy()
# if outdata.dtype != 'uint8':
# outdata = np.uint8(outdata*255)
# return imageio.imwrite(imageio.RETURN_BYTES, outdata, format='PNG-PIL')