diff --git a/README.md b/README.md index d2c27ff..30a754c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,13 @@ # trackpointers Implementations of trackpointer methods. Track object by giving track point or track coordinate frame. -## installation instruction +## Install + +Install the following repositories from the source: + +- [improcessor](https://github.com/ivapylibs/improcessor) +- [detector](https://github.com/ivapylibs/detector.git) +- [Lie](https://github.com/ivapylibs/Lie) ``` git clone git@github.com:ivapylibs/trackpointer.git @@ -11,5 +17,5 @@ pip3 install -e trackpointer/ The test files are shell command line executable and should work when invoked, presuming that pip installation has been performed. If no modifications to the source code will be performed then the ``-e`` flag -is not neessary (e.g., use the flag if the underlying code will be +is not necessary (e.g., use the flag if the underlying code will be modified). diff --git a/testing/fakeTri02.py b/testing/fakeTri02.py index afe7561..8431906 100644 --- a/testing/fakeTri02.py +++ b/testing/fakeTri02.py @@ -33,8 +33,9 @@ import numpy as np import matplotlib.pyplot as plt -from fakeTriangle import fakeTriangle +from trackpointer.utils.fakeTriangle import fakeTriangle import Lie.group.SE2.Homog + #==[1] Specify the marker geometry on the rigid body, and other related # parameters. Instantiate the simulated image generator. # @@ -83,4 +84,4 @@ plt.draw() # -#=============================== fakeTri02 =============================== \ No newline at end of file +#=============================== fakeTri02 =============================== diff --git a/testing/trackTri01.py b/testing/trackTri01.py index 94d0a13..bbe50a9 100755 --- a/testing/trackTri01.py +++ b/testing/trackTri01.py @@ -37,7 +37,7 @@ import numpy as np import matplotlib.pyplot as plt -from fakeTriangle import fakeTriangle +from trackpointer.utils.fakeTriangle import fakeTriangle import Lie.group.SE2.Homog import improcessor.basic as improcessor import detector.inImage as detector @@ -106,7 +106,7 @@ dI = binDet.Ip trackptr.process(dI) - tstate = trackptr.getstate() + tstate = trackptr.getState() # @todo # There is no setting in the trackTri01.m to enable the display of the tracker as it uses @@ -114,10 +114,10 @@ # For now, we manully set the state. if ii==0: # Start tracking - trackptr.setstate(tstate.tpt) + trackptr.setState(tstate) plt.cla() - trackptr.displayState(tstate) + trackptr.displayState() plt.imshow(I, cmap='Greys') plt.pause(0.001) diff --git a/testing/trackTri02.py b/testing/trackTri02.py index d6f773d..d86a8b6 100755 --- a/testing/trackTri02.py +++ b/testing/trackTri02.py @@ -36,7 +36,7 @@ import numpy as np import matplotlib.pyplot as plt -from fakeTriangle import fakeTriangle +from trackpointer.utils.fakeTriangle import fakeTriangle import Lie.group.SE2.Homog import improcessor.basic as improcessor import detector.inImage as detector @@ -123,7 +123,7 @@ dI = binDet.Ip trackptr.process(dI) - tstate = trackptr.getstate() + tstate = trackptr.getState() # @todo # There is no setting in the trackTri01.m to enable the display of the tracker as it uses @@ -131,10 +131,10 @@ # For now, we manully set the state. if ii==0: # Start tracking - trackptr.setstate(tstate.tpt) + trackptr.setState(tstate) plt.cla() - trackptr.displayState(tstate) + trackptr.displayState() plt.imshow(I, cmap='Greys') plt.pause(0.001) diff --git a/trackpointer/centroid.py b/trackpointer/centroid.py index 6585516..ed746c3 100644 --- a/trackpointer/centroid.py +++ b/trackpointer/centroid.py @@ -35,17 +35,22 @@ import numpy as np import matplotlib.pyplot as plt +from dataclasses import dataclass -class State(object): - - def __init__(self, tpt=None, haveMeas=None): - self.tpt = tpt - self.haveMeas = haveMeas +@dataclass +class State: + tpt: np.ndarray = np.array([]) + haveMeas: bool = False +@dataclass class Params(object): + """The parameters for the centroid tracker - def __init__(self): - pass + Args: + plotStyle (str): The plot style from the matplotlib for the centroid. Defaults to "rx". \ + Detailed choices see: https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.plot.html + """ + plotStyle:str = "rx" class centroid(object): @@ -56,17 +61,20 @@ class centroid(object): # @param[in] iPt The initial track point coordinates. # params The parameter structure. # - def __init__(self, iPt=None, params=None): - - if iPt: - self.tpt = iPt + def __init__(self, iPt=None, params=Params()): - if params is None: + if not isinstance(params, Params): params = self.setIfMissing(params,'plotStyle','rx') self.tparams = params self.haveMeas = False + if iPt: + self.tpt = iPt + self.haveMeas = True + else: + self.tpt = None + #=============================== set =============================== # # @brief Set parameters for the tracker. @@ -94,37 +102,37 @@ def get(self, fname): pass - #============================= emptystate ============================ + #============================= emptyState ============================ # # @brief Return an empty state structure. # # - def emptystate(self): + def emptyState(self): - estate= State(tpt=[], haveMeas=False) + estate= State(tpt=np.array([]), haveMeas=False) return estate - #============================== setstate ============================= + #============================== setState ============================= # # @brief Set the state vector. # - # @param[in] g The desired state. + # @param[in] dPt The desired state. # - def setstate(self, g): + def setState(self, dPt): - self.tpt = g - self.haveMeas = True + self.tpt = dPt.tpt + self.haveMeas = dPt.haveMeas - #============================== getstate ============================= + #============================== getState ============================= # # @brief Return the track-pointer state. # # @param[out] tstate The track point state structure. # - def getstate(self): + def getState(self): tstate = State(tpt=self.tpt, haveMeas=self.haveMeas) @@ -177,22 +185,18 @@ def measure(self, I): else: Ip = I - + # y,x in OpenCV ibin, jbin = np.nonzero(Ip) - self.tpt = np.array([np.mean(jbin), np.mean(ibin)]).reshape(-1,1) - - self.haveMeas = self.tpt.shape[1] > 0 - - # center = transpose(size(image)*[0 1;1 0]+1)/2; - # trackpoint = trackpoint - center; + if ibin.size == 0: + self.tpt = None + self.haveMeas = False + else: + # x,y in OpenCV + self.tpt = np.array([np.mean(jbin), np.mean(ibin)]).reshape(-1,1) + self.haveMeas = True - # @todo - # Not sure if the translation is correct - # if (nargout == 1): - # mstate = this.getstate(); - # end - mstate = self.getstate() + mstate = self.getState() return mstate @@ -215,14 +219,20 @@ def process(self, I): # existing elements that should remain, then hold should be enabled prior to # invoking this function. # - def displayState(self, dstate = None): + def displayState(self, dstate = None, ax=None): - if dstate: + if ax is None: + ax = plt.gca() + + if isinstance(dstate, State): if dstate.haveMeas: - plt.plot(dstate.tpt[0,:], dstate.tpt[1,:], self.tparams.plotStyle) + # Change to OpenCV style + ax.plot(dstate.tpt[0,:], dstate.tpt[1,:], self.tparams.plotStyle) else: if self.haveMeas: - plt.plot(self.tpt[0,:], self.tpt[1,:], self.tparams.plotStyle) + # Change to OpenCV style + ax.plot(self.tpt[0,:], self.tpt[1,:], self.tparams.plotStyle) + #========================= displayDebugState ========================= @@ -249,7 +259,7 @@ def setIfMissing(self, params, pname, pval): # @todo # Need double check on this translation - if params is None or not isinstance(params): + if not isinstance(params, Params): params = Params() setattr(params, pname, pval) return params diff --git a/trackpointer/centroidMulti.py b/trackpointer/centroidMulti.py index d48bb6b..6a159e6 100644 --- a/trackpointer/centroidMulti.py +++ b/trackpointer/centroidMulti.py @@ -33,22 +33,10 @@ #================================ centroid =============================== import numpy as np -import matplotlib.pyplot as plt import cv2 from skimage.measure import regionprops -from trackpointer.centroid import centroid - -class State(object): - - def __init__(self, tpt=None, haveMeas=None): - self.tpt = tpt - self.haveMeas = haveMeas - -class Params(object): - - def __init__(self): - pass +from trackpointer.centroid import centroid, State, Params class centroidMulti(centroid): @@ -59,9 +47,9 @@ class centroidMulti(centroid): # @param[in] iPt The initial track point coordinates. # params The parameter structure. # - def __init__(self, iPt=None, params=None): + def __init__(self, iPt=None, params=Params()): - super(centroidMulti,self).__init__(iPt,params) + super(centroidMulti,self).__init__(iPt, params) #=============================== set =============================== @@ -106,16 +94,14 @@ def measure(self, I): Ip = I binReg = centroidMulti.regionProposal(Ip) - self.tpt = np.array(binReg).T # from N x 2 to 2 x N + self.tpt = np.array(binReg).T # from N x 2 to 2 x N - self.haveMeas = self.tpt.shape[1] > 0 + if len(self.tpt) == 0: + self.haveMeas = False + else: + self.haveMeas = self.tpt.shape[1] > 0 - # @todo - # Not sure if the translation is correct - # if (nargout == 1): - # mstate = this.getstate(); - # end - mstate = self.getstate() + mstate = self.getState() return mstate @@ -129,34 +115,6 @@ def process(self, I): self.measure(I) - #============================ displayState =========================== - # - # @brief Displays the current track pointer measurement. - # - # Assumes that the current figure to plot to is activate. If the plot has - # existing elements that should remain, then hold should be enabled prior to - # invoking this function. - # - def displayState(self, dstate = None): - - if dstate: - if dstate.haveMeas: - plt.plot(dstate.tpt[0,:], dstate.tpt[1,:], self.tparams.plotStyle) - else: - if self.haveMeas: - plt.plot(self.tpt[0,:], self.tpt[1,:], self.tparams.plotStyle) - - - #========================= displayDebugState ========================= - # - # @brief Displays internally stored intermediate process output. - # - # Currently, there is no intermediate output, though that might change - # in the future. - # - def displayDebugState(self, dbstate=None): - pass - #========================= regionProposal ========================= # # @brief Find out the centroid for multiple objects @@ -165,8 +123,9 @@ def displayDebugState(self, dbstate=None): # @ staticmethod def regionProposal(I): - mask = np.zeros_like(I) - cnts = cv2.findContours(I, cv2.RETR_EXTERNAL, + Ip = I.astype(np.uint8) + mask = np.zeros_like(Ip) + cnts = cv2.findContours(Ip, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # For OpenCV 4+ cnts = cnts[0] @@ -176,6 +135,8 @@ def regionProposal(I): # Note that regionprops assumes different areas are with different labels # See https://stackoverflow.com/a/61591279/5269146 + + # Have been transferred to the OpenCV style as regionprops is from skimage binReg = [[i.centroid[1], i.centroid[0]] for i in regionprops(mask)] return binReg diff --git a/trackpointer/utils/__init__.py b/trackpointer/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/testing/fakeTriangle.py b/trackpointer/utils/fakeTriangle.py similarity index 100% rename from testing/fakeTriangle.py rename to trackpointer/utils/fakeTriangle.py