diff --git a/CHANGELOG.md b/CHANGELOG.md
index 676b4b7..125d4b2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,21 @@
# Changelog
+## [0.3.0] - 2024-11-27
+
+### Added
+- New sub GUI *2DInv* in **NUCLEUSinv** for 2D T1-T2 inversion (works so far for HELIOS, DART and JAVELIN (Vista Clara) and RockCoreAnalyzer (Magritek) data)
+- New import routines in **NUCLEUSinv** for the NMR Dart Logging tool
+- New *Extra* menu entry in **NUCLEUSinv** to find duplicate NMR signals in the file list
+- New fitting options in the *PhaseView* sub GUI of **NUCLEUSinv**
+
+### Changed
+- Unified the appearance along several sub GUIs for consistency
+- Updated the wait bar increments for very long batch runs
+- When *HELIOS* data is imported, the user can now decide if several files should be stacked together
+
+### Fixed
+- Fixed a bug when importing data and the Statistics Toolbox is not available
+
## [0.2.1] - 2024-02-11
### Added
@@ -215,6 +231,7 @@
Initial Version
+[0.3.0]: https://github.com/ThoHiller/nmr-nucleus/compare/v.0.2.1...v.0.3.0
[0.2.1]: https://github.com/ThoHiller/nmr-nucleus/compare/v.0.2.0...v.0.2.1
[0.2.0]: https://github.com/ThoHiller/nmr-nucleus/compare/v.0.1.14...v.0.2.0
[0.1.14]: https://github.com/ThoHiller/nmr-nucleus/compare/v.0.1.13...v.0.1.14
diff --git a/NUCLEUSinv/NUCLEUSinv.m b/NUCLEUSinv/NUCLEUSinv.m
index 3e75c63..674a4cc 100644
--- a/NUCLEUSinv/NUCLEUSinv.m
+++ b/NUCLEUSinv/NUCLEUSinv.m
@@ -38,8 +38,8 @@
if ~isempty(h0); close(h0); end
%% GUI 'header' info and defaults
-myui.version = '0.2.1';
-myui.date = '11.02.2024';
+myui.version = '0.3.0';
+myui.date = '27.11.2024';
myui.author = {'Stephan Costabel','Thomas Hiller'};
myui.email = 'thomas.hiller[at]bgr.de';
myui.fontsize = 10;
@@ -68,6 +68,7 @@
data.uncert = defaults.uncert;
data.calib = defaults.calib;
data.invjoint = defaults.invjoint;
+data.inv2D = defaults.inv2D;
data.pressure = defaults.pressure;
gui.myui = myui;
diff --git a/NUCLEUSinv/NUCLEUSinv_createMenus.m b/NUCLEUSinv/NUCLEUSinv_createMenus.m
index 465e9a4..7a3658e 100644
--- a/NUCLEUSinv/NUCLEUSinv_createMenus.m
+++ b/NUCLEUSinv/NUCLEUSinv_createMenus.m
@@ -48,88 +48,108 @@
gui.menu.file_import_lab = uimenu(gui.menu.file_import,...
'Label','Lab');
-% 1.1.1.1 BAM
+% 1.1.1.1 AARHUS
+gui.menu.file_import_lab_aarhus = uimenu(gui.menu.file_import_lab,...
+ 'Label','AARHUS');
+% 1.1.1.1.1 AARHUS Dart T1T2
+gui.menu.file_import_lab_aarhus_dartT1T2 = uimenu(gui.menu.file_import_lab_aarhus,...
+ 'Label','Dart T1T2','Tag','Lab','Callback',@onMenuImport);
+% 1.1.1.1.2 AARHUS Dart T2 logging
+gui.menu.file_import_lab_aarhus_dartT2 = uimenu(gui.menu.file_import_lab_aarhus,...
+ 'Label','Dart T2 logging','Tag','Lab','Callback',@onMenuImport);
+
+% 1.1.1.2 BAM
gui.menu.file_import_lab_bam = uimenu(gui.menu.file_import_lab,...
'Label','BAM');
-% 1.1.1.1.1 BAM TOM
+% 1.1.1.2.1 BAM TOM
gui.menu.file_import_lab_bam_tom = uimenu(gui.menu.file_import_lab_bam,...
'Label','BAM TOM','Tag','Lab','Callback',@onMenuImport);
-% 1.1.1.2 BGR
+% 1.1.1.3 BGR
gui.menu.file_import_lab_bgr = uimenu(gui.menu.file_import_lab,...
'Label','BGR');
-% 1.1.1.2.1 BGR std
+% 1.1.1.3.1 BGR std
gui.menu.file_import_lab_bgr_std = uimenu(gui.menu.file_import_lab_bgr,...
'Label','BGR std','Tag','Lab','Callback',@onMenuImport);
-% 1.1.1.2.2 BGR mat
+% 1.1.1.3.2 BGR mat
gui.menu.file_import_lab_bgr_mat = uimenu(gui.menu.file_import_lab_bgr,...
'Label','BGR mat','Tag','Lab','Callback',@onMenuImport);
-% 1.1.1.2.3 Mouse CPMG data, single data subfolders from CPMG
+% 1.1.1.3.3 Mouse CPMG data, single data subfolders from CPMG
gui.menu.file_import_lab_bgr_mouse_cpmg = uimenu(gui.menu.file_import_lab_bgr,...
'Label','MouseCPMG','Tag','Lab','Callback',@onMenuImport);
-% 1.1.1.2.4 Mouse plus Lift, all data subfolders from t1test,...
+% 1.1.1.3.4 Mouse plus Lift, all data subfolders from t1test,...
% cpmgfastautotest, or (old Prospa Versions) cpmgfastauto
gui.menu.file_import_lab_bgr_mouse_lift = uimenu(gui.menu.file_import_lab_bgr,...
'Label','MouseLift','Tag','Lab','Callback',@onMenuImport);
-% 1.1.1.2.5 Helios CPMG standard data, single subfolders with individual data files
+% 1.1.1.3.5 Helios CPMG standard data, single subfolders with individual data files
gui.menu.file_import_lab_bgr_helios_cpmg = uimenu(gui.menu.file_import_lab_bgr,...
'Label','HeliosCPMG','Tag','Lab','Callback',@onMenuImport);
-% 1.1.1.2.6 Helios series of CPMG data, several files of a series in the target
+% 1.1.1.3.6 Helios series of CPMG data, several files of a series in the target
% folder, as used e.g. for T1 measurements
gui.menu.file_import_lab_bgr_helios_series = uimenu(gui.menu.file_import_lab_bgr,...
'Label','HeliosSeries','Tag','Lab','Callback',@onMenuImport);
-% 1.1.1.3 LIAG
+% 1.1.1.4 LIAG
gui.menu.file_import_lab_liag = uimenu(gui.menu.file_import_lab,...
'Label','LIAG');
-% 1.1.1.3.1 LIAG
+% 1.1.1.4.1 LIAG
gui.menu.file_import_lab_liag_single = uimenu(gui.menu.file_import_lab_liag,...
'Label','LIAG single','Tag','Lab','Callback',@onMenuImport);
-% 1.1.1.3.2 LIAG
+% 1.1.1.4.2 LIAG
gui.menu.file_import_lab_liag_project = uimenu(gui.menu.file_import_lab_liag,...
'Label','LIAG from project','Tag','Lab','Callback',@onMenuImport);
-% 1.1.1.3.3 LIAG
+% 1.1.1.4.3 LIAG
gui.menu.file_import_lab_liag_core = uimenu(gui.menu.file_import_lab_liag,...
'Label','LIAG core','Tag','Lab','Callback',@onMenuImport);
-% 1.1.1.4 RWTH
+% 1.1.1.5 RUTGERS
+gui.menu.file_import_lab_rutgers = uimenu(gui.menu.file_import_lab,...
+ 'Label','RUTGERS');
+% 1.1.1.5.1 RUTGERS
+gui.menu.file_import_lab_rutgers_T1T2 = uimenu(gui.menu.file_import_lab_rutgers,...
+ 'Label','RoCA T1T2','Tag','Lab','Callback',@onMenuImport);
+
+% 1.1.1.6 RWTH
gui.menu.file_import_lab_rwth = uimenu(gui.menu.file_import_lab,...
'Label','RWTH');
-% 1.1.1.4.1 IBAC
+% 1.1.1.6.1 IBAC
gui.menu.file_import_lab_ibac = uimenu(gui.menu.file_import_lab_rwth,...
'Label','IBAC');
-% 1.1.1.4.1.1 IBAC
+% 1.1.1.6.1.1 IBAC
gui.menu.file_import_lab_ibac_pm5 = uimenu(gui.menu.file_import_lab_ibac,...
'Label','PM5','Tag','Lab','Callback',@onMenuImport);
-% 1.1.1.4.1.2 IBAC
+% 1.1.1.6.1.2 IBAC
gui.menu.file_import_lab_ibac_pm25 = uimenu(gui.menu.file_import_lab_ibac,...
'Label','PM25','Tag','Lab','Callback',@onMenuImport);
-% 1.1.1.4.2 GGE
+% 1.1.1.6.2 GGE
gui.menu.file_import_lab_gge = uimenu(gui.menu.file_import_lab_rwth,...
'Label','GGE');
-% 1.1.1.4.2.1 GGE ascii
+% 1.1.1.6.2.1 GGE ascii
gui.menu.file_import_lab_gge_ascii = uimenu(gui.menu.file_import_lab_gge,...
'Label','GGE ascii','Tag','Lab','Callback',@onMenuImport);
-% 1.1.1.4.2.2 GGE field
+% 1.1.1.6.2.2 GGE field
gui.menu.file_import_lab_gge_field = uimenu(gui.menu.file_import_lab_gge,...
'Label','GGE field','Tag','Lab','Callback',@onMenuImport);
-% 1.1.1.4.2.3 GGE Dart
+% 1.1.1.6.2.3 GGE Dart
gui.menu.file_import_lab_gge_dart = uimenu(gui.menu.file_import_lab_gge,...
'Label','GGE Dart','Tag','Lab','Callback',@onMenuImport);
-% 1.1.1.5 OTHER
+% 1.1.1.7 OTHER
gui.menu.file_import_lab_other = uimenu(gui.menu.file_import_lab,...
'Label','OTHER');
-% 1.1.1.5.1 CoreLab ascii
+% 1.1.1.7.1 CoreLab ascii
gui.menu.file_import_lab_corelab = uimenu(gui.menu.file_import_lab_other,...
'Label','CoreLab ascii','Tag','Lab','Callback',@onMenuImport);
-% 1.1.1.5.2 MOUSE
+% 1.1.1.7.2 MOUSE
gui.menu.file_import_lab_mouse = uimenu(gui.menu.file_import_lab_other,...
'Label','MOUSE','Tag','Lab','Callback',@onMenuImport);
-% 1.1.1.5.3 DART (University of Vienna)
+% 1.1.1.7.3 DART (University of Vienna)
gui.menu.file_import_lab_dart = uimenu(gui.menu.file_import_lab_other,...
'Label','DART','Tag','Lab','Callback',@onMenuImport);
+% 1.1.1.7.4 DART (incl. Burst echoes)
+gui.menu.file_import_lab_dartburst = uimenu(gui.menu.file_import_lab_other,...
+ 'Label','DART (+Burst)','Tag','Lab','Callback',@onMenuImport);
% 1.1.2 Ascii
gui.menu.file_import_ascii = uimenu(gui.menu.file_import,...
@@ -167,6 +187,9 @@
% 1.1.5.2 NUCLEUSmod from GUI
gui.menu.file_import_nmrmod_gui = uimenu(gui.menu.file_import_nmrmod,....
'Label','GUI','Tag','NUCLEUSmod','Callback',@onMenuImport);
+% 1.1.5.3 NUCLEUSmod from 2D GUI
+gui.menu.file_import_nmrmod_gui2d = uimenu(gui.menu.file_import_nmrmod,....
+ 'Label','2D GUI','Tag','NUCLEUSmod2d','Callback',@onMenuImport);
% 1.2 Export
gui.menu.file_export = uimenu(gui.menu.file,...
@@ -318,6 +341,9 @@
% 2.9 UncertaintyVIEW GUI
gui.menu.extra_uncert = uimenu(gui.menu.view,...
'Label','UncertView GUI','Enable','off','Callback',@onMenuSubGUIs);
+% 2.9 2DInv GUI
+gui.menu.extra_T1T2map = uimenu(gui.menu.view,...
+ 'Label','2DInv GUI','Enable','off','Callback',@onMenuSubGUIs);
%% 3. Extras
gui.menu.extra = uimenu(gui.figh,...
@@ -374,6 +400,11 @@
'Label','Surface relaxivity bounds','Enable','off',...
'Callback',@onMenuExtraRhoBounds);
+% 3.6 find duplicate data
+gui.menu.extra_find_duplicates = uimenu(gui.menu.extra,...
+ 'Label','Find duplicate signals','Enable','on',...
+ 'Callback',@onMenuExtraFindDuplicates);
+
%% 4. Color theme
gui.menu.color_theme = uimenu(gui.figh,...
diff --git a/NUCLEUSinv/NUCLEUSinv_createPanelInversionStd.m b/NUCLEUSinv/NUCLEUSinv_createPanelInversionStd.m
index ce5aab7..966a819 100644
--- a/NUCLEUSinv/NUCLEUSinv_createPanelInversionStd.m
+++ b/NUCLEUSinv/NUCLEUSinv_createPanelInversionStd.m
@@ -184,7 +184,7 @@
gui.push_handles.uncert = uicontrol('Parent',gui.panels.invstd.HBox7,'Enable','off',...
'String','CALC.','FontSize',myui.fontsize,'BackGroundColor','g',...
'Tag','uncert','UserData',1,'Callback',@onPushRun);
-set(gui.panels.invstd.HBox7,'Widths',[200 -1 -1]);
+set(gui.panels.invstd.HBox7,'Widths',[200 -1 -2]);
%% Java Hack to adjust vertical alignment of text fields
jh = findjobj(gui.text_handles.invstd_InvType);
diff --git a/NUCLEUSinv/NUCLEUSinv_loadDefaults.m b/NUCLEUSinv/NUCLEUSinv_loadDefaults.m
index 4c53dc3..a3b8047 100644
--- a/NUCLEUSinv/NUCLEUSinv_loadDefaults.m
+++ b/NUCLEUSinv/NUCLEUSinv_loadDefaults.m
@@ -195,6 +195,65 @@
% corresponding scale factors - 1 | 1e-3 | 1e-6 | 1e-5
out.pressure.unitfac = 1;
+%% 2D inversion GUI settings
+% system / fluid properties
+% diffusion coefficient [m²/s]
+out.inv2D.prop.D = 2.025e-9;
+% gradient [T/m]
+out.inv2D.prop.G0 = 0;
+% echo time [s]
+out.inv2D.prop.te = 200e-6;
+% start echo
+out.inv2D.prop.first = 1;
+% last echo
+out.inv2D.prop.last = 1;
+
+% inversion settings
+% IR/SR factor
+out.inv2D.inv.T1IRfac = 2;
+% IR kernel type (1 or 2)
+out.inv2D.inv.IRtype = 1;
+% T1 range minimum [s]
+out.inv2D.inv.T1min = 1e-4;
+% T1 range maximum [s]
+out.inv2D.inv.T1max = 10;
+% T1 number of points in range
+out.inv2D.inv.T1N = 51;
+% T2 range minimum [s]
+out.inv2D.inv.T2min = 1e-4;
+% T2 range maximum [s]
+out.inv2D.inv.T2max = 10;
+% T2 number of points in range
+out.inv2D.inv.T2N = 51;
+% T1 regularization parameter lambda
+out.inv2D.inv.T1lambda = 5;
+% T1 order of smoothness constraint
+out.inv2D.inv.T1order = 1;
+% T2 regularization parameter lambda
+out.inv2D.inv.T2lambda = 2;
+% T2 order of smoothness constraint
+out.inv2D.inv.T2order = 1;
+
+% information settings / properties
+% T1 minimum [s]
+out.inv2D.info.T1min = 1e-3;
+% T1 maximum [s]
+out.inv2D.info.T1max = 1;
+% T2 minimum [s]
+out.inv2D.info.T2min = 1e-3;
+% T2 minimum [s]
+out.inv2D.info.T2max = 1;
+% initial amplitude E0 [a.u.]
+out.inv2D.info.E0 = 0;
+% T1 log mean time
+out.inv2D.info.T1tlgm = 0;
+% T2 log mean time
+out.inv2D.info.T2tlgm = 0;
+% T1 maximum time
+out.inv2D.info.T1tmax = 0;
+% T2 maximum time
+out.inv2D.info.T2tmax = 0;
+
return
%------------- END OF CODE --------------
diff --git a/NUCLEUSmod/NUCLEUSmod.m b/NUCLEUSmod/NUCLEUSmod.m
index 02e53b5..ccab327 100644
--- a/NUCLEUSmod/NUCLEUSmod.m
+++ b/NUCLEUSmod/NUCLEUSmod.m
@@ -41,8 +41,8 @@
if ~isempty(h0); close(h0); end
%% GUI 'header' info and defaults
-myui.version = '0.2.1';
-myui.date = '11.02.2024';
+myui.version = '0.3.0';
+myui.date = '27.11.2024';
myui.author = {'Stephan Costabel','Thomas Hiller'};
myui.email = 'thomas.hiller[at]bgr.de';
myui.fontsize = 10;
@@ -65,6 +65,7 @@
data.geometry = defaults.geometry;
data.pressure = defaults.pressure;
data.nmr = defaults.nmr;
+data.mod2D = defaults.mod2D;
gui.myui = myui;
% save the data struct within the GUI
diff --git a/NUCLEUSmod/NUCLEUSmod_createMenus.m b/NUCLEUSmod/NUCLEUSmod_createMenus.m
index 34b7dd8..f20907a 100644
--- a/NUCLEUSmod/NUCLEUSmod_createMenus.m
+++ b/NUCLEUSmod/NUCLEUSmod_createMenus.m
@@ -99,9 +99,13 @@
% 2.2 Figure Toolbar
gui.menu.view_toolbar = uimenu(gui.menu.view,...
'Label','Figure Toolbar','Callback',@onMenuView);
-% 2.3 hydraulic conductivity
+% 2.3 2D modelling GUI
+gui.menu.view_2dmod = uimenu(gui.menu.view,...
+ 'Label','2DMod GUI','Separator','on','Enable','on',...
+ 'Callback',@onMenuSubGUIs);
+% 2.4 hydraulic conductivity
gui.menu.view_conduct = uimenu(gui.menu.view,...
- 'Label','ConductView GUI','Separator','on','Enable','off',...
+ 'Label','ConductView GUI','Enable','off',...
'Callback',@onMenuSubGUIs);
%% 3. Color theme
diff --git a/NUCLEUSmod/NUCLEUSmod_createPanelNMR.m b/NUCLEUSmod/NUCLEUSmod_createPanelNMR.m
index 4c707ad..95b2d2a 100644
--- a/NUCLEUSmod/NUCLEUSmod_createPanelNMR.m
+++ b/NUCLEUSmod/NUCLEUSmod_createPanelNMR.m
@@ -114,7 +114,6 @@
set(gui.panels.nmr.HBox3,'Widths',[200 -1]);
%% noise & porosity
-
tstr = ['NMR data noise method.
',...
'A noise level will be used globally for all NMR signals.
',...
'A signal-to-ratio will be used individually on every single NMR signal.
',...
diff --git a/NUCLEUSmod/NUCLEUSmod_loadDefaults.m b/NUCLEUSmod/NUCLEUSmod_loadDefaults.m
index 28050d7..eab5cb5 100644
--- a/NUCLEUSmod/NUCLEUSmod_loadDefaults.m
+++ b/NUCLEUSmod/NUCLEUSmod_loadDefaults.m
@@ -92,6 +92,57 @@
% use linear y-axes as default (log=1, lin=2)
out.nmr.logliny = 2;
+%% 2D settings
+% system / fluid properties
+% diffusion coefficient [m²/s]
+out.mod2D.prop.D = 2.025e-9;
+% gradient [T/m]
+out.mod2D.prop.G0 = 0;
+% echo time [s]
+out.mod2D.prop.te = 0;
+% bulk relaxation [s]
+out.mod2D.prop.Tbulk = 1e6;
+
+% model space settings
+% 2D type
+out.mod2D.mod.type = 'T1T2';
+% T1 range minimum [s]
+out.mod2D.mod.T1min = 1e-4;
+% T1 range maximum [s]
+out.mod2D.mod.T1max = 10;
+% T1 number of points in range
+out.mod2D.mod.T1N = 151;
+% T2 range minimum [s]
+out.mod2D.mod.T2min = 1e-4;
+% T2 range maximum [s]
+out.mod2D.mod.T2max = 10;
+% T2 number of points in range
+out.mod2D.mod.T2N = 151;
+
+% number of 2D distribution
+out.mod2D.mod.Ndist = 3;
+% center point of 2D distribution
+out.mod2D.mod.mu = [1 1;0.01 0.03;0.5 0.005];
+% covariance matrix of 2D distribution
+out.mod2D.mod.sigma{1} = [0.1 0;0 0.1];
+out.mod2D.mod.sigma{2} = [0.2 0.3;0 0.2];
+out.mod2D.mod.sigma{3} = [0.3 0;0 0.01];
+% amplitude of 2D distribution
+out.mod2D.mod.amp = [1 1 1];
+
+% NMR data
+out.mod2D.nmr.T1IRfac = 2;
+out.mod2D.nmr.IRtype = 1;
+out.mod2D.nmr.T1trmin = 1e-3;
+out.mod2D.nmr.T1trmax = 1;
+out.mod2D.nmr.T1trN = 21;
+out.mod2D.nmr.T2te = 200e-6;
+out.mod2D.nmr.T2teN = 500;
+% noise creation type 'level' or 'SNR'
+out.mod2D.nmr.noisetype = 'level';
+% noise level [0:1] or SNR [-]
+out.mod2D.nmr.noise = 0;
+
return
%------------- END OF CODE --------------
diff --git a/README.md b/README.md
index 0b71eaa..100aae5 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# NUCLEUS
-
+
modeling and i**N**version of n**UCL**ear magnetic r**E**sonance data with ang**U**lar pore**S**
@@ -31,20 +31,30 @@ modeling and i**N**version of n**UCL**ear magnetic r**E**sonance data with ang**
1. Generate pore size distributions (PSD) that can have a cylindrical, rectangular or polygonal cross section
2. Calculate a capillary pressure saturation curve (CPSC) for the PSD by applying a range of non-zero air pressures (the capillaries are assumed to be water filled and completely water-wet); different saturations for drainage and imbibition conditions are considered
3. Based on the different saturation levels along the CPSC, calculate the corresponding geometry-dependent forward NMR signals
+4. 2D forward modeling of T1-T2 data
+
+|
![nucleusmod](images/nucleusmod_gui.png)
| ![nucleusmod2d](images/2dmod_gui.png)
|
+|:-------------------------------------------------------------------:|:-------------------------------------------------------------:|
+| NUCLEUSmod | NUCLEUSmod - 2D (T1-T2) |
-
### NUCLEUSinv basic features
1. Can import **NUCLEUSmod** data (directly from the open GUI or from a saved session file) and a wide range of different laboratory NMR data files (please contact me if you need a specific import routine for your data)
2. Expert mode for more features (Standard mode has basic settings which should be sufficient for most users)
-3. Simple pre-processing of NMR signals (cutting, gating, normalizing)
+3. Simple pre-processing of NMR signals (cutting, gating, normalizing, phasing)
4. Different inversion options to process NMR data (e.g. mono-exponential fit, bi-exponential fit, multi-exponential fit) and estimate the uncertainty of the resulting relaxation time distributions (RTDs)
5. Different regularization options for multi-exponential fitting (e.g. manual, L-curve, SVD tools)
6. Joint inversion of NMR and CPS data to directly infer a PSD (non-linear inversion of surface relaxivity and PSD)
+7. 2D inversion of T1-T2 data
+
+| ![nucleusinv](images/nucleusinv_gui.png)
| ![nucleusinv2d](images/2dinv_gui.png)
|
+|:-------------------------------------------------------------------:|:--------------------------------------------------------------:|
+| NUCLEUSinv | NUCLEUSinv - 2D (T1-T2) |
+| ![uncert](images/uncertview_gui.png)
| ![phaseview](images/phaseview_gui.png)
|
+| NUCLEUSinv - RTD Uncertainty View | NUCLEUSinv - Phase View |
-
- - -
@@ -53,7 +63,7 @@ modeling and i**N**version of n**UCL**ear magnetic r**E**sonance data with ang**
In order to work properly you need to meet the following requirements:
-1. The [Mathworks](https://www.mathworks.com) MATLABTM software development environment (tested with R2014b and newer)
+1. The [Mathworks](https://www.mathworks.com) MATLABTM software development environment (tested with R2016b and newer)
- The Optimization toolbox (optional)
- The Statistics toolbox (optional)
2. The GUI Layout Toolbox (get it from [FEX](https://de.mathworks.com/matlabcentral/fileexchange/47982-gui-layout-toolbox)) (required)
@@ -62,6 +72,7 @@ In order to work properly you need to meet the following requirements:
5. `fminsearchbnd` (get it from [FEX](https://de.mathworks.com/matlabcentral/fileexchange/8277-fminsearchbnd-fminsearchcon)) (required)
6. `dynamicDateTicks` (get it from [FEX](https://de.mathworks.com/matlabcentral/fileexchange/27075-intelligent-dynamic-date-ticks)) (optional)
7. `kde` kernel density estimator (get it from [FEX](https://de.mathworks.com/matlabcentral/fileexchange/14034-kernel-density-estimator)) (optional; not needed for R2023b and newer)
+8. `imagescnan` (get it from [FEX](https://de.mathworks.com/matlabcentral/fileexchange/20516-imagescnan-m-v2-1-aug-2009)) (optional; only needed for the 2D inversion)
If you do not have the Optimization or Statistics toolboxes then not all features are available (especially parts of the joint inversion). However, the general functionality of obtaining relaxation time distributions (RTDs) form NMR relaxometry data is of course working.
@@ -123,17 +134,20 @@ In no particular order and without guarantee that it will ever happen :-) :
## Cite as
If you use NUCLEUS for your research, please cite it as:
-Thomas Hiller. (2024, Feb 11). ThoHiller/nmr-nucleus: v0.2.1 (Version v0.2.1). Zenodo. [https://doi.org/10.5281/zenodo.4022195]
+Thomas Hiller. (2024, Nov 27). ThoHiller/nmr-nucleus: v0.3.0 (Version v0.3.0). Zenodo. [https://doi.org/10.5281/zenodo.4022195]
Note: Even though the version number might change due to updates, this DOI is permanent (represents all versions) and always links to the latest version.
## References
-1. Costabel, S., Hiller, T. and Houben, G. "Nuclear magnetic resonance at the laboratory and field scale as a tool for detecting redox fronts in aquifers", *GEOPHYSICS*, **88**(2), 2023, KS13-KS25, [DOI](https://doi.org/10.1190/geo2022-0127.1)
-2. Costabel, S., Hiller, T., Dlugosch, R., Kruschwitz, S. and Müller-Petke, M. "Evaluation of single-sided nuclear magnetic resonance technology for usage in geosciences", *Measurement Science and Technology*, **34**(1), 2023, 015112, [DOI](https://dx.doi.org/10.1088/1361-6501/ac9800)
-3. Hiller, T., Costabel, S., Radic, T., Dlugosch, R. and Müller-Petke, M. "Feasibility study on prepolarized surface nuclear magnetic resonance for soil moisture measurements", *Vadose Zone Journal*, **20**(5), 2021, e20138, [DOI](https://doi.org/10.1002/vzj2.20138)
-4. Costabel, S. and Hiller, T., "Soil hydraulic interpretation of nuclear magnetic resonance measurements based on circular and triangular capillary models", *Vadose Zone Journal*, **20**(2), 2021, e20104, [DOI](https://doi.org/10.1002/vzj2.20104)
-5. Hiller, T. and Klitzsch, N., "Joint inversion of nuclear magnetic resonance data from partially saturated rocks using a triangular pore model", *GEOPHYSICS*, **83**(4), JM15-JM28, 2018, [DOI](https://doi.org/10.1190/geo2017-0697.1)
+1. Lorenzoni,R., Cunningham, P., Fritsch, T., Schmidt, W., Kruschwitz, S. and Bruno, G. "Microstructure analysis of cement-biochar composites", *Materials and Structures*, **57**, 2024, 175, [DOI](https://doi.org/10.1617/s11527-024-02452-5)
+2. Kruschwitz, S., Munsch, S., Telong, M., Schmidt, W., Bintz, T., Fladt, M. and Stelzner, L., "The NMR core analyzing tomograph: a multi-functional tool for non-destructive testing of building materials", *Magnetic Resonance Letters*. **3**(3), 2023, 207-219, [DOI](https://doi.org/10.1016/j.mrl.2023.03.004)
+3. Costabel, S., Hiller, T. and Houben, G. "Nuclear magnetic resonance at the laboratory and field scale as a tool for detecting redox fronts in aquifers", *GEOPHYSICS*, **88**(2), 2023, KS13-KS25, [DOI](https://doi.org/10.1190/geo2022-0127.1)
+4. Costabel, S., Hiller, T., Dlugosch, R., Kruschwitz, S. and Müller-Petke, M. "Evaluation of single-sided nuclear magnetic resonance technology for usage in geosciences", *Measurement Science and Technology*, **34**(1), 2023, 015112, [DOI](https://dx.doi.org/10.1088/1361-6501/ac9800)
+5. Munsch, S., Bintz, T., Heyn, R., Hirsch, H., Grunewald, J. and Kruschwitz, S., "Detailed investigation of capillary active insulation materials by 1H nuclear magnetic resonance (NMR) and thermogravimetric drying", International Symposium on Non-Destructive Testing in Civil Engineering (NDT-CE 2022), 16-18 August 2022, Zurich, Switzerland, *e-Journal of Nondestructive Testing*, **27**(9), [DOI](https://doi.org/10.58286/27205)
+6. Hiller, T., Costabel, S., Radic, T., Dlugosch, R. and Müller-Petke, M. "Feasibility study on prepolarized surface nuclear magnetic resonance for soil moisture measurements", *Vadose Zone Journal*, **20**(5), 2021, e20138, [DOI](https://doi.org/10.1002/vzj2.20138)
+7. Costabel, S. and Hiller, T., "Soil hydraulic interpretation of nuclear magnetic resonance measurements based on circular and triangular capillary models", *Vadose Zone Journal*, **20**(2), 2021, e20104, [DOI](https://doi.org/10.1002/vzj2.20104)
+8. Hiller, T. and Klitzsch, N., "Joint inversion of nuclear magnetic resonance data from partially saturated rocks using a triangular pore model", *GEOPHYSICS*, **83**(4), JM15-JM28, 2018, [DOI](https://doi.org/10.1190/geo2017-0697.1)
- - -
diff --git a/callbacks/contextmenus/onContextAxisLogLin.m b/callbacks/contextmenus/onContextAxisLogLin.m
index 5911082..c7f170a 100644
--- a/callbacks/contextmenus/onContextAxisLogLin.m
+++ b/callbacks/contextmenus/onContextAxisLogLin.m
@@ -38,11 +38,9 @@ function onContextAxisLogLin(src,~)
% get the label of the context menu
label = get(src,'Label');
-% get the tag of the context menu
-tag = get(src,'Tag');
% change the label depending on the current status
-if ~isempty(strfind(label,'log')) % current axis is lin -> switch to log
+if contains(label,'log') % current axis is lin -> switch to log
label = strrep(label,'log','lin');
else % current axis is log -> switch to lin
label = strrep(label,'lin','log');
diff --git a/callbacks/listboxes/onListboxData.m b/callbacks/listboxes/onListboxData.m
index d2cad8d..061f8a5 100644
--- a/callbacks/listboxes/onListboxData.m
+++ b/callbacks/listboxes/onListboxData.m
@@ -192,6 +192,8 @@ function onListboxData(src,~)
% reset all RUN buttons
set(gui.push_handles.invstd_run,'String','RUN',...
'BackgroundColor','g','Enable','on','Callback',@onPushRun);
+ set(gui.push_handles.uncert,'String','CALC.',...
+ 'BackgroundColor','g','Enable','on','Callback',@onPushRun);
set(gui.push_handles.invjoint_run,'String','RUN',...
'BackgroundColor','g','Enable','on','Callback',@onPushRun);
diff --git a/callbacks/menus/onMenuExpert.m b/callbacks/menus/onMenuExpert.m
index 53273b3..0b9b80e 100644
--- a/callbacks/menus/onMenuExpert.m
+++ b/callbacks/menus/onMenuExpert.m
@@ -72,6 +72,12 @@ function onMenuExpert(src,~)
delete(fig_uncert);
end
set(gui.menu.extra_uncert,'Enable','off');
+ % check if the figure is already open
+ fig_T1T2map = findobj('Tag','T1T2MAP');
+ if ~isempty(fig_T1T2map)
+ delete(fig_T1T2map);
+ end
+ set(gui.menu.extra_T1T2map,'Enable','off');
% deactivate solver menu and set to default
onMenuSolver(gui.menu.extra_solver_lsqnonneg);
@@ -109,6 +115,8 @@ function onMenuExpert(src,~)
set(gui.menu.extra_fixedtime,'Enable','on');
% activate Uncertainty View GUI
set(gui.menu.extra_uncert,'Enable','on');
+ % activate T1T2map GUI
+ set(gui.menu.extra_T1T2map,'Enable','on');
% activate solver menu if optimization toolbox is available
switch data.info.has_optim
diff --git a/callbacks/menus/onMenuExtraFindDuplicates.m b/callbacks/menus/onMenuExtraFindDuplicates.m
new file mode 100644
index 0000000..64a204a
--- /dev/null
+++ b/callbacks/menus/onMenuExtraFindDuplicates.m
@@ -0,0 +1,158 @@
+function onMenuExtraFindDuplicates(src,~)
+%onMenuExtraFindDuplicates finds duplicate NMR signals (mostly a HELIOS issue)
+%
+% Syntax:
+% onMenuExtraFindDuplicates(src,~)
+%
+% Inputs:
+% src - handle of the calling object
+%
+% Outputs:
+% none
+%
+% Example:
+% onMenuExtraFindDuplicates(src)
+%
+% Other m-files required:
+% none
+%
+% Subfunctions:
+% none
+%
+% MAT-files required:
+% none
+%
+% See also: NUCLEUSinv
+% Author: see AUTHORS.md
+% email: see AUTHORS.md
+% License: MIT License (at end)
+
+%------------- BEGIN CODE --------------
+
+%% get GUI handle and data
+fig = ancestor(src,'figure','toplevel');
+gui = getappdata(fig,'gui');
+data = getappdata(fig,'data');
+INVdata = getappdata(fig,'INVdata');
+
+hasData = false;
+if isfield(data.import,'NMR')
+ hasData = true;
+end
+
+if hasData
+
+ %% 1. before we do anything, sort the data by date (time)
+ time = zeros(numel(data.import.NMR.filesShort),1);
+ timestamp = zeros(numel(data.import.NMR.filesShort),1);
+ for i = 1:numel(data.import.NMR.filesShort)
+ time(i,1) = data.import.NMR.data{i}.datenum;
+ timestamp(i,1) = data.import.NMR.para{i}.timestamp;
+ end
+ [~,ix] = sort(time);
+ % now apply changes - resort the imported data
+ data.import.NMR.data = {data.import.NMR.data{ix'}}; %#ok<*CCAT1>
+ data.import.NMR.para = {data.import.NMR.para{ix'}};
+ data.import.NMR.files = data.import.NMR.files(ix);
+ data.import.NMR.filesShort = {data.import.NMR.filesShort{ix'}};
+ % the timestamps can also indicate duplicate signals
+ timestamp = timestamp(ix);
+ dts = [0; diff(timestamp)];
+ % and resort possible already stored inversion data
+ INVdata = {INVdata{ix(:)}}';
+ % update the listbox entries
+ shownames = get(gui.listbox_handles.signal,'String');
+ set(gui.listbox_handles.signal,'String',{shownames{ix}});
+ set(gui.listbox_handles.signal,'Value',[],'Max',2,'Min',0);
+
+ %% 2. checking starts at the second signal
+ dup_ids = zeros(1,1);
+ keep_ids = 1:1:numel(data.import.NMR.data);
+ keep_ids = keep_ids(:);
+ c = 0;
+ for i1 = 2:numel(data.import.NMR.data)
+
+ s0 = data.import.NMR.data{i1-1}.signal;
+ s1 = data.import.NMR.data{i1}.signal;
+
+ if s0==s1
+ c = c + 1;
+ dup_ids(c,1) = i1;
+ keep_ids(keep_ids == i1) = [];
+ end
+ end
+ % if we found something, ask what to do
+ if sum(dup_ids) > 0
+ answer = questdlg('What to do with the duplicate signals?',...
+ 'Duplicates Found',...
+ 'Keep & Mark','Delete','Nothing','Delete');
+ switch answer
+ case 'Keep & Mark'
+ shownames = data.import.NMR.filesShort;
+ for i1 = 1:numel(dup_ids)
+ shownames{dup_ids(i1)} = [shownames{dup_ids(i1)},'_dup'];
+ end
+ data.import.NMR.filesShort = shownames;
+ set(gui.listbox_handles.signal,'String',shownames);
+ set(gui.listbox_handles.signal,'Value',[],'Max',2,'Min',0);
+
+ msgbox([num2str(numel(dup_ids)),...
+ ' signals have been marked as duplicate.']);
+
+ case 'Delete'
+ ix = keep_ids;
+ data.import.NMR.data = {data.import.NMR.data{ix'}}; %#ok<*CCAT1>
+ data.import.NMR.para = {data.import.NMR.para{ix'}};
+ data.import.NMR.files = data.import.NMR.files(ix);
+ data.import.NMR.filesShort = {data.import.NMR.filesShort{ix'}};
+ % and resort possible already stored inversion data
+ INVdata = {INVdata{ix(:)}}';
+
+ % update the listbox entries
+ shownames = get(gui.listbox_handles.signal,'String');
+ set(gui.listbox_handles.signal,'String',{shownames{ix}});
+ set(gui.listbox_handles.signal,'Value',[],'Max',2,'Min',0);
+
+ msgbox([num2str(numel(dup_ids)),' signals have been deleted.']);
+
+ case 'Nothing'
+ end
+ else
+ msgbox('No duplicate signals have been found.');
+ end
+
+ % update the GUI data
+ setappdata(fig,'data',data);
+ setappdata(fig,'INVdata',INVdata);
+
+else
+ helpdlg('Nothing to do because there is no data loaded!',...
+ 'onMenuExtraFindDuplicates: Load NMR data first.');
+end
+
+end
+
+%------------- END OF CODE --------------
+
+%% License:
+% MIT License
+%
+% Copyright (c) 2024 Thomas Hiller
+%
+% Permission is hereby granted, free of charge, to any person obtaining a copy
+% of this software and associated documentation files (the "Software"), to deal
+% in the Software without restriction, including without limitation the rights
+% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+% copies of the Software, and to permit persons to whom the Software is
+% furnished to do so, subject to the following conditions:
+%
+% The above copyright notice and this permission notice shall be included in all
+% copies or substantial portions of the Software.
+%
+% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+% SOFTWARE.
\ No newline at end of file
diff --git a/callbacks/menus/onMenuImport.m b/callbacks/menus/onMenuImport.m
index 4114d12..8ba8276 100644
--- a/callbacks/menus/onMenuImport.m
+++ b/callbacks/menus/onMenuImport.m
@@ -54,7 +54,9 @@ function onMenuImport(src,~)
case 'NUCLEUSinv'
importINV2INV(src);
case 'NUCLEUSmod'
- importMOD2INV(src);
+ importMOD2INV(src);
+ case 'NUCLEUSmod2d'
+ importMOD2D2INV(src);
case 'Excel'
importEXCELdata(src);
case 'Ascii'
diff --git a/callbacks/menus/onMenuSubGUIs.m b/callbacks/menus/onMenuSubGUIs.m
index f4bfd9a..edbd2d9 100644
--- a/callbacks/menus/onMenuSubGUIs.m
+++ b/callbacks/menus/onMenuSubGUIs.m
@@ -84,6 +84,16 @@ function onMenuSubGUIs(src,~)
'Cannot continue because there is no data!'},...
'No inversion data.');
end
+ case '2DInv GUI'
+ if isfield(data.import,'T1T2map')
+ Inv2DView(src);
+ else
+ helpdlg({'function: Inv2DView',...
+ 'Cannot continue because there is no data!'},...
+ 'No T1T2 data.');
+ end
+ case '2DMod GUI'
+ Mod2DView(src);
end
end
diff --git a/callbacks/menus/onMenuView.m b/callbacks/menus/onMenuView.m
index 74e949e..80e7341 100644
--- a/callbacks/menus/onMenuView.m
+++ b/callbacks/menus/onMenuView.m
@@ -43,7 +43,7 @@ function onMenuView(src,~)
onoff = lower(get(src,'Checked'));
switch fig_tag
-
+
case 'INV'
switch label
case 'Tooltips'
@@ -60,7 +60,7 @@ function onMenuView(src,~)
% update ini-file
gui.myui.inidata.tooltips = data.info.ToolTips;
gui = makeINIfile(gui,'update');
-
+
case 'Figure Toolbar' % switch on/off the default Figure Toolbar
switch onoff
case 'on' % it it's on, switch it off
@@ -70,7 +70,7 @@ function onMenuView(src,~)
set(gui.menu.view_toolbar,'Checked','on');
viewmenufcn('FigureToolbar');
end
-
+
case 'INFO fields'
switch onoff
case 'on' % it it's on, switch it off
@@ -79,7 +79,7 @@ function onMenuView(src,~)
set(gui.push_handles.info,'String','<');
end
onPushShowHide(gui.push_handles.info);
-
+
case 'CLI Inv. Info'
switch onoff
case 'on' % it it's on, switch it off
@@ -93,7 +93,20 @@ function onMenuView(src,~)
gui.myui.inidata.invinfo = data.info.InvInfo;
gui = makeINIfile(gui,'update');
end
-
+
+ case {'2DINV','PHASEVIEW','UNCERTVIEW','CONDUCT'}
+ switch label
+ case 'Figure Toolbar' % switch on/off the default Figure Toolbar
+ switch onoff
+ case 'on' % it it's on, switch it off
+ set(gui.menu.view_toolbar,'Checked','off');
+ viewmenufcn('FigureToolbar');
+ case 'off'
+ set(gui.menu.view_toolbar,'Checked','on');
+ viewmenufcn('FigureToolbar');
+ end
+ end
+
case 'MOD'
switch label
case 'Tooltips'
@@ -117,6 +130,18 @@ function onMenuView(src,~)
viewmenufcn('FigureToolbar');
end
end
+ case '2DMOD'
+ switch label
+ case 'Figure Toolbar' % switch on/off the default Figure Toolbar
+ switch onoff
+ case 'on' % it it's on, switch it off
+ set(gui.menu.view_toolbar,'Checked','off');
+ viewmenufcn('FigureToolbar');
+ case 'off'
+ set(gui.menu.view_toolbar,'Checked','on');
+ viewmenufcn('FigureToolbar');
+ end
+ end
end
% update GUI data
diff --git a/doc/menu.html b/doc/menu.html
index 51000b8..a03b14a 100644
--- a/doc/menu.html
+++ b/doc/menu.html
@@ -16,7 +16,7 @@ Matlab Index
Matlab Directories
-
+
Generated by m2html © 2005
diff --git a/doc/nucleus/NUCLEUSinv/NUCLEUSinv.html b/doc/nucleus/NUCLEUSinv/NUCLEUSinv.html
index ed81de1..5890d3d 100644
--- a/doc/nucleus/NUCLEUSinv/NUCLEUSinv.html
+++ b/doc/nucleus/NUCLEUSinv/NUCLEUSinv.html
@@ -111,8 +111,8 @@