#!/usr/bin/env python # -*- coding: utf-8 -*- # TODO import as cv import cv2 import numpy as np import peakutils from .helpers import luminance from copy import deepcopy from . import contrasts # subclassing numpy ndarray # Vorgehen: https://docs.scipy.org/doc/numpy/user/basics.subclassing.html # resize Probleme https://sourceforge.net/p/numpy/mailman/message/12594801/ # andere ownership Probleme könne angeblich mit out= gelöst werden # "Use __new__ when you need to control the creation of a new instance. # Use __init__ when you need to control initialization of a new instance." class View(np.ndarray): """Core class for the representation of colour contrasts in Movies View is the basis class for specific ways to represent colour contrasts. It does hold the definitions of contrasts itself. Objects of class View subclass the numpy array class and hence inherit numpy methods. However, it is not recommended to use functions which manipulate the array in terms of structure. In this case some of the additional functions which are implemented by this class and its subclasses might not lead to reasonable results. Attributes: TODO Docstring komplettieren und Verfahren überprüfen """ def __new__(cls, frames, input_array=None): """instantiates the view class instantiation complies with the recommendation for subclassing numpy.ndarray Parameters ---------- frames : itten.movie.Frames input_array : itten.contrasts.View # TODO wie schaffte np lower case object contrast : String # Modifizieren Channel Integer to String frame_step : Int savefig : Boolean Returns ------- Object : View Empty numpy.ndarray of type View """ obj = input_array if type(obj) == np.ndarray: obj = np.asarray(input_array, dtype=np.uint32).view(cls).copy() else: input_array = np.empty((0, 3), dtype=np.uint32) obj = np.asarray(input_array).view(cls).copy() obj._frames = frames obj._contrast = 2 obj._frame_step = 10 obj._bins = 256 return obj def __array_finalize__(self, obj): if obj is None: return self._frames = getattr(obj, '_frames', None) self._contrast = getattr(obj, '_contrast', None) self._frame_step = getattr(obj, '_frame_step', None) self._bins = getattr(obj, '_bins', None) def __array_wrap__(self, out_arr, context=None): return np.ndarray.__array_wrap__(self, out_arr, context) def _get_ctrst_cls_name(self, param): """Returns corresponding class name for the contrast parameter value Parameters ---------- param : String String provided by the ctrst parameter in a call to the populate method Returns ------- cls_name : Object Class object which corresponds to the parameter """ cls_names = {'light_dark': 'LightDark', 'saturation': 'Saturation', 'hue': 'Hue', 'cold-warm': 'ColdWarm', 'complementary': 'Complementary'} cls = getattr(contrasts, cls_names[param]) return cls # subclassing subclass of numpy http://stackoverflow.com/questions/7342637/how-to-subclass-a-subclass-of-numpy-ndarray # TODO es gibt noch das Problem, dass numpy nach mehreren Berechnungen von drive eine max recursion Warnung ausgiebt, warum? Brauche ich __del__ class MultivariateSequence(View): def __new__(cls, frames, input_array=None): """Represents a movie contrast in terms of stacked histograms For each defined frame a histogram is calculated and each bin that exceeds threshold is considered in the output array Parameters ---------- Returns ------- Object : MultivariateSequence VHistStack is a 2-dimensional numpy array which contains the frame number, the bin number and a quantifier which represents the relative weight of the bin in the frame """ obj = View.__new__(cls, frames, input_array=input_array) obj._threshold = 60000 return obj def __array_finalize__(self, obj): if obj is None: return View.__array_finalize__(self, obj) self._threshold = getattr(obj, '_threshold', None) def populate(self, ctrst='light_dark', method='luminance', frm_stp=10, bins=16, thrsh=60000, start=1, end=0): """doc (aus __new__ zusammentragen) """ # set class properties self._contrast = ctrst self._method = method self._frame_step = frm_stp self._bins = bins self._threshold = thrsh # TODO dafür müssen erst getters und setters in movie.frames definiert werden # selef._frames.start = start # self._frames.end = end contrast_points = np.empty((0, 3), dtype=np.uint32) # pwd list sollte in Frames sein und hier nur durchlaufen werden for frm_nr in range(self._frames.start, self._frames.end, self._frame_step): pwd = self._frames.folder + self._frames.prefix + str(frm_nr) + '.png' img = cv2.imread(pwd) # BGR ctrst_cls = self._get_ctrst_cls_name(self._contrast) ctrst_img = ctrst_cls(img).ctrst shape = ctrst_img.shape ctrst_img = np.reshape(ctrst_img, (shape[0] * shape[1])) hist_value, _ = np.histogram(ctrst_img, bins=self._bins, range=(0, 256)) for bin_index, point in enumerate(hist_value): if int(point) > self._threshold: entry = np.array([[frm_nr, bin_index, point]], dtype=np.uint32) contrast_points = np.vstack((contrast_points, entry)) # irgendwie prüfen, ob ich contrast_points insgesamt durch self ersetzen kann contrast_points = np.asarray(contrast_points, np.uint32) shape = contrast_points.shape self.resize(shape, refcheck=False) self[:, :] = contrast_points contrast_points = None return deepcopy(self) # TODO does not create a new object class BivariateSequence(View): def __new__(cls, frames, input_array=None): """Represents a movie contrast in terms minbound maxbound values For each defined frame a histogram is calculated and each bin that exceeds threshold is considered in the output array Parameters ---------- Returns ------- Object : BivariateSequence """ obj = View.__new__(cls, frames, input_array=input_array) return obj def __array_finalize__(self, obj): if obj is None: return View.__array_finalize__(self, obj) # das kann ich jetzt auch mit quantilen machen def populate(self, variant='peak', ctrst='light_dark', method='luminance', frm_stp=10, bins=256): """Creates a scatterplot for the dynamic of contrast across movie frames variant: peak or bin (one of max/min peak; uper/lower mean) threshold: for the peak variant TODO smooth: smooth plot TODO """ # set class properties self._variant = variant self._contrast = ctrst self._method = method self._frame_step = frm_stp self._bins = bins # TODO um start und end in der Methode zu parametrisieren müssen erst # getters und setters in movie.frames definiert werden # self._frames.start = start # self._frames.end = end contrast_points = np.empty((0, 2), dtype=np.uint32) # sofern kein oder nur ein Peak gefunden wird, man könnte dann auch # noch einen Durchlauf mit geringeren thres und min_dist versuceh lastmin = 0 lastmax = 256 # pwd list sollte in Frames sein und hier nur durchlaufen werden for frm_nr in range(self._frames.start, self._frames.end, self._frame_step): pwd = self._frames.folder + self._frames.prefix + str(frm_nr) + '.png' img = cv2.imread(pwd) # BREAK: Funktioniert nicht if method == 'bin': hist_values = cv2.calcHist([img_hsv], [self._channel], None, [256], [0, 256]) hist_mins = hist_values[0:127].flatten().tolist() hist_maxs = hist_values[128:256].flatten().tolist() # Für jeden Frame wird jeder Kontrastwert mit der Anzahl der # Pixel für diesen Kontrastwert multipliziert # So wird der gesamte Kontrastwert des Frames sowie errechnet bin_total = 0 total_value = 0 for bin_id, points in enumerate(hist_mins): bin_total = (bin_id + 1) * points total_value += bin_total sum_mins = int(sum(hist_mins)) sum_maxs = int(sum(hist_maxs)) if int(total_value) == 0: total_value = [(bin_nr, value) for bin_nr, value in enumerate(hist_maxs) if int(value) > 0][0][0] mins.append(total_value) else: mins.append(int(total_value / sum_mins)) bin_total = 0 total_value = 0 for bin_id, points in enumerate(hist_maxs): bin_total = (bin_id + 1) * points total_value += bin_total # if there are no values in hist_max take highes bin with value # from hist_mins if int(total_value) == 0: total_value = [(bin_nr, value) for bin_nr, value in enumerate(hist_mins) if int(value) > 0][-1][0] maxs.append(total_value) else: maxs.append(int(total_value / sum(hist_maxs)) + 127) # peak variant else: ctrst_cls = self._get_ctrst_cls_name(self._contrast) ctrst_img = ctrst_cls(img).ctrst hist_value, _ = np.histogram(ctrst_img.flatten(), bins=self._bins, range=(0, 256)) peaks = peakutils.indexes(hist_value.flatten(), thres=0.2, min_dist=15) # Abfangen von von nur 1 oder keinem Peak if len(peaks) > 1: lastmin = peaks[0] lastmax = peaks[-1] contrast_points = np.vstack((contrast_points, [lastmin, lastmax])) elif len(peaks) == 1: # je nachdem ob der neue peak dem alten min oder max wert # näher liegt wird er dem einen oder Anderen zugeschlagen if (lastmax - peaks[0]) < (peaks[0] - lastmin): lastmax = peaks[0] contrast_points = np.vstack((contrast_points, [lastmin, lastmax])) else: lastmin = peaks[0] contrast_points = np.vstack((contrast_points, [lastmin, lastmax])) else: contrast_points = np.vstack((contrast_points, [lastmin, lastmax])) contrast_points = np.asarray(contrast_points, np.uint32) shape = contrast_points.shape self.resize(shape, refcheck=False) self[:, :] = contrast_points contrast_points = None class UnivariateSequence(View): def __new__(cls, frames, input_array=None): """Represents a movie contrast in terms of a single feature Parameters ---------- Returns ------- Object : BivariateSequence """ obj = View.__new__(cls, frames, input_array=input_array) obj.feature = None return obj def __array_finalize__(self, obj): if obj is None: return self._threshold = getattr(obj, 'feature', None) View.__array_finalize__(self, obj) # TODO später ersetzen durch numpy mean/deviation in Kombination mit anderen Möglichkeiten @staticmethod def meanmad(values): values = values.flatten() length = values.size points = np.sum(values) # range nicht array und histogram verschachtelt (siehe slice) bin_values = np.apply_along_axis(lambda x: x * values[x-1], 0, range(1, length+1)) mean = np.sum(bin_values) / points absolutes = np.apply_along_axis(lambda x: abs(x - mean) * values[x-1], 0, range(1, length+1)) mad = np.sum(absolutes) / points return mean, mad # TODO: Kurve muß unbeding von cuts bereinigt werde und interpolation funktioniert da nicht def seqmean(self, ctrst='light_dark', method='luminance', frm_stp=10, bins=256): """Creates a scatterplot for the dynamic of contrast across movie frames frm_fld: path to folder with movie images frm_pref: file nave in fron of the count value frm_step: take every x frame channel: channel in the HSV color space save: save plot also to disk """ # set class properties self.feature = 'mean' self._contrast = ctrst self._method = method self._frame_step = frm_stp self._bins = bins # TODO um start und end in der Methode zu parametrisieren müssen erst # getters und setters in movie.frames definiert werden # self._frames.start = start # self._frames.end = end contrast_points = np.empty((0), dtype=np.uint32) # pwd list sollte in Frames sein und hier nur durchlaufen werden for frm_nr in range(self._frames.start, self._frames.end, self._frame_step): pwd = self._frames.folder + self._frames.prefix + str(frm_nr) + '.png' img = cv2.imread(pwd) ctrst_cls = self._get_ctrst_cls_name(self._contrast) ctrst_img = ctrst_cls(img).ctrst hist_value, _ = np.histogram(ctrst_img.flatten(), bins=self._bins, range=(0, 256)) hist_value = hist_value.flatten() hist_value = hist_value.astype(np.uint32, copy=False) # eigenes mean und devi durch numpy ersetzt # TODO Ergebnisse der unterschiedlichen Verfahrenstimmt nicht überein # contrast = hist_value.mean() # deviation = hist_value.std() contrast, _ = UnivariateSequence.meanmad(hist_value) contrast_points = np.hstack((contrast_points, contrast)) contrast_points = np.asarray(contrast_points, np.uint32) shape = contrast_points.shape self.resize(shape, refcheck=False) self[:] = contrast_points contrast_points = None # TODO: Kurve muß unbeding von cuts bereinigt werde und interpolation funktioniert da nicht def seqmad(self, ctrst='light_dark', method='luminance', frm_stp=10, bins=256): """Creates a scatterplot for the dynamic of contrast across movie frames frm_fld: path to folder with movie images frm_pref: file nave in fron of the count value frm_step: take every x frame channel: channel in the HSV color space save: save plot also to disk """ # set class properties self.feature = 'absolute_deviation' self._contrast = ctrst self._method = method self._frame_step = frm_stp self._bins = bins # TODO um start und end in der Methode zu parametrisieren müssen erst # getters und setters in movie.frames definiert werden # self._frames.start = start # self._frames.end = end contrast_points = np.empty((0), dtype=np.uint32) # pwd list sollte in Frames sein und hier nur durchlaufen werden for frm_nr in range(self._frames.start, self._frames.end, self._frame_step): pwd = self._frames.folder + self._frames.prefix + str(frm_nr) + '.png' img = cv2.imread(pwd) ctrst_cls = self._get_ctrst_cls_name(self._contrast) ctrst_img = ctrst_cls(img).ctrst hist_value, _ = np.histogram(ctrst_img.flatten(), bins=self._bins, range=(0, 256)) hist_value = hist_value.flatten() hist_value = hist_value.astype(np.uint32, copy=False) # eigenes mean und devi durch numpy ersetzt # TODO Ergebnisse der unterschiedlichen Verfahrenstimmt nicht überein # contrast = hist_value.mean() # deviation = hist_value.std() _, deviation = UnivariateSequence.meanmad(hist_value) contrast_points = np.hstack((contrast_points, deviation)) contrast_points = np.asarray(contrast_points, np.uint32) shape = contrast_points.shape self.resize(shape, refcheck=False) self[:] = contrast_points contrast_points = None def seqper(self, ctrst='light_dark', method='luminance', perc=50, frm_stp=10, bins=256): """Creates a scatterplot for the dynamic of contrast across movie frames frm_fld: path to folder with movie images frm_pref: file nave in fron of the count value frm_step: take every x frame channel: channel in the HSV color space save: save plot also to disk """ # set class properties self.feature = 'percentile ' + str(perc) self._contrast = ctrst self._method = method self._frame_step = frm_stp self._bins = bins # TODO um start und end in der Methode zu parametrisieren müssen erst # getters und setters in movie.frames definiert werden # self._frames.start = start # self._frames.end = end contrast_points = np.empty((0), dtype=np.uint32) # pwd list sollte in Frames sein und hier nur durchlaufen werden for frm_nr in range(self._frames.start, self._frames.end, self._frame_step): pwd = self._frames.folder + self._frames.prefix + str(frm_nr) + '.png' img = cv2.imread(pwd) ctrst_cls = self._get_ctrst_cls_name(self._contrast) ctrst_img = ctrst_cls(img).ctrst percentile = np.percentile(ctrst_img.flatten(), perc) contrast_points = np.hstack((contrast_points, percentile)) contrast_points = np.asarray(contrast_points, np.uint32) shape = contrast_points.shape self.resize(shape, refcheck=False) self[:] = contrast_points contrast_points = None def seqvar(self, ctrst='light_dark', method='luminance', perc=50, frm_stp=10, bins=256): """Creates a scatterplot for the dynamic of contrast across movie frames frm_fld: path to folder with movie images frm_pref: file nave in fron of the count value frm_step: take every x frame channel: channel in the HSV color space save: save plot also to disk """ # set class properties self.feature = 'variance' self._contrast = ctrst self._method = method self._frame_step = frm_stp self._bins = bins # TODO um start und end in der Methode zu parametrisieren müssen erst # getters und setters in movie.frames definiert werden # self._frames.start = start # self._frames.end = end contrast_points = np.empty((0), dtype=np.uint32) # pwd list sollte in Frames sein und hier nur durchlaufen werden for frm_nr in range(self._frames.start, self._frames.end, self._frame_step): pwd = self._frames.folder + self._frames.prefix + str(frm_nr) + '.png' img = cv2.imread(pwd) ctrst_cls = self._get_ctrst_cls_name(self._contrast) ctrst_img = ctrst_cls(img).ctrst variance = np.var(ctrst_img.flatten()) contrast_points = np.hstack((contrast_points, variance)) contrast_points = np.asarray(contrast_points, np.uint32) shape = contrast_points.shape self.resize(shape, refcheck=False) self[:] = contrast_points contrast_points = None