diff --git a/sohstationviewer/conf/constants.py b/sohstationviewer/conf/constants.py
index d27768d785c5935791658d0f0a79c8b214b5ee4a..69409d50330745a2ab4eab6dd4712a58b2bf8026 100644
--- a/sohstationviewer/conf/constants.py
+++ b/sohstationviewer/conf/constants.py
@@ -1 +1,24 @@
+# to calc min()
 HIGHEST_INT = 1E100
+
+# warn user if file bigger than this size
+BIG_FILE_SIZE = 10**8
+
+# Perfomance will be slow if data point total > than this limit
+# => downsampled
+CHAN_SIZE_LIMIT = 10**7
+
+# If total waveform datapoint > than this limit, not recalculate
+RECAL_SIZE_LIMIT = 10**9
+
+# rate of values to be cut of from means
+CUT_FROM_MEAN_FACTOR = 0.1
+
+# to split to time range (not use for now, but implemented in mseed)
+FILE_PER_CHAN_LIMIT = 20
+
+# default start time
+DEFAULT_START_TIME = "1970-01-01"
+
+# TIME FORMAT according to Qt.ISODate
+TM_FM = "%Y-%m-%d"
diff --git a/sohstationviewer/conf/dbSettings.py b/sohstationviewer/conf/dbSettings.py
index de734017dc7217de7e4c51ade5ef3f3d52dad9d9..2a913a89d55879d55d25e256f70905e66809b80d 100755
--- a/sohstationviewer/conf/dbSettings.py
+++ b/sohstationviewer/conf/dbSettings.py
@@ -3,14 +3,46 @@ import re
 """
 seisRE: Seismic data channels' regex:
 First letter(Band Code): ABCDEFGHLMOPQRSTUV
-Second letter (Instrument Code): GHLMN
+Second letter (Instrument Code): GHLMN  (remove GM - Alissa)
 Third letter (Orientation Code): ZNE123
 => to check if the channel is seismic data: if conf['seisRE'].match(chan):
 """
 
-conf = {
+dbConf = {
     'dbpath': 'sohstationviewer/database/soh.db',
-    'seisRE': re.compile('[A-HLM-V][GHLMN][ZNE123]'),
+    'seisRE': re.compile('[A-HLM-V][HLN][ZNE123]'),
+    'wfReq': re.compile('^[A-HLM-V\*]([HLN\*][ZNE123\*]?)?$'),    # noqa: W605
+    # key is last char of chan
+    'seisLabel': {'1': 'NS', '2': 'EW', 'N': 'NS', 'E': 'EW', 'Z': 'V'},
     # +0.2:Y
-    'valColRE': re.compile('^\+?\-?[0-9]+\.?[0-9]?:[RYGMC]')     # noqa: W605
+    'valColRE': re.compile('^\+?\-?[0-9]+\.?[0-9]?:[RYGMC]'),     # noqa: W605
+    'plotFunc': {
+        'linesDots': (
+            ("Lines, one color dots. "),
+            "plotLinesDots"),
+        'linesSRate': (
+            ("Lines, one color dots, bitweight info. "),
+            "plotLinesSRate"),
+        'linesMasspos': (
+            ("multi-line mass position, multi-color dots. "),
+            "plotLinesMasspos"),
+        # 'dotsMasspos': (
+        #     ("mass position, multi-color, single line. "),
+        #     "plotDotsMasspos"),
+        'dotForTime': (
+            "Dots according to timestamp. Color defined by valueColors. Ex: G",
+            "plotTimeDots"),
+        'multiColorDots': (
+            ("Multicolor dots with colors defined by valueColors. "
+             "Value from low to high. "
+             "Ex:*:W or -1:_|0:R|2.3:Y|+2.3:G. "
+             "With colors: RYGMC: _: not plot"),
+            "plotMultiColorDots"
+        ),
+        'upDownDots': (
+            ("Show data with 2 different values: first down/ second up. "
+             "With colors defined by valueColors. Ex: 1:R|0:Y"),
+            'plotUpDownDots'
+        )
+    }
 }
diff --git a/sohstationviewer/controller/plottingData.py b/sohstationviewer/controller/plottingData.py
index 545be6195fade756195bbfeb29ceaf04dea5485f..e127117afca857c3856bbafd16f52a2f622d73dc 100755
--- a/sohstationviewer/controller/plottingData.py
+++ b/sohstationviewer/controller/plottingData.py
@@ -3,15 +3,14 @@ Functions that process data before plotting
 """
 
 import math
+import numpy as np
 
 from obspy import UTCDateTime
 
-from sohstationviewer.controller.util import displayTrackingInfo
-from sohstationviewer.conf.dbSettings import conf
-
 
 maxInt = 1E100
 maxFloat = 1.0E100
+
 # TODO: put this in DB
 MassPosVoltRanges = {"regular": [0.5, 2.0, 4.0, 7.0],
                      "trillium": [0.5, 1.8, 2.4, 3.5]}
@@ -55,7 +54,7 @@ def getMassposValueColors(rangeOpt, chan, cMode, errors, retType='str'):
     return valueColors
 
 
-def formatTime(parent, time, dateMode, timeMode=None):
+def formatTime(time, dateMode, timeMode=None):
     """
     :param parent: parent GUI to display tracking info
     :param time: time to be format, can be UTCDateTime or epoch time
@@ -77,10 +76,6 @@ def formatTime(parent, time, dateMode, timeMode=None):
         format = '%Y%m%d'
     elif dateMode == 'YYYY:DOY':
         format = '%Y:%j'
-    else:
-        displayTrackingInfo(parent,
-                            "not defined format:'%s'" % dateMode,
-                            "error")
     if timeMode == 'HH:MM:SS':
         format += " %H:%M:%S"
 
@@ -88,7 +83,7 @@ def formatTime(parent, time, dateMode, timeMode=None):
     return ret
 
 
-def getTitle(parent, setID, plottingData, dateMode):
+def getTitle(staID, minTime, maxTime, dateMode):
     """
     :param setID: (netID, statID, locID)
     :param plottingData: a ditionary including:
@@ -99,14 +94,12 @@ def getTitle(parent, setID, plottingData, dateMode):
           latestUTC: the latest time of all channels
     :return: title for the plot
     """
-    diff = plottingData['latestUTC'] - plottingData['earliestUTC']
+    diff = maxTime - minTime
     hours = diff/3600
     return ("Station: %s  %s  to  %s  (%s)" %
-            (setID[1],
-             formatTime(parent, plottingData['earliestUTC'],
-                        dateMode, "HH:MM:SS"),
-             formatTime(parent, plottingData['latestUTC'],
-                        dateMode, "HH:MM:SS"),
+            (staID,
+             formatTime(minTime, dateMode, "HH:MM:SS"),
+             formatTime(maxTime, dateMode, "HH:MM:SS"),
              round(hours, 2))
             )
 
@@ -164,7 +157,7 @@ def getTimeTicks(earliest, latest, dateFormat, labelTotal):
     time += interval
     while time < latest:
         times.append(time)
-        timeLabel = formatTime(None, time, dateFormat, 'HH:MM:SS')
+        timeLabel = formatTime(time, dateFormat, 'HH:MM:SS')
         if mode == "DD" or mode == "D":
             timeLabel = timeLabel[:-9]
         elif mode == "H":
@@ -181,7 +174,14 @@ def getTimeTicks(earliest, latest, dateFormat, labelTotal):
     return times, majorTimes, majorTimelabels
 
 
-def getUnitBitweight(chanDB, bitweightOpt):
+def getDayTicks():
+    times = list(range(1, 24))
+    majorTimes = [t for t in list(range(4, 24, 4))]
+    majorTimeLabels = ["%02d" % t for t in majorTimes]
+    return times, majorTimes, majorTimeLabels
+
+
+def getUnitBitweight(chanDB, bitweightOpt, isWF=False):
     """
     :param chanDB: channel's info got from database
     :param bitweightOpt: qpeek's OPTBitWeightRVar
@@ -198,12 +198,14 @@ def getUnitBitweight(chanDB, bitweightOpt):
     except Exception:
         fixPoint = 0
     unitBitweight = ''
+
     if plotType in ['linesDots', 'linesSRate', 'linesMasspos']:
+
         if fixPoint == 0:
             unitBitweight = "{}%s" % unit
         else:
             unitBitweight = "{:.%sf}%s" % (fixPoint, unit)
-    if conf['seisRE'].match(chanDB['channel']):
+    if chanDB['channel'] == 'SEISMIC':
         if fixPoint != 0:
             unitBitweight = "{:.%sf}%s" % (fixPoint, unit)
         else:
@@ -212,3 +214,14 @@ def getUnitBitweight(chanDB, bitweightOpt):
             else:
                 unitBitweight = "{}%s" % unit
     return unitBitweight
+
+
+def convertWFactor(cData, convertFactor):
+    """
+    convertFactor = 150mV/count = 150V/1000count
+    => unit data * convertFactor= data *150/1000 V
+    convert data in numpy arrays to a reduced list
+    according to max number of datapoint needed
+    """
+    cData['decData'] = np.multiply(cData['data'], [convertFactor])
+    return cData
diff --git a/sohstationviewer/controller/processing.py b/sohstationviewer/controller/processing.py
index 87173891420575428f2adc25bb60c646522f27fc..893406e0b88ea837347c223b35d074fe96aaefe0 100644
--- a/sohstationviewer/controller/processing.py
+++ b/sohstationviewer/controller/processing.py
@@ -1,8 +1,9 @@
 import os
 import json
 import re
+
 from obspy.core import read as read_ms
-from obspy.io.reftek.core import Reftek130, Reftek130Exception
+from obspy.io.reftek.core import Reftek130Exception
 
 from sohstationviewer.model.mseed.mseed import MSeed
 from sohstationviewer.model.reftek.reftek import RT130
@@ -10,7 +11,8 @@ from sohstationviewer.database.extractData import signatureChannels
 from sohstationviewer.controller.util import validateFile, displayTrackingInfo
 
 
-def loadData(dataType, parent, listOfDir, reqInfoChans, reqDSs):
+def loadData(dataType, parent, listOfDir, reqWFChans=[], reqSOHChans=[],
+             readStart=None, readEnd=None):
     """
     Go through root dir and read all files in that dir and its subdirs
     """
@@ -18,27 +20,34 @@ def loadData(dataType, parent, listOfDir, reqInfoChans, reqDSs):
     for d in listOfDir:
         if dataObject is None:
             if dataType == 'RT130':
-                dataObject = RT130(parent, d, reqInfoChans, reqDSs=reqDSs)
+                dataObject = RT130(
+                    parent, d, reqWFChans=reqWFChans, reqSOHChans=reqSOHChans,
+                    readStart=readStart, readEnd=readEnd)
             else:
                 try:
-                    dataObject = MSeed(parent, d, reqInfoChans)
+                    dataObject = MSeed(
+                        parent, d, reqWFChans=reqWFChans,
+                        reqSOHChans=reqSOHChans,
+                        readStart=readStart, readEnd=readEnd)
                 except Exception as e:
                     msg = f"Dir {d} can't be read due to error: {str(e)}"
                     displayTrackingInfo(parent, msg, "Warning")
                     pass
-
-            if dataObject.hasData():
-                continue
+            # if dataObject.hasData():
+            #     continue
             # If no data can be read from the first dir, throw exception
-            raise Exception("No data can be read from ", d)
-        else:
-            dataObject.readDir(d)
+            # raise Exception("No data can be read from ", d)
+        # TODO: will work with select more than one dir later
+        # else:
+        #     dataObject.readDir(d)
 
-    return dataObject.plottingData
+    # return dataObject.plottingData
+    return dataObject
 
 
 def readChannels(parent, listOfDir):
     """
+    TODO: have to solve this later for changes of data type
     Scan available channels for channel prefer dialog
     """
     dataObject = None
@@ -47,6 +56,8 @@ def readChannels(parent, listOfDir):
             # dataObject = Reftek.Reftek(parent, d)
             # if dataObject.hasData():
             #     continue
+
+            # dataObject = MSeed_Text(parent, d, readChanOnly=True)
             dataObject = MSeed(parent, d, readChanOnly=True)
             if len(dataObject.channels) == 0:
                 # If no data can be read from the first dir, throw exception
@@ -64,25 +75,24 @@ def detectDataType(parent, listOfDir):
         + None if there are more than one types of data detected
         + dataType found, Unknown data maybe return
     """
-    # Looks like an import was missing -- I'm guessing this wasn't
-    # running before the reftek stuff was put in??
-    # sign_chan_dataType_dict = extractData.signatureChannels()
     sign_chan_dataType_dict = signatureChannels()
+
     dirDataTypeDict = {}
     for d in listOfDir:
+        print("d:", d)
         dataType = "Unknown"
         for path, subdirs, files in os.walk(d):
             for fileName in files:
-                if not validateFile(path, fileName):
+                path2file = os.path.join(path, fileName)
+                if not validateFile(path2file, fileName):
                     continue
-                ret = getDataTypeFromFile(os.path.join(path, fileName),
-                                          sign_chan_dataType_dict)
+                ret = getDataTypeFromFile(path2file, sign_chan_dataType_dict)
                 if ret is not None:
-                    dataType = ret
+                    dataType, chan = ret
                     break
             if dataType != "Unknown":
                 break
-        dirDataTypeDict[d] = dataType
+        dirDataTypeDict[d] = (dataType, chan)
     dataTypeList = {d for d in dirDataTypeDict.values()}
     if len(dataTypeList) > 1:
         dirDataTypeStr = json.dumps(dirDataTypeDict)
@@ -101,23 +111,16 @@ def detectDataType(parent, listOfDir):
     return list(dirDataTypeDict.values())[0]
 
 
-def getDataTypeFromFile(filePath, sign_chan_dataType_dict):
+def getDataTypeFromFile(path2file, sign_chan_dataType_dict):
     stream = None
     try:
-        stream = read_ms(os.path.join(filePath))
+        stream = read_ms(path2file)
     except TypeError:
         return
     except Reftek130Exception:
-        pass
-
-    if not stream:
-        try:
-            Reftek130.from_file(os.path.join(filePath))
-        except (TypeError, Reftek130Exception):
-            return
-        return 'RT130'
+        return 'RT130', '_'
 
     for trace in stream:
         chan = trace.stats['channel']
         if chan in sign_chan_dataType_dict.keys():
-            return sign_chan_dataType_dict[chan]
+            return sign_chan_dataType_dict[chan], chan
diff --git a/sohstationviewer/controller/util.py b/sohstationviewer/controller/util.py
index 76e632bd63641f6e962c9ea31581e90cdc22f3fc..dab43adfc36d6452c7f877631ca8afc04bde5eee 100644
--- a/sohstationviewer/controller/util.py
+++ b/sohstationviewer/controller/util.py
@@ -4,18 +4,18 @@ from datetime import datetime
 from obspy import UTCDateTime
 
 
-def validateFile(path, fileName):
+def validateFile(path2file, fileName):
     if fileName.strip() == '.DS_Store' or fileName.startswith('._'):
         # skip mac's info file
         return False
 
-    if not os.path.isfile(os.path.join(path, fileName)):
+    if not os.path.isfile(os.path.join(path2file)):
         return False
     return True
 
 
-def displayTrackingInfo(parent, text, type='info'):
-    if parent is None:
+def displayTrackingInfo(trackingBox, text, type='info'):
+    if trackingBox is None:
         print(f"{type}: {text}")
         return
 
@@ -34,8 +34,23 @@ def displayTrackingInfo(parent, text, type='info'):
             %(text)s
         </div>
         </body>"""
-    parent.trackingInfoTextBrowser.setHtml(htmlText % msg)
-    parent.update()
+    trackingBox.setHtml(htmlText % msg)
+    # parent.update()
+    trackingBox.repaint()
+
+
+def getDirSize(dir):
+    totalSize = 0
+    totalFile = 0
+    for path, subdirs, files in os.walk(dir):
+        for fileName in files:
+            if not validateFile(path, fileName):
+                continue
+            fp = os.path.join(path, fileName)
+            # print("file %s: %s" % (fp, os.path.getsize(fp)))
+            totalSize += os.path.getsize(fp)
+            totalFile += 1
+    return totalSize, totalFile
 
 
 def getTime6(timeStr):
@@ -95,6 +110,10 @@ def getVal(text):
     return float(re.search(REVal, text).group())
 
 
+def isBinaryStr(text):
+    return lambda b: bool(b.translate(None, text))
+
+
 ######################################
 # BEGIN: rtnPattern(In, Upper = False)
 # LIB:rtnPattern():2006.114 - Logpeek
diff --git a/sohstationviewer/database/extractData.py b/sohstationviewer/database/extractData.py
index 215e563879c081ff221edf10769ffed942a3700a..6b28f847664a78a080849de6e59f6128f3c8ab92 100755
--- a/sohstationviewer/database/extractData.py
+++ b/sohstationviewer/database/extractData.py
@@ -1,10 +1,7 @@
-from sohstationviewer.database.proccessDB import executeDB_dict
-from sohstationviewer.conf.dbSettings import conf
 
+from sohstationviewer.database.proccessDB import executeDB_dict
 
-# key is last char of chan
-SEIS_LABEL = {'1': 'NS', '2': 'EW',
-              'N': 'NS', 'E': 'EW', 'Z': 'V'}
+from sohstationviewer.conf.dbSettings import dbConf
 
 
 def getChanPlotInfo(orgChan, dataType):
@@ -25,7 +22,7 @@ def getChanPlotInfo(orgChan, dataType):
         chan = 'DS?'
     if orgChan.startswith('Disk Usage'):
         chan = 'Disk Usage?'
-    if conf['seisRE'].match(chan):
+    if dbConf['seisRE'].match(chan):
         chan = 'SEISMIC'
 
     sql = ("SELECT channel, plotType, height, unit, linkedChan,"
@@ -36,7 +33,7 @@ def getChanPlotInfo(orgChan, dataType):
     else:
         sql = (f"{sql} WHERE channel='{chan}' and C.param=P.param"
                f" and dataType='{dataType}'")
-    print("SQL:", sql)
+    # print("SQL:", sql)
     chanInfo = executeDB_dict(sql)
 
     if len(chanInfo) == 0:
@@ -44,7 +41,7 @@ def getChanPlotInfo(orgChan, dataType):
             f"{sql} WHERE channel='DEFAULT' and C.param=P.param")
     else:
         if chanInfo[0]['channel'] == 'SEISMIC':
-            chanInfo[0]['label'] = SEIS_LABEL[orgChan[-1]]
+            chanInfo[0]['label'] = dbConf['seisLabel'][orgChan[-1]]
         chanInfo[0]['channel'] = orgChan
 
     chanInfo[0]['label'] = (
@@ -61,6 +58,19 @@ def getChanPlotInfo(orgChan, dataType):
     return chanInfo[0]
 
 
+def getWFPlotInfo(orgChan):
+    chanInfo = executeDB_dict(
+        "SELECT * FROM Parameters WHERE param='Seismic data'")
+    if orgChan.startswith("DS"):
+        chanInfo[0]['label'] = orgChan
+    else:
+        chanInfo[0]['label'] = orgChan + '-' + dbConf['seisLabel'][orgChan[-1]]
+
+    chanInfo[0]['unit'] = ''
+    chanInfo[0]['channel'] = 'SEISMIC'
+    return chanInfo[0]
+
+
 def signatureChannels():
     """
     return the dict {channel: dataType} in which channel is unique for dataType
diff --git a/sohstationviewer/database/proccessDB.py b/sohstationviewer/database/proccessDB.py
index 19edc92afe84072cda2abb62a10e98a7d1f7ba7e..6533e82b16ef00d438b5c485851d05aa42ac090a 100755
--- a/sohstationviewer/database/proccessDB.py
+++ b/sohstationviewer/database/proccessDB.py
@@ -1,13 +1,13 @@
 import sqlite3
 
-from sohstationviewer.conf.dbSettings import conf
+from sohstationviewer.conf.dbSettings import dbConf
 
 
 def executeDB(sql):
     """
     used for both execute and query data
     """
-    conn = sqlite3.connect(conf['dbpath'])
+    conn = sqlite3.connect(dbConf['dbpath'])
     cur = conn.cursor()
     try:
         cur.execute(sql)
@@ -24,7 +24,7 @@ def executeDB_dict(sql):
     """
     query data and return rows in dictionary with fields as keys
     """
-    conn = sqlite3.connect(conf['dbpath'])
+    conn = sqlite3.connect(dbConf['dbpath'])
     conn.row_factory = sqlite3.Row
     cur = conn.cursor()
     try:
@@ -42,7 +42,7 @@ def trunc_addDB(table, sqls):
     truncate table and refill with new data
     """
     try:
-        conn = sqlite3.connect(conf['dbpath'])
+        conn = sqlite3.connect(dbConf['dbpath'])
         cur = conn.cursor()
         cur.execute('BEGIN')
         cur.execute(f'DELETE FROM {table}')
diff --git a/sohstationviewer/model/dataType.py b/sohstationviewer/model/dataType.py
deleted file mode 100644
index c5c37ecc626a6b72ab296ed405e9e8827acc5d5e..0000000000000000000000000000000000000000
--- a/sohstationviewer/model/dataType.py
+++ /dev/null
@@ -1,102 +0,0 @@
-import os
-
-from sohstationviewer.controller.util import validateFile, displayTrackingInfo
-from sohstationviewer.conf.dbSettings import conf
-
-
-class WrongDataTypeError(Exception):
-    def __init__(self, *args, **kwargs):
-        self.args = (args, kwargs)
-
-
-class DataType():
-    def __init__(self, parent, dir, reqInfoChans=[],
-                 readChanOnly=False, reqDSs=[]):
-        print("reqDSs:", reqDSs)
-        self.parentGUI = parent
-        self.dir = dir
-        self.reqInfoChans = reqInfoChans
-        self.noneReqChans = set()
-        self.curKey = ('_', '_', '_')
-        self.processingLog = []     # [(message, type)]
-        self.logData = {}
-        self.plottingData = {}
-        self.streams = {}
-        self.channels = set()
-        self.reqDSs = reqDSs
-        self.readDir(dir, readChanOnly)
-
-    def checkChan(self, chanID):
-        if self.reqInfoChans == []:
-            return True
-        if chanID in self.reqInfoChans:
-            return True
-        if 'EX?' in self.reqInfoChans and chanID.startswith('EX'):
-            if chanID[2] in ['1', '2', '3']:
-                return True
-        if 'VM?' in self.reqInfoChans and chanID.startswith('VM'):
-            if chanID[2] in ['0', '1', '2', '3', '4', '5', '6']:
-                return True
-        if 'SEISMIC' in self.reqInfoChans and conf['seisRE'].match(chanID):
-            return True
-        return False
-
-    def readFile(self, pathToFile):
-        pass
-
-    def combineData(self):
-        """
-        Merge channels in each stream
-        """
-        pass
-
-    def readDir(self, dir, readChanOnly=False):
-        self.readChanOnly = readChanOnly
-        count = 0
-        for path, subdirs, files in os.walk(dir):
-            for fileName in files:
-                if not validateFile(path, fileName):
-                    continue
-                self.readFile(path, fileName)
-                count += 1
-                if count % 50 == 0:
-                    displayTrackingInfo(
-                        self.parentGUI, "Reading file %s" % count)
-            if readChanOnly:
-                return
-        self.combineData()
-        # print("logs:", self.logData[('A195', 0, 19)].keys())
-        # print("logs:", self.logData[('A195', 0, 19)]['SH'])
-
-    def hasData(self):
-        if len(self.logData) == 0 and len(self.plottingData) == 0:
-            return False
-        return True
-
-    def trackInfo(self, text, type):
-        displayTrackingInfo(self.parent, text, type)
-        if type != 'info':
-            self.processingLog.append((text, type))
-
-    def readText(self, path, fileName):
-        """
-        Read log file and add to logData under channel TEXT
-        """
-        if self.readChanOnly and 'LOG' in self.channels:
-            return
-        with open(os.path.join(path, fileName), 'r') as file:
-            try:
-                content = file.read()
-            except UnicodeDecodeError:
-                self.trackInfo("Can't process file: %s" % fileName, 'error')
-                return
-            if self.readChanOnly:
-                self.channels.add('LOG')
-            logText = "\n\n** STATE OF HEALTH: %s\n" % fileName
-            logText += content
-            self.addLog('TEXT', logText)
-
-    def addLog(self, chan_pkt, logInfo):
-        if chan_pkt not in self.logData[self.curKey].keys():
-            self.logData[self.curKey][chan_pkt] = []
-        self.logData[self.curKey][chan_pkt].append(logInfo)
diff --git a/sohstationviewer/model/data_type_model.py b/sohstationviewer/model/data_type_model.py
new file mode 100644
index 0000000000000000000000000000000000000000..c95065c82287654f97f511f4d1645b521620d686
--- /dev/null
+++ b/sohstationviewer/model/data_type_model.py
@@ -0,0 +1,76 @@
+import os
+
+from tempfile import mkdtemp
+import shutil
+
+from sohstationviewer.controller.util import displayTrackingInfo
+from sohstationviewer.conf import constants
+
+
+class WrongDataTypeError(Exception):
+    def __init__(self, *args, **kwargs):
+        self.args = (args, kwargs)
+
+
+class DataTypeModel():
+    def __init__(self, trackingBox, folder, readChanOnly=False,
+                 reqWFChans=[], reqSOHChans=[],
+                 readStart=0, readEnd=constants.HIGHEST_INT,
+                 *args, **kwargs):
+        self.trackingBox = trackingBox
+        self.dir = folder
+        self.reqSOHChans = reqSOHChans
+        self.reqWFChans = reqWFChans
+        self.readChanOnly = readChanOnly
+
+        self.readStart = readStart      # start time to read in epoch
+        self.readEnd = readEnd          # end time to read in epoch
+        self.processingLog = []     # [(message, type)]
+        self.logData = {'TEXT': []}     # 'TEXT': for text only file
+        self.waveformData = {}
+        self.SOHData = {}
+        self.massPosData = {}
+        self.dataTime = {}       # (earliestepoch,latestepoch) for each station
+
+        self.channels = set()
+        self.station = None
+        self.nets = set()
+        self.stats = set()
+        self.gaps = {}
+        self.timeRanges = {}
+        self.selectedStaID = None
+        # # channel with the longest traces total for each station
+        # self.maxTraceTotalChan = {}
+        self.tmpDir = mkdtemp()
+        # print("currentdir:", os.getcwd())
+        # tmpDirName = 'datatmp'
+        # self.tmpDir = os.path.join(os.getcwd(), tmpDirName)
+        try:
+            os.mkdir(self.tmpDir)
+        except FileExistsError:
+            shutil.rmtree(self.tmpDir)
+            os.mkdir(self.tmpDir)
+
+        # self.readDir(folder, readChanOnly)
+
+    def __del__(self):
+        print("delete dataType Object")
+        try:
+            shutil.rmtree(self.tmpDir)
+        except OSError as e:
+            self.trackInfo(
+                "Error deleting %s : %s" % (self.tmpDir, e.strerror),
+                "error")
+            print("Error deleting %s : %s" % (self.tmpDir, e.strerror))
+        print("finish deleting")
+
+    def hasData(self):
+        if (len(self.logData) == 0 and len(self.SOHData) == 0 and
+                len(self.massPosData) == 0 and len(self.waveformData) == 0):
+            return False
+        return True
+
+    def trackInfo(self, text, type):
+        displayTrackingInfo(self.trackingBox, text, type)
+        if type != 'info':
+            self.processingLog.append((text, type))
diff --git a/sohstationviewer/model/handling_data.py b/sohstationviewer/model/handling_data.py
new file mode 100644
index 0000000000000000000000000000000000000000..2a953b97abb41cb745b5ce490a5d0b126a889c08
--- /dev/null
+++ b/sohstationviewer/model/handling_data.py
@@ -0,0 +1,538 @@
+
+import os
+import math
+from struct import unpack
+
+import numpy as np
+from obspy.core import Stream, read as read_ms
+from PySide2 import QtWidgets
+
+from sohstationviewer.model.mseed.blockettes_reader import (
+    readNextBlkt, ReadBlocketteError)
+from sohstationviewer.conf.dbSettings import dbConf
+from sohstationviewer.conf import constants
+from sohstationviewer.model.reftek.from_rt2ms import core
+
+
+def readSOHMSeed(path2file, fileName,
+                 SOHStreams, logData, netsProbInFile, trackInfo):
+    """
+    Read ms and add trace in self.streams to merge later
+    Or read log wrap in ms and add to logData under channel in ms header
+    """
+    stream = read_ms(path2file)
+
+    file = None
+    nets = set()
+    stats = set()
+    netStats = set()
+    chanIDs = set()
+    newnetID = None
+    for trace in stream:
+        netID = trace.stats['network'].strip()
+        if netID != "None":
+            nets.add(netID)
+        if len(nets) > 1:
+            for netProb in netsProbInFile:
+                netProbSet = set(netProb)
+                if nets.issubset(netProbSet) or netProbSet.issubset(nets):
+                    newnetID = netsProbInFile[netProb]
+                    if nets != netProbSet:
+                        netsProbInFile[tuple(nets)] = newnetID
+                    break
+            if newnetID is None:
+                msg = (f"There are more than one net in file {fileName}.\n"
+                       "Please select one.")
+                msgBox = QtWidgets.QMessageBox()
+                msgBox.setText(msg)
+                netButtons = []
+                _nets = sorted(list(nets))
+                for net in _nets:
+                    netButtons.append(msgBox.addButton(
+                        net, QtWidgets.QMessageBox.ActionRole))
+                msgBox.exec_()
+
+                selectedIdx = netButtons.index(msgBox.clickedButton())
+                newnetID = _nets[selectedIdx]
+                netsProbInFile[tuple(nets)] = newnetID
+
+    for trace in stream:
+        if newnetID is not None:
+            # use one net ID for all traces in a file
+            trace.stats['network'] = newnetID
+        netID = trace.stats['network'].strip()
+        chanID = trace.stats['channel'].strip()
+        staID = trace.stats['station'].strip()
+        stats.add(staID)
+        chanIDs.add(chanID)
+        netStats.add((netID, staID))
+        if trace.stats.mseed['encoding'] == 'ASCII':
+            file = readASCII(
+                path2file, file, staID, chanID, trace, logData, trackInfo)
+        else:
+            if staID not in SOHStreams:
+                SOHStreams[staID] = {}
+            if chanID not in SOHStreams[staID]:
+                SOHStreams[staID][chanID] = Stream()
+            SOHStreams[staID][chanID].append(trace)
+
+    if file is not None:
+        file.close()
+    return {'nets': list(nets),
+            'stats': sorted(list(stats)),
+            'netStats': sorted(list(netStats)),
+            'chanIDs': sorted(list(chanIDs))
+            }
+
+
+def readSOHTrace(trace):
+    tr = {}
+    tr['chanID'] = trace.stats['channel']
+    tr['samplerate'] = trace.stats['sampling_rate']
+    tr['startTmEpoch'] = trace.stats['starttime'].timestamp
+    tr['endTmEpoch'] = trace.stats['endtime'].timestamp
+    """
+    trace time start with 0 => need to add with epoch starttime
+    times and data have type ndarray
+    """
+    tr['times'] = trace.times() + trace.stats['starttime'].timestamp
+    tr['data'] = trace.data
+    return tr
+
+
+def readMPTrace(trace):
+    tr = {}
+    tr['chanID'] = trace.stats['channel']
+    tr['samplerate'] = trace.stats['sampling_rate']
+    tr['startTmEpoch'] = trace.stats['starttime'].timestamp
+    tr['endTmEpoch'] = trace.stats['endtime'].timestamp
+    """
+    trace time start with 0 => need to add with epoch starttime
+    times and data have type ndarray
+    """
+    tr['times'] = trace.times() + trace.stats['starttime'].timestamp
+    # TODO: MP only has 4 different values, data can be simplified if too big
+    tr['data'] = np.round_(trace.data / 3276.7, 1)
+    return tr
+
+
+def readWaveformTrace(trace, staID, chanID, tracesInfo, tmpDir):
+    """
+    read data from Trace and save data to files to save mem for processing
+    since waveform data are big
+    """
+    # gaps for SOH only for now
+    tr = {}
+    tr['samplerate'] = trace.stats['sampling_rate']
+    tr['startTmEpoch'] = trace.stats['starttime'].timestamp
+    tr['endTmEpoch'] = trace.stats['endtime'].timestamp
+    """
+    trace time start with 0 => need to add with epoch starttime
+    times and data have type ndarray
+    """
+    times = trace.times() + trace.stats['starttime'].timestamp
+    data = trace.data
+    tr['size'] = times.size
+
+    trIdx = len(tracesInfo)
+    tr['times_zf'] = tr['times_f'] = saveData2File(
+        tmpDir, 'times', staID, chanID, times, trIdx, tr['size'])
+    tr['data_zf'] = tr['data_f'] = saveData2File(
+        tmpDir, 'data', staID, chanID, data, trIdx, tr['size'])
+    return tr
+
+
+def readWaveformMSeed(path2file, fileName, staID, chanID,
+                      tracesInfo, dataTime, tmpDir):
+    """
+    Read ms and add trace in self.streams to merge later
+    Or read log wrap in ms and add to logData under channel in ms header
+    """
+    stream = read_ms(path2file)
+    for trace in stream:
+        tr = readWaveformTrace(trace, staID, chanID, tracesInfo, tmpDir)
+        dataTime[0] = min(tr['startTmEpoch'], dataTime[0])
+        dataTime[1] = max(tr['endTmEpoch'], dataTime[1])
+        tracesInfo.append(tr)
+
+
+def readWaveformReftek(rt130, staID, readData, dataTime, tmpDir):
+    stream = core.Reftek130.to_stream(
+        rt130,
+        headonly=False,
+        verbose=False,
+        sort_permuted_package_sequence=True)
+    for trace in stream:
+        chanID = trace.stats['channel']
+        samplerate = trace.stats['sampling_rate']
+        if chanID not in readData:
+            readData[chanID] = {
+                "tracesInfo": [],
+                "samplerate": samplerate}
+        tracesInfo = readData[chanID]['tracesInfo']
+        tr = readWaveformTrace(trace, staID, chanID, tracesInfo, tmpDir)
+        dataTime[0] = min(tr['startTmEpoch'], dataTime[0])
+        dataTime[1] = max(tr['endTmEpoch'], dataTime[1])
+        tracesInfo.append(tr)
+
+
+def readASCII(path2file, file, staID, chanID, trace, logData, trackInfo):
+    # TODO: read this in MSEED HEADER
+    byteorder = trace.stats.mseed['byteorder']
+    h = trace.stats
+    logText = "\n\n**** STATE OF HEALTH: "
+    logText += ("From:%s  To:%s\n" % (h.starttime, h.endtime))
+    textFromData = trace.data.tobytes().decode()
+    logText += textFromData
+    if textFromData != '':
+        logText += textFromData
+    else:
+        recLen = h.mseed['record_length']
+        if file is None:
+            file = open(path2file, 'rb')
+        databytes = file.read(recLen)
+        followingBlktsTotal = unpack('%s%s' % (byteorder, 'B'),
+                                     databytes[39:40])[0]
+        if followingBlktsTotal > 1:
+            nextBlktByteNo = 48 + 8  # header + blkt1000(SEED info)
+        else:
+            nextBlktByteNo = 0
+        while nextBlktByteNo != 0:
+            try:
+                nextBlktByteNo, info = readNextBlkt(
+                    nextBlktByteNo, databytes, byteorder)
+                logText += info
+            except ReadBlocketteError as e:
+                trackInfo(f"{staID} - {chanID}: {e.msg}", 'error')
+
+    if staID not in logData:
+        logData[staID] = {}
+    if h.channel not in logData[staID]:
+        logData[staID][h.channel] = []
+    logData[staID][h.channel].append(logText)
+    return file
+
+
+def readText(path2file, fileName, textLogs, ):
+    """
+    Read log file and add to logData under channel TEXT
+    """
+    with open(path2file, 'r') as file:
+        try:
+            content = file.read()
+        except UnicodeDecodeError:
+            raise Exception("Can't process file: %s" % fileName, 'error')
+
+        logText = "\n\n** STATE OF HEALTH: %s\n" % fileName
+        logText += content
+        textLogs.append(logText)
+
+        return True
+
+
+def saveData2File(tmpDir, tm_data, staID, chanID,
+                  tr, trIdx, trSize, postfix=''):
+    """
+    save time/data to file to free memory for processing
+    :param tmpDir: the temp dir to save file in
+    :param tm_data: "times"/"data"
+    :param staID: station id
+    :param chaID: channel id
+    :param tr: numpy array of trace data
+    :param trIdx: trace index
+    :param trSize: trace size
+    :param postfix: in case of decimating for zooming,
+        add Z at the end of filename
+    """
+    memFileName = os.path.join(tmpDir,
+                               f"{staID}-{chanID}-{tm_data}-{trIdx}{postfix}")
+    # print("memFileName:%s - %s" % (memFileName, trSize))
+    memFile = np.memmap(memFileName, dtype='int64', mode='w+',
+                        shape=(1, trSize))
+    memFile[:] = tr[:]
+    del tr
+    del memFile
+    return memFileName
+
+
+def checkChan(chanID, reqSOHChans, reqWFChans):
+    """
+    Check if chanID is required
+    return type if pass, False if not required
+    """
+    ret = checkWFChan(chanID, reqWFChans)
+    if ret[0] == 'WF':
+        if ret[1]:
+            return "WF"
+        else:
+            return False
+    if checkSOHChan(chanID, reqSOHChans):
+        return "SOH"
+    return False
+
+
+def checkSOHChan(chanID, reqSOHChans):
+    """
+    check if chanID is an SOH channel or mass position (mseed channel)
+    check if it required by user
+    no reqSOHChans means read all
+    """
+    if reqSOHChans == []:
+        return True
+    if chanID in reqSOHChans:
+        return True
+    if 'EX?' in reqSOHChans and chanID.startswith('EX'):
+        if chanID[2] in ['1', '2', '3']:
+            return True
+    # if 'VM?' in reqSOHChans and chanID.startswith('VM'):
+    if chanID.startswith('VM'):
+        # always read mass position
+        # TODO: add reqMPChans
+        if chanID[2] in ['0', '1', '2', '3', '4', '5', '6']:
+            return True
+    return False
+
+
+def checkWFChan(chanID, reqWFChans):
+    """
+    Check if chanID is a waveform data and is required by user
+    TODO: check with more wild card reqWFChans
+    """
+    wf = ''
+    hasChan = False
+    if dbConf['seisRE'].match(chanID):
+        wf = 'WF'
+    if reqWFChans == ['*']:
+        hasChan = True
+    if chanID in reqWFChans:
+        hasChan = True
+    return wf, hasChan
+
+
+def sortData(dataDict):
+    """
+    Sort data in 'tracesInfo' according to 'startTmEpoch'
+    """
+    for staID in dataDict:
+        for chanID in dataDict[staID]['readData']:
+            tracesInfo = dataDict[staID]['readData'][chanID]['tracesInfo']
+            tracesInfo = sorted(
+                tracesInfo, key=lambda i: i['startTmEpoch'])
+
+
+def squash_gaps(gaps):
+    """
+    :param gaps: list of gaps: (start, end, length)
+    :return: squased_gaps: all related gaps are squashed extending to
+        min start and max end
+    """
+    squashed_gaps = []
+    sgap_indexes = []
+    for idx, g in enumerate(gaps):
+        if idx in sgap_indexes:
+            continue
+        squashed_gaps.append(g)
+        sgap_indexes.append(idx)
+        for idx_, g_ in enumerate(gaps):
+            if idx_ in sgap_indexes:
+                continue
+            if ((g[0] <= g_[0] <= g[1]) or (g[0] <= g_[1] <= g[1])
+                    or (g_[0] <= g[0] <= g_[1]) or (g_[0] <= g[1] <= g_[1])):
+                sgap_indexes.append(idx_)
+                squashed_gaps[-1][0] = min(g[0], g_[0])
+                squashed_gaps[-1][1] = max(g[1], g_[1])
+            else:
+                break
+    return squashed_gaps
+
+
+def downsample(times, data, rq_points):
+    if times.size <= rq_points:
+        return times, data
+    dataMax = max(abs(data.max()), abs(data.min()))
+    dataMean = abs(data.mean())
+    indexes = np.where(
+        abs(data - data.mean()) >
+        (dataMax - dataMean) * constants.CUT_FROM_MEAN_FACTOR)
+    times = times[indexes]
+    data = data[indexes]
+    # return constant_rate(times, data, rq_points)
+    return chunk_minmax(times, data, rq_points)
+
+
+def constant_rate(times, data, rq_points):
+    if times.size <= rq_points:
+        return times, data
+    rate = int(times.size/rq_points)
+    if rate == 1:
+        return times, data
+
+    indexes = np.arange(0, times.size, rate)
+    times = times[indexes]
+    data = data[indexes]
+
+    return times, data
+
+
+def chunk_minmax(times, data, rq_points):
+    x, y = times, data
+    final_points = 0
+    if x.size <= rq_points:
+        final_points += x.size
+        return x, y
+
+    if rq_points < 2:
+        return np.empty((1, 0)), np.empty((1, 0))
+
+    # Since grabbing the min and max from each
+    # chunk, need to div the requested number of points
+    # by 2.
+    size = rq_points // 2
+    cs = math.ceil(times.size / size)
+
+    if cs * size > times.size:
+        cs -= 1
+        # Length of the trace is not divisible by the number of requested
+        # points. So split into an array that is divisible by the requested
+        # size, and an array that contains the excess. Downsample both,
+        # and combine. This case gives slightly more samples than
+        # the requested sample size, but not by much.
+        x0 = times[:cs * size]
+        y0 = data[:cs * size]
+
+        x1 = times[cs * size:]
+        y1 = data[cs * size:]
+
+        dx0, dy0 = downsample(x0, y0, rq_points)
+
+        # right-most subarray is always smaller than
+        # the initially requested number of points.
+        dx1, dy1 = downsample(x1, y1, cs)
+
+        dx = np.zeros(dx0.size + dx1.size)
+        dy = np.zeros(dy0.size + dy1.size)
+
+        # print(dx0.size + dx1.size)
+
+        dx[:dx0.size] = dx0
+        dy[:dy0.size] = dy0
+
+        dx[dx0.size:] = dx1
+        dy[dy0.size:] = dy1
+        del x0
+        del y0
+        del x1
+        del y1
+        del times
+        del data
+        return dx, dy
+
+    x = x.reshape(size, cs)
+    y = y.reshape(size, cs)
+
+    imin = np.argmin(y, axis=1)
+    imax = np.argmax(y, axis=1)
+
+    rows = np.arange(size)
+
+    mask = np.zeros(shape=(size, cs), dtype=bool)
+    mask[rows, imin] = True
+    mask[rows, imax] = True
+
+    dx = x[mask]
+    dy = y[mask]
+    return dx, dy
+
+
+def trim_downsample_SOHChan(chan, startTm, endTm, firsttime):
+    """
+    trim off non-included time from chan[orgTrace], downsample, and save to
+    chan[times], chan[data] and [logIdx] if the key exist
+    """
+    # TODO, add logIdx to downsample if using reftex
+    # zoom in to the given time
+    tr = chan['orgTrace']
+
+    indexes = np.where((startTm <= tr['times']) & (tr['times'] <= endTm))
+    chan['times'], chan['data'] = downsample(
+        tr['times'][indexes], tr['data'][indexes],
+        constants.CHAN_SIZE_LIMIT)
+
+
+def trim_downsample_WFChan(chan, startTm, endTm, firsttime):
+    """
+    trim off all chans with non included time
+    if totalSize of the rest chans > RECAL_SIZE_LIMIT => need to be downsampled
+    Read data from tr's filename, downsample the data
+    Return data for plotting
+    """
+    if 'fulldata' in chan:
+        # data is small, already has full in the first trim
+        return
+    # zoom in to the given range
+    chan['startIdx'] = 0
+    chan['endIdx'] = len(chan['tracesInfo'])
+
+    if ((startTm > chan['tracesInfo'][-1]['endTmEpoch']) or
+            (endTm < chan['tracesInfo'][0]['startTmEpoch'])):
+        return False
+
+    indexes = [index for index, tr in enumerate(chan['tracesInfo'])
+               if tr['startTmEpoch'] > startTm]
+    if indexes != []:
+        chan['startIdx'] = indexes[0]
+        if chan['startIdx'] > 0:
+            chan['startIdx'] -= 1  # startTm in middle of trace
+    else:
+        chan['startIdx'] = 0
+
+    indexes = [idx for (idx, tr) in enumerate(chan['tracesInfo'])
+               if tr['endTmEpoch'] <= endTm]
+    if indexes != []:
+        chan['endIdx'] = indexes[-1]
+        if chan['endIdx'] < len(chan['tracesInfo']) - 1:
+            chan['endIdx'] += 1     # endTm in middle of trace
+    else:
+        chan['endIdx'] = 0
+    chan['endIdx'] += 1  # a[x:y+1] = [a[x], ...a[y]
+
+    zTracesInfo = chan['tracesInfo'][chan['startIdx']:chan['endIdx']]
+    totalSize = sum([tr['size'] for tr in zTracesInfo])
+    if not firsttime and totalSize > constants.RECAL_SIZE_LIMIT:
+        # size too big, recalc only when it's small enough
+        return
+    try:
+        del chan['times']
+        del chan['data']
+    except Exception:
+        pass
+    rq_points = 0
+    if totalSize > constants.CHAN_SIZE_LIMIT:
+        rq_points = int(constants.CHAN_SIZE_LIMIT / len(zTracesInfo))
+    elif firsttime:
+        chan['fulldata'] = True
+
+    chan['times'] = []
+    chan['data'] = []
+    for trIdx, tr in enumerate(zTracesInfo):
+        times = np.memmap(tr['times_f'],
+                          dtype='int64', mode='r',
+                          shape=(1, tr['size']))
+        data = np.memmap(tr['data_f'],
+                         dtype='int64', mode='r',
+                         shape=(1, tr['size']))
+        indexes = np.where((startTm <= times) & (times <= endTm))
+        times = times[indexes]
+        data = data[indexes]
+        if rq_points != 0:
+            times, data = downsample(times, data, rq_points)
+        chan['times'].append(times)
+        chan['data'].append(data)
+
+    chan['times'] = np.hstack(chan['times'])
+    chan['data'] = np.hstack(chan['data'])
+    return
+
+
+if __name__ == '__main__':
+    print(checkChan('HH1', [], []))
diff --git a/sohstationviewer/model/mseed/blockettes_reader.py b/sohstationviewer/model/mseed/blockettes_reader.py
index a627fd3d4cccaad00c907a7982262313997d6385..71b96435928aaea4d51908d41f46196e19236479 100644
--- a/sohstationviewer/model/mseed/blockettes_reader.py
+++ b/sohstationviewer/model/mseed/blockettes_reader.py
@@ -50,13 +50,11 @@ def readNextBlkt(bNo, databytes, byteorder):
 
     try:
         # readBlkt will skip first 4 bytes (HH) as they are already read
-        info = eval("readBlkt%s(%s, %s, '%s')" %
-                    (blocketteType, bNo, databytes, byteorder))
+        info = eval("readBlkt%s(%s, %s, '%s')"
+                    % (blocketteType, bNo, databytes, byteorder))
     except NameError:
-        raise ReadBlocketteError(
-            f"Function to read blockette {blocketteType} isn't implemented "
-            f"yet. Implementer needs to write and add the function to the "
-            f"dict self.readBlkt in Mseed.__init__()")
+        raise ReadBlocketteError(f"Function to read blockette {blocketteType} "
+                                 f"isn't implemented yet.")
     return nextBNo, info
 
 
@@ -117,7 +115,7 @@ def readBlkt2000(bNo, databytes, byteorder):
     opaqueDataLength = blktLen - 15 - headerLen
     logText += "\nOpaque Data: %s" % unpack(
         '%s%s' % (byteorder, '%ss' % opaqueDataLength),
-        databytes[n + headerLen: n + headerLen + opaqueDataLength])
+        databytes[n + headerLen:n + headerLen + opaqueDataLength])
 
     return logText
 
diff --git a/sohstationviewer/model/mseed/from_mseedpeek/__init__.py b/sohstationviewer/model/mseed/from_mseedpeek/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/sohstationviewer/model/mseed/from_mseedpeek/mseed_header.py b/sohstationviewer/model/mseed/from_mseedpeek/mseed_header.py
new file mode 100644
index 0000000000000000000000000000000000000000..1033828d377c0a35021cadf26be8936060f9bc7f
--- /dev/null
+++ b/sohstationviewer/model/mseed/from_mseedpeek/mseed_header.py
@@ -0,0 +1,492 @@
+import struct
+from sohstationviewer.controller.util import getTime6
+from sohstationviewer.model.handling_data import (
+    readSOHMSeed, readText, checkChan)
+
+
+class futils:
+    """
+    file utilities class
+    """
+
+    def __init__(self, infile):
+        self.infile = open(infile, 'r+b')
+
+    def close(self):
+        self.infile.close()
+
+    def where(self):
+        return self.infile.tell()
+
+
+#########################################################
+class FixedHeader:
+    """
+    mseed fixhdr
+    """
+    # first 20char
+    textblk = timeblk = sampblk = miscblk = []
+    textblk = []
+    Serial = DHQual = Res = Stat = Chan = Loc = Net = None
+    # 20:30
+    timeblk = []
+    Year = Day = Hour = Min = Sec = Micro = None
+    # 30:36
+    sampblk = []
+    NumSamp = SampFact = SampMult = None
+    # 36:48
+    miscblk = []
+    act = io = DataDHQualFL = numblock = timcorr = bdata = bbblock = None
+
+
+#########################################################
+class MseedHeader(futils):
+
+    def __init__(self, infile):
+        """
+        initialize file, determine byteorder, sizes, time, and load first fixed
+        header
+        """
+        self.type = self.rate = None
+        # try:
+        futils.__init__(self, infile)
+        # local variable to class
+        self.infileseek = self.infile.seek
+        self.infilewrite = self.infile.write
+        self.infileread = self.infile.read
+        self.sunpack = struct.unpack
+        self.spack = struct.pack
+
+        self.byteorder = self.ByteOrder()
+
+        if self.byteorder != "unknown":
+            # otherwise it might be mseed
+            self.type = "mseed"
+            # read 1st fixed header in file
+            self.fixedhdr()
+            (self.filesize, self.blksize) = self.sizes()
+
+        # except Exception as e:
+        #     print("e:",e)
+        #     pass
+
+    #########################################################
+
+    def ByteOrder(self, seekval=20):
+        """
+        read file as if it is mseed just pulling time info
+        from fixed header and determine if it makes sense unpacked
+        as big endian or little endian
+        """
+        Order = "unknown"
+        try:
+            # seek to timeblock and read
+            self.infileseek(seekval)
+            timeblock = self.infileread(10)
+
+            # assume big endian
+            (Year, Day, Hour, Min, Sec, junk, Micro) = \
+                self.sunpack('>HHBBBBH', timeblock)
+            # test if big endian read makes sense
+            if 1950 <= Year <= 2050 and \
+                    1 <= Day <= 366 and \
+                    0 <= Hour <= 23 and \
+                    0 <= Min <= 59 and \
+                    0 <= Sec <= 59:
+                Order = "big"
+                self.fmt_order = ">"
+            else:
+                # try little endian read
+                (Year, Day, Hour, Min, Sec, junk, Micro) = \
+                    self.sunpack('<HHBBBBH', timeblock)
+                # test if little endian read makes sense
+                if 1950 <= Year <= 2050 and \
+                        1 <= Day <= 366 and \
+                        0 <= Hour <= 23 and \
+                        0 <= Min <= 59 and \
+                        0 <= Sec <= 59:
+                    Order = "little"
+                    self.fmt_order = "<"
+        except Exception:
+            pass
+
+        return Order
+
+#########################################################
+#
+# for blockette descriptions below
+# from SEED manual
+#
+# Field     nbits   Description
+# UBYTE     8       Unsigned quantity
+# BYTE      8       Two's complement signed quantity
+# UWORD     16      Unsigned quantity
+# WORD      16      Two's complement signed quantity
+# ULONG     32      Unsigned quantity
+# LONG      32      Two's complement signed quantity
+# CHAR*n    n*8     n characters, each 8 bit and each with
+#                   a 7-bit ASCII character (high bit always 0)
+# FLOAT     32      IEEE Floating point number
+#
+# BTIME
+#   UWORD   16  Year (e.g. 1987)
+#   UWORD   16  J-Day
+#   UBYTE   8   Hours of day (0-23)
+#   UBYTE   8   Minutes of hour (0-59)
+#   UBYTE   8   Seconds of minute (0-59, 60 for leap seconds)
+#   UBYTE   8   Unused for data (required for alignment)(
+#   UWORD   16  .0001 seconds (0-9999)
+#########################################################
+    def fixedhdr(self, seekval=0):
+        """
+        Reads fixed header of 48 bytes (see SEED manual)
+        Returns four tuples
+                textblk (SeqNum, DHQual, res, Stat, Loc, Chan, Net)
+                        Sequence Number (CHAR*6)
+                        Data header/quality indicator (CHAR*1)
+                        Reserved (CHAR*1)
+                        Station identifier code (CHAR*5)
+                        Location identifier (CHAR*2)
+                        Channel identifier (CHAR*3)
+                        Network Code (CHAR*2)
+                timeblk (Year, Day, Hour, Min, Sec, junk, Micro)
+                        Year (UWORD, 2)
+                        Jday (UWORD, 2)
+                        Hour (UBYTE, 1)
+                        Min (UBYTE, 1)
+                        Sec (UBYTE, 1)
+                        unused (UBYTE, 1)
+                        .0001 seconds (0-9999) (UWORD, 2)
+                sampblk (NumSamp, SampFact, SampMult)
+                        Number of samples (UWORD, 2)
+                        Sample rate factor (see calcrate) (WORD, 2)
+                        Sample rate multiplier (see calcrate) (WORD, 2)
+                miscblk (act, io, DataDHQualFl, numblock, timcorr, bdata,
+                         bblock)
+                        Activity flags (UBYTE, 1)
+                        I/O and clock flags (UBYTE, 1)
+                        Data quality flags (UBYTE, 1)
+                        Number of blockettes that follow (UBYTE, 1)
+                        Time correction (LONG, 4)
+                        Offset to beginning of data (UWORD, 2)
+                        Offset to beginning of next blockette (UWORD, 2)
+        """
+        # local variable
+        # self.sunpack=self.sunpack
+        try:
+            del self.FH
+        except Exception:
+            pass
+        self.FH = FixedHeader()
+        try:
+            self.infileseek(seekval)
+            fhblk = self.infileread(48)
+            # station info
+            fmtstr = self.fmt_order + "6scc5s2s3s2s"
+            self.FH.textblk = self.sunpack(fmtstr, fhblk[0:20])
+
+            # time info
+            fmtstr = self.fmt_order + "HHBBBBH"
+            self.FH.timeblk = self.sunpack(fmtstr, fhblk[20:30])
+
+            # sample info
+            fmtstr = self.fmt_order + "Hhh"
+            self.FH.sampblk = self.sunpack(fmtstr, fhblk[30:36])
+
+            # misc info
+            fmtstr = self.fmt_order + "BBBBlHH"
+            tmpblk = self.sunpack(fmtstr, fhblk[36:48])
+            # decompose tmpblk[0-2] into bit fields, create miscblk
+            for i in range(3):
+                self.FH.miscblk = self.UbytetoStr(tmpblk, i)
+                tmpblk = self.FH.miscblk  # recast tmpblk as list
+
+            (self.FH.Serial, self.FH.DHQual, res, self.FH.Stat,
+             self.FH.Loc, self.FH.Chan, self.FH.Net) = self.FH.textblk
+            (self.FH.Year, self.FH.Day, self.FH.Hour, self.FH.Min,
+             self.FH.Sec, junk, self.FH.Micro) = self.FH.timeblk
+            (self.FH.NumSamp, self.FH.SampFact,
+             self.FH.SampMult) = self.FH.sampblk
+            self.rate = self.calcrate()
+
+            return (self.FH.textblk, self.FH.timeblk, self.FH.sampblk,
+                    self.FH.miscblk)
+
+        except Exception as e:
+            raise Exception("error reading fixed header: %s" % str(e))
+
+    def sizes(self, seekval=0):
+        """
+        Finds Blockette 1000 and returns file size & Data Record Length
+        """
+        # try:
+        # determine file size
+        self.infileseek(0, 2)
+        filesize = self.infile.tell()
+        # proceed to seekval
+        self.infileseek(seekval)
+        # self.infileseek(39)
+        # assign number of blockettes and offset to next blockette
+        nblock = self.FH.miscblk[3]
+        nextblock = self.FH.miscblk[6]
+        n = 0
+        # find blockette 1000
+        while n < nblock:
+            self.infileseek(nextblock)
+            (blktype, newblock) = self.typenxt(nextblock)
+            if not blktype:
+                return None, None
+            if blktype == 1000:
+                (type, next, self.encode, order, length,
+                 res) = self.blk1000(nextblock)
+                return filesize, 2**length
+            nextblock = newblock
+            n += 1
+        # except Exception:
+        #     return None, None
+
+    #########################################################
+
+    def UbytetoStr(self, tup, offset):
+        """
+        converts unsign byte to string values
+        """
+        list = []
+        strval = ""
+        # mask bit fields and build string
+        for i in range(8):
+            mask = 2 ** i
+            if tup[offset] & mask:
+                strval = "1" + strval
+            else:
+                strval = "0" + strval
+
+        # build new list with decomposed bit string
+        for i in range(len(tup)):
+            if i == offset:
+                list.append(strval)
+            else:
+                list.append(tup[i])
+        return list
+
+    #########################################################
+
+    def calcrate(self):
+        """
+        this routine assumes that idread has been called first
+
+        calculate the sample rate of mseed data
+        If Sample rate factor > 0 and Sample rate Multiplier > 0,
+                rate = Sampfact X Sampmult
+        If Sample rate factor > 0 and Sample rate Multiplier < 0,
+                rate = -1 X Sampfact/Sampmult
+        If Sample rate factor < 0 and Sample rate Multiplier > 0,
+                rate = -1 X Sampmult/Sampfact
+        If Sample rate factor < 0 and Sample rate Multiplier < 0,
+                rate = 1/(Sampfact X Sampmult)
+        """
+        sampFact = float(self.FH.SampFact)
+        sampMult = float(self.FH.SampMult)
+        if sampFact > 0 and sampMult > 0:
+            rate = sampFact * sampMult
+        elif sampFact > 0 and sampMult < 0:
+            rate = -1.0 * (sampFact / sampMult)
+        elif sampFact < 0 and sampMult > 0:
+            rate = -1.0 * (sampMult / sampFact)
+        elif sampFact < 0 and sampMult < 0:
+            rate = 1.0 / (sampFact * sampMult)
+        else:
+            rate = sampFact
+        return rate
+
+    #########################################################
+
+    def typenxt(self, seekval=0):
+        """
+        Reads first 4 bytes of blockette
+        Returns blockette type and next blockette offset
+        """
+        try:
+            self.infileseek(seekval)
+            fmtstr = self.fmt_order + "HH"
+            (type, next) = self.sunpack(fmtstr, self.infileread(4))
+
+            # reset back to beginning of blockette
+            self.infileseek(-4, 1)
+            return type, next
+        except Exception:
+            return None, None
+
+    #########################################################
+
+    def blk1000(self, seekval=0):
+        """
+        Data Only SEED Blockette 1000 (8 bytes)
+        Returns tuple
+                blk
+        Blockette type (UWORD, 2)
+        Next blockette's byte offset relative to fixed section of header
+                (UWORD, 2)
+        Encoding Format (BYTE, 1)
+        Word Order (UBYTE, 1)
+        Data Record Length (UBYTE, 1)
+        Reserved (UBYTE, 1)
+        """
+        self.infileseek(seekval)
+        fmtstr = self.fmt_order + "HHbBBB"
+        blk = self.sunpack(fmtstr, self.infileread(8))
+        return list(blk)
+
+    #########################################################
+
+    def blk1001(self, seekval=0):
+        """
+        Data Extension Blockette 1001 (8 bytes)
+        Returns tuple
+                blk
+        Blockette type (UWORD, 2)
+        Next blockette's byte offset relative to fixed section of header
+                (UWORD, 2)
+        Timing Quality (UBYTE, 1)
+        microsec (UBYTE, 1)
+        Reserved (UBYTE, 1)
+        Frame count (UBYTE, 1)
+        """
+        self.infileseek(seekval)
+        fmtstr = self.fmt_order + "HHBBBB"
+        blk = self.sunpack(fmtstr, self.infileread(8))
+        return list(blk)
+
+    #########################################################
+
+    def blk500(self, seekval=0):
+        """
+        Timing Blockette 500 (200 bytes)
+        Returns tuple
+                blk
+        Blockette type (UWORD, 2)
+        Next blockette's byte offset relative to fixed section of header
+                (UWORD, 2)
+        VCO correction (FLOAT, 4)
+        Time of exception (BTime expanded, 10)
+        microsec (UBYTE, 1)
+        Reception quality (UBYTE, 1)
+        Exception count (ULONG, 4)
+        Exception type (CHAR*16)
+        Clock model (CHAR*32)
+        Clock status (CHAR*128)
+        """
+        self.infileseek(seekval)
+        fmtstr = self.fmt_order + "HHf"
+        blk1 = self.sunpack(fmtstr, self.infileread(8))
+        fmtstr = self.fmt_order + "HHBBBBH"
+        timeblk = self.sunpack(fmtstr, self.infileread(10))
+        fmtstr = self.fmt_order + "BBI16s32s128s"
+        blk2 = self.sunpack(fmtstr, self.infileread(182))
+
+        # incorporate timeblk tuple into blk list
+        blk = list(blk1)
+        blk.append(timeblk)
+        blk = blk + list(blk2)
+
+        return blk
+
+    #########################################################
+
+    def isMseed(self):
+        """
+        determines if processed file is mseed (return 1) or unknown type
+        (return 0)
+        """
+        # if we don't know byteorder it must not be mseed
+        if self.byteorder == "unknown":
+            return 0
+        else:
+            return 1
+
+
+def readHdrs(path2file, fileName,
+             SOHStreams, logData,
+             reqSOHChans, reqWFChans,
+             netsProbInFile, trackInfo):
+    """
+    read headers of a given file build dictionary for quick access
+    """
+
+    # create object (we've already tested all files for mseed)
+    # and get some base info
+    # print("fileName:", fileName)
+    rdfile = MseedHeader(path2file)
+    if rdfile.isMseed():
+        try:
+            filesize = rdfile.filesize
+            blksize = rdfile.blksize
+            encode = rdfile.encode
+            chanID = rdfile.FH.Chan.strip().decode()
+            chanType = checkChan(chanID, reqSOHChans, reqWFChans)
+            if not chanType:
+                rdfile.close()
+                return
+        except Exception:
+            rdfile.close()
+            raise Exception("Cannot determine file and block sizes. File: %s"
+                            % fileName)
+    else:
+        # not Mseed()
+        rdfile.close()
+        readText(path2file, fileName, logData['TEXT'])
+        return
+
+    if chanType == 'SOH':
+        readSOHMSeed(path2file, fileName, SOHStreams,
+                     logData, netsProbInFile, trackInfo)
+        return
+    if reqWFChans == []:
+        return
+    (numblocks, odd_size) = divmod(filesize, blksize)
+    nets = set()
+    stats = set()
+    netStats = set()
+    chanIDs = set()
+    epochs = []
+    startTms = []
+    gaps = []
+    # looping over total number of blocks in files
+    for n in range(numblocks):
+        rdfile.fixedhdr(n * blksize)
+
+        chanID = rdfile.FH.Chan.strip().decode()
+
+        chanIDs.add(chanID)
+        nets.add(rdfile.FH.Net.strip().decode())
+        stats.add(rdfile.FH.Stat.strip().decode())
+        netStats.add((rdfile.FH.Net.strip().decode(),
+                     rdfile.FH.Stat.strip().decode()))
+
+        t_str = "%d:%03d:%02d:%02d:%02d:%06d" % (
+            rdfile.FH.Year, rdfile.FH.Day, rdfile.FH.Hour,
+            rdfile.FH.Min, rdfile.FH.Sec, rdfile.FH.Micro)
+        startTms.append(t_str)
+        startepoch, _ = getTime6(t_str)
+        endepoch = None
+        if rdfile.rate != 0:
+            endepoch = startepoch + rdfile.FH.NumSamp / rdfile.rate
+
+        epochs.append((startepoch, endepoch))
+
+    rdfile.close()
+
+    return {'path2file': path2file,
+            'fileName': fileName,
+            'nets': list(nets),
+            'stats': sorted(list(stats)),
+            'netStats': sorted(list(netStats)),
+            'chanIDs': sorted(list(chanIDs)),
+            'gaps': gaps,
+            'encode': encode,
+            'startEpoch': epochs[0][0],
+            'endEpoch': epochs[-1][1],
+            'tracesTotal': len(epochs),
+            'spr': rdfile.rate,
+            'read': False
+            }
diff --git a/sohstationviewer/model/mseed/mseed.py b/sohstationviewer/model/mseed/mseed.py
index 41fc063e3d25bfb4aa8196a2606a1405a9c8559e..88ecb0fbafee7c8152daf361539c8c86b866e099 100644
--- a/sohstationviewer/model/mseed/mseed.py
+++ b/sohstationviewer/model/mseed/mseed.py
@@ -1,194 +1,280 @@
+"""
+waveform: + indexing and read only the files in selected time,
+          + apply saving memory
+          + down sample traces before combine data
+          + data not use for plotting is kept in files to free memory
+          for processing.
+SOH: merge all traces (with obspy stream) before down sample.
+gaps: for SOH only, can manually calc. gaps for waveform but takes time
+
+"""
+
+
 import os
 
-from obspy.core import Stream, read as read_ms
-from obspy import UTCDateTime
-from struct import unpack
+from PySide2 import QtWidgets
 
-from sohstationviewer.model.dataType import DataType
+from sohstationviewer.view.selectbuttonsdialog import SelectButtonDialog
+from sohstationviewer.model.data_type_model import DataTypeModel
 
-from sohstationviewer.model.mseed.blockettes_reader import (
-    readNextBlkt, ReadBlocketteError)
 from sohstationviewer.conf import constants
 
+from sohstationviewer.model.mseed.from_mseedpeek.mseed_header import readHdrs
+from sohstationviewer.controller.util import validateFile
+from sohstationviewer.model.handling_data import (
+    readWaveformMSeed, squash_gaps, checkWFChan,
+    sortData, readSOHTrace)
 
-class MSeed(DataType):
-    def __init__(self, parent, dir, reqInfoChans=[], readChanOnly=False):
-        super().__init__(parent, dir, reqInfoChans, readChanOnly)
-
-    def readFile(self, path, fileName):
-        if not self.readMiniseed(path, fileName):
-            self.readText(path, fileName)
-
-    def combineData(self):
-        for k in self.streams:           # k=netID, statID, locID
-            stream = self.streams[k]
-            stream.merge()
-            # gaps is list of [network, station, location, channel, starttime,
-            # endtime, duration, number of missing samples]
-            gaps = stream.get_gaps()
-            # stream.print_gaps()
-            gaps = self.squashGaps(gaps)
-            self.plottingData[k] = {'gaps': gaps,
-                                    'channels': {}}
-            maxEndtime = UTCDateTime(0)
-            minStarttime = UTCDateTime(20E10)
-            for trace in stream:
-                startTm, endTm = self.readTrace(
-                    trace, self.plottingData[k]['channels'])
-                if startTm < minStarttime:
-                    minStarttime = startTm
-                if endTm > maxEndtime:
-                    maxEndtime = endTm
-            self.plottingData[k]['earliestUTC'] = minStarttime.timestamp
-            self.plottingData[k]['latestUTC'] = maxEndtime.timestamp
-
-    def addLog(self, chan, logText):
-        if chan not in self.logData[self.curNetStatLoc].keys():
-            self.logData[self.curNetStatLoc][chan] = []
-        self.logData[self.curNetStatLoc][chan].append(logText)
-
-    def readText(self, path, fileName):
-        """
-        Read log file and add to logData under channel TEXT
-        """
-        if self.readChanOnly and 'LOG' in self.channels:
+
+class MSeed(DataTypeModel):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        # keys: 1+ nets     values: net user choose
+        self.netsProbInFile = {}
+        self.readSOH_indexWaveform(self.dir)
+        self.selectedKey = self.selectStaID()
+        if self.selectedKey is None:
             return
-        with open(os.path.join(path, fileName), 'r') as file:
-            try:
-                content = file.read()
-            except UnicodeDecodeError:
-                self.trackInfo("Can't process file: %s" % fileName, 'error')
-                return
-            if self.readChanOnly:
-                self.channels.add('LOG')
-            logText = "\n\n** STATE OF HEALTH: %s\n" % fileName
-            logText += content
-            self.addLog('TEXT', logText)
-
-    def readMiniseed(self, path, fileName):
-        """
-        Read ms and add trace in self.streams to merge later
-        Or read log wrap in ms and add to logData under channel in ms header
-        """
-        try:
-            stream = read_ms(os.path.join(path, fileName))
-        except TypeError:
-            return False
-        file = None
-        for trace in stream:
-            chanID = trace.stats['channel']
-            if self.readChanOnly:
-                self.channels.add(chanID)
-                continue
-            if not self.checkChan(chanID):
-                self.noneReqChans.add(chanID)
+        if len(self.reqWFChans) != 0:
+            # self.reselectTimeRange(self.selectedKey)
+            self.readWFFiles(self.selectedKey)
+
+    def readSOH_indexWaveform(self, folder):
+        netStatProb = {}
+        statProb = {}
+        chanProb = {}
+        count = 0
+        SOHStreams = {}
+        for path, subdirs, files in os.walk(folder):
+            for fileName in files:
+                path2file = os.path.join(path, fileName)
+                if not validateFile(path2file, fileName):
+                    continue
+                count += 1
+                if count % 50 == 0:
+                    self.trackInfo(
+                        f'Read {count} file headers/ SOH files', 'info')
+
+                ret = readHdrs(
+                    path2file, fileName, SOHStreams, self.logData,
+                    self.reqSOHChans, self.reqWFChans,
+                    self.netsProbInFile, self.trackInfo)
+                if ret is None:
+                    continue
+
+                self.nets.update(ret['nets'])
+                self.stats.add(ret['stats'][0])
+
+                if len(ret['stats']) > 1:
+                    k = tuple(ret['stats'])
+                    if k not in statProb:
+                        statProb[k] = []
+                    statProb[k].append(fileName)
+                else:
+                    if len(ret['netStats']) > 1:
+                        k = tuple(ret['netStats'])
+                        if k not in netStatProb:
+                            netStatProb[k] = []
+                        netStatProb[k].append(fileName)
+                if len(ret['chanIDs']) > 1:
+                    k = tuple(ret['chanIDs'])
+                    if k not in chanProb:
+                        chanProb[k] = []
+                    chanProb[k].append(fileName)
+                self.channels.update(ret['chanIDs'])
+                if 'read' not in ret:
+                    # not waveform
+                    continue
+
+                # TODO currently for waveform not read the file
+                #  with 2 stations, 2 channels
+                # correctly. Use the first one in the file only
+                # When have time will dig more to this
+                staID = ret['stats'][0]
+                chanID = ret['chanIDs'][0]
+                if staID not in self.waveformData:
+                    self.waveformData[staID] = {"filesInfo": {},
+                                                "readData": {}}
+                    self.dataTime[staID] = [constants.HIGHEST_INT, 0]
+                if chanID not in self.waveformData[staID]:
+                    self.waveformData[staID]["filesInfo"][chanID] = []
+                    self.waveformData[staID]["readData"][chanID] = {
+                        "samplerate": ret['spr'],
+                        "tracesInfo": []}
+                self.waveformData[staID]['filesInfo'][chanID].append(ret)
+
+        self.channels = sorted(list(self.channels))
+        self.nets = sorted(list(self.nets))
+        self.stats = sorted(list(self.stats))
+
+        if len(statProb) > 0:
+            errmsg = (f"More than one stations in a file: {statProb}. "
+                      f"Will use the first one.")
+            self.trackInfo(errmsg, "error")
+        if len(netStatProb) > 0:
+            errmsg = "More than one netIDs in a file: %s" % netStatProb
+            self.trackInfo(errmsg, "warning")
+        if len(chanProb) > 0:
+            errmsg = (f"More than one channels in a file: {chanProb} "
+                      f"\nThis is a CRITICAL ERROR.")
+            self.trackInfo(errmsg, "error")
+        # merge SOHStreams - squash gaps - gaps for SOH only
+        allGaps = []
+        for staID in SOHStreams:
+            self.dataTime[staID] = [constants.HIGHEST_INT, 0]
+            self.SOHData[staID] = {}
+            self.massPosData[staID] = {}
+            for chanID in SOHStreams[staID]:
+                stream = SOHStreams[staID][chanID]
+
+                stream.merge()
+                if len(stream) > 1:
+                    nets = [tr.stats['network'].strip() for tr in stream]
+                    nets += [f"Combine to {n}" for n in nets]
+                    msg = (f"There are more than one net for sta {staID}.\n"
+                           "Please select one or combine all to one.")
+                    msgBox = SelectButtonDialog(message=msg,
+                                                buttonLabels=nets)
+                    msgBox.exec_()
+                    selNet = nets[msgBox.ret]
+                    if "Combine" not in selNet:
+                        tr = [tr for tr in stream
+                              if tr.stats['network'] == selNet][0]
+                    else:
+                        for tr in stream:
+                            selNet = selNet.replace("Combine to ", "")
+                            tr.stats['network'] = selNet
+                        stream.merge()
+                        tr = stream[0]
+                else:
+                    tr = stream[0]
+
+                gaps = stream.get_gaps()
+                allGaps += [[g[4].timestamp, g[5].timestamp] for g in gaps]
+                traceInfo = readSOHTrace(tr)
+                if chanID.startswith('VM'):
+                    self.massPosData[staID][chanID] = {'orgTrace': traceInfo}
+                else:
+                    self.SOHData[staID][chanID] = {'orgTrace': traceInfo}
+                self.dataTime[staID][0] = min(traceInfo['startTmEpoch'],
+                                              self.dataTime[staID][0])
+                self.dataTime[staID][1] = max(traceInfo['endTmEpoch'],
+                                              self.dataTime[staID][1])
+
+            self.gaps[staID] = squash_gaps(allGaps)
+
+        # For each staID, get chanID of the channel with
+        # the most total of files. Will consider files instead of trace for now
+        # for staID in self.waveformData:
+        #     maxTraceTotal = 0
+        #     chanIdxs = self.waveformData[staID]
+        #     for chanID in chanIdxs:
+        #         traceTotal = len(chanIdxs[chanID])
+        #         if traceTotal > maxTraceTotal:
+        #             maxTraceTotal = traceTotal
+        #             maxChanID = self.maxTraceTotalChan[staID] = chanID
+        #     for chanID in spr0Chans:
+        #         chanIdxs[chanID][-1]['endEpoch'] = chanIdxs[
+        #             maxChanID][-1]['endEpoch']
+
+    def selectStaID(self):
+        selectedStaID = self.stats[0]
+        if len(self.stats) > 1:
+            msg = ("There are more than one stations in the given data.\n"
+                   "Please select one to one to display")
+            msgBox = QtWidgets.QMessageBox()
+            msgBox.setText(msg)
+            staButtons = []
+            for staID in self.stats:
+                staButtons.append(msgBox.addButton(
+                    staID, QtWidgets.QMessageBox.ActionRole))
+            abortButton = msgBox.addButton(QtWidgets.QMessageBox.Abort)
+
+            msgBox.exec_()
+
+            if msgBox.clickedButton() == abortButton:
+                return selectedStaID
+            selectedIdx = staButtons.index(msgBox.clickedButton())
+            selectedStaID = self.stats[selectedIdx]
+        self.trackInfo(f'Select Station {selectedStaID}', 'info')
+        return selectedStaID
+    #
+    # def reselectTimeRange(self, staID):
+    #     """
+    #     If there are no time ranges created for the station, create them
+    #     Provide a dialog for user to choose a time range. Each button is
+    #     a time range. User click one, time range will be changed in
+    #     MainWindow (TODO), the dialog will be closed. If necessary,
+    #     will change the way to do this later.
+    #     """
+    #     if staID not in self.timeRanges:
+    #         chanID = self.maxTraceTotalChan[staID]
+    #         filesInfo = self.waveformData[staID][chanID]['filesInfo']
+    #         cutTrace = [
+    #             tr for tr in filesInfo
+    #             if (self.readStart <= tr['startEpoch'] < self.readEnd) or
+    #                (self.readStart < tr['endEpoch'] <= self.readEnd)]
+    #         cutTraceTotal = len(cutTrace)
+    #         print(f"{chanID } cutTraceTotal: {cutTraceTotal}")
+    #         traceTotalDiv = int(
+    #             cutTraceTotal / constants.FILE_PER_CHAN_LIMIT)
+    #         if traceTotalDiv < 2:
+    #             # readStart, readEnd remain unchanged
+    #             return
+    #         self.timeRanges[staID] = []
+    #         FILE_LIM = constants.FILE_PER_CHAN_LIMIT
+    #         for i in range(traceTotalDiv):
+    #             startTm = cutTrace[i * FILE_LIM]['startEpoch']
+    #             if i < (traceTotalDiv - 1):
+    #                 endTm = cutTrace[i * FILE_LIM + FILE_LIM - 1]['endEpoch']
+    #             else:
+    #                 endTm = cutTrace[-1]['endEpoch']
+    #             self.timeRanges[staID].append((startTm, endTm))
+    #         msg = "Data in the selected time is too big to display.\n"
+    #     else:
+    #         msg = ""
+    #
+    #     msg += "Please choose one of the suggested time range below."
+    #     tmStrList = []
+    #     for tm in self.timeRanges[staID]:
+    #         startTm = UTCDateTime(int(tm[0])).strftime("%Y-%m-%d %H:%M")
+    #         endTm = UTCDateTime(int(tm[1])).strftime("%Y-%m-%d %H:%M")
+    #         tmStrList.append("%s-%s" % (startTm, endTm))
+    #     msgBox = SelectButtonDialog(message=msg, buttonLabels=tmStrList)
+    #     msgBox.exec_()
+    #
+    #     self.readStart, self.readEnd = self.timeRanges[staID][msgBox.ret]
+    #     self.trackInfo(f"Select time {self.readStart, self.readEnd}", "info")
+
+    def readWFFiles(self, staID):
+        count = 0
+        for chanID in self.waveformData[staID]['filesInfo']:
+            # check chanID
+            hasChan = checkWFChan(chanID, self.reqWFChans)
+            if not hasChan:
                 continue
-            netID = trace.stats['network']
-            statID = trace.stats['station']
-            locID = trace.stats['location']
-            self.curNetStatLoc = k = (netID, statID, locID)
-            if trace.stats.mseed['encoding'] == 'ASCII':
-                file = self.readASCII(path, fileName, file, trace, k, chanID)
-            else:
-                if k not in self.streams.keys():
-                    self.streams[k] = Stream()
-                self.streams[k].append(trace)
-        if file is not None:
-            file.close()
-        return True
-
-    def readTrace(self, trace, channels):
-        chan = {}
-        chan['netID'] = trace.stats['network']
-        chan['statID'] = trace.stats['station']
-        chan['locID'] = trace.stats['location']
-        chanID = chan['chanID'] = trace.stats['channel']
-        chan['samplerate'] = trace.stats['sampling_rate']
-        chan['startTmEpoch'] = trace.stats['starttime'].timestamp
-        chan['endTmEpoch'] = trace.stats['endtime'].timestamp
-        """
-        trace time start with 0 => need to add with epoch starttime
-        """
-        chan['times'] = trace.times() + trace.stats['starttime'].timestamp
-        chan['data'] = trace.data
-        if chanID not in channels.keys():
-            channels[chanID] = chan
-        else:
-            msg = ("Something wrong with code logic."
-                   "After stream is merged there should be only one trace "
-                   "for channel %s: %s" % (chanID, trace))
-            self.trackInfo(msg, "error")
-        return trace.stats['starttime'], trace.stats['endtime']
-
-    def squashGaps(self, gaps):
-        """
-        Squash different lists of gaps for all channels to be one list of
-        (minStarttime, maxEndtime) gaps
-        : param gaps: list of [network, station, location, channel, starttime,
-        endtime, duration, number of missing samples]
-        : return squashedGaps
-        """
-        if len(gaps) == 0:
-            return gaps
-
-        # create dict consist of list of gaps for each channel
-        gaps_dict = {}
-        for g in gaps:
-            if g[3] not in gaps_dict:
-                gaps_dict[g[3]] = []
-            gaps_dict[g[3]].append((g[4].timestamp, g[5].timestamp))
-
-        firstLen = len(gaps_dict[gaps[0][3]])
-        diffLenList = [len(gaps_dict[k]) for k in gaps_dict.keys()
-                       if len(gaps_dict[k]) != firstLen]
-        if len(diffLenList) > 0:
-            msg = "Number of Gaps for different channel are not equal.\n"
-            for k in gaps_dict.keys():
-                msg += "%s: %s\n" % (k, len(gaps_dict[k]))
-            self.trackInfo(msg, 'error')
-            return []
-        squashedGaps = []
-        for i in range(firstLen):
-            maxEndtime = 0
-            minStarttime = constants.HIGHEST_INT
-            for val in gaps_dict.values():
-                if val[i][0] < minStarttime:
-                    minStarttime = val[i][0]
-                if val[i][1] > maxEndtime:
-                    maxEndtime = val[i][1]
-            squashedGaps.append((minStarttime, maxEndtime))
-        return squashedGaps
-
-    def readASCII(self, path, fileName, file, trace, key, chanID):
-        byteorder = trace.stats.mseed['byteorder']
-        h = trace.stats
-        logText = "\n\n**** STATE OF HEALTH: "
-        logText += ("From:%s  To:%s\n" % (h.starttime, h.endtime))
-        textFromData = trace.data.tobytes().decode()
-        if textFromData != '':
-            logText += textFromData
-        else:
-            recLen = h.mseed['record_length']
-            if file is None:
-                file = open(os.path.join(path, fileName), 'rb')
-            databytes = file.read(recLen)
-            followingBlktsTotal = unpack('%s%s' % (byteorder, 'B'),
-                                         databytes[39:40])[0]
-            if followingBlktsTotal > 1:
-                nextBlktByteNo = 48 + 8  # header + blkt1000(SEED info)
-            else:
-                nextBlktByteNo = 0
-            while nextBlktByteNo != 0:
-                try:
-                    nextBlktByteNo, info = readNextBlkt(
-                        nextBlktByteNo, databytes, byteorder)
-                    logText += info
-                except ReadBlocketteError as e:
-                    self.trackInfo(f"{key} - {chanID}: {e.msg}", 'error')
-
-        if key not in self.logData:
-            self.logData[key] = {}
-        if h.channel not in self.logData[key]:
-            self.logData[key][h.channel] = []
-        self.logData[key][h.channel].append(logText)
-        return file
+            tracesInfo = self.waveformData[staID][
+                'readData'][chanID]['tracesInfo']
+
+            for fileInfo in self.waveformData[staID]['filesInfo'][chanID]:
+                # file have been read
+                if fileInfo['read']:
+                    continue
+
+                # check time
+                hasData = False
+
+                if ((self.readStart <= fileInfo['startEpoch'] <= self.readEnd) or       # noqa: E501
+                        (self.readStart <= fileInfo['endEpoch'] <= self.readEnd)):      # noqa: E501
+                    hasData = True
+                if not hasData:
+                    continue
+                readWaveformMSeed(fileInfo['path2file'], fileInfo['fileName'],
+                                  staID, chanID, tracesInfo,
+                                  self.dataTime[staID], self.tmpDir)
+                fileInfo['read'] = True
+                count += 1
+                if count % 50 == 0:
+                    self.trackInfo(f'Read {count} waveform files', 'info')
+
+        sortData(self.waveformData)
diff --git a/sohstationviewer/model/reftek/logInfo.py b/sohstationviewer/model/reftek/logInfo.py
index 1947dbe7adf2717999bbe1feab5e0f36cb86f81d..dd9f8caa9aabe7d3b78ac060e39d5e9b0a66b2d4 100644
--- a/sohstationviewer/model/reftek/logInfo.py
+++ b/sohstationviewer/model/reftek/logInfo.py
@@ -1,16 +1,15 @@
 
 from sohstationviewer.conf import constants
 from sohstationviewer.controller.util import (
-    displayTrackingInfo, getTime6, getTime4, getVal,
-    rtnPattern)
+    getTime6, getTime4, getVal, rtnPattern)
 
 
 class LogInfo():
-    def __init__(self, parent, parentGUI, logText, key, packetType, reqDSs,
+    def __init__(self, parent, trackInfo, logText, key, packetType, reqDSs,
                  isLogFile=False):
         self.packetType = packetType
         self.parent = parent
-        self.parentGUI = parentGUI
+        self.trackInfo = trackInfo
         self.logText = logText
         self.key = key
         self.unitID, self.expNo = key
@@ -30,7 +29,7 @@ class LogInfo():
             self.model = "72A"
         self.maxEpoch = 0
         self.minEpoch = constants.HIGHEST_INT
-        self.chans = self.parent.plottingData[self.key]['channels']
+        self.chans = self.parent.SOHData[self.key]
         self.CPUVers = set()
         self.GPSVers = set()
         self.extractInfo()
@@ -76,7 +75,7 @@ class LogInfo():
                    "read, but now there is information for DAS %s in "
                    "the same State Of Health messages.\n"
                    "Skip reading State Of Health." % (self.unitID, unitID))
-            displayTrackingInfo(self.parentGUI, msg, 'error')
+            self.trackInfo(msg, 'error')
             False
         return epoch
 
@@ -210,15 +209,16 @@ class LogInfo():
 
     def addChanInfo(self, chanName, t, d, idx):
         if chanName not in self.chans:
-            self.chans[chanName] = {
+            self.chans[chanName] = {}
+            self.chans[chanName]['orgTrace'] = {
                 'unitID': self.unitID,
                 'expNo': self.expNo,
                 'times': [],
                 'data': [],
                 'logIdx': []}
-        self.chans[chanName]['times'].append(t)
-        self.chans[chanName]['data'].append(d)
-        self.chans[chanName]['logIdx'].append(idx)
+        self.chans[chanName]['orgTrace']['times'].append(t)
+        self.chans[chanName]['orgTrace']['data'].append(d)
+        self.chans[chanName]['orgTrace']['logIdx'].append(idx)
 
     def extractInfo(self):
 
diff --git a/sohstationviewer/model/reftek/reftek.py b/sohstationviewer/model/reftek/reftek.py
index 48e3a4e891c4f1e88e579f61c8da5fcff220cb45..36c77b8e4ad22085af642f47f4115e8ea727a1c3 100755
--- a/sohstationviewer/model/reftek/reftek.py
+++ b/sohstationviewer/model/reftek/reftek.py
@@ -2,28 +2,105 @@
 import os
 import numpy as np
 
-from obspy import UTCDateTime
+from PySide2 import QtWidgets
 from obspy.core import Stream
 
 from sohstationviewer.model.reftek.from_rt2ms import (
     core, soh_packet, packet)
 from sohstationviewer.model.reftek.logInfo import LogInfo
+from sohstationviewer.model.data_type_model import DataTypeModel
+from sohstationviewer.model.handling_data import (
+    readWaveformReftek, squash_gaps, sortData, readMPTrace, readText)
+
 from sohstationviewer.conf import constants
 
-from sohstationviewer.model.dataType import DataType
+from sohstationviewer.controller.util import validateFile
 
 
-class RT130(DataType):
+class RT130(DataTypeModel):
     def __init__(self, *args, **kwarg):
         self.EH = {}
         super().__init__(*args, **kwarg)
+        self.keys = set()
+        self.reqDSs = self.reqWFChans
+        self.massPosStream = {}
+        self.readSOH_indexWaveform(self.dir)
 
-    def readFile(self, path, fileName):
-        if not self.readReftek130(path, fileName):
-            self.readText(path, fileName)
-
-    def readReftek130(self, path, fileName):
-        path2file = os.path.join(path, fileName)
+        self.selectedKey = self.selectKey()
+        if self.selectedKey is None:
+            return
+        if len(self.reqWFChans) != 0:
+            self.readWFFiles(self.selectedKey)
+
+    def readSOH_indexWaveform(self, dir):
+        count = 0
+        for path, subdirs, files in os.walk(dir):
+            for fileName in files:
+                path2file = os.path.join(path, fileName)
+                if not validateFile(path2file, fileName):
+                    continue
+                if not self.readReftek130(path2file, fileName):
+                    readText(path2file, fileName, self.logData['TEXT'])
+                count += 1
+                if count % 50 == 0:
+                    self.trackInfo(
+                        f'Read {count} file headers/ SOH files', 'info')
+
+        self.combineData()
+
+    def selectKey(self):
+        self.keys = sorted(list(self.keys))
+        selectedKey = self.keys[0]
+        if len(self.keys) > 1:
+            msg = ("There are more than one keys in the given data.\n"
+                   "Please select one to one to display")
+            msgBox = QtWidgets.QMessageBox()
+            msgBox.setText(msg)
+            staButtons = []
+            for key in self.keys:
+                staButtons.append(msgBox.addButton(
+                    key, QtWidgets.QMessageBox.ActionRole))
+            abortButton = msgBox.addButton(QtWidgets.QMessageBox.Abort)
+
+            msgBox.exec_()
+
+            if msgBox.clickedButton() == abortButton:
+                return selectedKey
+            selectedIdx = staButtons.index(msgBox.clickedButton())
+            selectedKey = self.keys[selectedIdx]
+        self.trackInfo(f'Select Key {selectedKey}', 'info')
+        return selectedKey
+
+    def readWFFiles(self, staID):
+        count = 0
+        for DS in self.waveformData[staID]['filesInfo']:
+            readData = self.waveformData[staID]['readData']
+            for fileInfo in self.waveformData[staID]['filesInfo'][DS]:
+                # file have been read
+                if fileInfo['read']:
+                    continue
+
+                # check time
+                hasData = False
+                if ((self.readStart <= fileInfo['startEpoch'] <= self.readEnd) or   # noqa: E501
+                        (self.readStart <= fileInfo['endEpoch'] <= self.readEnd)):  # noqa: E501
+                    hasData = True
+                if not hasData:
+                    continue
+                readWaveformReftek(fileInfo['rt130'], staID, readData,
+                                   self.dataTime[staID], self.tmpDir)
+                fileInfo['read'] = True
+                count += 1
+                if count % 50 == 0:
+                    self.trackInfo(f'Read {count} waveform files', 'info')
+        sortData(self.waveformData)
+
+    def addLog(self, chan_pkt, logInfo):
+        if chan_pkt not in self.logData[self.curKey].keys():
+            self.logData[self.curKey][chan_pkt] = []
+        self.logData[self.curKey][chan_pkt].append(logInfo)
+
+    def readReftek130(self, path2file, fileName):
         rt130 = core.Reftek130.from_file(path2file)
         unique, counts = np.unique(rt130._data["packet_type"],
                                    return_counts=True)
@@ -32,10 +109,25 @@ class RT130(DataType):
         if b"SH" in nbr_packet_type:
             self.readSH(path2file)
         if b"EH" in nbr_packet_type or b"ET" in nbr_packet_type:
-            self.readEHET(rt130)
+            self.readEHET_MP_indexWF(rt130)
         return True
 
-    def readEHET(self, rt130):
+    def readSH(self, path2file):
+        with open(path2file, "rb") as fh:
+            str = fh.read()
+        data = soh_packet._initial_unpack_packets_soh(str)
+        for ind, d in enumerate(data):
+            self.curKey = (d['unit_id'].decode(),
+                           d['experiment_number'])
+            if self.curKey not in self.logData:
+                self.logData[self.curKey] = {}
+            logs = soh_packet.Packet.from_data(d).__str__()
+            # self.addLog(d['packet_type'].decode(), (d['time'], logs))
+            if 'SOH' not in self.logData[self.curKey]:
+                self.logData[self.curKey]['SOH'] = []
+            self.logData[self.curKey]['SOH'].append((d['time'], logs))
+
+    def readEHET_MP_indexWF(self, rt130):
         DS = rt130._data['data_stream_number'][0] + 1
         if DS not in self.reqDSs + [9]:
             return
@@ -55,69 +147,61 @@ class RT130(DataType):
             if self.curKey not in self.logData:
                 self.logData[self.curKey] = {}
             logs = packet.EHPacket(d).eh_et_info(nbr_DT_samples)
-            self.addLog('EHET', (d['time'], logs))
+            if 'EHET' not in self.logData[self.curKey]:
+                self.logData[self.curKey]['EHET'] = []
+            self.logData[self.curKey]['EHET'].append((d['time'], logs))
+        if self.curKey not in self.dataTime:
+            self.dataTime[self.curKey] = [constants.HIGHEST_INT, 0]
+            self.keys.add(self.curKey)
+        if DS == 9:
+            self.readMassPos(rt130)
+        else:
+            self.indexWaveForm(rt130, DS)
+
+    def readMassPos(self, rt130):
+        if self.curKey not in self.massPosStream:
+
+            self.massPosStream[self.curKey] = Stream()
 
         stream = core.Reftek130.to_stream(
             rt130,
             headonly=False,
             verbose=False,
             sort_permuted_package_sequence=True)
-        for trace in stream:
-            k = (d['unit_id'].decode(), d['experiment_number'])
-            if k not in self.streams.keys():
-                self.streams[self.curKey] = Stream()
-            self.streams[k].append(trace)
+        for tr in stream:
+            self.massPosStream[self.curKey].append(tr)
+            self.dataTime[self.curKey][0] = min(
+                tr.stats['starttime'].timestamp, self.dataTime[self.curKey][0])
+            self.dataTime[self.curKey][1] = max(
+                tr.stats['endtime'].timestamp, self.dataTime[self.curKey][1])
+
+    def indexWaveForm(self, rt130, DS):
+        if self.curKey not in self.waveformData:
+            self.waveformData[self.curKey] = {"filesInfo": {},
+                                              "readData": {}}
+        if DS not in self.waveformData[self.curKey]["filesInfo"]:
+            self.waveformData[self.curKey]["filesInfo"][DS] = []
 
-    def readSH(self, path2file):
-        with open(path2file, "rb") as fh:
-            str = fh.read()
-        data = soh_packet._initial_unpack_packets_soh(str)
-        for ind, d in enumerate(data):
-            self.curKey = (d['unit_id'].decode(),
-                           d['experiment_number'])
-            if self.curKey not in self.logData:
-                self.logData[self.curKey] = {}
-            logs = soh_packet.Packet.from_data(d).__str__()
-            # self.addLog(d['packet_type'].decode(), (d['time'], logs))
-            self.addLog('SOH', (d['time'], logs))
-
-    def readTrace(self, trace, channels):
-        chan = {}
-        chan['netID'] = trace.stats['network']
-        chan['statID'] = trace.stats['station']
-        chan['locID'] = trace.stats['location']
-        chanID = chan['chanID'] = trace.stats['channel']
-
-        chan['samplerate'] = trace.stats['sampling_rate']
-        chan['startTmEpoch'] = trace.stats['starttime'].timestamp
-        chan['endTmEpoch'] = trace.stats['endtime'].timestamp
-        """
-        trace time start with 0 => need to add with epoch starttime
-        """
-        chan['times'] = trace.times() + trace.stats['starttime'].timestamp
-        chan['data'] = trace.data
-        if chanID.startswith('MP'):
-            # calculation based on Logpeek's rt130MPDecode()
-            # TODO: MP only has 4 different values, data can be simplified
-            # by removing data point with same values in a row
-            # after converting, the variety of values for MP even reduce more
-            chan['data'] = np.round_(chan['data'] / 3276.7, 1)
-
-        if chanID not in channels.keys():
-            channels[chanID] = chan
-        else:
-            msg = ("Something wrong with code logic."
-                   "After stream is merged there should be only one trace "
-                   "for channel %s: %s" % (chanID, trace))
-            self.trackInfo(msg, "error")
-        return trace.stats['starttime'], trace.stats['endtime']
+        stream = core.Reftek130.to_stream(
+            rt130,
+            headonly=True,
+            verbose=False,
+            sort_permuted_package_sequence=True)
+
+        self.waveformData[self.curKey]["filesInfo"][DS].append(
+            {'rt130': rt130,
+             'startEpoch': stream[0].stats['starttime'].timestamp,
+             'endEpoch': stream[0].stats['endtime'].timestamp,
+             'read': False})
 
     def combineData(self):
         for k in self.logData:
-            self.plottingData[k] = {
-                'gaps': [],
-                'channels': {}
-            }
+            if k == 'TEXT':
+                continue
+            if k not in self.dataTime:
+                self.dataTime[k] = [constants.HIGHEST_INT, 0]
+                self.keys.add(k)
+            self.SOHData[k] = {}
             logs = []
             for pktType in ['SOH', 'EHET']:
                 if pktType == 'EHET':
@@ -134,77 +218,27 @@ class RT130(DataType):
             logStr = ''.join(logs)
             self.logData[k][pktType] = logStr
             logObj = LogInfo(
-                self, self.parentGUI, logStr, k, pktType, self.reqDSs)
-            self.plottingData[k]['earliestUTC'] = logObj.minEpoch
-            self.plottingData[k]['latestUTC'] = logObj.maxEpoch
-            for cName in self.plottingData[k]['channels']:
-                c = self.plottingData[k]['channels'][cName]
+                self, self.trackInfo, logStr, k, pktType, self.reqDSs)
+            self.dataTime[k][0] = min(logObj.minEpoch, self.dataTime[k][0])
+            self.dataTime[k][1] = max(logObj.maxEpoch, self.dataTime[k][1])
+            for cName in self.SOHData[k]:
+                c = self.SOHData[k][cName]['orgTrace']
                 c['times'] = np.array(c['times'])
                 c['data'] = np.array(c['data'])
                 c['logIdx'] = np.array(c['logIdx'])
 
-        for k in self.streams:
-            stream = self.streams[k]
+        # look in mseed to do this merge
+        allGaps = []
+        for k in self.massPosStream:
+            self.massPosData[k] = {}
+            stream = self.massPosStream[k]
             stream.merge()
+            for tr in stream:
+                chanID = tr.stats['channel']
+                self.massPosData[k][chanID] = {}
+                self.massPosData[k][chanID]['orgTrace'] = readMPTrace(tr)
+            gaps = stream.get_gaps()
             # gaps is list of [network, station, location, channel, starttime,
             # endtime, duration, number of missing samples]
-            gaps = stream.get_gaps()
-            # stream.print_gaps()
-            gaps = self.squashGaps(gaps)
-            if k not in self.plottingData:
-                self.plottingData[k] = {'gaps': gaps,
-                                        'channels': {}}
-            maxEndtime = UTCDateTime(0)
-            minStarttime = UTCDateTime(20E10)
-            for trace in stream:
-                startTm, endTm = self.readTrace(
-                    trace, self.plottingData[k]['channels'])
-                if startTm < minStarttime:
-                    minStarttime = startTm
-                if endTm > maxEndtime:
-                    maxEndtime = endTm
-            self.plottingData[k]['earliestUTC'] = min(
-                minStarttime.timestamp,
-                self.plottingData[k]['earliestUTC'])
-            self.plottingData[k]['latestUTC'] = max(
-                maxEndtime.timestamp,
-                self.plottingData[k]['latestUTC'])
-
-    def squashGaps(self, gaps):
-        """
-        Squash different lists of gaps for all channels to be one list of
-        (minStarttime, maxEndtime) gaps
-        : param gaps: list of [network, station, location, channel, starttime,
-        endtime, duration, number of missing samples]
-        : return squashedGaps
-        """
-        if len(gaps) == 0:
-            return gaps
-
-        # create dict consist of list of gaps for each channel
-        gaps_dict = {}
-        for g in gaps:
-            if g[3] not in gaps_dict:
-                gaps_dict[g[3]] = []
-            gaps_dict[g[3]].append((g[4].timestamp, g[5].timestamp))
-
-        firstLen = len(gaps_dict[gaps[0][3]])
-        diffLenList = [len(gaps_dict[k]) for k in gaps_dict.keys()
-                       if len(gaps_dict[k]) != firstLen]
-        if len(diffLenList) > 0:
-            msg = "Number of Gaps for different channel are not equal.\n"
-            for k in gaps_dict.keys():
-                msg += "%s: %s\n" % (k, len(gaps_dict[k]))
-            self.trackInfo(msg, 'error')
-            return []
-        squashedGaps = []
-        for i in range(firstLen):
-            maxEndtime = 0
-            minStarttime = constants.HIGHEST_INT
-            for val in gaps_dict.values():
-                if val[i][0] < minStarttime:
-                    minStarttime = val[i][0]
-                if val[i][1] > maxEndtime:
-                    maxEndtime = val[i][1]
-            squashedGaps.append((minStarttime, maxEndtime))
-        return squashedGaps
+            allGaps += [[g[4].timestamp, g[5].timestamp] for g in gaps]
+            self.gaps[k] = squash_gaps(allGaps)
diff --git a/sohstationviewer/view/core/dbgui_superclass.py b/sohstationviewer/view/core/dbgui_superclass.py
index f24870287d8dbe3c415f9133a922cb75cb44c675..45dc5189241ad68adceaf157f2bfbb9d1bf452aa 100755
--- a/sohstationviewer/view/core/dbgui_superclass.py
+++ b/sohstationviewer/view/core/dbgui_superclass.py
@@ -72,6 +72,7 @@ class Ui_DBInfoDialog(QtWidgets.QWidget):
             #         "\nThe format for greater than value pair is: "
             #         "+value:color with "
             #         "color=R,Y,G,M,C.\n Ex: 2.3:C|+4:M")
+
             mainLayout.addWidget(QtWidgets.QLabel(instruction))
 
     def addWidget(self, dataList, rowidx, colidx, foreignkey=False,
diff --git a/sohstationviewer/view/core/plottingWidget.py b/sohstationviewer/view/core/plottingWidget.py
index b95de5c7f7cb9e6c3100b73af4f4f4dff5dcac0d..63ccb35e18d543fbc78cd180f670b2774a20ced1 100755
--- a/sohstationviewer/view/core/plottingWidget.py
+++ b/sohstationviewer/view/core/plottingWidget.py
@@ -1,4 +1,3 @@
-import math
 
 from PySide2 import QtCore, QtWidgets
 from matplotlib.backends.backend_qt5agg import (
@@ -10,40 +9,17 @@ import numpy as np
 
 from sohstationviewer.controller.plottingData import (
     getTitle, getGaps, getTimeTicks, getUnitBitweight, getMassposValueColors)
+from sohstationviewer.controller.util import displayTrackingInfo, getVal
+
 from sohstationviewer.conf import constants
 from sohstationviewer.conf.colorSettings import Clr, set_colors
+from sohstationviewer.conf.dbSettings import dbConf
+
 from sohstationviewer.database import extractData
-from sohstationviewer.controller.util import displayTrackingInfo, getVal
 
 
-plotFunc = {
-    'linesDots': (
-        ("Lines, one color dots. "),
-        "plotLinesDots"),
-    'linesSRate': (
-        ("Lines, one color dots, bitweight info. "),
-        "plotLinesSRate"),
-    'linesMasspos': (
-        ("multi-line mass position, multi-color dots. "),
-        "plotLinesMasspos"),
-    # 'dotsMasspos': (
-    #     ("mass position, multi-color, single line. "),
-    #     "plotDotsMasspos"),
-    'dotForTime': (
-        "Dots according to timestamp. Color defined by valueColors. Ex: G",
-        "plotTimeDots"),
-    'multiColorDots': (
-        ("Multicolor dots with colors defined by valueColors. "
-         "Value from low to high. "
-         "Ex:*:W or -1:_|0:R|2.3:Y|+2.3:G. With colors: RYGMC: _: not plot"),
-        "plotMultiColorDots"
-    ),
-    'upDownDots': (
-        ("Show data with 2 different values: first down/ second up. "
-         "With colors defined by valueColors. Ex: 1:R|0:Y"),
-        'plotUpDownDots'
-    )
-}
+BOTTOM = 0.996
+BOTTOM_PX = 200
 
 
 class PlottingWidget(QtWidgets.QScrollArea):
@@ -55,9 +31,11 @@ class PlottingWidget(QtWidgets.QScrollArea):
         gap, dots, : 3
     """
 
-    def __init__(self, parent=None):
+    def __init__(self, parent=None, name=''):
         super().__init__()
+        self.name = name
         self.parent = parent
+        self.peerPlottingWidgets = [self]
         self.plotNo = 0
         self.infoWidget = None
         self.widgt = QtWidgets.QWidget(parent)
@@ -65,9 +43,13 @@ class PlottingWidget(QtWidgets.QScrollArea):
         self.currplot_title = None
         self.hidden_plots = {}
         self.zoomMarker1Shown = False
+        print("set zoomMarker1Shown False %s" % self.name)
         self.axes = []
 
-        self.widthBase = 0.185
+        self.widthBase = 0.25
+        self.widthBasePx = 1546
+        self.ratioW = 1
+
         # width of plotting area
         self.plottingW = self.widthBase
         # X1: 0.19: Width = 20% of 50in (Figure's width)
@@ -76,6 +58,13 @@ class PlottingWidget(QtWidgets.QScrollArea):
         # height of plotting area
         #     + changed when plots are added or removed
         #     + changed when changing the V-magnify param
+
+        self.plottingBot = BOTTOM
+        # this is the height of a standard plot
+        # plotH = 0.01        # 0.01: Height = 1% of 100in (Figure's height)
+        # left of plotting area: no change
+        self.plottingLBase = 0.04
+        self.plottingLBase = self.plottingLBase
         self.plottingH = 0.996
         # this is the height of a standard plot
         # plotH = 0.01        # 0.01: Height = 1% of 100in (Figure's height)
@@ -86,6 +75,7 @@ class PlottingWidget(QtWidgets.QScrollArea):
         # distance from left axis to start of label
         self.labelPad = 100
         self.fontSize = 7
+
         """
         Set Figure size 50in width, 100in height.
         This is the maximum size of plotting container.
@@ -93,14 +83,30 @@ class PlottingWidget(QtWidgets.QScrollArea):
         The actual view for user based on size of self.widgt.
         """
         self.fig = pl.Figure(facecolor='white', figsize=(50, 100))
-        self.fig.canvas.mpl_connect('pick_event', self.__on_pick_on_artist)
-        self.fig.canvas.mpl_connect('button_press_event', self.__on_pick)
+        self.fig.canvas.mpl_connect('pick_event', self.on_pick_on_artist)
+        self.fig.canvas.mpl_connect('button_press_event', self.on_pick)
 
         self.canvas = Canvas(self.fig)
         self.canvas.setParent(self.widgt)
         self.setWidget(self.widgt)
         self.set_colors('B')
 
+    # ============================= EVENT ==============================
+    def resizeEvent(self, event):
+        # print("resizeEvent")
+        geo = self.maximumViewportSize()
+        # print("resize geo:", geo)
+
+        # set view size fit with the scroll's view port size
+        self.widgt.setFixedWidth(geo.width())
+        self.ratioW = geo.width()/self.widthBasePx
+        self.plottingW = self.ratioW * self.widthBase
+        self.plottingL = self.ratioW * self.plottingLBase
+        if self.plotNo == 0:
+            self.widgt.setFixedHeight(geo.height())
+
+        return super(PlottingWidget, self).resizeEvent(event)
+
     def contextMenuEvent(self, event):
         if self.axes == []:
             return
@@ -121,7 +127,7 @@ class PlottingWidget(QtWidgets.QScrollArea):
                 lambda *arg, index=i: self.show_hidden_plot(index))
         contextMenu.exec_(self.mapToGlobal(event.pos()))
 
-    def __getTimestamp(self, event):
+    def getTimestamp(self, event):
         x, y = event.x, event.y                 # coordinate data
         inv = self.axes[0].transData.inverted()
         # convert to timestamp, bc event.xdata is None in the space bw axes
@@ -129,22 +135,24 @@ class PlottingWidget(QtWidgets.QScrollArea):
         print('xdata of mouse: {:.2f}'.format(xdata))
         return xdata
 
-    def __zoomBwMarkers(self, xdata):
+    def zoomBwMarkers(self, xdata):
+        print("%s zoomBwMarkers" % self.name)
         if self.currMinX == xdata:
             return
         # self.fig.canvas.mpl_disconnect(self.follower)
-        self.__draw()
+        self.draw()
         self.zoomMarker1Shown = False
+        print("set zoomMarker1Shown False %s" % self.name)
         [self.currMinX, self.currMaxX] = sorted(
             [self.currMinX, xdata])
-        print("ZM2 self.currMinX:", self.currMinX)
-        print("ZM2 self.currMaxX:", self.currMaxX)
-        self.__set_lim()
+        # print("ZM2 self.currMinX:", self.currMinX)
+        # print("ZM2 self.currMaxX:", self.currMaxX)
+        self.set_lim()
         self.zoomMarker1.set_visible(False)
         self.zoomMarker2.set_visible(False)
-        self.__draw()
+        self.draw()
 
-    def __on_pick(self, event):
+    def on_pick(self, event):
         """
         xmouse, ymouse = event.mouseevent.xdata, event.mouseevent.ydata
         # x, y = artist.get_xdata(), artist.get_ydata()
@@ -163,46 +171,55 @@ class PlottingWidget(QtWidgets.QScrollArea):
         """
         # print(dir(event))
         modifiers = event.guiEvent.modifiers()
-        xdata = self.__getTimestamp(event)
-        if modifiers == QtCore.Qt.ShiftModifier:
-            print("shift+click")
-            if not self.zoomMarker1Shown:
-                self.ruler.set_visible(False)
-                self.__set_ruler_visibled(self.zoomMarker1, xdata)
-                self.currMinX = xdata
-                print("ZM1 self.currMinX:", self.currMinX)
-                self.zoomMarker1Shown = True
-                self.__set_ruler_visibled(self.zoomMarker2, xdata)
-                self.__draw()
+        xdata = self.getTimestamp(event)
+        for w in self.peerPlottingWidgets:
+            if modifiers == QtCore.Qt.ShiftModifier:
+                print(" %s shift+click" % w.name)
+                print("%s check zoomMarker1Shown: %s"
+                      % (w.name, w.zoomMarker1Shown))
+                if not w.zoomMarker1Shown:
+                    w.ruler.set_visible(False)
+                    w.set_ruler_visibled(w.zoomMarker1, xdata)
+                    w.currMinX = xdata
+                    # print("ZM1 self.currMinX:", self.currMinX)
+                    w.zoomMarker1Shown = True
+                    print("set zoomMarker1Shown True %s" % w.name)
+                    w.set_ruler_visibled(w.zoomMarker2, xdata)
+
+                    w.draw()
+                else:
+                    w.zoomBwMarkers(xdata)
+                    # if self.currMinX == xdata:
+                    #     return
+                    # self.fig.canvas.mpl_disconnect(self.follower)
+                    # self.draw()
+                    # self.zoomMarker1Shown = False
+                    # [self.currMinX, self.currMaxX] = sorted(
+                    #     [self.currMinX, xdata])
+                    # print("ZM2 self.currMinX:", self.currMinX)
+                    # print("ZM2 self.currMaxX:", self.currMaxX)
+                    # self.set_lim()
+                    # self.zoomMarker1.set_visible(False)
+                    # self.zoomMarker2.set_visible(False)
+                    # self.draw()
+            elif modifiers in [QtCore.Qt.ControlModifier,
+                               QtCore.Qt.MetaModifier]:
+                print("%s Ctrl+click" % w.name)
+                w.zoomMarker1.set_visible(False)
+                w.zoomMarker1Shown = False
+                print("set zoomMarker1Shown False %s" % w.name)
+                try:
+                    w.fig.canvas.mpl_disconnect(w.follower)
+                except Exception:
+                    pass
+                w.set_ruler_visibled(w.ruler, xdata)
             else:
-                self.__zoomBwMarkers(xdata)
-                # if self.currMinX == xdata:
-                #     return
-                # self.fig.canvas.mpl_disconnect(self.follower)
-                # self.__draw()
-                # self.zoomMarker1Shown = False
-                # [self.currMinX, self.currMaxX] = sorted(
-                #     [self.currMinX, xdata])
-                # print("ZM2 self.currMinX:", self.currMinX)
-                # print("ZM2 self.currMaxX:", self.currMaxX)
-                # self.__set_lim()
-                # self.zoomMarker1.set_visible(False)
-                # self.zoomMarker2.set_visible(False)
-                # self.__draw()
-        elif modifiers in [QtCore.Qt.ControlModifier,
-                           QtCore.Qt.MetaModifier]:
-            print("Ctrl+click")
-            self.zoomMarker1.set_visible(False)
-            self.zoomMarker1Shown = False
-            self.fig.canvas.mpl_disconnect(self.follower)
-            self.__set_ruler_visibled(self.ruler, xdata)
-        else:
-            print("click xmouse:", xdata)
-            if self.zoomMarker1Shown:
-                self.__zoomBwMarkers(xdata)
+                print("%s click xmouse: %s" % (w.name, xdata))
+                if w.zoomMarker1Shown:
+                    w.zoomBwMarkers(xdata)
 
-    def __on_pick_on_artist(self, event):
-        print("============__on_pick ============")
+    def on_pick_on_artist(self, event):
+        print("============ on_pick ============")
         """
         xmouse, ymouse = event.mouseevent.xdata, event.mouseevent.ydata
         # x, y = artist.get_xdata(), artist.get_ydata()
@@ -232,43 +249,53 @@ class PlottingWidget(QtWidgets.QScrollArea):
             self.currplot_index = self.axes.index(artist)
             self.currplot_title = "Plot %s" % self.currplot_index
 
-    def __set_ruler_visibled(self, ruler, x):
+    def set_ruler_visibled(self, ruler, x):
+        print("%s set_ruler_visibled" % self.name)
         ruler.set_visible(True)
         ruler.xy1 = (x, 0)
         ruler.xy2 = (x, self.bottom)
-        if ruler == self.zoomMarker2:
-            # make zoomMarker2 follow mouse.
-            # need to disconnect when state of rulers change
-            self.follower = self.fig.canvas.mpl_connect(
-                "motion_notify_event", self.__zoomMarker2_follow_mouse)
-        self.__draw()
-
-    def __zoomMarker2_follow_mouse(self, mouseevent):
-        xdata = self.__getTimestamp(mouseevent)
+        try:
+            if ruler == self.zoomMarker2:
+                print("\t=>%s connect zoomMarker2" % self.name)
+                # make zoomMarker2 follow mouse.
+                # need to disconnect when state of rulers change
+                self.follower = self.fig.canvas.mpl_connect(
+                    "motion_notify_event", self.zoomMarker2_follow_mouse)
+        except Exception:
+            pass
+        self.draw()
+
+    def zoomMarker2_follow_mouse(self, mouseevent):
+        # TODO: set zoomMarker2 for peerPlottingWidgets that not this current
+        # because for now zoomMarker2 has no interact in between
+        xdata = self.getTimestamp(mouseevent)
         self.zoomMarker2.xy1 = (xdata, 0)
         self.zoomMarker2.xy2 = (xdata, self.bottom)
-        self.__draw()
+        self.draw()
 
     def keyPressEvent(self, event):
         if event.key() == QtCore.Qt.Key_Escape:
             print("ESC")
-            self.ruler.set_visible(False)
-            self.zoomMarker1.set_visible(False)
-            self.zoomMarker2.set_visible(False)
-            self.zoomMarker1Shown = False
-            self.__draw()
+            for w in self.peerPlottingWidgets:
+                w.ruler.set_visible(False)
+                w.zoomMarker1.set_visible(False)
+                w.zoomMarker2.set_visible(False)
+                w.zoomMarker1Shown = False
+                print("set zoomMarker1Shown False %s" % w.name)
+                w.draw()
         return super(PlottingWidget, self).keyPressEvent(event)
+    # ============================= END EVENT ==============================
 
-    def __add_timestamp_bar(self, usedHeight, top=True):
+    def add_timestamp_bar(self, usedHeight, top=True, hasLabel=True):
         """
         set the axes to display timestampBar on top of the plotting area
         setting axis off to not display at the begining
         Color in qpeek is DClr[T0]
         :return:
         """
-        self.plottingH -= usedHeight
+        self.plottingBot -= usedHeight
         timestampBar = self.canvas.figure.add_axes(
-            [self.plottingL, self.plottingH, self.plottingW, 0.00005],
+            [self.plottingL, self.plottingBot, self.plottingW, 0.00005],
         )
         timestampBar.axis('off')
         timestampBar.xaxis.set_minor_locator(AutoMinorLocator())
@@ -287,18 +314,19 @@ class PlottingWidget(QtWidgets.QScrollArea):
         timestampBar.tick_params(which='minor', length=4, width=1,
                                  direction='inout',
                                  colors=self.displayColor['TM'])
-        timestampBar.set_ylabel('Hours',
-                                fontweight='bold',
-                                fontsize=self.fontSize+2,
-                                rotation=0,
-                                labelpad=self.labelPad,
-                                ha='left',
-                                color=self.displayColor['TM'])
-
-        # self.__update_timestamp_bar(timestampBar)
+        if hasLabel:
+            timestampBar.set_ylabel('Hours',
+                                    fontweight='bold',
+                                    fontsize=self.fontSize,
+                                    rotation=0,
+                                    labelpad=self.labelPad,
+                                    ha='left',
+                                    color=self.displayColor['TM'])
+
+        # self.update_timestamp_bar(timestampBar)
         return timestampBar
 
-    def __update_timestamp_bar(self, timestampBar):
+    def update_timestamp_bar(self, timestampBar):
         times, majorTimes, majorTimeLabels = getTimeTicks(
             self.currMinX, self.currMaxX, self.dateMode, self.timeTicksTotal)
         timestampBar.axis('on')
@@ -309,7 +337,7 @@ class PlottingWidget(QtWidgets.QScrollArea):
                                      fontsize=self.fontSize+2)
         timestampBar.set_xlim(self.currMinX, self.currMaxX)
 
-    def __create_axes(self, plotB, plotH, hasMinMaxLines=True):
+    def create_axes(self, plotB, plotH, hasMinMaxLines=True):
         ax = self.canvas.figure.add_axes(
             [self.plottingL, plotB, self.plottingW, plotH],
             picker=True
@@ -352,6 +380,7 @@ class PlottingWidget(QtWidgets.QScrollArea):
                 size=self.fontSize
             )
             titleVerAlignment = 'top'
+
         if linkedAx is None:
             # set title on left side
             ax.text(
@@ -414,14 +443,14 @@ class PlottingWidget(QtWidgets.QScrollArea):
             ax.spines['top'].set_visible(False)
             ax.spines['bottom'].set_visible(False)
         else:
-            minY = min(y)
-            maxY = max(y)
+            minY = y.min()
+            maxY = y.max()
             ax.spines['top'].set_visible(True)
             ax.spines['bottom'].set_visible(True)
             ax.unit_bw = getUnitBitweight(chanDB, self.parent.bitweightOpt)
-            self.__setAxesYlim(ax, minY, maxY)
+            self.setAxesYlim(ax, minY, maxY)
 
-    def __setAxesYlim(self, ax, minY, maxY):
+    def setAxesYlim(self, ax, minY, maxY):
         minY = round(minY, 7)
         maxY = round(maxY, 7)
         if maxY > minY:
@@ -443,10 +472,10 @@ class PlottingWidget(QtWidgets.QScrollArea):
         if self.parent.minGap is None:
             return
         self.gaps = getGaps(gaps, float(self.parent.minGap))
-        self.plottingH -= 0.003
-        self.gapBar = self.__create_axes(self.plottingH,
-                                         0.001,
-                                         hasMinMaxLines=False)
+        self.plottingBot -= 0.003
+        self.gapBar = self.create_axes(self.plottingBot,
+                                       0.001,
+                                       hasMinMaxLines=False)
         self.updateGapBar()
 
     def updateGapBar(self):
@@ -467,11 +496,11 @@ class PlottingWidget(QtWidgets.QScrollArea):
                                             lw=0.,
                                             zorder=3))  # on top of center line
 
-    def __get_height(self, ratio):
+    def get_height(self, ratio):
         plotH = 0.0012 * ratio  # ratio with figure height
         bwPlotsDistance = 0.0015
-        self.plottingH -= plotH + bwPlotsDistance
-        self.plottingHPixel += 19 * ratio
+        self.plottingBot -= plotH + bwPlotsDistance
+        self.plottingBotPixel += 19 * ratio
         return plotH
 
     # -------------------- Different color dots ----------------------- #
@@ -481,13 +510,13 @@ class PlottingWidget(QtWidgets.QScrollArea):
         """
         plotH = 0.00001
         bwPlotsDistance = 0.0001
-        self.plottingH -= plotH + bwPlotsDistance
-        ax = self.__create_axes(self.plottingH, plotH, hasMinMaxLines=False)
+        self.plottingBot -= plotH + bwPlotsDistance
+        ax = self.create_axes(self.plottingBot, plotH, hasMinMaxLines=False)
         ax.x = None
         ax.plot([0], [0], linestyle="")
         return ax
 
-    def plotMultiColorDots(self, cData, chanDB, chan, linkedAx):
+    def plotMultiColorDots(self, cData, chanDB, chan, ax, linkedAx):
         """
         plot scattered dots with colors defined by valueColors:
           *:W or -1:_|0:R|2.3:Y|+2.3:G
@@ -498,12 +527,12 @@ class PlottingWidget(QtWidgets.QScrollArea):
         :return:
         """
 
-        plotH = self.__get_height(chanDB['height'])
-        if linkedAx is None:
-            ax = self.__create_axes(
-                self.plottingH, plotH, hasMinMaxLines=False)
-        else:
+        plotH = self.get_height(chanDB['height'])
+        if linkedAx is not None:
             ax = linkedAx
+        if ax is None:
+            ax = self.create_axes(
+                self.plottingBot, plotH, hasMinMaxLines=False)
 
         x = []
         prevVal = -constants.HIGHEST_INT
@@ -519,15 +548,15 @@ class PlottingWidget(QtWidgets.QScrollArea):
                 continue
 
             if v.startswith('+'):
-                points = [cData['decTimes'][i]
-                          for i in range(len(cData['decData']))
-                          if cData['decData'][i] > val]
+                points = [cData['times'][i]
+                          for i in range(len(cData['data']))
+                          if cData['data'][i] > val]
             elif v == '*':
-                points = cData['decTimes']
+                points = cData['times']
             else:
-                points = [cData['decTimes'][i]
-                          for i in range(len(cData['decData']))
-                          if prevVal < cData['decData'][i] <= val]
+                points = [cData['times'][i]
+                          for i in range(len(cData['data']))
+                          if prevVal < cData['data'][i] <= val]
             x += points
 
             ax.plot(points, len(points) * [0], linestyle="",
@@ -554,16 +583,16 @@ class PlottingWidget(QtWidgets.QScrollArea):
     #     return self.plotMultiColorDots(cData, chanDB, chan, linkedAx)
 
     # ---------------------------- up/down dots ---------------------------- #
-    def plotUpDownDots(self, cData, chanDB, chan, linkedAx):
+    def plotUpDownDots(self, cData, chanDB, chan, ax, linkedAx):
         """
         data with 2 different values defined in valueColors
         """
-        plotH = self.__get_height(chanDB['height'])
-        if linkedAx is None:
-            ax = self.__create_axes(
-                self.plottingH, plotH, hasMinMaxLines=False)
-        else:
+        plotH = self.get_height(chanDB['height'])
+        if linkedAx is not None:
             ax = linkedAx
+        if ax is None:
+            ax = self.create_axes(
+                self.plottingBot, plotH, hasMinMaxLines=False)
 
         valCols = chanDB['valueColors'].split('|')
         pointsList = []
@@ -572,9 +601,9 @@ class PlottingWidget(QtWidgets.QScrollArea):
             v, c = vc.split(':')
             val = getVal(v)
 
-            points = [cData['decTimes'][i]
-                      for i in range(len(cData['decData']))
-                      if cData['decData'][i] == val]
+            points = [cData['times'][i]
+                      for i in range(len(cData['data']))
+                      if cData['data'][i] == val]
             pointsList.append(points)
             colors.append(c)
 
@@ -600,17 +629,17 @@ class PlottingWidget(QtWidgets.QScrollArea):
         return ax
 
     # ----------------------- dots for times, ignore data------------------- #
-    def plotTimeDots(self, cData, chanDB, chan, linkedAx):
-        plotH = self.__get_height(chanDB['height'])
-        if linkedAx is None:
-            ax = self.__create_axes(self.plottingH, plotH)
-        else:
+    def plotTimeDots(self, cData, chanDB, chan, ax, linkedAx):
+        plotH = self.get_height(chanDB['height'])
+        if linkedAx is not None:
             ax = linkedAx
+        if ax is None:
+            ax = self.create_axes(self.plottingBot, plotH)
 
         color = 'W'
         if chanDB['valueColors'] not in [None, 'None', '']:
             color = chanDB['valueColors'].strip()
-        x = cData['decTimes']
+        x = cData['times']
         self.setAxesInfo(ax, [len(x)], chanDB=chanDB, linkedAx=linkedAx)
 
         ax.myPlot = ax.plot(x, [0]*len(x), marker='s', markersize=1.5,
@@ -624,15 +653,15 @@ class PlottingWidget(QtWidgets.QScrollArea):
         return ax
 
     # ----------------------- lines - one color dots ----------------------- #
-    def plotLinesDots(self, cData, chanDB, chan, linkedAx, info=''):
+    def plotLinesDots(self, cData, chanDB, chan, ax, linkedAx, info=''):
         """ L:G|D:W """
-        plotH = self.__get_height(chanDB['height'])
-        if linkedAx is None:
-            ax = self.__create_axes(self.plottingH, plotH)
-        else:
+        plotH = self.get_height(chanDB['height'])
+        if linkedAx is not None:
             ax = linkedAx
+        if ax is None:
+            ax = self.create_axes(self.plottingBot, plotH)
 
-        x, y = cData['decTimes'], cData['decData']
+        x, y = cData['times'], cData['data']
         self.setAxesInfo(ax, [len(x)], chanDB=chanDB,
                          info=info, y=y, linkedAx=linkedAx)
         colors = {}
@@ -668,7 +697,7 @@ class PlottingWidget(QtWidgets.QScrollArea):
             ax.linkedY = y
         return ax
 
-    def plotLinesSRate(self, cData, chanDB, chan, linkedAx):
+    def plotLinesSRate(self, cData, chanDB, chan, ax, linkedAx):
         """
         multi-line line seismic, one color, line only,
             can apply bit weights in (get_unit_bitweight())
@@ -677,21 +706,10 @@ class PlottingWidget(QtWidgets.QScrollArea):
             info = "%dsps" % cData['samplerate']
         else:
             info = "%gsps" % cData['samplerate']
-        return self.plotLinesDots(cData, chanDB, chan, linkedAx, info=info)
+        return self.plotLinesDots(cData, chanDB, chan, ax, linkedAx, info=info)
 
     # ----------------------- lines - multi-color dots --------------------- #
-    def plotLinesMasspos(self, cData, chanDB, chan, linkedAx):
-
-        # if (chan.startswith("MP") and
-        #         chanDB['valueColors'] not in [None, 'None', '']):
-        #     _colors = chanDB['valueColors'].split("|")
-        #     _values = sorted(set(abs(cData['decData'])))
-        #
-        #     valueColors = [(_values[idx], _colors[idx])
-        #                    for idx in range(len(_values[:len(_colors)]))]
-        # else:
-        _values = sorted(set(abs(cData['decData'])))
-        print("_values:", _values)
+    def plotLinesMasspos(self, cData, chanDB, chan, ax, linkedAx):
         valueColors = getMassposValueColors(
             self.parent.massPosVoltRangeOpt, chan,
             self.cMode, self.errors, retType='tupleList')
@@ -699,8 +717,8 @@ class PlottingWidget(QtWidgets.QScrollArea):
         if valueColors is None:
             return
 
-        plotH = self.__get_height(chanDB['height'])
-        ax = self.__create_axes(self.plottingH, plotH)
+        plotH = self.get_height(chanDB['height'])
+        ax = self.create_axes(self.plottingBot, plotH)
 
         ax.x, ax.y = cData['times'], cData['data']
         self.setAxesInfo(ax, [len(ax.x)], chanDB=chanDB, y=ax.y)
@@ -726,10 +744,9 @@ class PlottingWidget(QtWidgets.QScrollArea):
                 count += 1
         ax.scatter(ax.x, ax.y, marker='s', c=colors, s=sizes, zorder=3)
         return ax
-
     # ---------------------------------------------------------#
 
-    def __add_ruler(self, color):
+    def add_ruler(self, color):
         ruler = ConnectionPatch(
             xyA=(0, 0),
             xyB=(0, self.bottom),
@@ -743,9 +760,16 @@ class PlottingWidget(QtWidgets.QScrollArea):
         self.timestampBarBottom.add_artist(ruler)
         return ruler
 
-    def __set_lim(self, orgSize=False):
-        self.__update_timestamp_bar(self.timestampBarTop)
-        self.__update_timestamp_bar(self.timestampBarBottom)
+    def set_lim(self, orgSize=False):
+        if not orgSize:
+            for chanID in self.plottingData1:
+                cData = self.plottingData1[chanID]
+                self.getZoomData(cData, chanID)
+            for chanID in self.plottingData2:
+                cData = self.plottingData2[chanID]
+                self.getZoomData(cData, chanID)
+        self.update_timestamp_bar(self.timestampBarTop)
+        self.update_timestamp_bar(self.timestampBarBottom)
         if hasattr(self, 'gapBar'):
             self.gapBar.set_xlim(self.currMinX, self.currMaxX)
             if not orgSize:
@@ -781,9 +805,9 @@ class PlottingWidget(QtWidgets.QScrollArea):
                     newY = ax.y[newMinXIndex:newMaxXIndex + 1]
                     newMinY = min(newY)
                     newMaxY = max(newY)
-                    self.__setAxesYlim(ax, newMinY, newMaxY)
+                    self.setAxesYlim(ax, newMinY, newMaxY)
 
-    def __set_title(self, title):
+    def set_title(self, title):
         self.fig.text(-0.15, 100, title,
                       verticalalignment='top',
                       horizontalalignment='left',
@@ -791,7 +815,7 @@ class PlottingWidget(QtWidgets.QScrollArea):
                       color=self.displayColor['TX'],
                       size=self.fontSize)
 
-    def __draw(self):
+    def draw(self):
         try:
             self.canvas.draw()
             # a bug on mac:
@@ -814,7 +838,7 @@ class PlottingWidget(QtWidgets.QScrollArea):
 
     def set_background_color(self, color='black'):
         self.fig.patch.set_facecolor(color)
-        self.__draw()
+        self.draw()
 
     def resetView(self):
         """
@@ -824,8 +848,8 @@ class PlottingWidget(QtWidgets.QScrollArea):
             return
         self.currMinX = self.minX
         self.currMaxX = self.maxX
-        self.__set_lim()
-        self.__draw()
+        self.set_lim()
+        self.draw()
 
     def clear(self):
         if self.zoomMarker1.get_visible():
@@ -834,30 +858,19 @@ class PlottingWidget(QtWidgets.QScrollArea):
             self.zoomMarker1.set_visible(True)
         # self.fig.clear()
         # self.axes = []
-        self.__draw()
+        self.draw()
 
-    def decimateWConvertFactor(self, cData, convertFactor, maxDP=50000):
+    def applyConvertFactor(self, cData, convertFactor):
         """
         convertFactor = 150mV/count = 150V/1000count
         => unit data * convertFactor= data *150/1000 V
-        convert data in numpy arrays to a reduced list
-        according to max number of datapoint needed
         """
         cData['data'] = np.multiply(cData['data'], [convertFactor])
-        ln = cData['times'].size
-        d = math.ceil(ln / maxDP)  # decimation factor
-
-        if d < 5:
-            cData['decTimes'] = cData['times']      # np array
-            cData['decData'] = cData['data']
-        else:
-            cData['decTimes'] = [cData['times'][i]
-                                 for i in range(ln) if i % d == 0]
-            cData['decData'] = [cData['data'][i]
-                                for i in range(ln) if i % d == 0]
-        return cData
 
-    def addPlots(self, setID, plottingData, reqInfoChans, timeTicksTotal):
+    # =============================== axes ================================
+    def plot_channels(self, startTm, endTm, staID, trim_downsample, dataTime,
+                      gaps, channelList, timeTicksTotal,
+                      plottingData1, plottingData2):
         """
         :param setID: (netID, statID, locID)
         :param plottingData: a ditionary including:
@@ -867,83 +880,110 @@ class PlottingWidget(QtWidgets.QScrollArea):
               earliestUTC: the earliest time of all channels
               latestUTC: the latest time of all channels
         :timeTicksTotal: max number of tick to show on time bar
+
         Data set: {channelname: [(x,y), (x,y)...]
         """
         # print('plottingData:', plottingData)
+        self.trim_downsample = trim_downsample
+        self.plottingData1 = plottingData1
+        self.plottingData2 = plottingData2
         self.processingLog = []     # [(message, type)]
-        self.plottingData = plottingData
         self.errors = []
         if self.axes != []:
             self.fig.clear()
         self.dateMode = self.parent.dateFormat.upper()
         self.timeTicksTotal = timeTicksTotal
-        self.minX = self.currMinX = plottingData['earliestUTC']
-        self.maxX = self.currMaxX = plottingData['latestUTC']
-        self.plotNo = len(plottingData['channels'])
-        title = getTitle(self, setID, plottingData, self.dateMode)
 
-        self.plottingHPixel = 200
-        # self.plottingH = self.plotGapB
+        self.minX = self.currMinX = max(dataTime[0], startTm)
+        self.maxX = self.currMaxX = min(dataTime[1], endTm)
+        self.plotNo = len(self.plottingData1) + len(self.plottingData2)
+        title = getTitle(staID, self.minX, self.maxX, self.dateMode)
+        self.plottingBot = BOTTOM
+        self.plottingBotPixel = BOTTOM_PX
+        # self.plottingBot = self.plotGapB
         self.axes = []
 
-        self.timestampBarTop = self.__add_timestamp_bar(0.003)
-        self.__set_title(title)
-        self.addGapBar(plottingData['gaps'])
-        notFoundChan = [c for c in reqInfoChans
-                        if c not in plottingData['channels'].keys()]
+        self.timestampBarTop = self.add_timestamp_bar(0.003)
+        self.set_title(title)
+        self.addGapBar(gaps)
+        notFoundChan = [c for c in channelList
+                        if c not in self.plottingData1.keys()]
         if len(notFoundChan) > 0:
             msg = (f"The following channels is in Channel Preferences but "
                    f"not in the given data: {notFoundChan}")
-            print(msg)
             self.processingLog.append((msg, 'warning'))
-        print("all channels:", plottingData['channels'].keys())
-        for chan in plottingData['channels'].keys():
-            print(">>>>CHAN:", chan)
-            chanDB = extractData.getChanPlotInfo(chan, self.parent.dataType)
-            print("chanDB:", chanDB)
+
+        for chanID in self.plottingData1:
+            print("chan soh ID:", chanID)
+            chanDB = extractData.getChanPlotInfo(chanID, self.parent.dataType)
+            # print("chanDB:", chanDB)
             if chanDB['height'] == 0:
                 # not draw
                 continue
             if chanDB['channel'] == 'DEFAULT':
-                msg = (f"Channel {chan}'s definition can't be found database.")
+                msg = (f"Channel {chanID}'s "
+                       f"definition can't be found database.")
                 displayTrackingInfo(self.parent, msg, 'warning')
 
-            plotType = chanDB['plotType']
             if chanDB['plotType'] == '':
                 continue
+            self.plottingData1[chanID]['chanDB'] = chanDB
+            self.getZoomData(self.plottingData1[chanID], chanID, True)
+
+        for chanID in self.plottingData2:
+            print("masspos1 chanID:", chanID)
+            chanDB = extractData.getChanPlotInfo(chanID, self.parent.dataType)
+            self.plottingData2[chanID]['chanDB'] = chanDB
+            self.getZoomData(self.plottingData2[chanID], chanID, True)
+
+        self.axes.append(self.plotNone())
+        self.timestampBarBottom = self.add_timestamp_bar(0.003, top=False)
+        self.set_lim(orgSize=True)
+        self.bottom = self.axes[-1].get_ybound()[0]
+        self.ruler = self.add_ruler(self.displayColor['TR'])
+        self.zoomMarker1 = self.add_ruler(self.displayColor['ZM'])
+        self.zoomMarker2 = self.add_ruler(self.displayColor['ZM'])
+        # Set view size fit with the given data
+        if self.widgt.geometry().height() < self.plottingBotPixel:
+            self.widgt.setFixedHeight(self.plottingBotPixel)
 
-            cData = self.decimateWConvertFactor(
-                plottingData['channels'][chan],
-                chanDB['convertFactor'],
-                50000)
+        self.draw()
+
+    def getZoomData(self, cData, chanID, firsttime=False):
+        """
+        :param setID: (netID, statID, locID)
+        :param plottingData: a ditionary including:
+            { gaps: [(t1,t2),(t1,t2),...]   (in epoch time)
+              channels:{cha: {netID, statID, locID, chanID, times, data,
+                              samplerate, startTmEpoch, endTmEpoch}       #
+              earliestUTC: the earliest time of all channels
+              latestUTC: the latest time of all channels
+        :timeTicksTotal: max number of tick to show on time bar
+
+        Data set: {channelname: [(x,y), (x,y)...]
+        """
+        chanDB = cData['chanDB']
+        plotType = chanDB['plotType']
+        self.trim_downsample(cData, self.currMinX, self.currMaxX, firsttime)
+        self.applyConvertFactor(cData, 1)
+        if 'ax' not in cData:
             linkedAx = None
             if chanDB['linkedChan'] not in [None, 'None', '']:
                 try:
-                    linkedAx = plottingData['channels'][
+                    linkedAx = self.plottingData['channels'][
                         chanDB['linkedChan']]['ax']
                 except KeyError:
                     pass
-            ax = getattr(self,
-                         plotFunc[plotType][1])(cData, chanDB, chan, linkedAx)
+            ax = getattr(self, dbConf['plotFunc'][plotType][1])(
+                cData, chanDB, chanID, None, linkedAx)
             if ax is None:
-                continue
-            plottingData['channels'][chan]['ax'] = ax
-            ax.chan = chan
-            if linkedAx is None:
-                self.axes.append(ax)
-
-        self.axes.append(self.plotNone())
-        self.timestampBarBottom = self.__add_timestamp_bar(0.003, top=False)
-        self.__set_lim(orgSize=True)
-        self.bottom = self.axes[-1].get_ybound()[0]
-        self.ruler = self.__add_ruler(self.displayColor['TR'])
-        self.zoomMarker1 = self.__add_ruler(self.displayColor['ZM'])
-        self.zoomMarker2 = self.__add_ruler(self.displayColor['ZM'])
-        # Set view size fit with the given data
-        if self.widgt.geometry().height() < self.plottingHPixel:
-            self.widgt.setFixedHeight(self.plottingHPixel)
-
-        self.__draw()
+                return
+            cData['ax'] = ax
+            ax.chan = chanID
+            self.axes.append(ax)
+        else:
+            getattr(self, dbConf['plotFunc'][plotType][1])(
+                cData, chanDB, chanID, cData['ax'], None)
 
     def hide_plots(self, plot_indexes):
         if self.axes == []:
@@ -966,7 +1006,7 @@ class PlottingWidget(QtWidgets.QScrollArea):
         # currently consider every plot height are all 100px height
         height = self.widgt.geometry().height() - 100 * len(plot_indexes)
         self.widgt.setFixedHeight(height)
-        self.__draw()
+        self.draw()
 
     def hide_currplot(self):
         pos = self.currplot.get_position()
@@ -982,7 +1022,7 @@ class PlottingWidget(QtWidgets.QScrollArea):
         height = self.widgt.geometry().height() - 100
         self.widgt.setFixedHeight(height)
         self.hidden_plots[self.currplot_index] = h
-        self.__draw()
+        self.draw()
 
     def show_hidden_plot(self, index):
         h = self.hidden_plots[index]
@@ -1000,7 +1040,7 @@ class PlottingWidget(QtWidgets.QScrollArea):
         self.widgt.setFixedHeight(height)
         del self.hidden_plots[index]
         pos = workplot.get_position()
-        self.__draw()
+        self.draw()
 
     def show_all_hidden_plots(self):
         plot_indexes = sorted(self.hidden_plots.keys())
@@ -1021,9 +1061,17 @@ class PlottingWidget(QtWidgets.QScrollArea):
         # currently consider every plot height are all 100px height
         height = self.widgt.geometry().height() + 100 * len(plot_indexes)
         self.widgt.setFixedHeight(height)
-        self.__draw()
+        self.draw()
+
+    def rePlot(self):
+        for ax in self.axes:
+            print("pos", self.ax.get_position())
 
+    # ============================= END AXES ==============================
     def set_colors(self, mode):
         self.cMode = mode
         self.displayColor = set_colors(mode)
         self.fig.patch.set_facecolor(self.displayColor['MF'])
+
+    def setPeerPlottingWidgets(self, widgets):
+        self.peerPlottingWidgets = widgets
diff --git a/sohstationviewer/view/mainwindow.py b/sohstationviewer/view/mainwindow.py
index b7a90bbec32f2ba16f2ac2ae8cb1561edcf28b4b..ce3cbd60ef4f24019ad3cda4be8dc6dd1a94abbd 100755
--- a/sohstationviewer/view/mainwindow.py
+++ b/sohstationviewer/view/mainwindow.py
@@ -1,5 +1,7 @@
 import pathlib
 import os
+import re
+from datetime import datetime
 
 from PySide2 import QtCore, QtWidgets
 
@@ -13,6 +15,13 @@ from sohstationviewer.view.channeldialog import ChannelDialog
 from sohstationviewer.view.plottypedialog import PlotTypeDialog
 from sohstationviewer.view.channelpreferdialog import ChannelPreferDialog
 from sohstationviewer.database.proccessDB import executeDB_dict
+from sohstationviewer.view.waveformdialog import WaveformDialog
+from sohstationviewer.view.time_power_squareddialog import (
+    TimePowerSquaredDialog)
+from sohstationviewer.controller.util import displayTrackingInfo
+from sohstationviewer.conf.constants import TM_FM
+from sohstationviewer.model.handling_data import (
+    trim_downsample_SOHChan, trim_downsample_WFChan)
 
 
 class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
@@ -29,9 +38,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
         self.bitweightOpt = ''
         self.getChannelPrefer()
         self.YYYY_MM_DDAction.triggered.emit()
+        self.waveformDlg = WaveformDialog(self)
+        self.TPSDlg = TimePowerSquaredDialog(self)
 
-    def resizeEvent(self, event):
-        self.plottingWidget.init_size()
+    # def resizeEvent(self, event):
+    #     self.plottingWidget.init_size()
 
     @QtCore.Slot()
     def openDataType(self):
@@ -80,7 +91,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
             self.currIDsNameLineEdit.setText('')
 
     @QtCore.Slot()
-    def replotLoadedData(self):
+    def resetView(self):
         self.plottingWidget.resetView()
 
     @QtCore.Slot()
@@ -127,18 +138,18 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
 
     @QtCore.Slot()
     def readSelectedFiles(self):
+        try:
+            del self.dataObject
+        except Exception:
+            pass
         print('Reading currently selected file.')
 
         # TODO: Launch a separate thread, so that
         # the UI doesn't hang while data is being read.
         # Otherwise, the user won't be able to cancel.
 
-        # reqInfoChans = [
-        #     'ACE', 'LOG', 'HH1', 'HH2', 'HHZ', 'LCE', 'LCQ',
-        #     'LH1', 'LH2', 'LHZ', 'VCO', 'VEA', 'VEC', 'VEP',
-        #     'VKI', 'VM1', 'VM2', 'VM3', 'VPB']
-        reqInfoChans = (self.IDs if not self.allChanCheckBox.isChecked()
-                        else [])
+        self.reqInfoChans = (self.IDs if not self.allChanCheckBox.isChecked()
+                             else [])
 
         # TODO: Having a form for user to create the list of channels to draw
         """
@@ -146,21 +157,17 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
         It can be all chans in db or preference list of chans
         For Reftek, the list of channels is fixed => may not need
         """
-        dirnames = [os.path.join(self.cwdLineEdit.text(), item.text())
-                    for item in self.openFilesList.selectedItems()]
-        if dirnames == []:
+        self.dirnames = [os.path.join(self.cwdLineEdit.text(), item.text())
+                         for item in self.openFilesList.selectedItems()]
+        if self.dirnames == []:
             msg = "No directories has been selected."
             QtWidgets.QMessageBox.warning(self, "Select directory", msg)
             return
-        self.dataType = detectDataType(self, dirnames)
+        self.dataType, _ = detectDataType(self, self.dirnames)
         if self.dataType is None:
             return
 
-        reqDSs = []
-        for idx, DSCheckbox in enumerate(self.dsCheckBoxes):
-            if DSCheckbox.isChecked():
-                reqDSs.append(idx + 1)
-
+        # get reqInfoChans
         if (not self.allChanCheckBox.isChecked() and
                 self.dataType != self.IDsDataType):
             msg = (f"DataType detected for the selected data set is "
@@ -175,27 +182,101 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
                 return
             self.allChanCheckBox.setChecked(True)
             self.currIDsNameLineEdit.setText('')
-            reqInfoChans = []
+            self.reqInfoChans = []
 
-        plottingDataSets = loadData(
-            self.dataType, self, dirnames, reqInfoChans, reqDSs)
+        self.reqWFChans = []
+        if self.dataType == 'RT130':
+            reqDSs = []
+            for idx, DSCheckbox in enumerate(self.dsCheckBoxes):
+                if DSCheckbox.isChecked():
+                    reqDSs.append(idx + 1)
+            self.reqWFChans = reqDSs
+        else:
+            if self.wfAllCheckBox.isChecked():
+                self.reqWFChans = ['*']
+            elif self.wfChansLineEdit.text().strip() != "":
+                self.reqWFChans = self.wfChansLineEdit.text().split(",")
+
+        startTmStr = self.timeFromDateEdit.date().toString(QtCore.Qt.ISODate)
+        endTmStr = self.timeToDateEdit.date().toString(QtCore.Qt.ISODate)
+        self.startTm = datetime.strptime(startTmStr, TM_FM).timestamp()
+        self.endTm = datetime.strptime(endTmStr, TM_FM).timestamp()
+
+        self.dataObject = loadData(self.dataType,
+                                   self.trackingInfoTextBrowser,
+                                   self.dirnames,
+                                   reqWFChans=self.reqWFChans,
+                                   reqSOHChans=self.reqInfoChans,
+                                   readStart=self.startTm,
+                                   readEnd=self.endTm)
+        self.replotLoadedData()
+
+    @QtCore.Slot()
+    def replotLoadedData(self):
         if self.detectGapCheckBox.isChecked():
             self.minGap = self.gapLenLineEdit.text()
         else:
             self.minGap = None
-        if len(plottingDataSets) == 1:
-            setID = list(plottingDataSets.keys())[0]
-            plottingData = plottingDataSets[setID]
+        do = self.dataObject
+
+        timeTickTotal = 5  # TODO: let user choose max ticks to be displayed
+
+        selKey = do.selectedKey
+
+        # do.createPlottingData(startTm, endTm)
+        # mainPlot
+        sohChans = (self.reqInfoChans if self.reqInfoChans != []
+                    else list(do.SOHData[selKey].keys()))
+        self.plottingWidget.plot_channels(
+            self.startTm, self.endTm, selKey,
+            trim_downsample_SOHChan, do.dataTime[selKey],
+            do.gaps[selKey], sohChans, timeTickTotal,
+            do.SOHData[selKey], do.massPosData[selKey])
+
+        peerPlottingWidgets = [self.plottingWidget]
+
+        # waveformReqChans = (self.reqInfoChans
+        #                     if (self.reqInfoChans not in [[], ['SEISMIC']])
+        #                     else list(plottingData['waveforms'].keys()))
+        # print("waveformReqChans:", waveformReqChans)
+        #
+        # if (self.TPSChansLineEdit.text().strip() != '' or
+        #         self.TPSAllCheckBox.isChecked()):
+        #     # TPSPlot
+        #     if self.TPSAllCheckBox.isChecked():
+        #         TPSReqChans = waveformReqChans
+        #     else:
+        #         TPSReqChans = getReqTPSChans(
+        #             self, waveformReqChans,
+        #             self.TPSChansLineEdit.text().split(','))
+        #
+        #     peerPlottingWidgets.append(self.TPSDlg.plottingWidget)
+        #     self.TPSDlg.setData(self.dataType, ','.join(self.dirnames))
+        #     self.TPSDlg.show()
+        #     self.TPSDlg.plottingWidget.addPlots(
+        #         setID, plottingData, TPSReqChans, timeTickTotal)
+        # else:
+        #     self.TPSDlg.hide()
+        #
+        if self.reqWFChans != []:
+            # waveformPlot
+            peerPlottingWidgets.append(self.waveformDlg.plottingWidget)
+            self.waveformDlg.setData(self.dataType, ','.join(self.dirnames))
+            self.waveformDlg.show()
+            wfChans = list(do.waveformData[do.selectedKey].keys())
+            self.waveformDlg.plottingWidget.plot_channels(
+                self.startTm, self.endTm, selKey,
+                trim_downsample_WFChan, do.dataTime[selKey],
+                wfChans, timeTickTotal,
+                do.waveformData[selKey]['readData'], do.massPosData[selKey])
         else:
-            # TODO: create form with buttons of all sets for user to choose
-            # which one to plot
-            print("ask user with set of net,stat,loc they want to look at")
-        timeTickTotal = 5     # TODO: let user choose max ticks to be displayed
-        reqInfoChans = (reqInfoChans if reqInfoChans != []
-                        else plottingData['channels'].keys())
-        print("MAin Window: all channels:", plottingData['channels'].keys())
-        self.plottingWidget.addPlots(setID, plottingData,
-                                     reqInfoChans, timeTickTotal)
+            self.waveformDlg.hide()
+
+        self.plottingWidget.setPeerPlottingWidgets(peerPlottingWidgets)
+        self.waveformDlg.plottingWidget.setPeerPlottingWidgets(
+            peerPlottingWidgets)
+        # self.TPSDlg.plottingWidget.setPeerPlottingWidsets(
+        #     peerPlottingWidgets)
 
     def setCurrentDirectory(self, path=''):
         # Remove entries when cwd changes
@@ -218,3 +299,18 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
             self.IDsName = rows[0]['name']
             self.IDs = [t.strip() for t in rows[0]['IDs'].split(',')]
             self.IDsDataType = rows[0]['dataType']
+
+
+def getReqTPSChans(parent, enteredTPSChans, waveformReqChans):
+    # TODO: unittest (may need to modify for better filter)
+    TPSReqChans = []
+    for c in enteredTPSChans:
+        TPSRE = re.compile(c.strip().replace('*', '[ZNE123]'))
+        reqChans = [chan for chan in waveformReqChans
+                    if TPSRE.match(chan)]
+        if reqChans == []:
+            msg = "%s doesn't match any seismic data channel." % c
+            displayTrackingInfo(parent, msg, 'warning')
+        else:
+            TPSReqChans += reqChans
+    return TPSReqChans
diff --git a/sohstationviewer/view/paramdialog.py b/sohstationviewer/view/paramdialog.py
index 9cdfcc2cf91ba7fa14f4c852194d23d23d6db72c..0315073a7ff1589cd15faaeaf81e1256d071c433 100755
--- a/sohstationviewer/view/paramdialog.py
+++ b/sohstationviewer/view/paramdialog.py
@@ -3,12 +3,15 @@ paramdialog.py
 GUI to add/dit/remove params
 NOTE: Cannot remove or change params that are already used for channels.
 """
+
 from PySide2 import QtWidgets
 
+
 from sohstationviewer.view.core.dbgui_superclass import Ui_DBInfoDialog
 from sohstationviewer.view.core import plottingWidget
 from sohstationviewer.database.proccessDB import executeDB
-from sohstationviewer.conf.dbSettings import conf
+
+from sohstationviewer.conf.dbSettings import dbConf
 
 
 class ParamDialog(Ui_DBInfoDialog):
@@ -25,9 +28,9 @@ class ParamDialog(Ui_DBInfoDialog):
         self.addWidget(self.dataList, rowidx, 1, foreignkey=fk)
         self.addWidget(self.dataList, rowidx, 2,
                        choices=[''] + sorted(plottingWidget.plotFunc.keys()))
+
         self.addWidget(self.dataList, rowidx, 3)
-        self.addWidget(self.dataList, rowidx, 4,
-                       range=[0, 10])
+        self.addWidget(self.dataList, rowidx, 4, range=[0, 10])
 
     def getDataList(self):
         paramRows = executeDB('SELECT * FROM Parameters')
@@ -42,7 +45,7 @@ class ParamDialog(Ui_DBInfoDialog):
             rowidx, 3).currentText().strip()
         valueColors = valueColorsString.split("|")
         for vc in valueColors:
-            if not conf['valColRE'].match(vc):
+            if not dbConf['valColRE'].match(vc):
                 msg = (f"The valueColor is requested for  '{vc}' at line "
                        f"{rowidx}does not match the required format:"
                        f"[+]value:color with color=R,Y,G,M,C."
diff --git a/sohstationviewer/view/selectbuttonsdialog.py b/sohstationviewer/view/selectbuttonsdialog.py
new file mode 100644
index 0000000000000000000000000000000000000000..c5e2e82cf4171271b82d493d7ba1dc8dcfec6f12
--- /dev/null
+++ b/sohstationviewer/view/selectbuttonsdialog.py
@@ -0,0 +1,57 @@
+import sys
+import platform
+import os
+from PySide2 import QtWidgets
+from functools import partial
+
+
+"""
+Dialog includes buttons.
+Click on a button will close the dialog and the index of the clicked button
+can be retrieved in obj.ret
+"""
+
+
+class SelectButtonDialog(QtWidgets.QDialog):
+    def __init__(self, parent=None, message='',
+                 buttonLabels=['test1', 'test2']):
+        super(SelectButtonDialog, self).__init__()
+        self.ret = -1
+        height = 50 * (1 + len(buttonLabels) / 3)
+        self.setGeometry(100, 100, 900, height)
+        mainLayout = QtWidgets.QVBoxLayout()
+        self.setLayout(mainLayout)
+
+        label = QtWidgets.QLabel(message)
+        mainLayout.addWidget(label, 0)
+
+        buttonsLayout = QtWidgets.QGridLayout()
+        mainLayout.addLayout(buttonsLayout)
+        buttons = []
+        r = -1
+        for idx, label in enumerate(buttonLabels):
+            c = idx % 3
+            if c == 0:
+                r += 1
+            buttons.append(QtWidgets.QPushButton(label))
+            buttons[idx].clicked.connect(partial(self.buttonClick, idx))
+            buttonsLayout.addWidget(buttons[idx], r, c)
+
+    def buttonClick(self, idx):
+        self.ret = idx
+        self.close()
+
+
+if __name__ == '__main__':
+    os_name, version, *_ = platform.platform().split('-')
+    if os_name == 'macOS':
+        os.environ['QT_MAC_WANTS_LAYER'] = '1'
+    app = QtWidgets.QApplication(sys.argv)
+
+    test = SelectButtonDialog(None, "Testing result from buttons",
+                              ['test01', 'test02', 'test03',
+                               'test11', 'test12', 'test13'])
+    test.exec_()
+
+    print("return Code:", test.ret)
+    sys.exit(app.exec_())
diff --git a/sohstationviewer/view/time_power_squareddialog.py b/sohstationviewer/view/time_power_squareddialog.py
new file mode 100755
index 0000000000000000000000000000000000000000..277e29c6cfebcef714672957e7662720a43ccab2
--- /dev/null
+++ b/sohstationviewer/view/time_power_squareddialog.py
@@ -0,0 +1,145 @@
+# UI and connectSignals for MainWindow
+import math
+
+from PySide2 import QtWidgets
+
+from sohstationviewer.view.core import plottingWidget as plottingWidget
+from sohstationviewer.controller.plottingData import getTitle, getDayTicks
+
+SEC_PER_DAY = 86400
+
+
+class TimePowerSquaredWidget(plottingWidget.PlottingWidget):
+
+    def addPlots(self, setID, plottingData, reqInfoChans, timeTicksTotal):
+        """
+        :param setID: (netID, statID, locID)
+        :param plottingData: a ditionary including:
+            { gaps: [(t1,t2),(t1,t2),...]   (in epoch time)
+              channels:[cha: {netID, statID, locID, chanID, times, data,
+                              samplerate, startTmEpoch, endTmEpoch}       #
+              earliestUTC: the earliest time of all channels
+              latestUTC: the latest time of all channels
+        :timeTicksTotal: max number of tick to show on time bar
+
+        """
+        self.processingLog = []  # [(message, type)]
+        self.plottingData = plottingData
+        self.errors = []
+        if self.axes != []:
+            self.fig.clear()
+        self.dateMode = self.parent.dateFormat.upper()
+        self.timeTicksTotal = timeTicksTotal
+        self.minX = self.currMinX = plottingData['earliestTmEpoch']
+        self.maxX = self.currMaxX = plottingData['latestTmEpoch']
+        self.plotNo = len(plottingData['waveforms'])
+        title = getTitle(self, setID, plottingData, self.dateMode)
+        self.plottingBot = plottingWidget.BOTTOM
+        self.plottingBotPixel = plottingWidget.BOTTOM_PX
+        self.axes = []
+        self.timestampBarTop = self.add_timestamp_bar(0.003, hasLabel=False)
+        self.set_title(title)
+        print("reqInfoChans:", reqInfoChans)
+        for chan in reqInfoChans:
+            ax = self.plotTPS(plottingData['waveforms'][chan], chan)
+            if ax is None:
+                continue
+            # ax.chan = chan
+            self.axes.append(ax)
+
+        self.axes.append(self.plotNone())
+        # self.timestampBarBottom = self.add_timestamp_bar(0.003, top=False)
+        # self.set_lim(orgSize=True)
+        self.update_timestamp_bar(self.timestampBarTop)
+        # Set view size fit with the given data
+        if self.widgt.geometry().height() < self.plottingBotPixel:
+            self.widgt.setFixedHeight(self.plottingBotPixel)
+
+        self.draw()
+
+    def plotTPS(self, cData, chan):
+        print("plotTPS")
+        print("(self.maxX - self.minX)=", (self.maxX - self.minX))
+        totalDays = 5 * math.ceil((self.maxX - self.minX) / SEC_PER_DAY)
+        print("totalDays:", totalDays)
+        plotH = self.get_height(totalDays)
+        ax = self.create_axes(self.plottingBot, plotH)
+        return ax
+
+    def create_axes(self, plotB, plotH, hasMinMaxLines=False):
+        ax = self.canvas.figure.add_axes(
+            [self.plottingL, plotB, self.plottingW, plotH],
+            picker=True
+        )
+        # ax.axis('off')
+        ax.spines['right'].set_visible(False)
+        ax.spines['left'].set_visible(False)
+        ax.xaxis.grid(True, which='major', color='r', linestyle='-')
+        ax.xaxis.grid(True, which='minor', color='b', linestyle='--')
+        ax.set_yticks([])
+        ax.tick_params(which='major', length=7, width=2,
+                       direction='inout',
+                       colors=self.displayColor['TM'],
+                       labelbottom=True,
+                       labeltop=False)
+        ax.tick_params(which='minor', length=4, width=1,
+                       direction='inout',
+                       colors=self.displayColor['TM'])
+
+        times, majorTimes, majorTimeLabels = getDayTicks()
+        ax.set_xticks(times, minor=True)
+        ax.set_xticks(majorTimes)
+        ax.set_xticklabels(majorTimeLabels, fontsize=self.fontSize)
+        ax.set_xlim(1, 24)
+        ax.patch.set_alpha(0)
+        return ax
+
+    def update_timestamp_bar(self, timestampBar):
+        times, majorTimes, majorTimeLabels = getDayTicks()
+        timestampBar.axis('on')
+        timestampBar.set_yticks([])
+        timestampBar.set_xticks(times, minor=True)
+        timestampBar.set_xticks(majorTimes)
+        timestampBar.set_xticklabels(majorTimeLabels,
+                                     fontsize=self.fontSize)
+        timestampBar.set_xlim(1, 24)
+
+
+class TimePowerSquaredDialog(QtWidgets.QWidget):
+    def __init__(self, parent):
+        super().__init__()
+        self.parent = parent
+        self.dateFormat = self.parent.dateFormat
+        self.bitweightOpt = self.parent.bitweightOpt
+        self.resize(1000, 700)
+        self.setWindowTitle("TPS Plot")
+
+        mainLayout = QtWidgets.QVBoxLayout()
+        self.setLayout(mainLayout)
+        mainLayout.setContentsMargins(5, 5, 5, 5)
+        mainLayout.setSpacing(0)
+
+        self.plottingWidget = TimePowerSquaredWidget(
+            self, "timepowersquaredwidget")
+        mainLayout.addWidget(self.plottingWidget, 2)
+
+        bottomLayout = QtWidgets.QHBoxLayout()
+        mainLayout.addLayout(bottomLayout)
+
+        self.writePSButton = QtWidgets.QPushButton('Write .ps', self)
+        bottomLayout.addWidget(self.writePSButton)
+        self.trackingInfoTextBrowser = QtWidgets.QTextBrowser(self)
+        self.trackingInfoTextBrowser.setFixedHeight(60)
+        bottomLayout.addWidget(self.trackingInfoTextBrowser)
+
+        self.connectSignals()
+
+    def setData(self, dataType, fileName):
+        self.dataType = dataType
+        self.setWindowTitle("TPS Plot %s - %s" % (dataType, fileName))
+
+    def resizeEvent(self, event):
+        self.plottingWidget.init_size()
+
+    def connectSignals(self):
+        print("connectSignals")
diff --git a/sohstationviewer/view/ui/main.ui b/sohstationviewer/view/ui/main.ui
deleted file mode 100755
index 117fd8eb7a8fd2f0f5354707e6160f2f2ce0f83e..0000000000000000000000000000000000000000
--- a/sohstationviewer/view/ui/main.ui
+++ /dev/null
@@ -1,1357 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>MainWindow</class>
- <widget class="QMainWindow" name="MainWindow">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>1798</width>
-    <height>1110</height>
-   </rect>
-  </property>
-  <property name="windowTitle">
-   <string>MainWindow</string>
-  </property>
-  <property name="unifiedTitleAndToolBarOnMac">
-   <bool>false</bool>
-  </property>
-  <widget class="QWidget" name="centralwidget">
-   <layout class="QGridLayout" name="gridLayout">
-    <property name="leftMargin">
-     <number>2</number>
-    </property>
-    <property name="topMargin">
-     <number>2</number>
-    </property>
-    <property name="rightMargin">
-     <number>2</number>
-    </property>
-    <property name="bottomMargin">
-     <number>2</number>
-    </property>
-    <item row="0" column="0">
-     <widget class="QPushButton" name="cwdPushButton">
-      <property name="text">
-       <string>Main Data Directory</string>
-      </property>
-     </widget>
-    </item>
-    <item row="0" column="1">
-     <widget class="QLineEdit" name="cwdLineEdit"/>
-    </item>
-    <item row="0" column="2">
-     <spacer name="horizontalSpacer">
-      <property name="orientation">
-       <enum>Qt::Horizontal</enum>
-      </property>
-      <property name="sizeType">
-       <enum>QSizePolicy::Preferred</enum>
-      </property>
-      <property name="sizeHint" stdset="0">
-       <size>
-        <width>40</width>
-        <height>20</height>
-       </size>
-      </property>
-     </spacer>
-    </item>
-    <item row="0" column="3">
-     <layout class="QGridLayout" name="timeFromGrid">
-      <item row="0" column="0">
-       <widget class="QLabel" name="timeFromLabel">
-        <property name="text">
-         <string>From</string>
-        </property>
-        <property name="buddy">
-         <cstring>timeFromDateEdit</cstring>
-        </property>
-       </widget>
-      </item>
-      <item row="0" column="1">
-       <widget class="QDateEdit" name="timeFromDateEdit">
-        <property name="sizePolicy">
-         <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
-          <horstretch>0</horstretch>
-          <verstretch>0</verstretch>
-         </sizepolicy>
-        </property>
-        <property name="displayFormat">
-         <string>yyyy-MM-dd</string>
-        </property>
-        <property name="calendarPopup">
-         <bool>true</bool>
-        </property>
-       </widget>
-      </item>
-     </layout>
-    </item>
-    <item row="0" column="4">
-     <layout class="QGridLayout" name="timeToGrid">
-      <item row="0" column="0">
-       <widget class="QLabel" name="timeToLabel">
-        <property name="text">
-         <string>To</string>
-        </property>
-        <property name="buddy">
-         <cstring>timeToDateEdit</cstring>
-        </property>
-       </widget>
-      </item>
-      <item row="0" column="1">
-       <widget class="QDateEdit" name="timeToDateEdit">
-        <property name="sizePolicy">
-         <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
-          <horstretch>0</horstretch>
-          <verstretch>0</verstretch>
-         </sizepolicy>
-        </property>
-        <property name="displayFormat">
-         <string>yyyy-MM-dd</string>
-        </property>
-        <property name="calendarPopup">
-         <bool>true</bool>
-        </property>
-       </widget>
-      </item>
-     </layout>
-    </item>
-    <item row="1" column="0" colspan="5">
-     <widget class="QSplitter" name="verticalSplit">
-      <property name="sizePolicy">
-       <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
-        <horstretch>0</horstretch>
-        <verstretch>0</verstretch>
-       </sizepolicy>
-      </property>
-      <property name="orientation">
-       <enum>Qt::Vertical</enum>
-      </property>
-      <property name="handleWidth">
-       <number>2</number>
-      </property>
-      <property name="childrenCollapsible">
-       <bool>false</bool>
-      </property>
-      <widget class="QWidget" name="mainWidget" native="true">
-       <layout class="QGridLayout" name="gridLayout_3">
-        <property name="leftMargin">
-         <number>0</number>
-        </property>
-        <property name="topMargin">
-         <number>0</number>
-        </property>
-        <property name="rightMargin">
-         <number>0</number>
-        </property>
-        <property name="bottomMargin">
-         <number>0</number>
-        </property>
-        <item row="0" column="0">
-         <widget class="QSplitter" name="horizontalSplit">
-          <property name="orientation">
-           <enum>Qt::Horizontal</enum>
-          </property>
-          <property name="handleWidth">
-           <number>2</number>
-          </property>
-          <property name="childrenCollapsible">
-           <bool>false</bool>
-          </property>
-          <widget class="QScrollArea" name="sideScrollArea">
-           <property name="sizePolicy">
-            <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
-             <horstretch>0</horstretch>
-             <verstretch>0</verstretch>
-            </sizepolicy>
-           </property>
-           <property name="minimumSize">
-            <size>
-             <width>0</width>
-             <height>0</height>
-            </size>
-           </property>
-           <property name="maximumSize">
-            <size>
-             <width>250</width>
-             <height>16777215</height>
-            </size>
-           </property>
-           <property name="widgetResizable">
-            <bool>true</bool>
-           </property>
-           <widget class="QWidget" name="sideScrollAreaContents">
-            <property name="geometry">
-             <rect>
-              <x>0</x>
-              <y>0</y>
-              <width>264</width>
-              <height>814</height>
-             </rect>
-            </property>
-            <layout class="QVBoxLayout" name="verticalLayout">
-             <property name="spacing">
-              <number>2</number>
-             </property>
-             <property name="leftMargin">
-              <number>0</number>
-             </property>
-             <property name="topMargin">
-              <number>0</number>
-             </property>
-             <property name="rightMargin">
-              <number>0</number>
-             </property>
-             <property name="bottomMargin">
-              <number>0</number>
-             </property>
-             <item>
-              <layout class="QGridLayout" name="primaryGrid">
-               <item row="24" column="1">
-                <layout class="QGridLayout" name="controlButtonGrid">
-                 <property name="spacing">
-                  <number>2</number>
-                 </property>
-                 <item row="0" column="3">
-                  <widget class="QPushButton" name="writePushButton">
-                   <property name="text">
-                    <string>Write .ps</string>
-                   </property>
-                  </widget>
-                 </item>
-                 <item row="0" column="2">
-                  <widget class="QPushButton" name="stopPushButton">
-                   <property name="text">
-                    <string>Stop</string>
-                   </property>
-                  </widget>
-                 </item>
-                 <item row="0" column="1">
-                  <widget class="QPushButton" name="readPushButton">
-                   <property name="text">
-                    <string>Read</string>
-                   </property>
-                  </widget>
-                 </item>
-                 <item row="0" column="0">
-                  <spacer name="horizontalSpacer_7">
-                   <property name="orientation">
-                    <enum>Qt::Horizontal</enum>
-                   </property>
-                   <property name="sizeHint" stdset="0">
-                    <size>
-                     <width>40</width>
-                     <height>20</height>
-                    </size>
-                   </property>
-                  </spacer>
-                 </item>
-                 <item row="0" column="4">
-                  <spacer name="horizontalSpacer_8">
-                   <property name="orientation">
-                    <enum>Qt::Horizontal</enum>
-                   </property>
-                   <property name="sizeHint" stdset="0">
-                    <size>
-                     <width>40</width>
-                     <height>20</height>
-                    </size>
-                   </property>
-                  </spacer>
-                 </item>
-                </layout>
-               </item>
-               <item row="26" column="0" colspan="3">
-                <widget class="QListWidget" name="listWidget_2"/>
-               </item>
-               <item row="7" column="1">
-                <widget class="Line" name="sep2">
-                 <property name="sizePolicy">
-                  <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
-                   <horstretch>0</horstretch>
-                   <verstretch>0</verstretch>
-                  </sizepolicy>
-                 </property>
-                 <property name="orientation">
-                  <enum>Qt::Horizontal</enum>
-                 </property>
-                </widget>
-               </item>
-               <item row="22" column="1">
-                <layout class="QGridLayout" name="dsGrid">
-                 <item row="2" column="3">
-                  <widget class="QCheckBox" name="ds6CheckBox">
-                   <property name="text">
-                    <string>6</string>
-                   </property>
-                  </widget>
-                 </item>
-                 <item row="0" column="3">
-                  <widget class="QCheckBox" name="ds3CheckBox">
-                   <property name="text">
-                    <string>3</string>
-                   </property>
-                  </widget>
-                 </item>
-                 <item row="3" column="2">
-                  <widget class="QCheckBox" name="dsTPSCheckBox">
-                   <property name="text">
-                    <string>TPS</string>
-                   </property>
-                  </widget>
-                 </item>
-                 <item row="2" column="0">
-                  <widget class="QLabel" name="dsLabel">
-                   <property name="text">
-                    <string>DSs:</string>
-                   </property>
-                   <property name="buddy">
-                    <cstring>ds1CheckBox</cstring>
-                   </property>
-                  </widget>
-                 </item>
-                 <item row="2" column="4">
-                  <spacer name="horizontalSpacer_6">
-                   <property name="orientation">
-                    <enum>Qt::Horizontal</enum>
-                   </property>
-                   <property name="sizeHint" stdset="0">
-                    <size>
-                     <width>40</width>
-                     <height>20</height>
-                    </size>
-                   </property>
-                  </spacer>
-                 </item>
-                 <item row="0" column="2">
-                  <widget class="QCheckBox" name="ds2CheckBox">
-                   <property name="text">
-                    <string>2</string>
-                   </property>
-                  </widget>
-                 </item>
-                 <item row="0" column="1">
-                  <widget class="QCheckBox" name="ds1CheckBox">
-                   <property name="text">
-                    <string>1</string>
-                   </property>
-                  </widget>
-                 </item>
-                 <item row="2" column="1">
-                  <widget class="QCheckBox" name="ds4CheckBox">
-                   <property name="text">
-                    <string>4</string>
-                   </property>
-                  </widget>
-                 </item>
-                 <item row="2" column="2">
-                  <widget class="QCheckBox" name="ds5checkBox">
-                   <property name="text">
-                    <string>5</string>
-                   </property>
-                  </widget>
-                 </item>
-                </layout>
-               </item>
-               <item row="20" column="1">
-                <layout class="QGridLayout" name="massPosGrid">
-                 <item row="0" column="0">
-                  <widget class="QLabel" name="massPosLabel">
-                   <property name="text">
-                    <string>Mass Pos:</string>
-                   </property>
-                   <property name="buddy">
-                    <cstring>lowChanCheckBox</cstring>
-                   </property>
-                  </widget>
-                 </item>
-                 <item row="0" column="2">
-                  <widget class="QCheckBox" name="hiChanCheckbox">
-                   <property name="text">
-                    <string>456</string>
-                   </property>
-                  </widget>
-                 </item>
-                 <item row="0" column="1">
-                  <widget class="QCheckBox" name="lowChanCheckBox">
-                   <property name="text">
-                    <string>123</string>
-                   </property>
-                  </widget>
-                 </item>
-                 <item row="0" column="3">
-                  <spacer name="horizontalSpacer_3">
-                   <property name="orientation">
-                    <enum>Qt::Horizontal</enum>
-                   </property>
-                   <property name="sizeHint" stdset="0">
-                    <size>
-                     <width>40</width>
-                     <height>20</height>
-                    </size>
-                   </property>
-                  </spacer>
-                 </item>
-                </layout>
-               </item>
-               <item row="12" column="1">
-                <layout class="QGridLayout" name="zoomGrid">
-                 <property name="leftMargin">
-                  <number>0</number>
-                 </property>
-                 <item row="0" column="1">
-                  <widget class="QSpinBox" name="horizontalZoomSpinBox"/>
-                 </item>
-                 <item row="0" column="0">
-                  <widget class="QLabel" name="horizontalZoomLabel">
-                   <property name="text">
-                    <string>Mag. X:</string>
-                   </property>
-                   <property name="buddy">
-                    <cstring>horizontalZoomSpinBox</cstring>
-                   </property>
-                  </widget>
-                 </item>
-                 <item row="1" column="1">
-                  <widget class="QSpinBox" name="verticalZoomSpinBox"/>
-                 </item>
-                 <item row="1" column="0">
-                  <widget class="QLabel" name="verticalZoomLabel">
-                   <property name="text">
-                    <string>Mag. Y:</string>
-                   </property>
-                   <property name="buddy">
-                    <cstring>verticalZoomSpinBox</cstring>
-                   </property>
-                  </widget>
-                 </item>
-                 <item row="0" column="2">
-                  <spacer name="horizontalSpacer_2">
-                   <property name="orientation">
-                    <enum>Qt::Horizontal</enum>
-                   </property>
-                   <property name="sizeHint" stdset="0">
-                    <size>
-                     <width>40</width>
-                     <height>20</height>
-                    </size>
-                   </property>
-                  </spacer>
-                 </item>
-                </layout>
-               </item>
-               <item row="4" column="0" colspan="2">
-                <layout class="QGridLayout" name="mainOptionsGrid">
-                 <property name="horizontalSpacing">
-                  <number>1</number>
-                 </property>
-                 <property name="verticalSpacing">
-                  <number>2</number>
-                 </property>
-                 <item row="2" column="0">
-                  <widget class="QLineEdit" name="searchLineEdit">
-                   <property name="sizePolicy">
-                    <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
-                     <horstretch>0</horstretch>
-                     <verstretch>0</verstretch>
-                    </sizepolicy>
-                   </property>
-                   <property name="minimumSize">
-                    <size>
-                     <width>100</width>
-                     <height>0</height>
-                    </size>
-                   </property>
-                   <property name="maximumSize">
-                    <size>
-                     <width>150</width>
-                     <height>16777215</height>
-                    </size>
-                   </property>
-                   <property name="placeholderText">
-                    <string>Search...</string>
-                   </property>
-                  </widget>
-                 </item>
-                 <item row="3" column="0">
-                  <layout class="QGridLayout" name="fileListGrid">
-                   <property name="spacing">
-                    <number>2</number>
-                   </property>
-                   <item row="0" column="1">
-                    <widget class="QCheckBox" name="fileListLogCheckBox">
-                     <property name="text">
-                      <string>.log</string>
-                     </property>
-                    </widget>
-                   </item>
-                   <item row="1" column="2">
-                    <widget class="QCheckBox" name="fileListZipCheckBox">
-                     <property name="text">
-                      <string>.zip</string>
-                     </property>
-                    </widget>
-                   </item>
-                   <item row="1" column="1">
-                    <widget class="QCheckBox" name="fileListCfCheckBox">
-                     <property name="text">
-                      <string>.cf</string>
-                     </property>
-                    </widget>
-                   </item>
-                   <item row="0" column="2">
-                    <widget class="QCheckBox" name="fileListRefCheckBox">
-                     <property name="text">
-                      <string>.ref</string>
-                     </property>
-                    </widget>
-                   </item>
-                   <item row="0" column="0">
-                    <widget class="QLabel" name="label_4">
-                     <property name="text">
-                      <string>List:</string>
-                     </property>
-                     <property name="buddy">
-                      <cstring>fileListLogCheckBox</cstring>
-                     </property>
-                    </widget>
-                   </item>
-                   <item row="0" column="5">
-                    <spacer name="horizontalSpacer_5">
-                     <property name="orientation">
-                      <enum>Qt::Horizontal</enum>
-                     </property>
-                     <property name="sizeHint" stdset="0">
-                      <size>
-                       <width>40</width>
-                       <height>20</height>
-                      </size>
-                     </property>
-                    </spacer>
-                   </item>
-                  </layout>
-                 </item>
-                 <item row="4" column="0">
-                  <layout class="QGridLayout" name="backgroundGrid">
-                   <property name="spacing">
-                    <number>2</number>
-                   </property>
-                   <item row="0" column="1">
-                    <widget class="QRadioButton" name="backgroundWhiteRadioButton">
-                     <property name="text">
-                      <string>B</string>
-                     </property>
-                     <property name="checked">
-                      <bool>true</bool>
-                     </property>
-                     <attribute name="buttonGroup">
-                      <string notr="true">buttonGroup</string>
-                     </attribute>
-                    </widget>
-                   </item>
-                   <item row="0" column="2">
-                    <widget class="QRadioButton" name="backgroundBlackRadioButton">
-                     <property name="text">
-                      <string>W</string>
-                     </property>
-                     <attribute name="buttonGroup">
-                      <string notr="true">buttonGroup</string>
-                     </attribute>
-                    </widget>
-                   </item>
-                   <item row="0" column="0">
-                    <widget class="QLabel" name="backgroundLabel">
-                     <property name="text">
-                      <string>Background:</string>
-                     </property>
-                     <property name="buddy">
-                      <cstring>backgroundWhiteRadioButton</cstring>
-                     </property>
-                    </widget>
-                   </item>
-                   <item row="0" column="3">
-                    <spacer name="horizontalSpacer_4">
-                     <property name="orientation">
-                      <enum>Qt::Horizontal</enum>
-                     </property>
-                     <property name="sizeHint" stdset="0">
-                      <size>
-                       <width>40</width>
-                       <height>20</height>
-                      </size>
-                     </property>
-                    </spacer>
-                   </item>
-                  </layout>
-                 </item>
-                 <item row="2" column="1" rowspan="2">
-                  <layout class="QGridLayout" name="plotControlGrid">
-                   <property name="spacing">
-                    <number>2</number>
-                   </property>
-                   <item row="0" column="0">
-                    <widget class="QPushButton" name="clearPushButton">
-                     <property name="text">
-                      <string>Clear</string>
-                     </property>
-                    </widget>
-                   </item>
-                   <item row="1" column="0">
-                    <widget class="QPushButton" name="replotPushButton">
-                     <property name="text">
-                      <string>Replot</string>
-                     </property>
-                    </widget>
-                   </item>
-                   <item row="2" column="0">
-                    <widget class="QPushButton" name="reloadPushButton">
-                     <property name="text">
-                      <string>Reload</string>
-                     </property>
-                    </widget>
-                   </item>
-                  </layout>
-                 </item>
-                </layout>
-               </item>
-               <item row="8" column="1">
-                <widget class="QCheckBox" name="sohCheckBox">
-                 <property name="text">
-                  <string>SOH Only</string>
-                 </property>
-                </widget>
-               </item>
-               <item row="23" column="1">
-                <widget class="Line" name="sep0">
-                 <property name="sizePolicy">
-                  <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
-                   <horstretch>0</horstretch>
-                   <verstretch>0</verstretch>
-                  </sizepolicy>
-                 </property>
-                 <property name="orientation">
-                  <enum>Qt::Horizontal</enum>
-                 </property>
-                </widget>
-               </item>
-               <item row="2" column="0" colspan="2">
-                <widget class="QListWidget" name="openFilesList"/>
-               </item>
-               <item row="21" column="1">
-                <widget class="Line" name="sep1">
-                 <property name="sizePolicy">
-                  <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
-                   <horstretch>0</horstretch>
-                   <verstretch>0</verstretch>
-                  </sizepolicy>
-                 </property>
-                 <property name="orientation">
-                  <enum>Qt::Horizontal</enum>
-                 </property>
-                </widget>
-               </item>
-              </layout>
-             </item>
-            </layout>
-           </widget>
-          </widget>
-          <widget class="QScrollArea" name="mainScrollArea">
-           <property name="sizePolicy">
-            <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
-             <horstretch>0</horstretch>
-             <verstretch>0</verstretch>
-            </sizepolicy>
-           </property>
-           <property name="widgetResizable">
-            <bool>true</bool>
-           </property>
-           <widget class="QWidget" name="mainScrollAreaContents">
-            <property name="geometry">
-             <rect>
-              <x>0</x>
-              <y>0</y>
-              <width>1540</width>
-              <height>828</height>
-             </rect>
-            </property>
-            <property name="sizePolicy">
-             <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
-              <horstretch>5</horstretch>
-              <verstretch>0</verstretch>
-             </sizepolicy>
-            </property>
-            <layout class="QGridLayout" name="gridLayout_2">
-             <property name="leftMargin">
-              <number>0</number>
-             </property>
-             <property name="topMargin">
-              <number>0</number>
-             </property>
-             <property name="rightMargin">
-              <number>0</number>
-             </property>
-             <property name="bottomMargin">
-              <number>0</number>
-             </property>
-             <property name="spacing">
-              <number>2</number>
-             </property>
-             <item row="0" column="0">
-              <widget class="PlottingWidget" name="plottingWidget"/>
-             </item>
-            </layout>
-           </widget>
-          </widget>
-         </widget>
-        </item>
-       </layout>
-      </widget>
-      <widget class="QTextBrowser" name="trackingInfoTextBrowser">
-       <property name="sizePolicy">
-        <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
-         <horstretch>0</horstretch>
-         <verstretch>0</verstretch>
-        </sizepolicy>
-       </property>
-       <property name="maximumSize">
-        <size>
-         <width>16777215</width>
-         <height>200</height>
-        </size>
-       </property>
-      </widget>
-     </widget>
-    </item>
-   </layout>
-  </widget>
-  <widget class="QMenuBar" name="menubar">
-   <property name="geometry">
-    <rect>
-     <x>0</x>
-     <y>0</y>
-     <width>1798</width>
-     <height>20</height>
-    </rect>
-   </property>
-   <widget class="QMenu" name="menu_File">
-    <property name="title">
-     <string>&amp;File</string>
-    </property>
-    <addaction name="deleteSetup"/>
-    <addaction name="separator"/>
-    <addaction name="quit"/>
-   </widget>
-   <widget class="QMenu" name="menu_Command">
-    <property name="title">
-     <string>&amp;Commands</string>
-    </property>
-    <widget class="QMenu" name="menu_Export_TT_Times_As">
-     <property name="title">
-      <string>&amp;Export TT Times As...</string>
-     </property>
-     <addaction name="exportDeploymentFile"/>
-     <addaction name="exportDeploymentBlock"/>
-     <addaction name="exportTSPGeometry"/>
-     <addaction name="exportShotInfo"/>
-    </widget>
-    <addaction name="openGPSPlots"/>
-    <addaction name="searchLog"/>
-    <addaction name="plotTimeRanges"/>
-    <addaction name="plotPositions"/>
-    <addaction name="separator"/>
-    <addaction name="menu_Export_TT_Times_As"/>
-   </widget>
-   <widget class="QMenu" name="menu_Options">
-    <property name="title">
-     <string>&amp;Options</string>
-    </property>
-    <addaction name="plotTimingProblems"/>
-    <addaction name="filterNonSOHLines"/>
-    <addaction name="separator"/>
-    <addaction name="sortFilesByType"/>
-    <addaction name="sortFilesAlphabetically"/>
-    <addaction name="calculateFileSizes"/>
-    <addaction name="warnIfBig"/>
-    <addaction name="separator"/>
-    <addaction name="addMassPositionToSOH"/>
-    <addaction name="colorMPRegular"/>
-    <addaction name="colorMPTrillium"/>
-    <addaction name="separator"/>
-    <addaction name="addPositionsToET"/>
-    <addaction name="separator"/>
-    <addaction name="readAntellopeLog"/>
-    <addaction name="separator"/>
-    <addaction name="showYYYYDOYDates"/>
-    <addaction name="showYYYY_MM_DDDates"/>
-    <addaction name="showYYYYMMMDDDates"/>
-    <addaction name="separator"/>
-    <addaction name="setFontSizes"/>
-   </widget>
-   <widget class="QMenu" name="menu_Help">
-    <property name="title">
-     <string>&amp;Help</string>
-    </property>
-    <addaction name="openCalendar"/>
-    <addaction name="openAbout"/>
-   </widget>
-   <widget class="QMenu" name="menuForm">
-    <property name="title">
-     <string>F&amp;orms</string>
-    </property>
-   </widget>
-   <widget class="QMenu" name="menuPlots">
-    <property name="title">
-     <string>&amp;Plots</string>
-    </property>
-    <addaction name="plotDSPClkDifference"/>
-    <addaction name="plotPhaseError"/>
-    <addaction name="plotJerk"/>
-    <addaction name="plotFileErrors"/>
-    <addaction name="plotGPSOnOffErr"/>
-    <addaction name="plotGPSLkUnlk"/>
-    <addaction name="plotTemperature"/>
-    <addaction name="plotVoltage"/>
-    <addaction name="plotBackupVoltage"/>
-    <addaction name="plotDumpCall"/>
-    <addaction name="plotAcquisitionOnOff"/>
-    <addaction name="plotResetPowerUp"/>
-    <addaction name="plotErrorWarning"/>
-    <addaction name="plotDescrepancies"/>
-    <addaction name="plotSOHDataDefinitions"/>
-    <addaction name="plotNetworkUpDown"/>
-    <addaction name="plotEvents"/>
-    <addaction name="plotDisk1Usage"/>
-    <addaction name="plotDisk2Usage"/>
-    <addaction name="plotMassPositions123"/>
-    <addaction name="plotMassPositions456"/>
-    <addaction name="separator"/>
-    <addaction name="plotAll"/>
-   </widget>
-   <addaction name="menu_File"/>
-   <addaction name="menu_Command"/>
-   <addaction name="menuPlots"/>
-   <addaction name="menu_Options"/>
-   <addaction name="menuForm"/>
-   <addaction name="menu_Help"/>
-  </widget>
-  <widget class="QStatusBar" name="statusbar"/>
-  <action name="quit">
-   <property name="text">
-    <string>&amp;Quit</string>
-   </property>
-  </action>
-  <action name="deleteSetup">
-   <property name="text">
-    <string>&amp;Delete Setup File</string>
-   </property>
-  </action>
-  <action name="openGPSPlots">
-   <property name="text">
-    <string>&amp;GPS Plotter</string>
-   </property>
-  </action>
-  <action name="searchLog">
-   <property name="text">
-    <string>Log &amp;Search</string>
-   </property>
-  </action>
-  <action name="plotTimeRanges">
-   <property name="text">
-    <string>Plot &amp;Time Ranges</string>
-   </property>
-  </action>
-  <action name="plotPositions">
-   <property name="text">
-    <string>Plot &amp;Positions</string>
-   </property>
-  </action>
-  <action name="exportDeploymentFile">
-   <property name="text">
-    <string>Deployment File (&amp;Line)</string>
-   </property>
-  </action>
-  <action name="exportDeploymentBlock">
-   <property name="text">
-    <string>Deployment File (&amp;Block)</string>
-   </property>
-  </action>
-  <action name="exportTSPGeometry">
-   <property name="text">
-    <string>&amp;TSP Shotfile / Geometry</string>
-   </property>
-  </action>
-  <action name="exportShotInfo">
-   <property name="text">
-    <string>&amp;Shot Info</string>
-   </property>
-  </action>
-  <action name="openCalendar">
-   <property name="text">
-    <string>&amp;Calendar</string>
-   </property>
-  </action>
-  <action name="plotDSPClkDifference">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>DSP-Clk Difference</string>
-   </property>
-  </action>
-  <action name="plotPhaseError">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>Phase Error</string>
-   </property>
-  </action>
-  <action name="plotJerk">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>Jerk/DSP Sets</string>
-   </property>
-  </action>
-  <action name="plotFileErrors">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>.err File Errors</string>
-   </property>
-  </action>
-  <action name="plotGPSOnOffErr">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>GPS On/Off/Err</string>
-   </property>
-  </action>
-  <action name="plotGPSLkUnlk">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>GPS Lk-Unlk</string>
-   </property>
-  </action>
-  <action name="plotTemperature">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>Temperature</string>
-   </property>
-  </action>
-  <action name="plotVoltage">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>Volts</string>
-   </property>
-  </action>
-  <action name="plotBackupVoltage">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>Backup Volts</string>
-   </property>
-  </action>
-  <action name="plotDumpCall">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>Dump Call</string>
-   </property>
-  </action>
-  <action name="plotAcquisitionOnOff">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>Acquisition On/Off</string>
-   </property>
-  </action>
-  <action name="plotResetPowerUp">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>Reset/Powerup</string>
-   </property>
-  </action>
-  <action name="plotErrorWarning">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>Error/Warning</string>
-   </property>
-  </action>
-  <action name="plotDescrepancies">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>Discrepancies</string>
-   </property>
-  </action>
-  <action name="plotSOHDataDefinitions">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>SOH/Data Definitions</string>
-   </property>
-  </action>
-  <action name="plotNetworkUpDown">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>Network Up/Down</string>
-   </property>
-  </action>
-  <action name="plotEvents">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>Events</string>
-   </property>
-  </action>
-  <action name="plotDisk1Usage">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>Disk 1 Usage</string>
-   </property>
-  </action>
-  <action name="plotDisk2Usage">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>Disk 2 Usage</string>
-   </property>
-  </action>
-  <action name="plotMassPositions123">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>Mass Positions 123</string>
-   </property>
-  </action>
-  <action name="plotMassPositions456">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>Mass Positions 456</string>
-   </property>
-  </action>
-  <action name="plotAll">
-   <property name="text">
-    <string>All Plots</string>
-   </property>
-  </action>
-  <action name="plotTimingProblems">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>Plot Timing Problems</string>
-   </property>
-  </action>
-  <action name="filterNonSOHLines">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>Filter Non-SOH Lines</string>
-   </property>
-  </action>
-  <action name="sortFilesByType">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>Sort Files List By Type</string>
-   </property>
-  </action>
-  <action name="sortFilesAlphabetically">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>Sort Files List Alphabetically</string>
-   </property>
-  </action>
-  <action name="calculateFileSizes">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>Calculate File Sizes</string>
-   </property>
-  </action>
-  <action name="warnIfBig">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>Warn If Big</string>
-   </property>
-  </action>
-  <action name="addMassPositionToSOH">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>Add Mass Positions to SOH Messages</string>
-   </property>
-  </action>
-  <action name="colorMPRegular">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>MP Coloring (Regular)</string>
-   </property>
-  </action>
-  <action name="colorMPTrillium">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>MP Coloring (Trillium)</string>
-   </property>
-  </action>
-  <action name="addPositionsToET">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>Add Positions to ET Lines</string>
-   </property>
-  </action>
-  <action name="readAntellopeLog">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>Read Antelope-Produced Log File</string>
-   </property>
-  </action>
-  <action name="showYYYYDOYDates">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>Show YYYY:DOY Dates</string>
-   </property>
-  </action>
-  <action name="showYYYY_MM_DDDates">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>Show YYYY-MM-DD Dates</string>
-   </property>
-  </action>
-  <action name="showYYYYMMMDDDates">
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
-   <property name="text">
-    <string>Show YYYYMMMDD Dates</string>
-   </property>
-  </action>
-  <action name="setFontSizes">
-   <property name="text">
-    <string>Set Font Sizes</string>
-   </property>
-  </action>
-  <action name="openAbout">
-   <property name="text">
-    <string>&amp;About</string>
-   </property>
-  </action>
- </widget>
- <customwidgets>
-  <customwidget>
-   <class>PlottingWidget</class>
-   <extends>QGraphicsView</extends>
-   <header>plottingwidget.h</header>
-   <slots>
-    <slot>replotLoadedData()</slot>
-   </slots>
-  </customwidget>
- </customwidgets>
- <resources/>
- <connections>
-  <connection>
-   <sender>clearPushButton</sender>
-   <signal>clicked()</signal>
-   <receiver>searchLineEdit</receiver>
-   <slot>clear()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>197</x>
-     <y>193</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>137</x>
-     <y>191</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>quit</sender>
-   <signal>triggered(bool)</signal>
-   <receiver>MainWindow</receiver>
-   <slot>close()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>-1</x>
-     <y>-1</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>506</x>
-     <y>299</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>readPushButton</sender>
-   <signal>clicked()</signal>
-   <receiver>MainWindow</receiver>
-   <slot>readSelectedFile()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>68</x>
-     <y>421</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>415</x>
-     <y>-8</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>stopPushButton</sender>
-   <signal>clicked()</signal>
-   <receiver>MainWindow</receiver>
-   <slot>stopFileRead()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>145</x>
-     <y>428</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>325</x>
-     <y>-12</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>writePushButton</sender>
-   <signal>clicked()</signal>
-   <receiver>MainWindow</receiver>
-   <slot>writePSFile()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>190</x>
-     <y>423</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>346</x>
-     <y>-28</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>replotPushButton</sender>
-   <signal>clicked()</signal>
-   <receiver>plottingWidget</receiver>
-   <slot>replotLoadedData()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>227</x>
-     <y>221</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>603</x>
-     <y>215</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>reloadPushButton</sender>
-   <signal>clicked()</signal>
-   <receiver>MainWindow</receiver>
-   <slot>reloadFile()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>222</x>
-     <y>233</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>510</x>
-     <y>-11</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>cwdPushButton</sender>
-   <signal>clicked()</signal>
-   <receiver>MainWindow</receiver>
-   <slot>changeCurrentDirectory()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>49</x>
-     <y>31</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>144</x>
-     <y>-24</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>MainWindow</sender>
-   <signal>currentDirectoryChanged(QString)</signal>
-   <receiver>cwdLineEdit</receiver>
-   <slot>setText(QString)</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>2</x>
-     <y>20</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>272</x>
-     <y>28</y>
-    </hint>
-   </hints>
-  </connection>
- </connections>
- <slots>
-  <signal>currentDirectoryChanged(QString)</signal>
-  <slot>readSelectedFile()</slot>
-  <slot>stopFileRead()</slot>
-  <slot>writePSFile()</slot>
-  <slot>reloadFile()</slot>
-  <slot>changeCurrentDirectory(QString)</slot>
- </slots>
- <buttongroups>
-  <buttongroup name="buttonGroup"/>
- </buttongroups>
-</ui>
diff --git a/sohstationviewer/view/ui/main_ui.py b/sohstationviewer/view/ui/main_ui.py
index aecc4a4d09575a99021c93b4aa6b7bf7a4e98b8c..1c9a7f69334cfcbac86c637a538981db632ba3f5 100755
--- a/sohstationviewer/view/ui/main_ui.py
+++ b/sohstationviewer/view/ui/main_ui.py
@@ -3,7 +3,9 @@
 from PySide2 import QtCore, QtGui, QtWidgets
 
 from sohstationviewer.view.core.calendarwidget import CalendarWidget
+
 from sohstationviewer.view.core.plottingWidget import PlottingWidget
+from sohstationviewer.conf import constants
 
 
 class Ui_MainWindow(object):
@@ -65,8 +67,8 @@ class Ui_MainWindow(object):
 
         self.setControlColumn(hLayout)
 
-        self.plottingWidget = PlottingWidget(
-            self.MainWindow)
+        self.plottingWidget = PlottingWidget(self.MainWindow, 'SOHWidget')
+
         hLayout.addWidget(self.plottingWidget, 2)
 
     def setControlColumn(self, parentLayout):
@@ -117,7 +119,9 @@ class Ui_MainWindow(object):
         searchGrid.addWidget(self.fileListZipCheckBox, 1, 2, 1, 1)
 
         self.replotButton = QtWidgets.QPushButton('RePlot', self.centralWidget)
-        self.replotButton.setFixedWidth(65)
+
+        self.replotButton.setFixedWidth(95)
+
         searchGrid.addWidget(self.replotButton, 1, 3, 1, 1)
 
         backgroundLayout = QtWidgets.QHBoxLayout()
@@ -133,30 +137,17 @@ class Ui_MainWindow(object):
 
         self.addSeperationLine(leftLayout)
 
-        TPS_gap_SOH_Layout = QtWidgets.QGridLayout()
-        TPS_gap_SOH_Layout.setContentsMargins(0, 0, 0, 10)
-        TPS_gap_SOH_Layout.setSpacing(5)
-        leftLayout.addLayout(TPS_gap_SOH_Layout)
-
-        self.showTPSCheckBox = QtWidgets.QCheckBox(
-            'TPS   Chans:', self.centralWidget)
-        TPS_gap_SOH_Layout.addWidget(self.showTPSCheckBox, 0, 0)
-        # TPS_gap_SOH_Layout.addWidget(
-        #     QtWidgets.QLabel('Chans:'), 0, 1, QtGui.Qt.AlignRight)
-        self.TPSChansLineEdit = QtWidgets.QLineEdit(self.centralWidget)
-        TPS_gap_SOH_Layout.addWidget(self.TPSChansLineEdit, 0, 1, 1, 3)
+        gapLayout = QtWidgets.QHBoxLayout()
+        leftLayout.addLayout(gapLayout)
 
         self.detectGapCheckBox = QtWidgets.QCheckBox(
             'DetectGap   Len:', self.centralWidget)
-        TPS_gap_SOH_Layout.addWidget(self.detectGapCheckBox, 1, 0, 1, 2)
+        gapLayout.addWidget(self.detectGapCheckBox)
 
-        # TPS_gap_SOH_Layout.addWidget(
-        #     QtWidgets.QLabel('Len:'), 1, 2, QtGui.Qt.AlignRight)
         self.gapLenLineEdit = QtWidgets.QLineEdit(self.centralWidget)
-        TPS_gap_SOH_Layout.addWidget(self.gapLenLineEdit, 1, 2, 1, 2)
+        gapLayout.addWidget(self.gapLenLineEdit)
 
-        self.sohCheckBox = QtWidgets.QCheckBox('SOH Only', self.centralWidget)
-        TPS_gap_SOH_Layout.addWidget(self.sohCheckBox, 2, 0)
+        gapLayout.addWidget(QtWidgets.QLabel('m'))
 
         massPosLayout = QtWidgets.QHBoxLayout()
         # massPosLayout.setContentsMargins(0, 0, 0, 0)
@@ -186,6 +177,17 @@ class Ui_MainWindow(object):
                     QtWidgets.QCheckBox('%s' % count, self.centralWidget))
                 dsGrid.addWidget(self.dsCheckBoxes[count - 1], r, c + 1)
 
+        wfGrid = QtWidgets.QGridLayout()
+        leftLayout.addLayout(wfGrid)
+        self.wfAllCheckBox = QtWidgets.QCheckBox(
+            'All  WFChans:', self.centralWidget)
+        wfGrid.addWidget(self.wfAllCheckBox, 0, 0, 1, 2)
+
+        self.wfChansLineEdit = QtWidgets.QLineEdit(self.centralWidget)
+        wfGrid.addWidget(self.wfChansLineEdit, 0, 3, 1, 2)
+
+        self.tpsCheckBox = QtWidgets.QCheckBox('TPS', self.centralWidget)
+        wfGrid.addWidget(self.tpsCheckBox, 1, 0)
         self.addSeperationLine(leftLayout)
 
         chanLayout = QtWidgets.QHBoxLayout()
@@ -443,12 +445,16 @@ class Ui_MainWindow(object):
         self.timeToDateEdit.setDate(QtCore.QDate.currentDate())
 
         self.timeFromDateEdit.setCalendarWidget(CalendarWidget(MainWindow))
-        self.timeFromDateEdit.setDate(QtCore.QDate.currentDate())
+        self.timeFromDateEdit.setDate(QtCore.QDate.fromString(
+            constants.DEFAULT_START_TIME, QtCore.Qt.ISODate
+        ))
 
         # second Row
         self.openFilesList.itemDoubleClicked.connect(
             MainWindow.openFilesListItemDoubleClicked)
 
+        # self.resetButton.clicked.connect(MainWindow.resetView)
+
         self.replotButton.clicked.connect(MainWindow.replotLoadedData)
 
         self.allChanCheckBox.clicked.connect(
diff --git a/sohstationviewer/view/waveformdialog.py b/sohstationviewer/view/waveformdialog.py
new file mode 100755
index 0000000000000000000000000000000000000000..75b10bd9ce8ca93c5ffc51b0781eec8dbd9a50f1
--- /dev/null
+++ b/sohstationviewer/view/waveformdialog.py
@@ -0,0 +1,148 @@
+# UI and connectSignals for MainWindow
+
+from PySide2 import QtWidgets
+
+from sohstationviewer.view.core import plottingWidget as plottingWidget
+
+from sohstationviewer.controller.plottingData import getTitle
+
+from sohstationviewer.database import extractData
+
+from sohstationviewer.conf.dbSettings import dbConf
+
+
+class WaveformWidget(plottingWidget.PlottingWidget):
+
+    def plot_channels(self, startTm, endTm, staID, trim_downsample,
+                      dataTime, channelList, timeTicksTotal,
+                      plottingData1, plottingData2):
+        """
+        :param setID: (netID, statID, locID)
+        :param plottingData: a ditionary including:
+            { gaps: [(t1,t2),(t1,t2),...]   (in epoch time)
+              channels:[cha: {netID, statID, locID, chanID, times, data,
+                              samplerate, startTmEpoch, endTmEpoch}       #
+              earliestUTC: the earliest time of all channels
+              latestUTC: the latest time of all channels
+        :timeTicksTotal: max number of tick to show on time bar
+
+        """
+        self.trim_downsample = trim_downsample
+        self.plottingData1 = plottingData1
+        self.plottingData2 = plottingData2
+        self.processingLog = []     # [(message, type)]
+        self.errors = []
+        if self.axes != []:
+            self.fig.clear()
+        self.dateMode = self.parent.dateFormat.upper()
+        self.timeTicksTotal = timeTicksTotal
+        self.minX = self.currMinX = max(dataTime[0], startTm)
+        self.maxX = self.currMaxX = min(dataTime[1], endTm)
+        self.plotNo = len(self.plottingData1) + len(self.plottingData2)
+        title = getTitle(staID, self.minX, self.maxX, self.dateMode)
+        self.plottingBot = plottingWidget.BOTTOM
+        self.plottingBotPixel = plottingWidget.BOTTOM_PX
+        self.axes = []
+
+        self.timestampBarTop = self.add_timestamp_bar(0.003)
+        self.set_title(title)
+
+        for chanID in self.plottingData1:
+            print("wf chanID:", chanID)
+            chanDB = extractData.getWFPlotInfo(chanID)
+            if chanDB['plotType'] == '':
+                continue
+            self.plottingData1[chanID]['chanDB'] = chanDB
+            self.getZoomData(self.plottingData1[chanID], chanID, True)
+        for chanID in self.plottingData2:
+            print("masspos2 chanID:", chanID)
+            chanDB = extractData.getChanPlotInfo(chanID, self.parent.dataType)
+            self.plottingData2[chanID]['chanDB'] = chanDB
+            self.getZoomData(self.plottingData2[chanID], chanID, True)
+
+        self.axes.append(self.plotNone())
+        self.timestampBarBottom = self.add_timestamp_bar(0.003, top=False)
+        self.set_lim(orgSize=True)
+        self.bottom = self.axes[-1].get_ybound()[0]
+        self.ruler = self.add_ruler(self.displayColor['TR'])
+        self.zoomMarker1 = self.add_ruler(self.displayColor['ZM'])
+        self.zoomMarker2 = self.add_ruler(self.displayColor['ZM'])
+        # Set view size fit with the given data
+        if self.widgt.geometry().height() < self.plottingBotPixel:
+            self.widgt.setFixedHeight(self.plottingBotPixel)
+
+        self.draw()
+
+    def getZoomData(self, cData, chanID, firsttime=False):
+        """
+        :param setID: (netID, statID, locID)
+        :param plottingData: a ditionary including:
+            { gaps: [(t1,t2),(t1,t2),...]   (in epoch time)
+              channels:{cha: {netID, statID, locID, chanID, times, data,
+                              samplerate, startTmEpoch, endTmEpoch}       #
+              earliestUTC: the earliest time of all channels
+              latestUTC: the latest time of all channels
+        :timeTicksTotal: max number of tick to show on time bar
+
+        Data set: {channelname: [(x,y), (x,y)...]
+        """
+        chanDB = cData['chanDB']
+        plotType = chanDB['plotType']
+        # data already processed for massposition in plottingWidget
+        if not (chanID.startswith('VM') or chanID.startswith('MP')):
+            self.trim_downsample(
+                cData, self.currMinX, self.currMaxX, firsttime)
+            self.applyConvertFactor(cData, 1)
+        # use ax_wf because with massposition, ax has been used
+        # in plottingWidget
+        if 'ax_wf' not in cData:
+            ax = getattr(self, dbConf['plotFunc'][plotType][1])(
+                cData, chanDB, chanID, None, None)
+            if ax is None:
+                return
+            cData['ax_wf'] = ax
+            ax.chan = chanID
+            self.axes.append(ax)
+        else:
+            getattr(self, dbConf['plotFunc'][plotType][1])(
+                cData, chanDB, chanID, cData['ax_wf'], None)
+
+
+class WaveformDialog(QtWidgets.QWidget):
+    def __init__(self, parent):
+        super().__init__()
+        self.parent = parent
+        self.dateFormat = self.parent.dateFormat
+        self.bitweightOpt = self.parent.bitweightOpt
+        self.massPosVoltRangeOpt = self.parent.massPosVoltRangeOpt
+        self.resize(1000, 700)
+        self.setWindowTitle("Raw Data Plot")
+
+        mainLayout = QtWidgets.QVBoxLayout()
+        self.setLayout(mainLayout)
+        mainLayout.setContentsMargins(5, 5, 5, 5)
+        mainLayout.setSpacing(0)
+
+        self.plottingWidget = WaveformWidget(self, "waveformWidget")
+        mainLayout.addWidget(self.plottingWidget, 2)
+
+        bottomLayout = QtWidgets.QHBoxLayout()
+        mainLayout.addLayout(bottomLayout)
+
+        self.writePSButton = QtWidgets.QPushButton('Write .ps', self)
+        bottomLayout.addWidget(self.writePSButton)
+        self.trackingInfoTextBrowser = QtWidgets.QTextBrowser(self)
+        self.trackingInfoTextBrowser.setFixedHeight(60)
+        bottomLayout.addWidget(self.trackingInfoTextBrowser)
+
+        self.connectSignals()
+
+    def setData(self, dataType, fileName):
+        self.dataType = dataType
+        self.setWindowTitle("Raw Data Plot %s - %s" % (dataType, fileName))
+
+    def resizeEvent(self, event):
+        self.plottingWidget.init_size()
+
+    def connectSignals(self):
+        print("connectSignals")