# import # ------------------------------------------------------------------------------------------ from .. import image import copy, functools import numpy as np import matplotlib.pyplot as plt # miam import import miam.math.Distance # ------------------------------------------------------------------------------------------ # MIAM project 2020 # ------------------------------------------------------------------------------------------ # author: remi.cozot@univ-littoral.fr # ------------------------------------------------------------------------------------------ class Histogram(object): """description of class""" def __init__(self, histValue, edgeValue, name, channel, logSpace = False): """ constructor """ self.name = name self.channel = channel self.histValue = histValue self.edgeValue = edgeValue self.logSpace = logSpace def __repr__(self): res = " Histogram{ name:" + self.name + "\n" + \ " nb bins: " + str(len(self.histValue)) + "\n" + \ " channel: " + str(self.channel.name)+"("+self.channel.colorSpace()+")" + "\n" + \ " logSpace: " + str(self.logSpace) + "\n }" return res def __str__(self): return self.__repr__() def normalise(self, norm=None): """ normalise histogram according to norm='probability' | 'dot' """ res = copy.deepcopy(self) if not norm: norm = 'probability' if norm == 'probability': sum = np.sum(res.histValue) res.histValue = res.histValue/sum elif norm == 'dot': dot2 = np.dot(res.histValue,res.histValue) res.histValue = res.histValue/np.sqrt(dot2) else: print("WARNING[miam.hisrogram.Histogram.normalise(",self.name,"): unknown norm:", norm,"!]") return res def build(img, channel, nbBins=100, range= None, logSpace = None): """ build an Histogram object from image @params: img - Required : input image from witch hsitogram will be build (miam.image.Image.Image) channel - Required : image channel used to build histogram (miam.image.channel.channel) nbBins - Optional : histogram number of bins (Int) range - Optional : range of histogram, if None min max of channel (Float,Float) logSpace - Optional : compute in log space if True, if None guess from image (Boolean) """ # logSpace if not logSpace: logSpace = 'auto' if isinstance(logSpace,str): if logSpace=='auto': if img.type == image.imageType.imageType.SDR : logSpace = False if img.type == image.imageType.imageType.HDR : logSpace = True elif not isinstance(logSpace,bool): logSpace = False channelVector = img.getChannelVector(channel) # range if not range: if channel.colorSpace() == 'Lab': range= (0.0,100.0) elif channel.colorSpace() == 'sRGB'or channel.colorSpace() == 'XYZ': range= (0.0,1.0) else: range= (0.0,1.0) print("WARNING[miam.hisrogram.Histogram.build(",img.name,"):", "colour space:",channel.colorSpace(), "not yet implemented > range(0.0,1.0)!]") # compute bins if logSpace: ((minR,maxR),(minG,maxG),(minB,maxB)) = img.getMinMaxPerChannel() minRGB = min(minR, minG, minB) maxRGB = max(maxR, maxG, maxB) #bins bins = 10 ** np.linspace(np.log10(minRGB), np.log10(maxRGB), nbBins+1) else: bins = np.linspace(range[0],range[1],nbBins+1) nphist, npedges = np.histogram(channelVector, bins) nphist = nphist/channelVector.shape return Histogram(nphist, npedges, img.name, #copy.deepcopy(img.colorSpace), channel, logSpace = logSpace ) def plot(self, ax,color='r', shortName =True,title=True): if not color : color = 'r' ax.plot(self.edgeValue[1:],self.histValue,color) if self.logSpace: ax.set_xscale("log") name = self.name.split("/")[-1]+"(H("+self.channel.name+"))"if shortName else self.name+"(Histogram:"+self.channel.name+")" if title: ax.set_title(name) def scale(alpha,h): res = copy.deepcopy(h) res.histValue = res.histValue * alpha return res def add(hu,hv): res = copy.deepcopy(hu) # check edges if (hu.edgeValue==hv.edgeValue).all(): # porceed to summation res.histValue = hu.histValue + hv.histValue else: # remap pass return res def computeDistance(hu,hv,distance=None): """ compute distance between histograms 'hu' and 'hv' according to distance 'distance' """ if not distance: # default distance is cosine distance = miam.math.Distance.Distance(miam.math.Distance.cosineDistance) res = 1 # some checking # histogram must have the same color space if hu.channel.name == hv.channel.name : # histogram must have the same number of bin if len(hu.histValue) == len(hv.histValue): res = distance.eval( hu.histValue, hv.histValue) else: print("WARNING[miam.hisrogram.Histogram.computeDistance(",str(hu),",",str(hv),"):", "have different length: return distance=1 !]") else : print("WARNING[miam.hisrogram.Histogram.computeDistance(",str(hu),",",str(hv),"):", "have different channels: return distance=1!]") return res def segmentPics(self, nbSegs=3): """ segment histogram by pics """ # local functions def isMaxWindow(a3): return ((a3[0] <= a3[1]) and (a3[1] >= a3[2])) def isMinWindow(a3): return ((a3[0] >= a3[1]) and (a3[1] <= a3[2])) def filterWindow(a3,weights): return (a3[0]*weights[0]+a3[1]*weights[1]+a3[2]*weights[2])/(weights[0]+weights[1]+weights[2]) def filter(v, weights): res = copy.deepcopy(v) res[0] = filterWindow([v[0],v[0],v[1]],weights) res[-1] = filterWindow([v[-2],v[-1],v[-1]],weights) for i in range(1, len(v)-1): res[i] = filterWindow(v[(i-1):(i+2)],weights) return res def getSegmentBoundaries(v): seg = [] seg.append(0) # add fist for i in range(1,len(v)-2): isMin = isMinWindow(v[i-1:i+2]) isMax = isMaxWindow(v[i-1:i+2]) if isMin and not isMax: seg.append(i) seg.append(len(v)-1) # add last return seg value = copy.deepcopy(self.histValue) while (len(getSegmentBoundaries(value))-1)> nbSegs: weights = [1,1,1] newValue = filter(value, weights) newnbs = len(getSegmentBoundaries(newValue))-1 # next iter value = newValue # plot for debug #segmentBoundaries = getSegmentBoundaries(value) #plt.figure("segments") #plt.plot(self.histValue,'k--') #for i in range(len(segmentBoundaries)): # plt.plot(segmentBoundaries[i],self.histValue[segmentBoundaries[i]],'ro') #plt.show(block=True) #print(segmentBoundaries) # index of boundaries segmentBoundaries = getSegmentBoundaries(value) # values of boundaries segmentBoundariesValues = list(map(lambda i: self.edgeValue[i] if i< len(self.edgeValue)/2 else self.edgeValue[i+1] ,segmentBoundaries)) return segmentBoundariesValues