Surface NMR processing and inversion GUI
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

akvoGUI.py 60KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353
  1. #/usr/bin/env python
  2. import sys
  3. #import readline
  4. try:
  5. from akvo.gui.main_ui import Ui_MainWindow
  6. uicerr = False
  7. except: # Fallback
  8. from akvo.gui.mainui import Ui_MainWindow
  9. uicerr = """
  10. USING THE DEFAULT GUI FILES, AKVO MAY NOT WORK CORRECTLY!
  11. See INSTALL.txt for details regarding GUI configuration
  12. if you are encountering problems.
  13. Clicking ignore will prevent this warning from showing
  14. each time you launch Akvo.
  15. """
  16. import matplotlib
  17. #matplotlib.use("QT4Agg")
  18. from PyQt5 import QtCore, QtGui, QtWidgets
  19. import numpy as np
  20. import time
  21. import os
  22. from copy import deepcopy
  23. from matplotlib.backends.backend_qt4 import NavigationToolbar2QT #as NavigationToolbar
  24. import datetime, time
  25. from akvo.tressel import mrsurvey
  26. import pkg_resources # part of setuptools
  27. version = pkg_resources.require("Akvo")[0].version
  28. import yaml
  29. # Writes out numpy arrays into Eigen vectors as serialized by Lemma
  30. class MatrixXr(yaml.YAMLObject):
  31. yaml_tag = u'MatrixXr'
  32. def __init__(self, rows, cols, data):
  33. self.rows = rows
  34. self.cols = cols
  35. self.data = np.zeros((rows,cols))
  36. def __repr__(self):
  37. return "%s(rows=%r, cols=%r, data=%r)" % (self.__class__.__name__, self.rows, self.cols, self.data)
  38. class VectorXr(yaml.YAMLObject):
  39. yaml_tag = r'VectorXr'
  40. def __init__(self, array):
  41. self.size = np.shape(array)[0]
  42. self.data = array.tolist()
  43. def __repr__(self):
  44. # Converts to numpy array on import
  45. return "np.array(%r)" % (self.data)
  46. from collections import OrderedDict
  47. #def represent_ordereddict(dumper, data):
  48. # print("representing IN DA HOUSE!!!!!!!!!!!!!!!!!!!!!")
  49. # value = []
  50. # for item_key, item_value in data.items():
  51. # node_key = dumper.represent_data(item_key)
  52. # node_value = dumper.represent_data(item_value)
  53. # value.append((node_key, node_value))
  54. # return yaml.nodes.MappingNode(u'tag:yaml.org,2002:map', value)
  55. #yaml.add_representer(OrderedDict, represent_ordereddict)
  56. def setup_yaml():
  57. """ https://stackoverflow.com/a/8661021 """
  58. represent_dict_order = lambda self, data: self.represent_mapping('tag:yaml.org,2002:map', data.items())
  59. yaml.add_representer(OrderedDict, represent_dict_order)
  60. setup_yaml()
  61. class AkvoYamlNode(yaml.YAMLObject):
  62. yaml_tag = u'AkvoData'
  63. def __init__(self):
  64. self.Akvo_VERSION = version
  65. self.Import = OrderedDict() # {}
  66. self.Processing = OrderedDict()
  67. #def __init__(self, node):
  68. # self.Akvo_VERSION = node["version"]
  69. # self.Import = OrderedDict( node["Import"] ) # {}
  70. # self.Processing = OrderedDict( node["Processing"] )
  71. def __repr__(self):
  72. return "%s(name=%r, Akvo_VESION=%r, Import=%r, Processing=%r)" % (
  73. #self.__class__.__name__, self.Akvo_VERSION, self.Import, self.Processing )
  74. self.__class__.__name__, self.Akvo_VERSION, self.Import, OrderedDict(self.Processing) )
  75. #self.__class__.__name__, self.Akvo_VERSION, self.Import, OrderedDict(self.Processing))
  76. try:
  77. import thread
  78. except ImportError:
  79. import _thread as thread #Py3K compatibility
  80. class MyPopup(QtWidgets.QWidget):
  81. def __init__(self, name):
  82. super().__init__()
  83. self.name = name
  84. self.initUI()
  85. def initUI(self):
  86. lblName = QtWidgets.QLabel(self.name, self)
  87. class ApplicationWindow(QtWidgets.QMainWindow):
  88. def __init__(self):
  89. QtWidgets.QMainWindow.__init__(self)
  90. self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
  91. akvohome = os.path.expanduser("~") + "/.akvo"
  92. if not os.path.exists(akvohome):
  93. os.makedirs(akvohome)
  94. self.ui = Ui_MainWindow()
  95. self.ui.setupUi(self)
  96. if uicerr != False and not os.path.exists(akvohome+"/pyuic-warned"):
  97. reply = QtGui.QMessageBox.warning(self, 'Warning', uicerr, QtGui.QMessageBox.Ok, QtGui.QMessageBox.Ignore)
  98. if reply == 1024: # "0x400" in hex
  99. pass
  100. elif reply == 1048576: # "0x100000" in hex
  101. warn = open( akvohome+"/pyuic-warned" ,"w" )
  102. warn.write("Gui files were not compiled locally using pyuic! Further warnings have been supressed")
  103. warn.close()
  104. self.RAWDataProc = None
  105. self.YamlNode = AkvoYamlNode()
  106. # initialise some stuff
  107. self.ui.lcdNumberTauPulse2.setEnabled(0)
  108. self.ui.lcdNumberTauPulse1.setEnabled(0)
  109. self.ui.lcdNumberNuTx.setEnabled(0)
  110. self.ui.lcdNumberTuneuF.setEnabled(0)
  111. self.ui.lcdNumberSampFreq.setEnabled(0)
  112. self.ui.lcdNumberTauDelay.setEnabled(0)
  113. self.ui.lcdNumberNQ.setEnabled(0)
  114. #MAK 20170126: add in a list to hold processing steps
  115. self.logText = []
  116. ####################
  117. # Make connections #
  118. ####################
  119. # Menu items
  120. self.ui.actionOpen_GMR.triggered.connect(self.openGMRRAWDataset)
  121. self.ui.actionSave_Preprocessed_Dataset.triggered.connect(self.SavePreprocess)
  122. self.ui.actionExport_Preprocessed_Dataset.triggered.connect(self.ExportPreprocess)
  123. self.ui.actionExport_Preprocessed_Dataset.setEnabled(False)
  124. self.ui.actionOpen_Preprocessed_Dataset.triggered.connect(self.OpenPreprocess)
  125. self.ui.actionAboutAkvo.triggered.connect(self.about)
  126. # Buttons
  127. # #QtCore.QObject.connect(self.ui.fullWorkflowPushButton, QtCore.SIGNAL("clicked()"), self.preprocess )
  128. self.ui.loadDataPushButton.pressed.connect(self.loadRAW)
  129. self.ui.sumDataGO.pressed.connect( self.sumDataChans )
  130. self.ui.bandPassGO.pressed.connect( self.bandPassFilter )
  131. self.ui.filterDesignPushButton.pressed.connect( self.designFilter )
  132. self.ui.fdDesignPushButton.pressed.connect( self.designFDFilter )
  133. self.ui.downSampleGO.pressed.connect( self.downsample )
  134. self.ui.windowFilterGO.pressed.connect( self.windowFilter )
  135. # self.ui.despikeGO.pressed.connect( self.despikeFilter ) # use smart stack instead
  136. self.ui.adaptGO.pressed.connect( self.adaptFilter )
  137. self.ui.adaptFDGO.pressed.connect( self.adaptFilterFD )
  138. self.ui.qdGO.pressed.connect( self.quadDet )
  139. self.ui.gateIntegrateGO.pressed.connect( self.gateIntegrate )
  140. self.ui.calcQGO.pressed.connect( self.calcQ )
  141. self.ui.FDSmartStackGO.pressed.connect( self.FDSmartStack )
  142. self.ui.harmonicGO.pressed.connect( self.harmonicModel )
  143. self.ui.plotQD.setEnabled(False)
  144. self.ui.plotQD.pressed.connect( self.plotQD )
  145. self.ui.plotGI.setEnabled(False)
  146. self.ui.plotGI.pressed.connect( self.plotGI )
  147. # hide header info box
  148. #self.ui.headerFileBox.setVisible(False)
  149. self.ui.headerFileBox.clicked.connect( self.headerBoxShrink )
  150. self.ui.headerBox2.setVisible(False)
  151. # Clean up the tab widget
  152. self.ui.actionPreprocessing.triggered.connect(self.addPreProc)
  153. self.ui.actionModelling.triggered.connect(self.addModelling)
  154. self.ui.actionInversion.triggered.connect(self.addInversion)
  155. # tabs
  156. #self.ui.ProcTabs.tabCloseRequested.connect( self.closeTabs )
  157. #self.ui.ProcTabs.tabBar().setTabButton(7, QtWidgets.QTabBar.RightSide,None)
  158. self.ui.ProcTabs.removeTab(4)
  159. self.ui.ProcTabs.removeTab(4)
  160. self.ui.ProcTabs.removeTab(4)
  161. self.ui.ProcTabs.removeTab(4)
  162. #self.ui.LoadTab.close( )
  163. # Add progressbar to statusbar
  164. self.ui.barProgress = QtWidgets.QProgressBar()
  165. self.ui.statusbar.addPermanentWidget(self.ui.barProgress, 0);
  166. self.ui.barProgress.setMaximumSize(100, 16777215);
  167. self.ui.barProgress.hide();
  168. self.ui.mplwidget_navigator.setCanvas(self.ui.mplwidget)
  169. #self.ui.mplwidget_navigator_2.setCanvas(self.ui.mplwidget)
  170. ##########################################################################
  171. # Loop Table
  172. self.ui.loopTableWidget.setRowCount(80)
  173. self.ui.loopTableWidget.setColumnCount(6)
  174. self.ui.loopTableWidget.setHorizontalHeaderLabels( ["ch. tag", \
  175. "Northing [m]","Easting [m]","Height [m]", "Radius","Tx"] )
  176. for ir in range(0, self.ui.loopTableWidget.rowCount() ):
  177. for ic in range(1, self.ui.loopTableWidget.columnCount() ):
  178. pCell = QtWidgets.QTableWidgetItem()
  179. #pCell.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable)
  180. pCell.setFlags(QtCore.Qt.NoItemFlags) # not selectable
  181. pCell.setBackground( QtGui.QColor("lightgrey").lighter(110) )
  182. self.ui.loopTableWidget.setItem(ir, ic, pCell)
  183. self.ui.loopTableWidget.cellChanged.connect(self.loopCellChanged)
  184. #self.ui.loopTableWidget.cellPressed.connect(self.loopCellChanged)
  185. self.ui.loopTableWidget.itemClicked.connect(self.loopCellClicked)
  186. #self.ui.loopTableWidget.cellPressed.connect(self.loopCellClicked)
  187. self.ui.loopTableWidget.setDragDropOverwriteMode(False)
  188. self.ui.loopTableWidget.setDragEnabled(False)
  189. #self.ui.loopTableWidget.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove)
  190. self.loops = {}
  191. ##########################################################################
  192. # layer Table
  193. self.ui.layerTableWidget.setRowCount(80)
  194. self.ui.layerTableWidget.setColumnCount(3)
  195. self.ui.layerTableWidget.setHorizontalHeaderLabels( [r"top [m]", r"bottom [m]", "σ [ Ωm]" ] )
  196. # do we want this
  197. self.ui.layerTableWidget.setDragDropOverwriteMode(False)
  198. self.ui.layerTableWidget.setDragEnabled(True)
  199. self.ui.layerTableWidget.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove)
  200. pCell = QtWidgets.QTableWidgetItem()
  201. pCell.setFlags(QtCore.Qt.NoItemFlags) # not selectable
  202. pCell.setBackground( QtGui.QColor("lightgrey").lighter(110) )
  203. self.ui.layerTableWidget.setItem(0, 1, pCell)
  204. for ir in range(1, self.ui.layerTableWidget.rowCount() ):
  205. for ic in range(0, self.ui.layerTableWidget.columnCount() ):
  206. pCell = QtWidgets.QTableWidgetItem()
  207. #pCell.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable)
  208. pCell.setFlags(QtCore.Qt.NoItemFlags) # not selectable
  209. pCell.setBackground( QtGui.QColor("lightgrey").lighter(110) )
  210. self.ui.layerTableWidget.setItem(ir, ic, pCell)
  211. self.ui.layerTableWidget.cellChanged.connect(self.sigmaCellChanged)
  212. def closeTabs(self):
  213. #self.ui.ProcTabs.removeTab(idx)
  214. self.ui.ProcTabs.clear( )
  215. def addPreProc(self):
  216. if self.ui.actionPreprocessing.isChecked():
  217. self.ui.actionModelling.setChecked(False)
  218. self.ui.actionInversion.setChecked(False)
  219. self.ui.ProcTabs.clear( )
  220. self.ui.ProcTabs.insertTab( 0, self.ui.LoadTab, "Load" )
  221. self.ui.ProcTabs.insertTab( 1, self.ui.NCTab, "NC" )
  222. self.ui.ProcTabs.insertTab( 2, self.ui.QCTab, "QC" )
  223. self.ui.ProcTabs.insertTab( 3, self.ui.METATab, "META" )
  224. self.ui.ProcTabs.insertTab( 4, self.ui.LogTab, "Log" )
  225. else:
  226. self.ui.ProcTabs.removeTab(0)
  227. self.ui.ProcTabs.removeTab(0)
  228. self.ui.ProcTabs.removeTab(0)
  229. self.ui.ProcTabs.removeTab(0)
  230. def addModelling(self):
  231. if self.ui.actionModelling.isChecked():
  232. self.ui.actionPreprocessing.setChecked(False)
  233. self.ui.actionInversion.setChecked(False)
  234. self.ui.ProcTabs.clear( )
  235. self.ui.ProcTabs.insertTab( 0, self.ui.KernTab, "Kernel" )
  236. self.ui.ProcTabs.insertTab( 1, self.ui.ModelTab, "Modelling" )
  237. self.ui.ProcTabs.insertTab( 2, self.ui.LogTab, "Log" )
  238. else:
  239. self.ui.ProcTabs.removeTab(0)
  240. self.ui.ProcTabs.removeTab(0)
  241. def addInversion(self, idx):
  242. if self.ui.actionInversion.isChecked():
  243. self.ui.actionPreprocessing.setChecked(False)
  244. self.ui.actionModelling.setChecked(False)
  245. self.ui.ProcTabs.clear( )
  246. self.ui.ProcTabs.insertTab( 0, self.ui.InvertTab, "Inversion" )
  247. self.ui.ProcTabs.insertTab( 1, self.ui.AppraiseTab, "Appraisal" )
  248. self.ui.ProcTabs.insertTab( 2, self.ui.LogTab, "Log" )
  249. else:
  250. self.ui.ProcTabs.removeTab(0)
  251. self.ui.ProcTabs.removeTab(0)
  252. def headerBoxShrink(self):
  253. #self.ui.headerFileBox.setVisible(False)
  254. if self.ui.headerFileBox.isChecked( ):
  255. #self.ui.headerFileBox.setMinimumSize(460,250)
  256. self.ui.headerBox2.setVisible(True)
  257. else:
  258. #self.ui.headerFileBox.setMinimumSize(460,50)
  259. self.ui.headerBox2.setVisible(False)
  260. def sigmaCellChanged(self):
  261. self.ui.layerTableWidget.cellChanged.disconnect(self.sigmaCellChanged)
  262. # TODO consider building the model whenever this is called. Would be nice to be able to
  263. # do that. Would require instead dist of T2 I guess.
  264. jj = self.ui.layerTableWidget.currentColumn()
  265. ii = self.ui.layerTableWidget.currentRow()
  266. val = "class 'NoneType'>"
  267. try:
  268. val = eval (str( self.ui.layerTableWidget.item(ii, jj).text() ))
  269. except:
  270. #if jj != 0:
  271. # Error = QtWidgets.QMessageBox()
  272. # Error.setWindowTitle("Error!")
  273. # Error.setText("Non-numeric value encountered")
  274. self.ui.layerTableWidget.cellChanged.connect(self.sigmaCellChanged)
  275. return
  276. if jj == 1:
  277. #item.setFlags(QtCore.Qt.ItemIsEnabled)
  278. pCell = self.ui.layerTableWidget.item(ii, jj)
  279. pCell.setBackground( QtGui.QColor("white"))
  280. pCell = self.ui.layerTableWidget.item(ii+1, jj-1)
  281. if str(type(pCell)) == "<class 'NoneType'>":
  282. pCell = QtWidgets.QTableWidgetItem()
  283. pCell.setFlags(QtCore.Qt.ItemIsEnabled)
  284. self.ui.layerTableWidget.setItem(ii+1, jj-1, pCell)
  285. if ii == 0:
  286. pCell.setText(str(val))
  287. #pCell3 = self.ui.layerTableWidget.item(ii+1, jj)
  288. #print ("setting", ii, jj, type(pCell3))
  289. #print ( "setting", ii, jj, type(pCell3))
  290. #pCell3.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled )
  291. #pCell3.setFlags( QtCore.Qt.ItemIsEditable )
  292. elif ii > 0:
  293. val2 = eval (str( self.ui.layerTableWidget.item(ii-1, jj).text() ))
  294. #print ("val2", val2, val, type(val))
  295. #if str(type(pCell)) == "<class 'NoneType'>":
  296. if type(val) == str or val > val2:
  297. pCell.setText(str(val))
  298. else:
  299. Error = QtWidgets.QMessageBox()
  300. Error.setWindowTitle("Error!")
  301. Error.setText("Non-increasing layer detected")
  302. Error.setDetailedText("Each layer interface must be below the one above it.")
  303. Error.exec_()
  304. pCell2 = self.ui.layerTableWidget.item(ii, jj)
  305. pCell2.setText(str(""))
  306. self.ui.layerTableWidget.cellChanged.connect(self.sigmaCellChanged)
  307. return
  308. # enable next layer
  309. pCell4 = self.ui.layerTableWidget.item(ii+1, jj)
  310. pCell4.setBackground( QtGui.QColor("lightblue") ) #.lighter(110))
  311. pCell4.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled )
  312. pCell5 = self.ui.layerTableWidget.item(ii+1, jj+1)
  313. pCell5.setBackground( QtGui.QColor("white"))
  314. pCell5.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled )
  315. print("ii", ii, "jj", jj)
  316. if ii == 0 and jj == 0:
  317. pCell = self.ui.layerTableWidget.item(0, 1)
  318. pCell.setBackground( QtGui.QColor("lightblue")) #.lighter(110) )
  319. pCell.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled )
  320. self.ui.layerTableWidget.cellChanged.connect(self.sigmaCellChanged)
  321. def loopCellClicked(self, item):
  322. print("checkstate", item.checkState(),item.row())
  323. #self.ui.loopTableWidget.itemClicked.disconnect(self.loopCellClicked)
  324. jj = item.column()
  325. ii = item.row()
  326. tp = type(self.ui.loopTableWidget.item(ii, 0))
  327. print("tp", tp, ii, jj)
  328. if str(tp) == "<class 'NoneType'>":
  329. return
  330. #print("Clicked", ii, jj)
  331. if jj == 5 and self.ui.loopTableWidget.item(ii, 0).text() in self.loops.keys():
  332. #print("jj=5")
  333. self.loops[ self.ui.loopTableWidget.item(ii, 0).text() ]["Tx"] = self.ui.loopTableWidget.item(ii, 5).checkState()
  334. # update surrogates
  335. print("updating surrogates")
  336. for point in self.loops[ self.ui.loopTableWidget.item(ii, 0).text() ]["points"][1:]:
  337. pCell = self.ui.loopTableWidget.item(point, 5)
  338. if self.ui.loopTableWidget.item(ii, 5).checkState():
  339. pCell.setCheckState(QtCore.Qt.Checked);
  340. else:
  341. pCell.setCheckState(QtCore.Qt.Unchecked);
  342. #print( "loops", self.loops[ self.ui.loopTableWidget.item(ii, 0).text() ]["Tx"])
  343. #self.ui.loopTableWidget.itemClicked.connect(self.loopCellClicked)
  344. def loopCellChanged(self):
  345. self.ui.loopTableWidget.cellChanged.disconnect(self.loopCellChanged)
  346. jj = self.ui.loopTableWidget.currentColumn()
  347. ii = self.ui.loopTableWidget.currentRow()
  348. if jj == 0 and len( self.ui.loopTableWidget.item(ii, jj).text().strip()) == 0:
  349. for jjj in range(jj+1,jj+6):
  350. pCell = self.ui.loopTableWidget.item(ii, jjj)
  351. pCell.setBackground( QtGui.QColor("white") )
  352. pCell.setFlags( QtCore.Qt.NoItemFlags | QtCore.Qt.ItemIsUserCheckable ) # not selectable
  353. elif jj == 0 and len( self.ui.loopTableWidget.item(ii, jj).text().strip() ): # ch. tag modified
  354. for jjj in range(jj+1,jj+5):
  355. pCell = self.ui.loopTableWidget.item(ii, jjj)
  356. pCell.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled )
  357. pCell.setBackground( QtGui.QColor("lightblue") )
  358. if self.ui.loopTableWidget.item(ii, jj).text() not in self.loops.keys():
  359. # This is a new loop ID
  360. self.loops[ self.ui.loopTableWidget.item(ii, jj).text() ] = {}
  361. self.loops[ self.ui.loopTableWidget.item(ii, jj).text() ]["Tx"] = self.ui.loopTableWidget.item(ii, 5).checkState()
  362. self.loops[ self.ui.loopTableWidget.item(ii, jj).text() ]["points"] = [ii]
  363. # Transmitter cell
  364. pCell = self.ui.loopTableWidget.item(ii, jj+5)
  365. pCell.setCheckState(QtCore.Qt.Unchecked)
  366. pCell.setFlags( QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled )
  367. pCell.setBackground( QtGui.QColor("lightblue") )
  368. else:
  369. # This is an existing loop ID
  370. self.loops[ self.ui.loopTableWidget.item(ii, jj).text() ]["points"].append( ii )
  371. pCell = self.ui.loopTableWidget.item(ii, jj+5)
  372. pCell.setFlags(QtCore.Qt.NoItemFlags) # not selectable
  373. if self.loops[ self.ui.loopTableWidget.item(ii, 0).text() ]["Tx"]:
  374. pCell.setCheckState(QtCore.Qt.Checked);
  375. else:
  376. pCell.setCheckState(QtCore.Qt.Unchecked);
  377. #pCell.setFlags( )
  378. pCell.setBackground( QtGui.QColor("lightblue") )
  379. self.plotLoops()
  380. self.ui.loopTableWidget.cellChanged.connect(self.loopCellChanged)
  381. def plotLoops(self):
  382. print("Plotting loopz")
  383. self.ui.mplwidget.reAxH(1)
  384. #self.ui.mplwidget.ax1.clear()
  385. #self.ui.mplwidget.ax2.clear()
  386. nor = dict()
  387. eas = dict()
  388. dep = dict()
  389. for ii in range( self.ui.loopTableWidget.rowCount() ):
  390. for jj in range( self.ui.loopTableWidget.columnCount() ):
  391. tp = type(self.ui.loopTableWidget.item(ii, jj))
  392. if str(tp) == "<class 'NoneType'>":
  393. pass
  394. elif not len(self.ui.loopTableWidget.item(ii, jj).text()):
  395. pass
  396. else:
  397. if jj == 0:
  398. idx = self.ui.loopTableWidget.item(ii, 0).text()
  399. if idx not in nor.keys():
  400. nor[idx] = list()
  401. eas[idx] = list()
  402. dep[idx] = list()
  403. if jj == 1:
  404. nor[idx].append( eval(self.ui.loopTableWidget.item(ii, 1).text()) )
  405. elif jj == 2:
  406. eas[idx].append( eval(self.ui.loopTableWidget.item(ii, 2).text()) )
  407. elif jj == 3:
  408. dep[idx].append( eval(self.ui.loopTableWidget.item(ii, 3).text()) )
  409. for ii in nor.keys():
  410. try:
  411. self.ui.mplwidget.ax1.plot( np.array(nor[ii]), np.array(eas[ii]) )
  412. except:
  413. pass
  414. #self.ui.mplwidget.figure.axes().set
  415. self.ui.mplwidget.ax1.set_aspect('equal') #, adjustable='box')
  416. self.ui.mplwidget.draw()
  417. def about(self):
  418. # TODO proper popup with info
  419. #self.w = MyPopup("""About Akvo \n
  420. # Akvo is an open source project developed primarily by Trevor Irons.
  421. #""")
  422. #self.w.setGeometry(100, 100, 400, 200)
  423. #self.w.show()
  424. # Just a splash screen for now
  425. logo = pkg_resources.resource_filename(__name__, 'akvo_about.png')
  426. pixmap = QtGui.QPixmap(logo)
  427. self.splash = QtWidgets.QSplashScreen(pixmap, QtCore.Qt.WindowStaysOnTopHint)
  428. self.splash.show()
  429. def connectGMRDataProcessor(self):
  430. self.RAWDataProc = mrsurvey.GMRDataProcessor()
  431. self.RAWDataProc.progressTrigger.connect(self.updateProgressBar)
  432. self.RAWDataProc.enableDSPTrigger.connect(self.enableDSP)
  433. self.RAWDataProc.doneTrigger.connect(self.doneStatus)
  434. self.RAWDataProc.updateProcTrigger.connect(self.updateProc)
  435. def openGMRRAWDataset(self):
  436. """ Opens a GMR header file
  437. """
  438. try:
  439. with open('.gmr.last.path') as f:
  440. fpath = f.readline()
  441. pass
  442. except IOError as e:
  443. fpath = '.'
  444. self.headerstr = QtWidgets.QFileDialog.getOpenFileName(self, 'Open File', fpath)[0] # arg2 = File Type 'All Files (*)'
  445. self.ui.headerFileTextBrowser.clear()
  446. self.ui.headerFileTextBrowser.append(self.headerstr)
  447. if len(self.headerstr) == 0:
  448. return
  449. # clear the processing log
  450. self.ui.logTextBrowser.clear()
  451. self.logText = [] #MAK 20170126
  452. path,filen=os.path.split(str(self.headerstr))
  453. f = open('.gmr.last.path', 'w')
  454. f.write( str(self.headerstr) ) # prompt last file
  455. self.connectGMRDataProcessor()
  456. self.RAWDataProc.readHeaderFile(str(self.headerstr))
  457. # If we got this far, enable all the widgets
  458. self.ui.lcdNumberTauPulse1.setEnabled(True)
  459. self.ui.lcdNumberNuTx.setEnabled(True)
  460. self.ui.lcdNumberTuneuF.setEnabled(True)
  461. self.ui.lcdNumberSampFreq.setEnabled(True)
  462. self.ui.lcdNumberNQ.setEnabled(True)
  463. self.ui.headerFileBox.setEnabled(True)
  464. self.ui.headerFileBox.setChecked( True )
  465. self.ui.headerBox2.setVisible(True)
  466. self.ui.inputRAWParametersBox.setEnabled(True)
  467. self.ui.loadDataPushButton.setEnabled(True)
  468. # make plots as you import the dataset
  469. self.ui.plotImportCheckBox.setEnabled(True)
  470. self.ui.plotImportCheckBox.setChecked(True)
  471. # Update info from the header into the GUI
  472. self.ui.pulseTypeTextBrowser.clear()
  473. self.ui.pulseTypeTextBrowser.append(self.RAWDataProc.pulseType)
  474. self.ui.lcdNumberNuTx.display(self.RAWDataProc.transFreq)
  475. self.ui.lcdNumberTauPulse1.display(1e3*self.RAWDataProc.pulseLength[0])
  476. self.ui.lcdNumberTuneuF.display(self.RAWDataProc.TuneCapacitance)
  477. self.ui.lcdNumberSampFreq.display(self.RAWDataProc.samp)
  478. self.ui.lcdNumberNQ.display(self.RAWDataProc.nPulseMoments)
  479. self.ui.DeadTimeSpinBox.setValue(1e3*self.RAWDataProc.deadTime)
  480. self.ui.CentralVSpinBox.setValue( self.RAWDataProc.transFreq )
  481. if self.RAWDataProc.pulseType != "FID":
  482. self.ui.lcdNumberTauPulse2.setEnabled(1)
  483. self.ui.lcdNumberTauPulse2.display(1e3*self.RAWDataProc.pulseLength[1])
  484. self.ui.lcdNumberTauDelay.setEnabled(1)
  485. self.ui.lcdNumberTauDelay.display(1e3*self.RAWDataProc.interpulseDelay)
  486. self.ui.FIDProcComboBox.clear()
  487. if self.RAWDataProc.pulseType == "4PhaseT1":
  488. self.ui.FIDProcComboBox.insertItem(0, "Pulse 1")
  489. self.ui.FIDProcComboBox.insertItem(1, "Pulse 2")
  490. self.ui.FIDProcComboBox.insertItem(2, "Both")
  491. self.ui.FIDProcComboBox.setCurrentIndex (1)
  492. elif self.RAWDataProc.pulseType == "FID":
  493. self.ui.FIDProcComboBox.insertItem(0, "Pulse 1")
  494. self.ui.FIDProcComboBox.setCurrentIndex (0)
  495. def ExportPreprocess(self):
  496. """ This method export to YAML
  497. """
  498. try:
  499. with open('.akvo.last.yaml.path') as f:
  500. fpath = f.readline()
  501. pass
  502. except IOError as e:
  503. fpath = '.'
  504. fdir = os.path.dirname(fpath)
  505. # Pickle the preprocessed data dictionary
  506. SaveStr = QtWidgets.QFileDialog.getSaveFileName(self, "Save as", fdir, r"Processed data (*.yaml)")[0]
  507. spath,filen=os.path.split(str(SaveStr))
  508. f = open('.akvo.last.yaml.path', 'w')
  509. f.write( str(spath) ) # prompt last file
  510. INFO = {}
  511. INFO["headerstr"] = str(self.headerstr)
  512. INFO["pulseType"] = self.RAWDataProc.pulseType
  513. INFO["transFreq"] = self.RAWDataProc.transFreq.tolist()
  514. INFO["pulseLength"] = self.RAWDataProc.pulseLength.tolist()
  515. INFO["TuneCapacitance"] = self.RAWDataProc.TuneCapacitance.tolist()
  516. #INFO["samp"] = self.RAWDataProc.samp
  517. INFO["nPulseMoments"] = self.RAWDataProc.nPulseMoments
  518. #INFO["deadTime"] = self.RAWDataProc.deadTime
  519. INFO["processed"] = "Akvo v. 1.0, on " + time.strftime("%d/%m/%Y")
  520. # Pulse current info
  521. ip = 0
  522. INFO["Pulses"] = {}
  523. for pulse in self.RAWDataProc.DATADICT["PULSES"]:
  524. qq = []
  525. qv = []
  526. for ipm in range(self.RAWDataProc.DATADICT["nPulseMoments"]):
  527. #for istack in self.RAWDataProc.DATADICT["stacks"]:
  528. # print ("stack q", self.RAWDataProc.DATADICT[pulse]["Q"][ipm,istack-1])
  529. qq.append(np.mean( self.RAWDataProc.DATADICT[pulse]["Q"][ipm,:]) )
  530. qv.append(np.std( self.RAWDataProc.DATADICT[pulse]["Q"][ipm,:]/self.RAWDataProc.pulseLength[ip] ))
  531. INFO["Pulses"][pulse] = {}
  532. INFO["Pulses"][pulse]["units"] = "A"
  533. INFO["Pulses"][pulse]["current"] = VectorXr(np.array(qq)/self.RAWDataProc.pulseLength[ip])
  534. INFO["Pulses"][pulse]["variance"] = VectorXr(np.array(qv))
  535. ip += 1
  536. # Data
  537. if self.RAWDataProc.gated == True:
  538. INFO["Gated"] = {}
  539. INFO["Gated"]["abscissa units"] = "ms"
  540. INFO["Gated"]["data units"] = "nT"
  541. for pulse in self.RAWDataProc.DATADICT["PULSES"]:
  542. INFO["Gated"][pulse] = {}
  543. INFO["Gated"][pulse]["abscissa"] = VectorXr( self.RAWDataProc.GATEDABSCISSA )
  544. INFO["Gated"][pulse]["windows"] = VectorXr( self.RAWDataProc.GATEDWINDOW )
  545. for ichan in self.RAWDataProc.DATADICT[pulse]["chan"]:
  546. INFO["Gated"][pulse]["Chan. " + str(ichan)] = {}
  547. INFO["Gated"][pulse]["Chan. " + str(ichan)]["STD"] = VectorXr( np.std(self.RAWDataProc.GATED[ichan]["NR"], axis=0) )
  548. for ipm in range(self.RAWDataProc.DATADICT["nPulseMoments"]):
  549. INFO["Gated"][pulse]["Chan. " + str(ichan)]["Q-"+str(ipm) + " CA"] = VectorXr(self.RAWDataProc.GATED[ichan]["CA"][ipm])
  550. INFO["Gated"][pulse]["Chan. " + str(ichan)]["Q-"+str(ipm) + " RE"] = VectorXr(self.RAWDataProc.GATED[ichan]["RE"][ipm])
  551. INFO["Gated"][pulse]["Chan. " + str(ichan)]["Q-"+str(ipm) + " IM"] = VectorXr(self.RAWDataProc.GATED[ichan]["IM"][ipm])
  552. #INFO["Gated"][pulse]["Chan. " + str(ichan)]["Q-"+str(ipm) + " IP"] = VectorXr(self.RAWDataProc.GATED[ichan]["IP"][ipm])
  553. #INFO["Gated"][pulse]["Chan. " + str(ichan)]["Q-"+str(ipm) + " NR"] = VectorXr(self.RAWDataProc.GATED[ichan]["NR"][ipm])
  554. #INFO["Gated"][pulse]["Chan. " + str(ichan)]["Q-"+str(ipm) + " STD" ] = VectorXr(self.RAWDataProc.GATED[ichan]["SIGMA"][ipm])
  555. # we have gated data
  556. # Window edges
  557. # Window centres
  558. with open(SaveStr, 'w') as outfile:
  559. #for line in self.logText:
  560. # outfile.write(line+"\n")
  561. yaml.dump(self.YamlNode, outfile)
  562. yaml.dump(INFO, outfile, default_flow_style=False)
  563. def SavePreprocess(self):
  564. #if "Saved" not in self.YamlNode.Processing.keys():
  565. # self.YamlNode.Processing["Saved"] = []
  566. #self.YamlNode.Processing["Saved"].append(datetime.datetime.now().isoformat())
  567. #self.Log()
  568. import pickle, os
  569. try:
  570. with open('.akvo.last.path') as f:
  571. fpath = f.readline()
  572. pass
  573. except IOError as e:
  574. fpath = '.'
  575. fdir = os.path.dirname(fpath)
  576. # Pickle the preprocessed data dictionary
  577. SaveStr = QtWidgets.QFileDialog.getSaveFileName(self, "Save as", fdir, r"Pickle (*.dmp)")
  578. print(SaveStr)
  579. spath,filen=os.path.split(str(SaveStr[0]))
  580. f = open('.akvo.last.path', 'w')
  581. f.write( str(spath) ) # prompt last file
  582. save = open(SaveStr[0], 'wb')
  583. # Add some extra info
  584. INFO = {}
  585. INFO["pulseType"] = self.RAWDataProc.pulseType
  586. INFO["transFreq"] = self.RAWDataProc.transFreq
  587. INFO["pulseLength"] = self.RAWDataProc.pulseLength
  588. INFO["TuneCapacitance"] = self.RAWDataProc.TuneCapacitance
  589. INFO["samp"] = self.RAWDataProc.samp
  590. INFO["nPulseMoments"] = self.RAWDataProc.nPulseMoments
  591. INFO["deadTime"] = self.RAWDataProc.deadTime
  592. INFO["transFreq"] = self.RAWDataProc.transFreq
  593. INFO["headerstr"] = str(self.headerstr)
  594. INFO["log"] = yaml.dump( self.YamlNode ) #self.logText #MAK 20170127
  595. self.RAWDataProc.DATADICT["INFO"] = INFO
  596. pickle.dump(self.RAWDataProc.DATADICT, save)
  597. save.close()
  598. # Export XML file suitable for USGS ScienceBase Data Release
  599. def ExportXML(self):
  600. """ This is a filler function for use by USGS collaborators
  601. """
  602. return 42
  603. def OpenPreprocess(self):
  604. import pickle
  605. try:
  606. with open('.akvo.last.path') as f:
  607. fpath = f.readline()
  608. pass
  609. except IOError as e:
  610. fpath = '.'
  611. #filename = QtWidgets.QFileDialog.getOpenFileName(self, 'Open File', '.')
  612. fpath = QtWidgets.QFileDialog.getOpenFileName(self, 'Open preprocessed file', fpath, r"Pickle Files (*.dmp)")[0]
  613. f = open('.akvo.last.path', 'w')
  614. f.write( str(fpath) ) # prompt last file
  615. self.ui.logTextBrowser.clear()
  616. self.logText = []
  617. if len(fpath) == 0:
  618. return
  619. pfile = open(fpath,'rb')
  620. unpickle = pickle.Unpickler(pfile)
  621. self.connectGMRDataProcessor()
  622. self.RAWDataProc.DATADICT = unpickle.load()
  623. self.RAWDataProc.readHeaderFile(self.RAWDataProc.DATADICT["INFO"]["headerstr"])
  624. self.headerstr = self.RAWDataProc.DATADICT["INFO"]["headerstr"]
  625. self.RAWDataProc.pulseType = self.RAWDataProc.DATADICT["INFO"]["pulseType"]
  626. self.RAWDataProc.transFreq = self.RAWDataProc.DATADICT["INFO"]["transFreq"]
  627. self.RAWDataProc.pulseLength = self.RAWDataProc.DATADICT["INFO"]["pulseLength"]
  628. self.RAWDataProc.TuneCapacitance = self.RAWDataProc.DATADICT["INFO"]["TuneCapacitance"]
  629. self.RAWDataProc.samp = self.RAWDataProc.DATADICT["INFO"]["samp"]
  630. self.RAWDataProc.nPulseMoments = self.RAWDataProc.DATADICT["INFO"]["nPulseMoments"]
  631. self.RAWDataProc.deadTime = self.RAWDataProc.DATADICT["INFO"]["deadTime"]
  632. self.RAWDataProc.transFreq = self.RAWDataProc.DATADICT["INFO"]["transFreq"]
  633. self.RAWDataProc.dt = 1./self.RAWDataProc.samp
  634. self.dataChan = self.RAWDataProc.DATADICT[ self.RAWDataProc.DATADICT["PULSES"][0] ]["chan"]
  635. # Keep backwards compatibility with prior saved pickles???
  636. #self.ui.logTextBrowser.clear()
  637. #self.ui.logTextBrowser.append( yaml.dump(self.YamlNode)) #, default_flow_style=False) )
  638. #for a in self.logText:
  639. # self.ui.logTextBrowser.append(str(a))
  640. #self.ui.logTextBrowser
  641. #self.ui.logTextBrowser.clear()
  642. #print ( self.RAWDataProc.DATADICT["INFO"]["log"] )
  643. self.logText = self.RAWDataProc.DATADICT["INFO"]["log"] # YAML
  644. self.YamlNode = AkvoYamlNode( ) #self.logText )
  645. self.YamlNode.Akvo_VERSION = (yaml.load( self.logText )).Akvo_VERSION
  646. self.YamlNode.Import = OrderedDict((yaml.load( self.logText )).Import)
  647. self.YamlNode.Processing = OrderedDict((yaml.load( self.logText )).Processing)
  648. #self.YamlNode.Akvo_VERSION = 2 #yaml.load( self.logText )["Akvo_VERSION"] #, Loader=yaml.RoundTripLoader) # offending line!
  649. #self.YamlNode = AkvoYamlNode( self.logText ) # offending line!
  650. #print("import type", type( self.YamlNode.Processing ))
  651. self.Log()
  652. #self.ui.logTextBrowser.append( yaml.dump(self.YamlNode)) #, default_flow_style=False) )
  653. #except KeyError:
  654. # pass
  655. # Remove "Saved" and "Loaded" from processing flow
  656. #if "Loaded" not in self.YamlNode.Processing.keys():
  657. # self.YamlNode.Processing["Loaded"] = []
  658. #self.YamlNode.Processing["Loaded"].append(datetime.datetime.now().isoformat())
  659. #self.Log()
  660. # If we got this far, enable all the widgets
  661. self.ui.lcdNumberTauPulse1.setEnabled(True)
  662. self.ui.lcdNumberNuTx.setEnabled(True)
  663. self.ui.lcdNumberTuneuF.setEnabled(True)
  664. self.ui.lcdNumberSampFreq.setEnabled(True)
  665. self.ui.lcdNumberNQ.setEnabled(True)
  666. self.ui.headerFileBox.setEnabled(True)
  667. self.ui.headerFileBox.setChecked( False )
  668. #self.ui.headerBox2.setVisible(True)
  669. self.ui.inputRAWParametersBox.setEnabled(True)
  670. self.ui.loadDataPushButton.setEnabled(True)
  671. # make plots as you import the dataset
  672. self.ui.plotImportCheckBox.setEnabled(True)
  673. self.ui.plotImportCheckBox.setChecked(True)
  674. # Update info from the header into the GUI
  675. self.ui.pulseTypeTextBrowser.clear()
  676. self.ui.pulseTypeTextBrowser.append(self.RAWDataProc.pulseType)
  677. self.ui.lcdNumberNuTx.display(self.RAWDataProc.transFreq)
  678. self.ui.lcdNumberTauPulse1.display(1e3*self.RAWDataProc.pulseLength[0])
  679. self.ui.lcdNumberTuneuF.display(self.RAWDataProc.TuneCapacitance)
  680. self.ui.lcdNumberSampFreq.display(self.RAWDataProc.samp)
  681. self.ui.lcdNumberNQ.display(self.RAWDataProc.nPulseMoments)
  682. self.ui.DeadTimeSpinBox.setValue(1e3*self.RAWDataProc.deadTime)
  683. self.ui.CentralVSpinBox.setValue( self.RAWDataProc.transFreq )
  684. if self.RAWDataProc.pulseType != "FID":
  685. self.ui.lcdNumberTauPulse2.setEnabled(1)
  686. self.ui.lcdNumberTauPulse2.display(1e3*self.RAWDataProc.pulseLength[1])
  687. self.ui.lcdNumberTauDelay.setEnabled(1)
  688. self.ui.lcdNumberTauDelay.display(1e3*self.RAWDataProc.interpulseDelay)
  689. self.ui.FIDProcComboBox.clear()
  690. if self.RAWDataProc.pulseType == "4PhaseT1":
  691. self.ui.FIDProcComboBox.insertItem(0, "Pulse 1") #, const QVariant & userData = QVariant() )
  692. self.ui.FIDProcComboBox.insertItem(1, "Pulse 2") #, const QVariant & userData = QVariant() )
  693. self.ui.FIDProcComboBox.insertItem(2, "Both") #, const QVariant & userData = QVariant() )
  694. if len( self.RAWDataProc.DATADICT["PULSES"]) == 2:
  695. self.ui.FIDProcComboBox.setCurrentIndex (2)
  696. elif self.RAWDataProc.DATADICT["PULSES"][0] == "Pulse 1":
  697. self.ui.FIDProcComboBox.setCurrentIndex (0)
  698. else:
  699. self.ui.FIDProcComboBox.setCurrentIndex (1)
  700. elif self.RAWDataProc.pulseType == "FID":
  701. self.ui.FIDProcComboBox.insertItem(0, "Pulse 1") #, const QVariant & userData = QVariant() )
  702. self.ui.FIDProcComboBox.setCurrentIndex (0)
  703. # QtCore.QObject.connect(self.RAWDataProc, QtCore.SIGNAL("updateProgress(int)"), self.updateProgressBar)
  704. # QtCore.QObject.connect(self.RAWDataProc, QtCore.SIGNAL("enableDSP()"), self.enableDSP)
  705. # QtCore.QObject.connect(self.RAWDataProc, QtCore.SIGNAL("doneStatus()"), self.doneStatus)
  706. self.RAWDataProc.progressTrigger.connect(self.updateProgressBar)
  707. self.RAWDataProc.enableDSPTrigger.connect(self.enableDSP)
  708. self.RAWDataProc.doneTrigger.connect(self.doneStatus)
  709. self.enableAll()
  710. def loadRAW(self):
  711. #################################################
  712. # Check to make sure we are ready to process
  713. # Header
  714. if self.RAWDataProc == None:
  715. err_msg = "You need to load a header first."
  716. reply = QtGui.QMessageBox.critical(self, 'Error',
  717. err_msg) #, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
  718. return
  719. # Stacks
  720. try:
  721. self.procStacks = np.array(eval(str("np.r_["+self.ui.stacksLineEdit.text())+"]"))
  722. except:
  723. err_msg = "You need to set your stacks correctly.\n" + \
  724. "This should be a Python Numpy interpretable list\n" + \
  725. "of stack indices. For example 1:24 or 1:4,8:24"
  726. QtGui.QMessageBox.critical(self, 'Error', err_msg)
  727. return
  728. # Data Channels
  729. #Chan = np.arange(0,9,1)
  730. try:
  731. self.dataChan = np.array(eval(str("np.r_["+self.ui.dataChanLineEdit.text())+"]"))
  732. except:
  733. #QMessageBox messageBox;
  734. #messageBox.critical(0,"Error","An error has occured !");
  735. #messageBox.setFixedSize(500,200);
  736. #quit_msg = "Are you sure you want to exit the program?"
  737. #reply = QtGui.QMessageBox.question(self, 'Message',
  738. # quit_msg, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
  739. err_msg = "You need to set your data channels correctly.\n" + \
  740. "This should be a Python Numpy interpretable list\n" + \
  741. "of indices. For example 1 or 1:3 or 1:3 5\n\n" + \
  742. "valid GMR data channels fall between 1 and 8. Note that\n" +\
  743. "1:3 is not inclusive of 3 and is the same as 1,2 "
  744. reply = QtGui.QMessageBox.critical(self, 'Error',
  745. err_msg) #, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
  746. return
  747. #############################
  748. # Reference Channels
  749. # TODO make sure no overlap between data and ref channels
  750. self.refChan = np.array( () )
  751. if str(self.ui.refChanLineEdit.text()): # != "none":
  752. try:
  753. self.refChan = np.array(eval(str("np.r_["+self.ui.refChanLineEdit.text())+"]"))
  754. except:
  755. err_msg = "You need to set your reference channels correctly.\n" + \
  756. "This should be a Python Numpy interpretable list\n" + \
  757. "of indices. For example 1 or 1:3 or 1:3 5\n\n" + \
  758. "valid GMR data channels fall between 1 and 8. Note that\n" +\
  759. "1:3 is not inclusive of 3 and is the same as 1,2 "
  760. QtGui.QMessageBox.critical(self, 'Error', err_msg)
  761. return
  762. #####################################################
  763. # Load data
  764. self.lock("loading RAW GMR dataset")
  765. if self.RAWDataProc.pulseType == "FID":
  766. self.procThread = thread.start_new_thread(self.RAWDataProc.loadFIDData, \
  767. (str(self.headerstr), self.procStacks, self.dataChan, self.refChan, \
  768. str(self.ui.FIDProcComboBox.currentText()), self.ui.mplwidget, \
  769. 1e-3 * self.ui.DeadTimeSpinBox.value( ), self.ui.plotImportCheckBox.isChecked() )) #, self))
  770. elif self.RAWDataProc.pulseType == "4PhaseT1":
  771. self.procThread = thread.start_new_thread(self.RAWDataProc.load4PhaseT1Data, \
  772. (str(self.headerstr), self.procStacks, self.dataChan, self.refChan, \
  773. str(self.ui.FIDProcComboBox.currentText()), self.ui.mplwidget, \
  774. 1e-3 * self.ui.DeadTimeSpinBox.value( ), self.ui.plotImportCheckBox.isChecked() )) #, self))
  775. self.YamlNode.Import["GMR Header"] = self.headerstr
  776. self.YamlNode.Import["opened"] = datetime.datetime.now().isoformat()
  777. self.YamlNode.Import["pulse Type"] = str(self.RAWDataProc.pulseType)
  778. self.YamlNode.Import["stacks"] = self.procStacks.tolist()
  779. self.YamlNode.Import["data channels"] = self.dataChan.tolist()
  780. self.YamlNode.Import["reference channels"] = self.refChan.tolist()
  781. self.YamlNode.Import["pulse records"] = str(self.ui.FIDProcComboBox.currentText())
  782. self.YamlNode.Import["instrument dead time"] = (1e-3 * self.ui.DeadTimeSpinBox.value( ))
  783. self.Log ( )
  784. # should be already done
  785. # QtCore.QObject.connect(self.RAWDataProc, QtCore.SIGNAL("updateProgress(int)"), self.updateProgressBar)
  786. # QtCore.QObject.connect(self.RAWDataProc, QtCore.SIGNAL("enableDSP()"), self.enableDSP)
  787. # QtCore.QObject.connect(self.RAWDataProc, QtCore.SIGNAL("doneStatus()"), self.doneStatus)
  788. #self.ui.ProcessedBox.setEnabled(True)
  789. self.ui.lcdNumberFID1Length.setEnabled(1)
  790. self.ui.lcdNumberFID2Length.setEnabled(1)
  791. self.ui.lcdNumberResampFreq.setEnabled(1)
  792. self.ui.lcdTotalDeadTime.setEnabled(1)
  793. self.ui.lcdTotalDeadTime.display( self.ui.DeadTimeSpinBox.value( ) )
  794. #self.ui.lcdNumberFID1Length.display(0)
  795. #self.ui.lcdNumberFID2Length.display(0)
  796. #self.ui.lcdNumberResampFreq.display( self.RAWDataProc.samp )
  797. self.mpl_toolbar = NavigationToolbar2QT(self.ui.mplwidget, self.ui.mplwidget)
  798. self.ui.mplwidget.draw()
  799. def Log(self):
  800. #for line in yaml.dump(self.YamlNode, default_flow_style=False):
  801. #for line in nlogText:
  802. # self.ui.logTextBrowser.append( line )
  803. # self.logText.append( line )
  804. self.ui.logTextBrowser.clear()
  805. self.ui.logTextBrowser.append( yaml.dump(self.YamlNode)) #, default_flow_style=False) )
  806. def disable(self):
  807. self.ui.BandPassBox.setEnabled(False)
  808. self.ui.downSampleGroupBox.setEnabled(False)
  809. self.ui.windowFilterGroupBox.setEnabled(False)
  810. # self.ui.despikeGroupBox.setEnabled(False)
  811. self.ui.adaptBox.setEnabled(False)
  812. self.ui.adaptFDBox.setEnabled(False)
  813. self.ui.qCalcGroupBox.setEnabled(False)
  814. self.ui.FDSmartStackGroupBox.setEnabled(False)
  815. self.ui.sumDataBox.setEnabled(False)
  816. self.ui.qdGroupBox.setEnabled(False)
  817. self.ui.gateBox.setEnabled(False)
  818. def enableAll(self):
  819. self.enableDSP()
  820. self.enableQC()
  821. def enableDSP(self):
  822. # Bandpass filter
  823. self.ui.BandPassBox.setEnabled(True)
  824. self.ui.BandPassBox.setChecked(True)
  825. self.ui.bandPassGO.setEnabled(False) # need to design first
  826. self.ui.plotBP.setEnabled(True)
  827. self.ui.plotBP.setChecked(True)
  828. # downsample
  829. self.ui.downSampleGroupBox.setEnabled(True)
  830. self.ui.downSampleGroupBox.setChecked(True)
  831. # window
  832. self.ui.windowFilterGroupBox.setEnabled(True)
  833. self.ui.windowFilterGroupBox.setChecked(True)
  834. # Despike
  835. # self.ui.despikeGroupBox.setEnabled(True)
  836. # self.ui.despikeGroupBox.setChecked(False)
  837. # Adaptive filtering
  838. self.ui.adaptBox.setEnabled(True)
  839. self.ui.adaptBox.setChecked(True)
  840. # FD Adaptive filtering
  841. self.ui.adaptFDBox.setEnabled(True)
  842. self.ui.adaptFDBox.setChecked(True)
  843. # sum group box
  844. try:
  845. if len(self.dataChan) > 1:
  846. self.ui.sumDataBox.setEnabled(True)
  847. self.ui.sumDataBox.setChecked(True)
  848. except:
  849. pass
  850. # Quadrature Detect
  851. self.ui.qdGroupBox.setEnabled(True)
  852. self.ui.qdGroupBox.setChecked(True)
  853. self.enableQC()
  854. def enableQC(self):
  855. # Q calc
  856. self.ui.qCalcGroupBox.setEnabled(True)
  857. self.ui.qCalcGroupBox.setChecked(True)
  858. # FD SmartStack
  859. self.ui.FDSmartStackGroupBox.setEnabled(True)
  860. self.ui.FDSmartStackGroupBox.setChecked(True)
  861. # Quadrature detect
  862. try:
  863. for pulse in self.RAWDataProc.DATADICT["PULSES"]:
  864. np.shape(self.RAWDataProc.DATADICT[pulse]["Q"])
  865. self.RAWDataProc.DATADICT["stack"]
  866. self.ui.qdGroupBox.setEnabled(True)
  867. self.ui.qdGroupBox.setChecked(True)
  868. except:
  869. self.ui.qdGroupBox.setEnabled(False)
  870. self.ui.qdGroupBox.setChecked(False)
  871. # Gating
  872. try:
  873. self.RAWDataProc.DATADICT["CA"]
  874. self.ui.gateBox.setEnabled(True)
  875. self.ui.gateBox.setChecked(True)
  876. except:
  877. self.ui.gateBox.setEnabled(False)
  878. self.ui.gateBox.setChecked(False)
  879. def despikeFilter(self):
  880. self.lock("despike filter")
  881. thread.start_new_thread(self.RAWDataProc.despike, \
  882. (self.ui.windowSpinBox.value(), \
  883. self.ui.thresholdSpinBox.value(), \
  884. str(self.ui.replComboBox.currentText()), \
  885. self.ui.rollOnSpinBox.value(), \
  886. self.ui.despikeInterpWinSpinBox.value(),
  887. self.ui.mplwidget))
  888. def calcQ(self):
  889. if "Calc Q" not in self.YamlNode.Processing.keys():
  890. #print("In CalcQ", yaml.dump(self.YamlNode.Processing) )
  891. self.YamlNode.Processing["Calc Q"] = True
  892. #print( yaml.dump(self.YamlNode.Processing) )
  893. self.Log()
  894. else:
  895. err_msg = "Q values have already been calculated"
  896. reply =QtWidgets.QMessageBox.critical(self, 'Error',
  897. err_msg)
  898. return
  899. self.lock("pulse moment calculation")
  900. thread.start_new_thread(self.RAWDataProc.effectivePulseMoment, \
  901. (self.ui.CentralVSpinBox.value(), \
  902. self.ui.mplwidget))
  903. def harmonicModel(self):
  904. self.lock("harmonic noise modelling")
  905. thread.start_new_thread(self.RAWDataProc.harmonicModel, \
  906. (self.ui.f0Spin.value(), \
  907. self.ui.mplwidget))
  908. def FDSmartStack(self):
  909. if "TD stack" not in self.YamlNode.Processing.keys():
  910. self.YamlNode.Processing["TD stack"] = {}
  911. self.YamlNode.Processing["TD stack"]["outlier"] = str( self.ui.outlierTestCB.currentText() )
  912. self.YamlNode.Processing["TD stack"]["cutoff"] = str( self.ui.MADCutoff.value() )
  913. self.Log()
  914. else:
  915. err_msg = "TD noise cancellation has already been applied!"
  916. reply =QtWidgets.QMessageBox.critical(self, 'Error',
  917. err_msg)
  918. return
  919. self.lock("time-domain smart stack")
  920. thread.start_new_thread(self.RAWDataProc.TDSmartStack, \
  921. (str(self.ui.outlierTestCB.currentText()), \
  922. self.ui.MADCutoff.value(),
  923. self.ui.mplwidget))
  924. def adaptFilter(self):
  925. if "TD noise cancellation" not in self.YamlNode.Processing.keys():
  926. self.YamlNode.Processing["TD noise cancellation"] = {}
  927. self.YamlNode.Processing["TD noise cancellation"]["n_Taps"] = str(self.ui.MTapsSpinBox.value())
  928. self.YamlNode.Processing["TD noise cancellation"]["lambda"] = str(self.ui.adaptLambdaSpinBox.value())
  929. self.YamlNode.Processing["TD noise cancellation"]["truncate"] = str(self.ui.adaptTruncateSpinBox.value())
  930. self.YamlNode.Processing["TD noise cancellation"]["mu"] = str(self.ui.adaptMuSpinBox.value())
  931. self.YamlNode.Processing["TD noise cancellation"]["PCA"] = str(self.ui.PCAComboBox.currentText())
  932. self.Log()
  933. else:
  934. err_msg = "TD noise cancellation has already been applied!"
  935. reply =QtWidgets.QMessageBox.critical(self, 'Error', err_msg)
  936. #return
  937. self.lock("TD noise cancellation filter")
  938. thread.start_new_thread(self.RAWDataProc.adaptiveFilter, \
  939. (self.ui.MTapsSpinBox.value(), \
  940. self.ui.adaptLambdaSpinBox.value(), \
  941. self.ui.adaptTruncateSpinBox.value(), \
  942. self.ui.adaptMuSpinBox.value(), \
  943. str(self.ui.PCAComboBox.currentText()), \
  944. self.ui.mplwidget))
  945. def sumDataChans(self):
  946. if "Data sum" not in self.YamlNode.Processing.keys():
  947. self.YamlNode.Processing["Data sum"] = True
  948. self.Log()
  949. else:
  950. err_msg = "Data channels have already been summed!"
  951. reply =QtWidgets.QMessageBox.critical(self, 'Error',
  952. err_msg)
  953. return
  954. self.lock("Summing data channels")
  955. self.dataChan = [self.dataChan[0]]
  956. self.ui.sumDataBox.setEnabled(False)
  957. thread.start_new_thread( self.RAWDataProc.sumData, ( self.ui.mplwidget, 7 ) )
  958. def adaptFilterFD(self):
  959. self.lock("FD noise cancellation filter")
  960. thread.start_new_thread(self.RAWDataProc.adaptiveFilterFD, \
  961. (str(self.ui.windowTypeComboBox.currentText()), \
  962. self.ui.windowBandwidthSpinBox.value(), \
  963. self.ui.CentralVSpinBox.value(), \
  964. self.ui.mplwidget))
  965. def bandPassFilter(self):
  966. if "Bandpass filter" not in self.YamlNode.Processing.keys():
  967. self.YamlNode.Processing["Bandpass filter"] = {}
  968. self.YamlNode.Processing["Bandpass filter"]["central_nu"] = str(self.ui.CentralVSpinBox.value())
  969. self.YamlNode.Processing["Bandpass filter"]["passband"] = str(self.ui.passBandSpinBox.value())
  970. self.YamlNode.Processing["Bandpass filter"]["stopband"] = str(self.ui.stopBandSpinBox.value())
  971. self.YamlNode.Processing["Bandpass filter"]["gpass"] = str(self.ui.gpassSpinBox.value())
  972. self.YamlNode.Processing["Bandpass filter"]["gstop"] = str(self.ui.gstopSpinBox.value())
  973. self.YamlNode.Processing["Bandpass filter"]["type"] = str(self.ui.fTypeComboBox.currentText())
  974. self.Log()
  975. else:
  976. err_msg = "Bandpass filter has already been applied!"
  977. reply =QtWidgets.QMessageBox.critical(self, 'Error',
  978. err_msg)
  979. return
  980. self.lock("bandpass filter")
  981. nv = self.ui.lcdTotalDeadTime.value( ) + self.ui.lcdNumberFTauDead.value()
  982. self.ui.lcdTotalDeadTime.display( nv )
  983. thread.start_new_thread(self.RAWDataProc.bandpassFilter, \
  984. (self.ui.mplwidget, 0, self.ui.plotBP.isChecked() ))
  985. def downsample(self):
  986. self.lock("resampling")
  987. if "Resample" not in self.YamlNode.Processing.keys():
  988. self.YamlNode.Processing["Resample"] = {}
  989. self.YamlNode.Processing["Resample"]["downsample factor"] = []
  990. self.YamlNode.Processing["Resample"]["truncate length"] = []
  991. self.YamlNode.Processing["Resample"]["downsample factor"].append( str(self.ui.downSampleSpinBox.value() ) )
  992. self.YamlNode.Processing["Resample"]["truncate length"].append( str( self.ui.truncateSpinBox.value() ) )
  993. self.Log( )
  994. thread.start_new_thread(self.RAWDataProc.downsample, \
  995. (self.ui.truncateSpinBox.value(), \
  996. self.ui.downSampleSpinBox.value(),
  997. self.ui.mplwidget))
  998. def quadDet(self):
  999. if "Quadrature detection" not in self.YamlNode.Processing.keys():
  1000. self.YamlNode.Processing["Quadrature detection"] = {}
  1001. self.YamlNode.Processing["Quadrature detection"]["trim"] = str( self.ui.trimSpin.value() )
  1002. self.Log()
  1003. else:
  1004. err_msg = "Quadrature detection has already been done!"
  1005. reply =QtWidgets.QMessageBox.critical(self, 'Error',
  1006. err_msg)
  1007. return
  1008. self.lock("quadrature detection")
  1009. thread.start_new_thread(self.RAWDataProc.quadDet, \
  1010. (self.ui.trimSpin.value(), int(self.ui.QDType.currentIndex()), self.ui.mplwidget))
  1011. self.ui.plotQD.setEnabled(True)
  1012. def plotQD(self):
  1013. self.lock("plot QD")
  1014. thread.start_new_thread(self.RAWDataProc.plotQuadDet, \
  1015. (self.ui.trimSpin.value(), int(self.ui.QDType.currentIndex()), self.ui.mplwidget))
  1016. def gateIntegrate(self):
  1017. if "Gate integrate" not in self.YamlNode.Processing.keys():
  1018. self.YamlNode.Processing["Gate integrate"] = {}
  1019. self.YamlNode.Processing["Gate integrate"]["gpd"] = str(self.ui.GPDspinBox.value( ) )
  1020. self.Log()
  1021. self.lock("gate integration")
  1022. thread.start_new_thread(self.RAWDataProc.gateIntegrate, \
  1023. (self.ui.GPDspinBox.value(), self.ui.trimSpin.value(), self.ui.mplwidget))
  1024. self.ui.actionExport_Preprocessed_Dataset.setEnabled(True)
  1025. self.ui.plotGI.setEnabled(True)
  1026. def plotGI(self):
  1027. self.lock("plot gate integrate")
  1028. thread.start_new_thread(self.RAWDataProc.plotGateIntegrate, \
  1029. (self.ui.GPDspinBox.value(), self.ui.trimSpin.value(), \
  1030. self.ui.QDType_2.currentIndex(), self.ui.mplwidget))
  1031. def designFilter(self):
  1032. [bord, fe] = self.RAWDataProc.designFilter( \
  1033. self.ui.CentralVSpinBox.value(), \
  1034. self.ui.passBandSpinBox.value(), \
  1035. self.ui.stopBandSpinBox.value(), \
  1036. self.ui.gpassSpinBox.value(), \
  1037. self.ui.gstopSpinBox.value(), \
  1038. str(self.ui.fTypeComboBox.currentText()),
  1039. self.ui.mplwidget)
  1040. self.ui.lcdNumberFilterOrder.display(bord)
  1041. self.ui.lcdNumberFTauDead.display(1e3*fe)
  1042. #self.ui.lcdNumberFilterOrder.display(bord)
  1043. self.ui.bandPassGO.setEnabled(1)
  1044. # self.ui.lcdNumberTauPulse2.display(1e3*self.RAWDataProc.pulseLength[1])
  1045. def windowFilter(self):
  1046. if "Window filter" not in self.YamlNode.Processing.keys():
  1047. self.YamlNode.Processing["Window filter"] = {}
  1048. self.YamlNode.Processing["Window filter"]["type"] = str(self.ui.windowTypeComboBox.currentText())
  1049. self.YamlNode.Processing["Window filter"]["width"] = str(self.ui.windowBandwidthSpinBox.value())
  1050. self.YamlNode.Processing["Window filter"]["centre"] = str(self.ui.CentralVSpinBox.value() )
  1051. self.Log()
  1052. else:
  1053. err_msg = "FD window has already been applied!"
  1054. reply =QtWidgets.QMessageBox.critical(self, 'Error',
  1055. err_msg)
  1056. return
  1057. self.lock("window filter")
  1058. thread.start_new_thread(self.RAWDataProc.windowFilter, \
  1059. (str(self.ui.windowTypeComboBox.currentText()), \
  1060. self.ui.windowBandwidthSpinBox.value(), \
  1061. self.ui.CentralVSpinBox.value(), \
  1062. self.ui.mplwidget))
  1063. def designFDFilter(self):
  1064. # thread.start_new_thread(self.RAWDataProc.computeWindow, ( \
  1065. # "Pulse 1",
  1066. # self.ui.windowBandwidthSpinBox.value(), \
  1067. # self.ui.CentralVSpinBox.value(), \
  1068. # str(self.ui.windowTypeComboBox.currentText()), \
  1069. # self.ui.mplwidget ))
  1070. a,b,c,d,dead = self.RAWDataProc.computeWindow( \
  1071. "Pulse 1",
  1072. self.ui.windowBandwidthSpinBox.value(), \
  1073. self.ui.CentralVSpinBox.value(), \
  1074. str(self.ui.windowTypeComboBox.currentText()), \
  1075. self.ui.mplwidget )
  1076. self.ui.lcdWinDead.display(dead)
  1077. def updateProgressBar(self, percent):
  1078. self.ui.barProgress.setValue(percent)
  1079. def updateProc(self):
  1080. if str(self.ui.FIDProcComboBox.currentText()) == "Pulse 1":
  1081. self.ui.lcdNumberFID1Length.display(self.RAWDataProc.DATADICT["Pulse 1"]["TIMES"][-1]- self.RAWDataProc.DATADICT["Pulse 1"]["TIMES"][0])
  1082. elif str(self.ui.FIDProcComboBox.currentText()) == "Pulse 2":
  1083. self.ui.lcdNumberFID2Length.display(self.RAWDataProc.DATADICT["Pulse 2"]["TIMES"][-1]- self.RAWDataProc.DATADICT["Pulse 2"]["TIMES"][0])
  1084. else:
  1085. self.ui.lcdNumberFID1Length.display(self.RAWDataProc.DATADICT["Pulse 1"]["TIMES"][-1]- self.RAWDataProc.DATADICT["Pulse 1"]["TIMES"][0])
  1086. self.ui.lcdNumberFID2Length.display(self.RAWDataProc.DATADICT["Pulse 2"]["TIMES"][-1]- self.RAWDataProc.DATADICT["Pulse 2"]["TIMES"][0])
  1087. self.ui.lcdNumberResampFreq.display( self.RAWDataProc.samp )
  1088. def doneStatus(self): # unlocks GUI
  1089. self.ui.statusbar.clearMessage ( )
  1090. self.ui.barProgress.hide()
  1091. self.updateProc()
  1092. self.enableAll()
  1093. def lock(self, string):
  1094. self.ui.statusbar.showMessage ( string )
  1095. self.ui.barProgress.show()
  1096. self.ui.barProgress.setValue(0)
  1097. self.disable()
  1098. def unlock(self):
  1099. self.ui.statusbar.clearMessage ( )
  1100. self.ui.barProgress.hide()
  1101. self.enableAll()
  1102. def done(self):
  1103. self.ui.statusbar.showMessage ( "" )
  1104. ################################################################
  1105. ################################################################
  1106. # Boiler plate main function
  1107. import pkg_resources
  1108. from pkg_resources import resource_string
  1109. import matplotlib.image as mpimg
  1110. import matplotlib.pyplot as plt
  1111. def main():
  1112. # splash screen logo
  1113. logo = pkg_resources.resource_filename(__name__, 'akvo.png')
  1114. logo2 = pkg_resources.resource_filename(__name__, 'akvo2.png')
  1115. qApp = QtWidgets.QApplication(sys.argv)
  1116. ssplash = False
  1117. if ssplash:
  1118. pixmap = QtGui.QPixmap(logo)
  1119. splash = QtWidgets.QSplashScreen(pixmap, QtCore.Qt.WindowStaysOnTopHint)
  1120. splash.show()
  1121. aw = ApplicationWindow()
  1122. img=mpimg.imread(logo)
  1123. for ax in [ aw.ui.mplwidget ]:
  1124. ax.fig.clear()
  1125. subplot = ax.fig.add_subplot(111)
  1126. #ax.fig.patch.set_facecolor( None )
  1127. #ax.fig.patch.set_alpha( .0 )
  1128. subplot.imshow(img)
  1129. subplot.xaxis.set_major_locator(plt.NullLocator())
  1130. subplot.yaxis.set_major_locator(plt.NullLocator())
  1131. ax.draw()
  1132. if ssplash:
  1133. splash.showMessage("Loading modules")
  1134. splash.finish(aw)
  1135. #time.sleep(1)
  1136. aw.setWindowTitle("Akvo v"+str(version))
  1137. aw.show()
  1138. qApp.setWindowIcon(QtGui.QIcon(logo2))
  1139. sys.exit(qApp.exec_())
  1140. if __name__ == "__main__":
  1141. main()