123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- # 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
|