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.

invertTA.py 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. from akvo.tressel.SlidesPlot import *
  2. import numpy as np
  3. import sys
  4. import matplotlib.pyplot as plt
  5. import cmocean
  6. from pylab import meshgrid
  7. from akvo.tressel.logbarrier import *
  8. import yaml,os
  9. import multiprocessing
  10. import itertools
  11. from scipy.linalg import svd
  12. from matplotlib.backends.backend_pdf import PdfPages
  13. from matplotlib.colors import LogNorm
  14. from matplotlib.colors import LightSource
  15. from matplotlib.ticker import ScalarFormatter
  16. from matplotlib.ticker import MaxNLocator
  17. from matplotlib.ticker import AutoMinorLocator
  18. from matplotlib.ticker import LogLocator
  19. from matplotlib.ticker import FormatStrFormatter
  20. from matplotlib.colors import Normalize
  21. import cmocean
  22. from akvo.tressel.lemma_yaml import *
  23. from akvo.tressel import nonlinearinv as nl
  24. import pandas as pd
  25. def buildKQT(K0,tg,T2Bins):
  26. """
  27. Constructs a QT inversion kernel from an initial amplitude one.
  28. """
  29. nlay, nq = np.shape(K0)
  30. nt2 = len(T2Bins)
  31. nt = len(tg)
  32. KQT = np.zeros( ( nq*nt,nt2*nlay), dtype=np.complex128 )
  33. for iq in range(nq):
  34. for it in range(nt):
  35. for ilay in range(nlay):
  36. for it2 in range(nt2):
  37. #KQT[iq*nt + it,ilay*nt2+it2] = K0[ilay,iq]*np.exp(-((10+tg[it])*1e-3)/(1e-3*T2Bins[it2]))
  38. KQT[iq*nt + it,ilay*nt2+it2] = K0[ilay,iq]*np.exp(-((10+tg[it])*1e-3)/(1e-3*T2Bins[it2]))
  39. return KQT
  40. def loadAkvoData(fnamein, chan):
  41. """ Loads data from an Akvo YAML file. The 0.02 is hard coded as the pulse length. This needs to be
  42. corrected in future kernel calculations. The current was reported but not the pulse length.
  43. """
  44. fname = (os.path.splitext(fnamein)[0])
  45. with open(fnamein, 'r') as stream:
  46. try:
  47. AKVO = (yaml.load(stream, Loader=yaml.Loader))
  48. except yaml.YAMLError as exc:
  49. print(exc)
  50. exit()
  51. Z = np.zeros( (AKVO.nPulseMoments, AKVO.Gated["Pulse 1"]["abscissa"].size ) )
  52. ZS = np.zeros( (AKVO.nPulseMoments, AKVO.Gated["Pulse 1"]["abscissa"].size ) )
  53. for q in range(AKVO.nPulseMoments):
  54. Z[q] = AKVO.Gated["Pulse 1"][chan]["Q-"+str(q) + " CA"].data
  55. if chan == "Chan. 1":
  56. ZS[q] = AKVO.Gated["Pulse 1"][chan]["STD"].data
  57. elif chan == "Chan. 2":
  58. ZS[q] = AKVO.Gated["Pulse 1"][chan]["STD"].data
  59. elif chan == "Chan. 3":
  60. ZS[q] = AKVO.Gated["Pulse 1"][chan]["STD"].data
  61. elif chan == "Chan. 4":
  62. ZS[q] = AKVO.Gated["Pulse 1"][chan]["STD"].data
  63. else:
  64. print("DOOM!!!")
  65. exit()
  66. #Z *= 1e-9
  67. #ZS *= 1e-9
  68. J = AKVO.Pulses["Pulse 1"]["current"].data
  69. J = np.append(J,J[-1]+(J[-1]-J[-2]))
  70. Q = AKVO.pulseLength[0]*J
  71. return Z, ZS, AKVO.Gated["Pulse 1"]["abscissa"].data #, Q
  72. def catLayers(K0):
  73. K = np.zeros( (len(K0.keys()), len(K0["layer-0"].data)) , dtype=complex )
  74. for lay in range(len(K0.keys())):
  75. #print(K0["layer-"+str(lay)].data) # print (lay)
  76. K[lay] =K0["layer-"+str(lay)].data # print (lay)
  77. return 1e9*K # invert in nV
  78. def loadK0(fname):
  79. """ Loads in initial amplitude kernel
  80. """
  81. print("loading K0", fname)
  82. with open(fname) as f:
  83. K0 = yaml.load(f, Loader=yaml.Loader)
  84. K = catLayers(K0.K0)
  85. ifaces = np.array(K0.Interfaces.data)
  86. return ifaces, K
  87. #return ifaces, np.abs(K)
  88. def main():
  89. if (len (sys.argv) < 2):
  90. print ("akvoQT invertParameters.yaml")
  91. exit()
  92. with open(sys.argv[1], 'r') as stream:
  93. try:
  94. cont = (yaml.load(stream, Loader=yaml.Loader))
  95. except yaml.YAMLError as exc:
  96. print(exc)
  97. ###############################################
  98. # Load in data
  99. ###############################################
  100. V = []
  101. VS = []
  102. tg = 0
  103. for dat in cont['data']:
  104. for ch in cont['data'][dat]['channels']:
  105. print("dat", dat, "ch", ch)
  106. v,vs,tg = loadAkvoData(dat, ch)
  107. V.append(v)
  108. VS.append(vs)
  109. for iv in range(1, len(V)):
  110. V[0] = np.concatenate( (V[0], V[iv]) )
  111. VS[0] = np.concatenate( (VS[0], VS[iv]) )
  112. V = V[0]
  113. VS = VS[0]
  114. ###############################################
  115. # Load in kernels
  116. ###############################################
  117. K0 = []
  118. for kern in cont["K0"]:
  119. ifaces,k0 = loadK0( kern )
  120. K0.append(k0)
  121. for ik in range(1, len(K0)):
  122. K0[0] = np.concatenate( (K0[0].T, K0[ik].T) ).T
  123. K0 = K0[0]
  124. #np.save("ifaces", ifaces)
  125. #exit()
  126. #plt.matshow(np.real(K0))
  127. #plt.show()
  128. #exit()
  129. ##########################################################
  130. # VERY Simple Sensitivity based calc. of noise per layer #
  131. maxq = np.argmax(np.abs(K0), axis=1)
  132. maxK = .1 * np.abs(K0)[ np.arange(0,len(ifaces)-1), maxq ] # 10% water is arbitrary
  133. SNR = maxK / (VS[0][0])
  134. ###############################################
  135. # Build full kernel
  136. ###############################################
  137. T2Bins = np.logspace( np.log10(cont["T2Bins"]["low"]), np.log10(cont["T2Bins"]["high"]), cont["T2Bins"]["number"], endpoint=True, base=10)
  138. T2Bins2 = np.append( T2Bins, T2Bins[-1] + (T2Bins[-1]-T2Bins[-2]) )
  139. NT2 = len(T2Bins)
  140. KQT = np.real(buildKQT(np.abs(K0),tg,T2Bins))
  141. ###############################################
  142. # Linear Inversion
  143. ###############################################
  144. print("Calling inversion", flush=True)
  145. inv, ibreak, errn, phim, phid, mkappa, Wd, Wm, alphastar = logBarrier(KQT, np.ravel(V), T2Bins, "lcurve", MAXITER=150, sigma=np.ravel(VS), alpha=1e6, smooth="Smallest" )
  146. ####################
  147. # Summary plots #
  148. ####################
  149. pre = np.dot(KQT,inv)
  150. PRE = np.reshape( pre, np.shape(V) )
  151. plt.matshow(PRE, cmap='Blues')
  152. plt.gca().set_title("linear predicted")
  153. plt.colorbar()
  154. DIFF = (PRE-V) / VS
  155. md = np.max(np.abs(DIFF))
  156. plt.matshow(DIFF, cmap=cmocean.cm.balance, vmin=-md, vmax=md)
  157. plt.gca().set_title("linear misfit / $\widehat{\sigma}$")
  158. plt.colorbar()
  159. plt.matshow(V, cmap='Blues')
  160. plt.gca().set_title("observed")
  161. plt.colorbar()
  162. ###############################################
  163. # Non-linear refinement!
  164. ###############################################
  165. nonLinearRefinement = cont['NonLinearRefinement']
  166. if nonLinearRefinement:
  167. KQTc = buildKQT(K0, tg, T2Bins)
  168. prec = np.abs(np.dot(KQTc, inv))
  169. phidc = np.linalg.norm(np.dot(Wd,prec-np.ravel(V)))**2
  170. print("PHID forward linear=", errn, "PHID forward nonlinear=", phidc/len(np.ravel(V)))
  171. res = nl.nonlinearinversion(inv, Wd, KQTc, np.ravel(V), Wm, alphastar )
  172. if res.success == True:
  173. INVc = np.reshape(res.x, (len(ifaces)-1,cont["T2Bins"]["number"]) )
  174. prec = np.abs(np.dot(KQTc, res.x))
  175. phidc = np.linalg.norm(np.dot(Wd,prec-np.ravel(V)))**2
  176. PREc = np.reshape( prec, np.shape(V) )
  177. print("PHID linear=", errn, "PHID nonlinear=", phidc/len(np.ravel(V)))
  178. while phidc/len(np.ravel(V)) > errn:
  179. phidc_old = phidc/len(np.ravel(V))
  180. #alphastar *= .9
  181. res = nl.nonlinearinversion(res.x, Wd, KQTc, np.ravel(V), Wm, alphastar )
  182. if res.success == True:
  183. INVc = np.reshape(res.x, (len(ifaces)-1,cont["T2Bins"]["number"]) )
  184. prec = np.abs(np.dot(KQTc, res.x))
  185. phidc = np.linalg.norm(np.dot(Wd,prec-np.ravel(V)))**2
  186. PREc = np.reshape( prec, np.shape(V) )
  187. print("PHID linear=", errn, "PHID nonlinear=", phidc/len(np.ravel(V)))
  188. else:
  189. break
  190. if phidc_old - phidc/len(np.ravel(V)) < 0.005:
  191. print("Not making progress reducing misfit in nonlinear refinement")
  192. break
  193. plt.matshow(PREc, cmap='Blues')
  194. plt.gca().set_title("nonlinear predicted")
  195. plt.colorbar()
  196. DIFFc = (PREc-V) / VS
  197. md = np.max(np.abs(DIFF))
  198. plt.matshow(DIFFc, cmap=cmocean.cm.balance, vmin=-md, vmax=md)
  199. plt.gca().set_title("nonlinear misfit / $\widehat{\sigma}$")
  200. plt.colorbar()
  201. ###############################################
  202. # Appraise DOI using simplified MRM
  203. ###############################################
  204. CalcDOI = cont['CalcDOI']
  205. if CalcDOI:
  206. pdf = PdfPages('resolution_analysis' + '.pdf' )
  207. MRM = np.zeros((len(ifaces)-1, len(ifaces)-1))
  208. #with multiprocessing.Pool() as pool:
  209. # invresults = pool.starmap(invert, zip(itertools.repeat(Time), GT[0:ni], GD[0:ni], SIG[0:ni], itertools.repeat(sys.argv[3]) ))
  210. # This could be parallelized
  211. for ilay in range(len(ifaces)-1):
  212. iDeltaT2 = len(T2Bins)//2
  213. deltaMod = np.zeros( (len(ifaces)-1, len(T2Bins)) )
  214. deltaMod[ilay][iDeltaT2] = 0.3
  215. dV = np.dot(KQT, np.ravel(deltaMod))
  216. # invert
  217. dinv, dibreak, derrn = logBarrier(KQT, dV, T2Bins, "single", MAXITER=1, sigma=np.ravel(VS), alpha=alphastar, smooth="Smallest" )
  218. print("Sum dinv from", str(ifaces[ilay]), "to", str(ifaces[ilay+1]), "=", np.sum(dinv))
  219. DINV = np.reshape(dinv, (len(ifaces)-1,cont["T2Bins"]["number"]) )
  220. MRM[ilay,:] = np.sum(DINV, axis=1)
  221. Y,X = meshgrid( ifaces, T2Bins2 )
  222. fig = plt.figure( figsize=(pc2in(20.0),pc2in(22.)) )
  223. ax1 = fig.add_axes( [.2,.15,.6,.7] )
  224. im = ax1.pcolor(X, Y, DINV.T, cmap=cmocean.cm.tempo, shading='auto')
  225. ax1.plot( T2Bins[iDeltaT2], (ifaces[ilay]+ifaces[ilay+1])/2, 's', markersize=6, markeredgecolor='black') #, markerfacecolor=None )
  226. im.set_edgecolor('face')
  227. ax1.set_xlabel(u"$T_2^*$ (ms)")
  228. ax1.set_ylabel(u"depth (m)")
  229. ax1.set_xlim( T2Bins2[0], T2Bins2[-1] )
  230. ax1.set_ylim( ifaces[-1], ifaces[0] )
  231. ax2 = ax1.twiny()
  232. ax2.plot( np.sum(DINV, axis=1), (ifaces[1:]+ifaces[0:-1])/2 , color='red' )
  233. ax2.set_xlabel(u"total water (m$^3$/m$^3$)")
  234. ax2.set_ylim( ifaces[-1], ifaces[0] )
  235. ax2.xaxis.set_major_locator( MaxNLocator(nbins = 3) )
  236. ax2.get_xaxis().set_major_formatter(FormatStrFormatter('%0.2f'))
  237. pdf.savefig(facecolor=[0,0,0,0])
  238. plt.close(fig)
  239. np.save("MRM", MRM)
  240. centres = (ifaces[0:-1]+ifaces[1:])/2
  241. X,Y = np.meshgrid(ifaces,ifaces)
  242. fig = plt.figure( figsize=(pc2in(20.0),pc2in(22.)) )
  243. ax1 = fig.add_axes( [.2,.15,.6,.7] )
  244. ax1.pcolor(X,Y,MRM, cmap = cmocean.cm.ice)
  245. ax1.set_ylim(ifaces[-1], ifaces[0])
  246. maxDepth = np.argmax(MRM, axis=0)
  247. plt.plot(centres[maxDepth], centres, color='white')
  248. # Determine DOI
  249. DOIMetric = centres[maxDepth]/centres #> 0.9
  250. DOI = ifaces[ np.where(DOIMetric < 0.9 ) ][0]
  251. plt.axhline(y=DOI, color='white', linestyle='-.')
  252. ax1.set_ylim( ifaces[-1], ifaces[0] )
  253. ax1.set_xlim( ifaces[0], ifaces[-1] )
  254. ax1.set_xlabel(u"depth (m)")
  255. ax1.set_ylabel(u"depth (m)")
  256. pdf.close()
  257. INV = np.reshape(inv, (len(ifaces)-1,cont["T2Bins"]["number"]) )
  258. #alphas = np.tile(SNR, (len(T2Bins)-1,1))
  259. #colors = Normalize(1e-6, np.max(INV.T), clip=True)(INV.T)
  260. #colors = cmocean.cm.tempo(colors)
  261. ##colors[..., -1] = alphas
  262. #print(np.shape(colors))
  263. #print(np.shape(INV.T))
  264. #greys = np.full((*(INV.T).shape, 3), 70, dtype=np.uint8)
  265. ############## LINEAR RESULT ##########################
  266. Y,X = meshgrid( ifaces, T2Bins2 )
  267. fig = plt.figure( figsize=(pc2in(20.0),pc2in(22.)) )
  268. ax1 = fig.add_axes( [.2,.15,.6,.7] )
  269. im = ax1.pcolor(X, Y, INV.T, cmap=cmocean.cm.tempo) #cmap='viridis')
  270. #im = ax1.pcolor(X[0:SNRidx,:], Y[0:SNRidx,:], INV.T[0:SNRidx,:], cmap=cmocean.cm.tempo) #cmap='viridis')
  271. #im = ax1.pcolor(X[SNRidx::,:], Y[SNRidx::,:], INV.T[SNRidx::,:], cmap=cmocean.cm.tempo, alpha=.5) #cmap='viridis')
  272. #im = ax1.pcolormesh(X, Y, INV.T, alpha=alphas) #, cmap=cmocean.cm.tempo) #cmap='viridis')
  273. #im = ax1.pcolormesh(X, Y, INV.T, alpha=alphas) #, cmap=cmocean.cm.tempo) #cmap='viridis')
  274. #ax1.axhline( y=ifaces[SNRidx], xmin=T2Bins[0], xmax=T2Bins[-1], color='black' )
  275. im.set_edgecolor('face')
  276. ax1.set_xlim( T2Bins[0], T2Bins2[-1] )
  277. ax1.set_ylim( ifaces[-1], ifaces[0] )
  278. cb = plt.colorbar(im, label = u"PWC (m$^3$/m$^3$)") #, format='%1.1f')
  279. cb.locator = MaxNLocator( nbins = 4)
  280. cb.ax.yaxis.set_offset_position('left')
  281. cb.update_ticks()
  282. ax1.set_xlabel(u"$T_2^*$ (ms)")
  283. ax1.set_ylabel(u"depth (m)")
  284. ax1.get_xaxis().set_major_formatter(FormatStrFormatter('%1.0f'))
  285. ax1.get_yaxis().set_major_formatter(FormatStrFormatter('%1.0f'))
  286. ax1.xaxis.set_major_locator( MaxNLocator(nbins = 4) )
  287. #ax1.xaxis.set_label_position('top')
  288. ax2 = ax1.twiny()
  289. ax2.plot( np.sum(INV, axis=1), (ifaces[1:]+ifaces[0:-1])/2 , color='red' )
  290. ax2.set_xlabel(u"total water (m$^3$/m$^3$)")
  291. ax2.set_ylim( ifaces[-1], ifaces[0] )
  292. ax2.xaxis.set_major_locator( MaxNLocator(nbins = 3) )
  293. ax2.get_xaxis().set_major_formatter(FormatStrFormatter('%0.2f'))
  294. #ax2.axhline( y=ifaces[SNRidx], xmin=0, xmax=1, color='black', linestyle='dashed' )
  295. if CalcDOI:
  296. ax2.axhline( y=DOI, xmin=0, xmax=1, color='black', linestyle='dashed' )
  297. #ax2.xaxis.set_label_position('bottom')
  298. plt.savefig("akvoInversion.pdf")
  299. ############## NONLINEAR RESULT ##########################
  300. if nonLinearRefinement:
  301. Y,X = meshgrid( ifaces, T2Bins )
  302. fig = plt.figure( figsize=(pc2in(20.0),pc2in(22.)) )
  303. ax1 = fig.add_axes( [.2,.15,.6,.7] )
  304. im = ax1.pcolor(X, Y, INVc.T, cmap=cmocean.cm.tempo) #cmap='viridis')
  305. #im = ax1.pcolor(X[0:SNRidx,:], Y[0:SNRidx,:], INV.T[0:SNRidx,:], cmap=cmocean.cm.tempo) #cmap='viridis')
  306. #im = ax1.pcolor(X[SNRidx::,:], Y[SNRidx::,:], INV.T[SNRidx::,:], cmap=cmocean.cm.tempo, alpha=.5) #cmap='viridis')
  307. #im = ax1.pcolormesh(X, Y, INV.T, alpha=alphas) #, cmap=cmocean.cm.tempo) #cmap='viridis')
  308. #im = ax1.pcolormesh(X, Y, INV.T, alpha=alphas) #, cmap=cmocean.cm.tempo) #cmap='viridis')
  309. #ax1.axhline( y=ifaces[SNRidx], xmin=T2Bins[0], xmax=T2Bins[-1], color='black' )
  310. im.set_edgecolor('face')
  311. ax1.set_xlim( T2Bins[0], T2Bins[-1] )
  312. ax1.set_ylim( ifaces[-1], ifaces[0] )
  313. cb = plt.colorbar(im, label = u"PWC (m$^3$/m$^3$)") #, format='%1.1f')
  314. cb.locator = MaxNLocator( nbins = 4)
  315. cb.ax.yaxis.set_offset_position('left')
  316. cb.update_ticks()
  317. ax1.set_xlabel(u"$T_2^*$ (ms)")
  318. ax1.set_ylabel(u"depth (m)")
  319. ax1.get_xaxis().set_major_formatter(FormatStrFormatter('%1.0f'))
  320. ax1.get_yaxis().set_major_formatter(FormatStrFormatter('%1.0f'))
  321. ax1.xaxis.set_major_locator( MaxNLocator(nbins = 4) )
  322. #ax1.xaxis.set_label_position('top')
  323. ax2 = ax1.twiny()
  324. ax2.plot( np.sum(INVc, axis=1), (ifaces[1:]+ifaces[0:-1])/2 , color='red' )
  325. ax2.set_xlabel(u"total water (m$^3$/m$^3$)")
  326. ax2.set_ylim( ifaces[-1], ifaces[0] )
  327. ax2.xaxis.set_major_locator( MaxNLocator(nbins = 3) )
  328. ax2.get_xaxis().set_major_formatter(FormatStrFormatter('%0.2f'))
  329. #ax2.axhline( y=ifaces[SNRidx], xmin=0, xmax=1, color='black', linestyle='dashed' )
  330. if CalcDOI:
  331. ax2.axhline( y=DOI, xmin=0, xmax=1, color='black', linestyle='dashed' )
  332. #ax2.xaxis.set_label_position('bottom')
  333. fig.suptitle("Non linear inversion")
  334. plt.savefig("akvoInversionNL.pdf")
  335. #############
  336. # water plot#
  337. fig2 = plt.figure( figsize=(pc2in(20.0),pc2in(22.)) )
  338. ax = fig2.add_axes( [.2,.15,.6,.7] )
  339. # Bound water cutoff
  340. Bidx = T2Bins<33.0
  341. twater = np.sum(INV, axis=1)
  342. bwater = np.sum(INV[:,Bidx], axis=1)
  343. ax.plot( twater, (ifaces[0:-1]+ifaces[1::])/2, label="NMR total water", color='blue' )
  344. ax.plot( bwater, (ifaces[0:-1]+ifaces[1::])/2, label="NMR bound water", color='green' )
  345. ax.fill_betweenx((ifaces[0:-1]+ifaces[1::])/2 , twater, bwater, where=twater >= bwater, facecolor='blue', alpha=.5)
  346. ax.fill_betweenx((ifaces[0:-1]+ifaces[1::])/2 , bwater, 0, where=bwater >= 0, facecolor='green', alpha=.5)
  347. ax.set_xlabel(r"$\theta_N$ (m$^3$/m$^3$)")
  348. ax.set_ylabel(r"depth (m)")
  349. ax.set_ylim( ifaces[-1], ifaces[0] )
  350. ax.set_xlim( 0, ax.get_xlim()[1] )
  351. #ax.axhline( y=ifaces[SNRidx], xmin=0, xmax=1, color='black', linestyle='dashed' )
  352. if CalcDOI:
  353. ax.axhline( y=DOI, xmin=0, xmax=1, color='black', linestyle='dashed' )
  354. plt.savefig("akvoInversionWC.pdf")
  355. plt.legend()
  356. # Report results into a text file
  357. fr = pd.DataFrame( INV, columns=T2Bins ) #[0:-1] )
  358. fr.insert(0, "layer top", ifaces[0:-1] )
  359. fr.insert(1, "layer bottom", ifaces[1::] )
  360. fr.insert(2, "NMR total water", np.sum(INV, axis=1) )
  361. fr.insert(3, "NMR bound water", bwater )
  362. fr.insert(4, "Layer SNR", SNR )
  363. if CalcDOI:
  364. fr.insert(5, "Resolution", DOIMetric )
  365. fr.to_csv("akvoInversion.csv")
  366. #fr.to_excel("akvoInversion.xlsx")
  367. plt.show()
  368. if __name__ == "__main__":
  369. main()