#!/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 # from . import movie # 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 base 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 = 4 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 = {'monochromatic': 'Monochromatic', 'saturation': 'Saturation', 'hue': 'Hue', 'cold-warm': 'ColdWarm', 'complementary': 'Complementary'} cls = getattr(contrasts, cls_names[param]) return cls # scheint niemals verwendet zu werden def frms_from_indcs(self, indices): """results frame numbers from a list of indices""" indices = np.add(indices, 1) frm_nrs = np.multiply(indices, self._frame_step) frm_nrs = np.subtract(frm_nrs, self._frame_step) return frm_nrs.flatten() # subclassing subclass of numpyhttp://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 ---------- TODO threshold : the minimum number of pixels that need to appear in a certain bin so that the bin will be included in the output data. (Needs to be modifiable in relation to the total number of pixels in the frame, that means on the frame size 240p60 etc.) Oder Threshold ganz raus, weil das eigentlich eine Sache der Visualisierung ist auszusortieren. 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 = 6000 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='monochromatic', method='luminance', frm_stp=4, bins=16, thrsh=6000, 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 # self._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): # FIX for legacy numbering scheme of files 1 instead of 0001 # TODO remove this ridiculous fix and the whole pwd building which # now solved by having implemented sorted(Path()…) in Frames pwd = "" if '_0' in str(self._frames.frames[0]): pwd = self._frames.folder + self._frames.prefix pwd += '{0:05d}'.format(frm_nr) + '.png' else: pwd = self._frames.folder + self._frames.prefix + str(frm_nr) pwd += '.png' img = cv2.imread(pwd) # BGR # Kontrast Parameternamen auf Klassennamen mappen ctrst_cls = self._get_ctrst_cls_name(self._contrast) ctrst_img = ctrst_cls(img, self._method).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 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) 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='monochromatic', method='luminance', frm_stp=4, 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 = "" if '_0' in str(self._frames.frames[0]): pwd = self._frames.folder + self._frames.prefix pwd += '{0:05d}'.format(frm_nr) + '.png' else: pwd = self._frames.folder + self._frames.prefix + str(frm_nr) pwd += '.png' img = cv2.imread(pwd) ctrst_cls = self._get_ctrst_cls_name(self._contrast) ctrst_img = ctrst_cls(img, method=self._method).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='monochromatic', 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 = "" if '_0' in str(self._frames.frames[0]): pwd = self._frames.folder + self._frames.prefix pwd += '{0:05d}'.format(frm_nr) + '.png' else: pwd = self._frames.folder + self._frames.prefix + str(frm_nr) pwd += '.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='monochromatic', 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 = "" if '_0' in str(self._frames.frames[0]): pwd = self._frames.folder + self._frames.prefix pwd += '{0:05d}'.format(frm_nr) + '.png' else: pwd = self._frames.folder + self._frames.prefix + str(frm_nr) pwd += '.png' img = cv2.imread(pwd) ctrst_cls = self._get_ctrst_cls_name(self._contrast) ctrst_img = ctrst_cls(img, method=self._method).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='monochromatic', 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 = "" if '_0' in str(self._frames.frames[0]): pwd = self._frames.folder + self._frames.prefix pwd += '{0:05d}'.format(frm_nr) + '.png' else: pwd = self._frames.folder + self._frames.prefix + str(frm_nr) pwd += '.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