From 26649c78fed779a80cf3da5d6a9a61af51d04bd7 Mon Sep 17 00:00:00 2001 From: thorstone25 Date: Tue, 6 Aug 2024 01:00:11 -0700 Subject: [PATCH 01/14] Update README.md --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c88a705..b20a412 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,10 @@ This package can readily be used to develop new transducer array designs by spec - Documentation via `help` and `doc` -## Installation +## Installation +### Prerequisites +QUPS requires the [Signal Processing Toolbox](https://www.mathworks.com/products/signal.html) and the [Parallel Computing Toolbox](https://www.mathworks.com/products/parallel-computing.html) to be installed. + ### MATLAB R2023b+ & git Starting in MATLAB R2023b+, QUPS and most of it's extension packages can be installed from within MATLAB via [buildtool](https://www.mathworks.com/help/matlab/ref/buildtool.html) if you have setup [git for MATLAB](https://www.mathworks.com/help/matlab/matlab_prog/set-up-git-source-control.html). 1. Install qups @@ -61,7 +64,7 @@ buildtool test ``` ### Legacy Installation -If the above procedure does not work for you, you can manually download and install each [extension](##Extensions). +You can manually download and install each [extension](##Extensions) separately. 1. Download the desired extension packages into a folder adjacent to the "qups" folder e.g. if qups is located at `/path/to/my/qups`, kWave should be downloaded to an adjacent folder `/path/to/my/kWave`. 2. Create a MATLAB [Project](https://www.mathworks.com/help/matlab/matlab_prog/create-projects.html) and add the root folder of the extension to the path e.g. `/path/to/my/kWave`. From 78297eacb9719404544bfdd3f2c8a6e6b3b1ece2 Mon Sep 17 00:00:00 2001 From: Thurston Date: Tue, 6 Aug 2024 01:13:09 -0700 Subject: [PATCH 02/14] Bug fix for #37 --- buildfile.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildfile.m b/buildfile.m index b3007ae..6efda46 100644 --- a/buildfile.m +++ b/buildfile.m @@ -81,7 +81,7 @@ mfls = dir(fullfile(base, "src", "**", "msfm*.c")); [mfls, nms] = deal(string(fullfile({mfls.folder}, {mfls.name})), string({mfls.name})); -ofls = replace(fullfile(base, "bin", nms), '.c', "."+mexext()); +ofls = fullfile(base, "bin", replace(nms, ".c" + lineBoundary("end"), "."+mexext())); tnm = "compile_mex_"+extractBefore(nms,'.c'); for i = 1:numel(mfls) plan(tnm(i)) = matlab.buildtool.tasks.MexTask(mfls(i), fileparts(ofls(i)), ... From e985700fac82f25b216ae9f9fd293882067d41d0 Mon Sep 17 00:00:00 2001 From: Thurston Date: Tue, 6 Aug 2024 02:01:40 -0700 Subject: [PATCH 03/14] buildfile : add 'compatability' task to report installed toolboxes vs. requirements. --- buildfile.m | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/buildfile.m b/buildfile.m index 6efda46..f1bb354 100644 --- a/buildfile.m +++ b/buildfile.m @@ -49,7 +49,6 @@ "Dependencies", "patch_"+[ext_nms([2 4 3])] ... ); - %% Testing and Artifacts % source files (folders) src = ["kern","src","utils"] + filesep; @@ -72,6 +71,7 @@ "Tag","syntax","SourceFiles",src ... ,CodeCoverageResults=fullfile("build","code-coverage-brief",["coverage.xml" "html/index.html"]) ... ,TestResults="build/test-results-brief/report.html" ... + ,Dependencies="compatability"... ); %% Add compilation dependencies @@ -417,3 +417,18 @@ function benchmarkTask(context) writelines(txt, ofl); end + +function compatabilityTask(context) +% Check for required toolboxes +req = ["Signal Processing", "Parallel Computing"]; +rec = ["Image Processing"]; +sug = reshape(string.empty,1,0); +rep = "The following packages are " + ["required", "recommended", "suggested"] ... + + ": " + sprintf('\t') + cellfun(@(s)join(s,", ",2),{req,rec,sug}); +arrayfun(@disp, rep(~ismissing(rep))); +vn = string({ver().Name}); +i = ismember(req + " Toolbox", vn); +assert(all(i), "The following toolboxes are required, but not installed:"+newline+join(req(~i)+ " Toolbox", newline)); +disp("All required toolboxes are installed."); + +end From 1cafe533a4028dff96cb265819d7f7a0fd37cd43 Mon Sep 17 00:00:00 2001 From: Thurston Date: Tue, 6 Aug 2024 12:10:39 -0700 Subject: [PATCH 04/14] greens() : bug fix (addresses #39) --- src/UltrasoundSystem.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UltrasoundSystem.m b/src/UltrasoundSystem.m index 3664eb2..9a1ed3b 100644 --- a/src/UltrasoundSystem.m +++ b/src/UltrasoundSystem.m @@ -698,7 +698,7 @@ function delete(us) k.GridSize = [ceil(QS ./ k.ThreadBlockSize(1)), N, M]; % get the computational bounds - sblk = (0 : k.GridSize - 1)' * k.ThreadBlockSize(1); % block starting time index + sblk = (0 : k.GridSize(1) - 1)' * k.ThreadBlockSize(1); % block starting time index blocks = sblk <= [-inf, sb(2,:), inf]; % point at which we are in bounds blocks = blocks(:,2:end) - blocks(:,1:end-1); % detect change sbk = cellfun(@(x) find(x,1,'first'), num2cell(blocks,2)); % get transition point From b5c3afe004a93a69f48f1c39c8bf969dbeb20f1e Mon Sep 17 00:00:00 2001 From: Thurston Date: Tue, 6 Aug 2024 22:00:51 -0700 Subject: [PATCH 05/14] Suppress test outputs - addresses #39 --- buildfile.m | 3 +- test/BFTest.m | 8 ++--- test/ChdTest.m | 18 +++++----- test/ExampleTest.m | 38 ++++++++++++--------- test/InitTest.m | 83 +++++++++++++++++++++++++++------------------- test/InteropTest.m | 21 +++++++++++- test/KernTest.m | 18 ++++++++++ test/interpTest.m | 4 +-- utils/animate.m | 2 +- 9 files changed, 126 insertions(+), 69 deletions(-) diff --git a/buildfile.m b/buildfile.m index f1bb354..82aa1a6 100644 --- a/buildfile.m +++ b/buildfile.m @@ -71,7 +71,8 @@ "Tag","syntax","SourceFiles",src ... ,CodeCoverageResults=fullfile("build","code-coverage-brief",["coverage.xml" "html/index.html"]) ... ,TestResults="build/test-results-brief/report.html" ... - ,Dependencies="compatability"... + ,Dependencies="compatability" ... + ,OutputDetail="Concise" ... ); %% Add compilation dependencies diff --git a/test/BFTest.m b/test/BFTest.m index 0541dcb..d8672b4 100644 --- a/test/BFTest.m +++ b/test/BFTest.m @@ -209,7 +209,7 @@ function reselectgpu() % Github test routine - methods(Test, ParameterCombination = 'sequential', TestTags={'Github'}) + methods(Test, ParameterCombination = 'sequential', TestTags={'Github', 'build'}) function github_psf(test, bf_name)%, prec, terp) if(any(bf_name == ["Eikonal","Adjoint"])), return; end % Too much compute @@ -247,9 +247,9 @@ function psf(test, gdev, bf_name, prec, terp, apodization) % for the Eikonal beamformer, pass if not given FSA delays if(bf_name == "Eikonal" && us.seq.type ~= "FSA"), return; end % if using a frequency domain method, skip half precision - the phase errors are too large - if(ismember(bf_name, ["Adjoint"]) && prec == "halfT"), return; end + if(ismember(bf_name, "Adjoint") && prec == "halfT"), return; end % weird phase error, but it's not important right now - skip it - if(ismember(bf_name, ["Adjoint"]) && us.seq.type == "VS") return; end % && isa(us.xdc, 'TransducerConvex')); + if(ismember(bf_name, "Adjoint") && us.seq.type == "VS"), return; end % && isa(us.xdc, 'TransducerConvex')); % is using the adjoint method, pagemtimes,pagetranspose must be supported test.assumeTrue( bf_name ~= "Adjoint" || (... logical(exist('pagemtimes' , 'builtin')) ... @@ -264,7 +264,7 @@ function psf(test, gdev, bf_name, prec, terp, apodization) % for frequency domain methods, the time-axis must be extended % so that the replications of the miage are outside of the % imaging range - if ismember(bf_name, ["Adjoint"]) + if ismember(bf_name, "Adjoint") dr = hypot(range(scanc.xb), range(scanc.zb)) / 2; % half the largest range across the image dT = round(2 * dr / scat.c0 * chd.fs); % temporal buffer in indices chd = zeropad(chd, dT, dT); % add buffer on both sides diff --git a/test/ChdTest.m b/test/ChdTest.m index 2a0fef0..5fa8c0e 100644 --- a/test/ChdTest.m +++ b/test/ChdTest.m @@ -39,7 +39,7 @@ function typeCheck(tst) % tall, sparse chdo = tall(chd); - sparse(ChannelData('data', randi([0 1], [16 32]))) + sparse(ChannelData('data', randi([0 1], [16 32]))); % unit conversion check chdo = angle(complex(chd)); @@ -75,9 +75,9 @@ function freqDomainCheck(tst) D3 = chd.getLowpassFilter(0.2); D4 = chd.getPassbandFilter([0.1 0.4]); for D = [D1 D2] - filter( chd, D) - filtfilt(chd, D) - fftfilt( chd, D) + filter( chd, D); + filtfilt(chd, D); + fftfilt( chd, D); end % sampling @@ -129,13 +129,13 @@ function transforming(tst) tst.assertEqual(-wvr.tend, wv.t0); tst.assertEqual(-wv.tend, wvr.t0); tst.assertEqual(wv.samples, reverse(reverse(wv)).samples); - for s = ["full", "same", "valid"], convt(chd, wv, s), end + for s = ["full", "same", "valid"], convt(chd, wv, s); end % join/splice - ChannelData.empty().join(4) - join(chd.splice(),4) - chd.splice(4) - chd.splice(1,4) + ChannelData.empty().join(4); + join(chd.splice(),4); + chd.splice(4); + chd.splice(1,4); % data order ord = [1,4,2,3]; diff --git a/test/ExampleTest.m b/test/ExampleTest.m index 2c662e3..be7610a 100644 --- a/test/ExampleTest.m +++ b/test/ExampleTest.m @@ -7,6 +7,9 @@ scat_lim (1,1) double % limit of scatterers pulse_lim (1,2) double % max number of pulses to sim in k-Wave, greens end + properties(Constant) + debug (1,1) logical = false; % whether to print debug warnings + end % blacklists properties @@ -56,9 +59,9 @@ 'FieldII', ["calc_scat"+["","_all", "_multi"], "getFieldII"+["Aperture","Patches","Positions"]], ... FieldII 'MUST', "simus", ... MUST 'fullwave', ["getFullwaveTransducer", "fullwaveSim", "fullwaveConf", "fullwaveJob", "mapToCoords"], ... % fullwave - 'mex', ["bfEikonal", "msfm"+["","2d","3d"]], ... mex binaries required ... compilation (optional, requires CUDA) + 'mex', "recompile" + ["","Mex"], ... mex binaries required ... compilation (optional, requires CUDA) 'gpu', ["wbilerpg","feval"], ... CUDAKernel or oclKernel support required - 'comp', ("recompile" + ["","Mex","CUDA"]), ... compilations setup required + 'comp', ("recompile" + ["","CUDA"]), ... compilations setup required 'ext', "vol3d", ... external functions }, 2, [])'; % + "("; bl_fcn(:,2) = cellfun(@(s){s+"("}, bl_fcn(:,2)); % append "(" @@ -93,12 +96,14 @@ if all(arrayfun(@(k) exist(fullfile(prj_rt, "bin", k), 'file'), kerns)) bl_fcn(bl_fcn(:,1) == "gpu",:) = []; end - kerns = ["msfm2d", "msfm3d"]+"."+mexext; % require mex binaries - if 1||all(arrayfun(@(k) exist(fullfile(prj_rt, "bin", k), 'file'), kerns)) + kerns = ["msfm2d", "msfm3d"]+".c"; % require mex source files + lst = arrayfun(@(k) dir(fullfile(prj_rt, "**", k)), kerns, 'uni', 0); + if all(ismember(kerns, {vertcat(lst{:}).name})) + % if all(arrayfun(@(k) exist(fullfile(prj_rt, "bin", k), 'file'), kerns)) bl_fcn(bl_fcn(:,1) == "mex",:) = []; end % fullwave executable must exist in 'bin' folder under this name - kerns = ["fullwave2_executable"]; + kerns = "fullwave2_executable"; if all(arrayfun(@(k) exist(fullfile(prj_rt, "bin", k), 'file'), kerns)) ... && exist("mapToCoords", "file") bl_fcn(bl_fcn(:,1) == "fullwave",:) = []; @@ -136,22 +141,22 @@ function filterBlacklist(test, code, blk, fls) ,"Matching packages: " + join(string(pkg(1,p)), ", ") ... ]), newline)); end - function silenceAcceptableWarnings(testCase) + function silenceAcceptableWarnings(test) lids = [... "QUPS:kspaceFirstOrder:upsampling", ... % in US "MATLAB:ver:ProductNameDeprecated", ... % in k-Wave "QUPS:recompile:UnableToRecompile" ... % in US ]; W = warning(); % get current state - arrayfun(@(l) warning( 'off', l), lids); % silence - testCase.addTeardown(@() warning(W)); % restore on exit + arrayfun(@(l) warning('off', l), lids); % silence + test.addTeardown(@() warning(W)); % restore on exit if ~isempty(gcp('nocreate')) % any pool - execute on each worker ws = parfevalOnAll(@warning, 1); wait(ws);% current state ws = fetchOutputs(ws); % retrieve [~, i] = unique(string({ws.identifier}), 'stable'); ws = ws(i); % select first unique set (assumer identical) wait(arrayfun(@(l) parfevalOnAll(@warning, 0, 'off', l), lids)); % silence - testCase.addTeardown(@() parfevalOnAll(@warning, 0, ws)); % restore on exit + test.addTeardown(@() parfevalOnAll(@warning, 0, ws)); % restore on exit end end function txt = truncateJobs(test, txt, kwargs) @@ -286,14 +291,14 @@ function cleanup_test(test) if isempty(bdln) bdln = nan; elseif ~isscalar(bdln) - warning("Multiple lines match " + string(pt(j)) + " in file " + fls_(m) + ": choosing the "+ord(j)+"."); + if ExampleTest.debug, warning("Multiple lines match " + string(pt(j)) + " in file " + fls_(m) + ": choosing the "+ord(j)+"."); end switch j, case 1, bdln = bdln(1); case 2, bdln = bdln(end); end end k(j) = bdln; %#ok end k = k + [1, -1]; % go after/before matching start/end if isnan(k(1)) - % warning("No example found for lines "+join(string(blk{:}([1 end])),"-")+" in file "+f+"."); + % if ExampleTest.debug, warning("No example found for lines "+join(string(blk{:}([1 end])),"-")+" in file "+f+"."); end continue; end if isnan(k(2)), k(2) = length(code_); end % can't find an ending - assume it's okay @@ -330,7 +335,7 @@ function run_main_example(test) test.assumeTrue(logical(exist("kspaceFirstOrder3D", "file")), "Missing package kWave" ); % kWave for f = fnms copyfile(which(f+".m"), "./"); % copy to here (temp folder) - eval(f); % run + evalc(f); % run end end end @@ -343,7 +348,7 @@ function run_kernel_examples(test, fls, lns) else base_dir = prj.RootFolder; end - flds = fullfile(base_dir, ["examples"]); % blacklist classes and examples + flds = fullfile(base_dir, "examples"); % blacklist classes and examples run_examples(test, fls, lns, flds); end end @@ -418,9 +423,9 @@ function run_examples(test, fls, lns, flt_fld) % assert a clean run test.silenceAcceptableWarnings(); if any(contains(code, "compile")) - f = str2func("@"+fnm); f(); % run directly + evalc(fnm); % run directly else - test.assertWarningFree(str2func("@"+fnm), "Example "+fnm+" did not complete without a warning!"); + test.assertWarningFree(@()evalc(fnm), "Example "+fnm+" did not complete without a warning!"); end end end @@ -437,7 +442,7 @@ function run_examples(test, fls, lns, flt_fld) i = find(contains(code,pat)); % lines with call to kspaceFirstOrder if isempty(i), V = 0; return; end % 0 if greens not found -if ~isscalar(i), i = i(1); warning("Multiple calls to "+fnm+"(): choosing the first."); end +if ~isscalar(i), i = i(1); if ExampleTest.debug, warning("Multiple calls to "+fnm+"(): choosing the first."); end, end v = strip(extractBetween(code(i), pat, ("," | ")"))); % us variable % write code to a file @@ -467,3 +472,4 @@ function run_examples(test, fls, lns, flt_fld) fclose(fid); end end + diff --git a/test/InitTest.m b/test/InitTest.m index 617d9db..f84c6f0 100644 --- a/test/InitTest.m +++ b/test/InitTest.m @@ -16,14 +16,6 @@ ]); end - properties - end - - methods(TestClassSetup, ParameterCombination = 'exhaustive') - end - methods(TestClassTeardown) - end - methods(TestMethodSetup) % Setup for each test function fig(~), figure; end @@ -31,6 +23,24 @@ methods(TestMethodTeardown) function cls(~), close; end end + methods(TestClassSetup) + function silenceAcceptableWarnings(tst) + lids = ... + "MATLAB:structOnObject" ... calling struct directly + ; + W = warning(); % get current state + arrayfun(@(l) warning('off', l), lids); % silence + tst.addTeardown(@() warning(W)); % restore on exit + if ~isempty(gcp('nocreate')) % any pool - execute on each worker + ws = parfevalOnAll(@warning, 1); wait(ws);% current state + ws = fetchOutputs(ws); % retrieve + [~, i] = unique(string({ws.identifier}), 'stable'); + ws = ws(i); % select first unique set (assumer identical) + wait(arrayfun(@(l) parfevalOnAll(@warning, 0, 'off', l), lids)); % silence + tst.addTeardown(@() parfevalOnAll(@warning, 0, ws)); % restore on exit + end + end + end methods(Test) function initxdc(test) % INITXDC - Assert that Transducer constructors initialize @@ -43,10 +53,10 @@ function initxdc(test) arrayfun(@plot, xdcs); % supports plotting arrayfun(@patch, xdcs); % supports patch arrayfun(@(x)patch(x, nexttile(), 'el_sub_div', [2 2]), xdcs); % supports args - xdcs(end+2) = xdcs(1), %#ok implicit empty value, display - arrayfun(@disp, xdcs) % display scalar - arrayfun(@(x)disp(x([])), xdcs) % display empty - x = copy(xdcs); x.delete(); x, arrayfun(@disp, x); % display deleted + xdcs(end+2) = xdcs(1); % implicit empty value + arrayfun(@edisp, xdcs) % display scalar + arrayfun(@(x)edisp(x([])), xdcs) % display empty + x = copy(xdcs); x.delete(); arrayfun(@edisp, x); % display deleted test.assertWarning(@()xdcs(1).ultrasoundTransducerImpulse(), "QUPS:Transducer:DeprecatedMethod"); [xdcs.origin] = deal([0 0 -10e-3]); % offset (to be deprecated?) [xdcs.rot] = deal([20 -10]); % offset (to be deprecated?) @@ -65,15 +75,15 @@ function initseq(test) [seqs([seqs.type] == "FSA").numPulse] = deal(1); arrayfun(@(seq) test.assertThat(seq, IsInstanceOf('Sequence')), seqs); arrayfun(@(scn) scale(scn, 'dist', 1e3), seqs, "UniformOutput",false); % can scale - seqs(end+2) = seqs(1), %#ok implicit empty value, display + seqs(end+2) = seqs(1); % implicit empty value seqs(end-1).numPulse = 1; % fix implicit seq s = arrayfun(@obj2struct, seqs, 'UniformOutput', false); % supports specialized struct conversion arrayfun(@plot, seqs, 'UniformOutput',false); % supports plotting cellfun(@(s) test.assertThat(s.pulse, IsInstanceOf('struct')), s); % recursive check - arrayfun(@disp, seqs) % display scalar - arrayfun(@(x)disp(x([])), seqs) % display empty + arrayfun(@edisp, seqs) % display scalar + arrayfun(@(x)edisp(x([])), seqs) % display empty arrayfun(@splice, seqs, 'UniformOutput', false); % can splice - x = copy(seqs); x.delete(); x, arrayfun(@disp, x); % display deleted + x = copy(seqs); x.delete(); arrayfun(@edisp, x); % display deleted % polar: manipulate range/angles seq = SequenceRadial("focus",[0 0 1]'); % fine @@ -82,7 +92,7 @@ function initseq(test) seq = SequenceRadial("ranges",[1 1 1]); % fine seq = SequenceRadial("angles",-1:1, "ranges",[1 1 1]); % fine seq = SequenceRadial("angles", 0, "ranges",[1 1 1]); % fine - seq = SequenceRadial("angles",-1:1, "ranges",[1]); % fine + seq = SequenceRadial("angles",-1:1, "ranges",1); % fine seq.angles = [-5 0 5]; % fine seq.ranges = [2 3 2]; % fine test.assertError(@()SequenceRadial("focus",[0 0 1]',"angles",0),""); % bad @@ -114,10 +124,10 @@ function initscan(test) arrayfun(@obj2struct, scns, 'UniformOutput', false); % supports specialized struct conversion arrayfun(@plot, scns); % supports plotting arrayfun(@(s) imagesc(s,randn(s.size)), scns); % supports imagesc - scns(end+2) = scns(1),%#ok implicit empty value, display - arrayfun(@disp, scns) % display scalar - arrayfun(@(x)disp(x([])), scns) % display empty - x = copy(scns); x.delete(); x, arrayfun(@disp, x); % display deleted + scns(end+2) = scns(1); % implicit empty value + arrayfun(@edisp, scns) % display scalar + arrayfun(@(x)edisp(x([])), scns) % display empty + x = copy(scns); x.delete(); arrayfun(@edisp, x); % display deleted % supports req'd overload methods [~,~,~] = arrayfun(@getImagingGrid, scns, "UniformOutput",false); @@ -144,8 +154,8 @@ function initscan(test) end % convert polar to cart - ScanCartesian(ScanPolar()), - ScanCartesian(ScanSpherical()), + ScanCartesian(ScanPolar()); + ScanCartesian(ScanSpherical()); % deprecations test.assertWarning(@()scanCartesian(ScanPolar() ), "QUPS:ScanPolar:syntaxDeprecated" ); @@ -155,7 +165,7 @@ function initscan(test) function initwv(test) % INITWV - Assert that the Waveform constructor initializes import matlab.unittest.constraints.IsInstanceOf; - wv = Waveform(); % init + wv = Waveform(); edisp(wv); % construct & display test.assertThat(wv, IsInstanceOf('Waveform')); % multiple ways to init @@ -186,7 +196,7 @@ function initchd(test) % INITCHD - Assert that a ChannelData constructor initializes % without arguments import matlab.unittest.constraints.IsInstanceOf; - chd = ChannelData('data',0), %#ok implicit display + chd = ChannelData('data',0); test.assertThat(chd, IsInstanceOf('ChannelData')); scale(chd, 'time', 1e6); arrayfun(@obj2struct, chd, 'UniformOutput', false); % supports specialized struct conversion @@ -200,19 +210,20 @@ function initus(test) % INITUS - Assert that an UltrasoundSystem constructor % initializes without arguments import matlab.unittest.constraints.IsInstanceOf; - us = UltrasoundSystem(), %#ok implicit display + us = UltrasoundSystem(); edisp(us); % construct & display test.assertThat(us, IsInstanceOf('UltrasoundSystem')); fld = us.tmp_folder; test.assertTrue(logical(exist(fld,"dir"))); UltrasoundSystem('copybin', true); - UltrasoundSystem('recompile',true); + evalc("UltrasoundSystem('recompile',true);"); us2 = copy(us); fld2 = us2.tmp_folder; % new instance test.assertFalse(fld == fld2); % different folders clear us2; test.assertFalse(logical(exist(fld2,'dir'))); % deleted on clear us2 = copy(us); fld2 = us2.tmp_folder; % new instance copyfile(which("Qups.prj"), fld2); % dirty test.addTeardown(@()delete(fullfile(fld2,"Qups.prj"))); % cleanup + try clear us2 % should error [~, lid] = lastwarn; test.assertTrue(lid == "MATLAB:class:DestructorError", ... @@ -242,8 +253,8 @@ function initus(test) uss = copy([us us us]); % array uss(end+2) = copy(us); % implicit creation - arrayfun(@disp, uss) % display scalar - arrayfun(@(x)disp(x([])), uss) % display empty + arrayfun(@edisp, uss) % display scalar + arrayfun(@(x)edisp(x([])), uss) % display empty % deprecation test.assertWarning(@()setfield(us,'sequence',Sequence()),"QUPS:UltrasoundSystem:syntaxDeprecated") @@ -257,8 +268,8 @@ function initmedscat(test) % initialize without arguments import matlab.unittest.constraints.IsInstanceOf; - sct = Scatterers(), %#ok implicit display - med = Medium(), %#ok implicit display + sct = Scatterers(); edisp(sct); % construct & display + med = Medium(); edisp(med); % construct & display test.assertThat(sct, IsInstanceOf('Scatterers')); test.assertThat(med, IsInstanceOf('Medium' )); scale(sct, "dist", 1e3, "time", 1e6); @@ -270,9 +281,9 @@ function initmedscat(test) % special constructors grd = ScanCartesian(); - Medium.Sampled(grd) - Scatterers.Diffuse(grd) - Scatterers.Grid() + Medium.Sampled(grd); + Scatterers.Diffuse(grd); + Scatterers.Grid(); % deprecated properties test.assertWarning(@()setfield(med,'alphap0',1.2), "QUPS:Medium:syntaxDeprecated") @@ -337,4 +348,6 @@ function propTest(test, clss) end end -end \ No newline at end of file +end + +function edisp(x), evalc("disp(x)"); end %#ok diff --git a/test/InteropTest.m b/test/InteropTest.m index cf6c759..bce14be 100644 --- a/test/InteropTest.m +++ b/test/InteropTest.m @@ -74,13 +74,32 @@ if isempty(vsx_files), vsx_files = {''}; end end end + methods(TestClassSetup) + function silenceAcceptableWarnings(tst) + lids = [... + "QUPS:Sequence:DeprecatedValue" ... in Sequence.UFF + "QUPS:QUPS2USTB:ambiguousSequence" ... in Sequence.QUPS2USTB + ]; + W = warning(); % get current state + arrayfun(@(l) warning('off', l), lids); % silence + tst.addTeardown(@() warning(W)); % restore on exit + if ~isempty(gcp('nocreate')) % any pool - execute on each worker + ws = parfevalOnAll(@warning, 1); wait(ws);% current state + ws = fetchOutputs(ws); % retrieve + [~, i] = unique(string({ws.identifier}), 'stable'); + ws = ws(i); % select first unique set (assumer identical) + wait(arrayfun(@(l) parfevalOnAll(@warning, 0, 'off', l), lids)); % silence + tst.addTeardown(@() parfevalOnAll(@warning, 0, ws)); % restore on exit + end + end + end methods(Test) function fieldII_xdc(tst, xdcs) tst.assumeTrue(logical(exist('field_init','file'))) % need FieldII for this import matlab.unittest.constraints.IsEqualTo; import matlab.unittest.constraints.AbsoluteTolerance; - xdcs.patches() % assumes defaults + xdcs.patches(); % assumes defaults pchq = xdcs.patches([4 2]); pchf = xdcs.getFieldIIPatches([4 2]); [pchq, pchf] = dealfun(@(x) cell2mat(swapdim(cellfun(@(x) {cat(3, x{:})}, x),1:2,4:5)), pchq, pchf); diff --git a/test/KernTest.m b/test/KernTest.m index 1bcd5ce..29206f5 100644 --- a/test/KernTest.m +++ b/test/KernTest.m @@ -62,6 +62,24 @@ function set_data(test) b = complex(randn(sz), randn(sz)); test.dargs{3} = {us, b}; end + + function silenceAcceptableWarnings(test) + lids = ... + "QUPS:animate:MatchingPermutation" ... in animate + ; + W = warning(); % get current state + arrayfun(@(l) warning('off', l), lids); % silence + test.addTeardown(@() warning(W)); % restore on exit + if ~isempty(gcp('nocreate')) % any pool - execute on each worker + ws = parfevalOnAll(@warning, 1); wait(ws);% current state + ws = fetchOutputs(ws); % retrieve + [~, i] = unique(string({ws.identifier}), 'stable'); + ws = ws(i); % select first unique set (assumer identical) + wait(arrayfun(@(l) parfevalOnAll(@warning, 0, 'off', l), lids)); % silence + test.addTeardown(@() parfevalOnAll(@warning, 0, ws)); % restore on exit + end + end + end methods function set_device(test, dev), test.setDev(dev); end end diff --git a/test/interpTest.m b/test/interpTest.m index a55053c..27e3172 100644 --- a/test/interpTest.m +++ b/test/interpTest.m @@ -47,7 +47,7 @@ function setupData(test, dsize,type, dev) tau_ = cfun(tau_); % gpu/cpu - switch dev, + switch dev case "CPU", x0_ = gather( x0_); case "GPU", x0_ = gpuArray(x0_); end @@ -90,7 +90,7 @@ function setDev(test, dev) % all permutations to test % ord = {[1,2,3,4], [1,3,2,4], [1,4,2,3]} % permute the data? terp = {'cubic', 'nearest', 'linear'}; % overlapping methods - dsum = {[], [2], [3,4], 6} % summation dimensions + dsum = {[], 2, [3,4], 6} % summation dimensions wvecd = {[], 3, [3,4]} % different outer-product weights dimensions end methods(Test, ParameterCombination = 'exhaustive') diff --git a/utils/animate.m b/utils/animate.m index 8e75866..d4310df 100644 --- a/utils/animate.m +++ b/utils/animate.m @@ -111,7 +111,7 @@ if r(1) == c, r = r(2); else, r = r(1); end % extract row dim if all([c r] == [1 2]), continue; end % already in order ord = [c r, setdiff(dvec, [c,r])]; % permutation order - put '[c r]' first - warning("Permuting "+ith(i)+" argument to match the image (ord = ["+join(string(ord),",")+"])."); + warning("QUPS:animate:MatchingPermutation","Permuting "+ith(i)+" argument to match the image (ord = ["+join(string(ord),",")+"])."); x{i} = permute(x{i}, ord); % permute end From cedddcea62f27e84161083dfd80980be30c243b2 Mon Sep 17 00:00:00 2001 From: thorstone25 Date: Wed, 7 Aug 2024 22:00:52 -0700 Subject: [PATCH 06/14] Update README.md 1st draft - new troubleshooting section --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index b20a412..b799256 100644 --- a/README.md +++ b/README.md @@ -221,3 +221,29 @@ First, be sure you can run `nvcc` from a terminal or command-line interface per #### Windows First, setup your system for CUDA per [CUDA installation instructions](https://docs.nvidia.com/cuda/cuda-installation-guide-microsoft-windows/index.html). On Windows you must set the path for both CUDA and the _correct_ MSVC compiler for C/C++. Start a PowerShell terminal within Visual Studio. Run `echo %CUDA_PATH%` to find the base CUDA_PATH and run `echo %VCToolsInstallDir%` to find the MSVC path. Then, in MATLAB, set these paths with `setenv('MW_NVCC_PATH', YOUR_CUDA_BIN_PATH); setenv('VCToolsInstallDir', YOUR_MSVC_PATH);`, where `YOUR_CUDA_BIN_PATH` is the path to the `bin` folder in the `CUDA_PATH` folder. Finally, run `setup CUDA`. From here the proper paths should be added. +## Troubleshooting +### Parpool management +Most functions will perform best with an active `parallel.ThreadPool` available. This can be started with `setup parallel` or `parpool Threads`. Alternatively, a `parallel.ProcessPool` can be started with `parpool Processes` (or `parpool local` on earlier MATLAB releases). Functions that allow specifying a parallel environment can avoid using the active parallel pool by using an argument of `0`, or can limit the number of . + +### GPU management +Some QUPS functions use the currently selected `parallel.gpu.CUDADevice`, or select a `parallel.gpu.CUDADevice` by default. Use [`gpuDevice`](https://www.mathworks.com/help/parallel-computing/parallel.gpu.gpudevice.html) to manually select the gpu. Within a parallel pool, each worker can have a unique selection. By default, GPUs are spread evenly across all workers. +If [CUDA support](#cuda-support) is enabled, [ptx-files](https://www.mathworks.com/help/parallel-computing/parallel.gpu.gpudevice.html) will be compiled to target the currently selected device. If the currently selected device changes, you may need to recompile binares using `UltrasoundSystem.recompileCUDA` or `setup cache`, particularly if the computer contains GPUs from different [virtual architectures](https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html#virtual-architecture-feature-list) or have different compute capabilities. + +### OOM (Out of memory) errors +Beamforming and simulation routines tend to require a trade-off between performance and memory usage. While QUPS attempts to balance this, you can still run into OOM errors if the GPU is almost full or if the problem is too large for either the GPU or CPU. There are several options to mitigate this such as: +* (GPU OOM) use `x = gather(x);` to move some variables from the GPU (device) to the CPU (host) - this works on `ChannelData` objects too. +* for beamformers, set the `bsize` optional keyword argument to a smaller value +* (GPU OOM) use `gpuDevice().reset()` to reset the currently selected GPU - NOTE: this will clear any variables that were not moved from the GPU to the CPU with `gather` first +* consider reducing the problem and using a for-loop e.g. by processing an image per frame or per depth, or simulating groups of 1000 scatterers at a time +* (GPU OOM) for `UltrasoundSystem.DAS` and `UltrasoundSystem.greens`, set the `device` argument to `0` to avoid using a GPU device. +* if a `parallel.Pool` is active and a function accepts an optional parallel environment keyword argument `penv`, use a small number e.g. `4` to limit the number of workers, or `0` to avoid using the active `parallel.Pool` +* if a `parallel.Pool` is active, shut it down with `delete(gcp('nocreate'))` and [disable automatic parallel pool creation](https://www.mathworks.com/help/parallel-computing/parallel-preferences.html). + + + + + + + + + From 5b5d028a968138f46e878e8fe2797b4f9fafbaa0 Mon Sep 17 00:00:00 2001 From: Thurston Date: Tue, 13 Aug 2024 17:38:09 -0700 Subject: [PATCH 07/14] swapdim() : slight compute performance optimization. --- utils/swapdim.m | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/utils/swapdim.m b/utils/swapdim.m index f975f18..0d5c498 100644 --- a/utils/swapdim.m +++ b/utils/swapdim.m @@ -31,12 +31,12 @@ ord0 = ord; % get permutation -if isempty(intersect(i, o)) % can be handled separately +if (noIntersect(i, o)) % can be handled separately ord([i o]) = [o i]; % swap dimensions else % must fill in missing indices l = min(min(i),min(o)) : max(max(i),max(o)); % all indices within swap - i = [i, setdiff(l, i)]; % expanded input indices - o = [o, setdiff(l, o)]; % expanded output indices + i = appendDiff(i,l); % expanded input indices + o = appendDiff(o,l); % expanded output indices ord(o) = i; % full permutation ordering end @@ -53,3 +53,8 @@ x = permute(x, ord); end +function tf = noIntersect(i,o), tf = all(i'~=o,'all'); +% replaces `isempty(intersect(i, o))` + +function i = appendDiff(i, l), i = [i,l(all(l~=i',1))]; +% replaces `i = [i, setdiff(l, i)];` From 1ab571589e9344673ddf954bfebf791cb379ae3e Mon Sep 17 00:00:00 2001 From: Thurston Date: Tue, 13 Aug 2024 17:50:01 -0700 Subject: [PATCH 08/14] pwznxcorr() : added lag vectorization and multi-channel support --- kern/pwznxcorr.m | 56 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/kern/pwznxcorr.m b/kern/pwznxcorr.m index 7a88c7f..4176f72 100644 --- a/kern/pwznxcorr.m +++ b/kern/pwznxcorr.m @@ -41,6 +41,18 @@ % y = PWZNXCORR(..., 'norm', true) normalizes the windowed vectors u and v % such that |u||_2 == ||v||_2 == 1. The default is true. % +% y = PWZNXCORR(..., 'tdim', tdim) uses dimension tdim as the time +% dimension. The default is 1. +% +% y = PWZNXCORR(..., 'ndim', ndim) uses dimension ndim as the channel +% dimension. The default is 2. +% +% y = PWZNXCORR(..., 'ldim', ldim) uses dimension ldim as the (output) lag +% dimension. The default is ndims(x) + 1. +% +% y = PWZNXCORR(..., 'lvec', true) vectorizes the lag dimension rather than +% computing each lag iteratively. The default is false. +% % y = PWZNXCORR(..., 'ref', 'neighbor') compares each channel n to the % neighboring channel n + 1. This is the default. % @@ -60,6 +72,10 @@ % end, end, end, end, % ``` % +% y = PWZNXCORR(..., 'ref', 'center', 'multi', true) compares each channel n +% to the central M == size(w, ndim) channels rather than the median channel. +% The default is false. +% % y = PWZNXCORR(..., 'ref', 'x0', 'x0', signal) compares each channel to % the given signal x0. The dimensions of x0 must be compatible with x. This % is roughly equivalent to modifying the psuedo-code above to contain the @@ -72,15 +88,6 @@ % end, end, end, end, % ``` % -% y = PWZNXCORR(..., 'tdim', tdim) uses dimension tdim as the time -% dimension. The default is 1. -% -% y = PWZNXCORR(..., 'ndim', ndim) uses dimension ndim as the channel -% dimension. The default is 2. -% -% y = PWZNXCORR(..., 'ldim', ldim) uses dimension ldim as the (output) lag -% dimension. The default is ndims(x) + 1. -% % y = PWZNXCORR(..., 'pad', false) disables 0-padding prior to computing % the cross-correlation. If `imfilter` (image processing toolbox) is not % available, this introduces wrap-around artefacts, but prevents the need @@ -130,13 +137,16 @@ kwargs.tdim (1,1) int64 {mustBeNumeric, mustBeInteger, mustBePositive} = 1 kwargs.ndim (1,1) int64 {mustBeNumeric, mustBeInteger, mustBePositive} = 2 kwargs.ldim (1,1) int64 {mustBeNumeric, mustBeInteger, mustBePositive} = ndims(x) + 1; + kwargs.multi (1,1) logical = false; % whether to reference multiple channels + kwargs.lvec (1,1) logical = true; % whether to vectorize lags end % alias x0 = kwargs.x0; % verify that w/W operates in the correct dimensions wsz = size(W,1:max(kwargs.tdim, kwargs.ndim)); -if any(wsz(setdiff(1:numel(wsz),[kwargs.tdim, kwargs.ndim])) ~= 1) +wsz_chk = wsz; wsz_chk([kwargs.tdim, kwargs.ndim]) = []; % size of W except in tdim,ndim +if any(wsz_chk ~= 1) error("QUPS:pwznxcorr:incompatibleWeightSize", ... "The filter weights w must be scalar in all dimensions except time (" ... +kwargs.tdim+") and channel ("+kwargs.ndim+")." ... @@ -155,6 +165,10 @@ [w, W] = deal(W, numel(W)); %#ok (for debug) end +% window sizing +[C, Wn] = deal(1, wsz(kwargs.ndim)); % number of channels: to correlate | normalization +if kwargs.multi, [C, Wn] = deal(Wn, C); end + % vector accumulation function isiflt = exist('imfilter', 'file'); % requires image processing toolbox if isiflt % whether to use imfilter @@ -187,11 +201,12 @@ case "center" % find the middle channel - mid = (N + 1) / 2; + mid = (N + 1 - C + 1)/2 + (0:C-1); n = unique([floor(mid), ceil(mid)]); % mean signal approach % n = floor(mid); % pick one signal, although spatially biased xl = x; % (T x N x ...) - xr = mean(sub(x, n, kwargs.ndim), kwargs.ndim); % (T x 1 x ...) + xr = sub(x, n, kwargs.ndim); + if ~kwargs.multi, xr = mean(xr, kwargs.ndim); end % (T x 1 x ...) case "x0" % TODO: validate data size / cast type etc. @@ -217,11 +232,13 @@ % windowed inner product for each lag % function y = windowed_inner_lag(lag) for i = numel(lags) : -1 : 1 - % get the lag for this iteration - lag = lags(i); - % delay right channel by lag - xr_l = conj(circshift(xr, -lag, tdim)); + if kwargs.lvec + xr_l = arrayfun(@(l){conj(circshift(xr, -l, tdim))}, lags); + xr_l = cat(kwargs.ldim, xr_l{:}); + else + xr_l = conj(circshift(xr, -lags(i), tdim)); + end % select portion if U > 1, xr_l = sub(xr_l, 1:U:T, tdim); end @@ -240,7 +257,7 @@ xrn_l = kernfun(real(xrz_l .* conj(xrz_l))); % normalization denominator - complex for (garbage) negative amplitudes - r = sqrt(complex(xln)) .* sqrt(complex(xrn_l)) .* sqrt(wsz(kwargs.ndim)) ; % denom + r = sqrt(complex(xln)) .* sqrt(complex(xrn_l)) .* sqrt(Wn); % denom % normalize y = y ./ r; @@ -248,11 +265,14 @@ % store yi{i} = y; + + % short-circuit + if kwargs.lvec, break; end end % compute for all lags and unpack % y = arrayfun(@windowed_inner_lag, lags, 'UniformOutput', false); -y = cast(cat(kwargs.ldim, yi{:}), 'like', x); % (T x [N|N-S] x ... x lags) +if ~kwargs.lvec, y = cast(cat(kwargs.ldim, yi{:}), 'like', x); end % (T x [N|N-S] x ... x lags) % undo manual 0-padding if kwargs.pad && ~isiflt, y = sub(y, 1:size(y,kwargs.tdim)-P, kwargs.tdim); end From 85318a8e88828f8add819e1afc048b95c74e24ec Mon Sep 17 00:00:00 2001 From: Thurston Date: Wed, 14 Aug 2024 15:59:07 -0700 Subject: [PATCH 09/14] Adding UltrasoundSystem.apCosineAngle() - cosine (acceptance) angle apodization on receive --- src/UltrasoundSystem.m | 44 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/src/UltrasoundSystem.m b/src/UltrasoundSystem.m index 9a1ed3b..aa863f8 100644 --- a/src/UltrasoundSystem.m +++ b/src/UltrasoundSystem.m @@ -4688,7 +4688,7 @@ function delete(us) % colormap gray; colorbar; title("Aperture growth apodization"); % % - % See also ULTRASOUNDSYSTEM/APACCEPTANCEANGLE + % See also APACCEPTANCEANGLE APCOSINEANGLE % defaults arguments @@ -4759,7 +4759,7 @@ function delete(us) % figure; imagesc(us.scan, ap); % animate(ap,'fs',1,'loop',false,"title","Angle: "+seq.angles); % - % See also apAcceptanceAngle + % See also APACCEPTANCEANGLE APCOSINEANGLE arguments us (1,1) UltrasoundSystem theta (1,:) = atan2d(us.seq.focus(1,:), us.seq.focus(3,:)); @@ -4817,7 +4817,7 @@ function delete(us) % nexttile(); imagesc(us.scan, bima, [-80 0] + max(bima(:))); % colormap gray; colorbar; title("Acceptance angle apodization"); % - % See also ULTRASOUNDSYSTEM/APAPERTUREGROWTH + % See also APAPERTUREGROWTH APCOSINEANGLE % defaults arguments @@ -4846,6 +4846,44 @@ function delete(us) % accept if greater than the cutoff angle apod = r >= cosd(theta); end + + + function apod = apCosineAngle(us, theta) + % APCOSINEANGLE - Create an cosine-weighted apodization array + % + % apod = APCOSINEANGLE(us) creates an ND-array to weight + % delayed data from the UltrasoundSystem us by the angle + % between the element normal and the element to pixel vector. + % The weighting is + % + % `cosd(min(90, (phi / theta)))` + % + % where phi is the angle and theta is the maximum angle. + % + % apod = APCOSINEANGLE(us, theta) uses an acceptance angle + % of theta in degrees. The default is 45. + % + % The output apod has dimensions I1 x I2 x I3 x N x 1 where + % I1 x I2 x I3 are the dimensions of the scan, N is the number + % of receive elements. + % + % See also APAPERTUREGROWTH APACCEPTANCEANGLE + + % defaults + arguments + us (1,1) UltrasoundSystem + theta (1,1) {mustBePositive} = 45 + end + + % cosine apodization + [pg, pn] = deal(us.scan.positions(), us.xdc.positions()); % pixels | elems + [~,~,nn] = us.xdc.orientations(); % elem normals + [pn, nn] = deal(swapdim(pn,2,5), swapdim(nn,2,5)); % match dims + r = (pg - pn); % elem -> pixel vector + r = r ./ vecnorm(r,2,1); % normalized + r = swapdim(pagemtimes(nn, 'transpose', r, 'none'), 2:6); % normalized inner product + apod = cosd(min(90,(90/theta)*acosd(r))); % cosine gradient with (scaled) angle + end end % dependent methods From 493027517379e02eadb4fd14c6180b0aba5d412c Mon Sep 17 00:00:00 2001 From: Thurston Date: Thu, 15 Aug 2024 13:52:27 -0700 Subject: [PATCH 10/14] beamforming_walkthrough() : bug fix (#40). --- .../beamforming/beamforming_walkthrough.mlx | Bin 12336 -> 12354 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/examples/beamforming/beamforming_walkthrough.mlx b/examples/beamforming/beamforming_walkthrough.mlx index 97c464311ae63d7acf9b17c31591b05e286895a8..f6578485b246c6c6c6c628112027d63d7e5b3b55 100644 GIT binary patch delta 10131 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 delta 10129 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 From 79ea6b44f8893fa0f1453b284d46c91be3945384 Mon Sep 17 00:00:00 2001 From: Thurston Brevett Date: Tue, 20 Aug 2024 19:12:33 -0700 Subject: [PATCH 11/14] interpd.cu : kernel optimizations --- src/interpd.cu | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/interpd.cu b/src/interpd.cu index 3bdc0c3..07be7b6 100644 --- a/src/interpd.cu +++ b/src/interpd.cu @@ -93,17 +93,17 @@ __device__ T2 cubic(const T2 * x, U tau, T2 no_v) { if (!(0 <= (ti - 1) && (ti + 2) < QUPS_T)) return no_v; - T2 s0 = x[ti - 1]; - T2 s1 = x[ti + 0]; - T2 s2 = x[ti + 1]; - T2 s3 = x[ti + 2]; + const T2 s0 = x[ti - 1]; + const T2 s1 = x[ti + 0]; + const T2 s2 = x[ti + 1]; + const T2 s3 = x[ti + 2]; // Cubic Hermite interpolation (increased precision using fused multiply-adds) // (Catmull-Rom) - U a0 = 0 + u * (-1 + u * (+2 * u - 1)); - U a1 = 2 + u * (+0 + u * (-5 * u + 3)); - U a2 = 0 + u * (+1 + u * (+4 * u - 3)); - U a3 = 0 + u * (+0 + u * (-1 * u + 1)); + const U a0 = 0.f + u * (-1.f + u * (+2.f * u - 1.f)); + const U a1 = 2.f + u * (+0.f + u * (-5.f * u + 3.f)); + const U a2 = 0.f + u * (+1.f + u * (+4.f * u - 3.f)); + const U a3 = 0.f + u * (+0.f + u * (-1.f * u + 1.f)); // // Cubic Hermite interpolation (naive, less precise implementation) // float a0 = -1 * u * u * u + 2 * u * u - 1 * u + 0; // float a1 = +3 * u * u * u - 5 * u * u + 0 * u + 2; From ecad2e8793bbb967f118e54c64ddb8355306b572 Mon Sep 17 00:00:00 2001 From: thorstone25 Date: Tue, 20 Aug 2024 22:56:03 -0700 Subject: [PATCH 12/14] beamforming_walkthrough() : update dialogue. --- .../beamforming/beamforming_walkthrough.mlx | Bin 12354 -> 12641 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/examples/beamforming/beamforming_walkthrough.mlx b/examples/beamforming/beamforming_walkthrough.mlx index f6578485b246c6c6c6c628112027d63d7e5b3b55..90f8fd05a72a0b1931ee5c777b32ee7c60f0aa0c 100644 GIT binary patch delta 10925 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 Mvy#GMvi!&VKZO|xv;Y7A delta 10623 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 From 60db28978c1793f520245967aaec653e0887a9b1 Mon Sep 17 00:00:00 2001 From: Thurston Date: Thu, 22 Aug 2024 15:04:00 -0700 Subject: [PATCH 13/14] UltrasoundSystem.recompileCUDA() : added mexcuda dispatch if nvcc not available. --- src/UltrasoundSystem.m | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/UltrasoundSystem.m b/src/UltrasoundSystem.m index aa863f8..58f4250 100644 --- a/src/UltrasoundSystem.m +++ b/src/UltrasoundSystem.m @@ -4981,10 +4981,10 @@ function delete(us) end end - function com = recompileCUDA(us, defs, arch) + function com = recompileCUDA(us, defs, arch, kwargs) % RECOMPILECUDA - Recompile CUDA ptx files % - % RECOMPILECUDA(us) recompiles all CUDA files and stores + % RECOMPILECUDA(us) recompiles all CUDA files to ptx and stores % them in us.tmp_folder. % % RECOMPILECUDA(us, defs) compiles for the compiler @@ -5001,6 +5001,12 @@ function delete(us) % If arch is an empty string, no architecture argument is used. % The default is the architecture of the current GPU device. % + % RECOMPILECUDA(..., 'mex', true) uses `mexcuda` rather than + % `nvcc` to compile ptx files (requires R2023a or later). + % Architecture selection, warning selection and suppression, + % and other compiler options are not available via mexcuda. The + % default is true if `nvcc` is not found on the path. + % % nvcom = RECOMPILECUDA(...) returns the command sent to nvcc. % This command can be tweaked and resent to debug or fix % compilation errors. @@ -5013,7 +5019,8 @@ function delete(us) % % change the source code ... % % % recompile the 3rd CUDA file manually - % system(nvcom(3)); + % system(nvcom(3)); % via nvcc + % % mexcuda(nvcom{3}{:}); % via mexcuda % % See also ULTRASOUNDSYSTEM.RECOMPILE ULTRASOUNDSYSTEM.RECOMPILEMEX % ULTRASOUNDSYSTEM.GENCUDADEFS @@ -5022,12 +5029,17 @@ function delete(us) us (1,1) UltrasoundSystem defs (1,:) struct = UltrasoundSystem.genCUDAdefs(); arch (:,1) string {mustBeScalarOrEmpty, mustBeArch(arch)} = nvarch() + kwargs.mex (1,1) logical = isempty(argn(2, @system, 'which nvcc')) && ~isMATLABReleaseOlderThan("R2023a") end + + % ensure that the output folder exists + if ~exist(us.tmp_folder,'dir'), warning("QUPS:UltrasoundSystem:ReinitializingTmpFolder","Recreating a tmp_folder that was deleted (somehow)."); mkdir(us.tmp_folder); addpath(us.tmp_folder); end % compile each for i = numel(defs):-1:1 d = defs(i); % make full command + if ~kwargs.mex % via nvcc com(i) = join(cat(1,... "nvcc ", ... "--ptx " + which(string(d.Source)), ... @@ -5039,8 +5051,22 @@ function delete(us) join("-W" + d.Warnings), ... join("-D" + d.DefinedMacros)... )); - - try s = system(com(i)); + comp = @system; + else % via mexcuda + com{i} = cellstr(cat(1,... + "-ptx", ... + "-output", fullfile(us.tmp_folder, argn(2, @fileparts, d.Source) + ".ptx"), ... + ... ("--" + d.CompileOptions),... + ("-I" + d.IncludePath), ... + ("-L" + d.Libraries), ... + ... ("-W" + d.Warnings), ... + ("-D" + d.DefinedMacros),... + which(string(d.Source)) ... + ... "-arch=" + arch + " ", ... compile for active gpu + )); + comp = @(s) mexcuda(s{1}{:}); + end + try s = comp(com(i)); if s, warning( ... "QUPS:recompile:UnableToRecompile", ... "Unable to recompile " + d.Source ... From fa346dda0d85ecce0f47d967eb5320972d5d6fa4 Mon Sep 17 00:00:00 2001 From: Thurston Date: Thu, 22 Aug 2024 16:47:03 -0700 Subject: [PATCH 14/14] Update CITATION.cff, build/coverage.xml --- CITATION.cff | 4 +- build/coverage.xml | 6378 ++++++++++++++++++++++---------------------- 2 files changed, 3243 insertions(+), 3139 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 7cdffcd..584ae38 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -6,7 +6,7 @@ authors: orcid: "https://orcid.org/0000-0002-0720-6955" title: "Quick Ultrasound Processing & Simulation" type: software -version: v1.1.1 +version: v1.2.1 license: Apache-2.0 -date-released: 2024-04-07 +date-released: 2024-08-22 repository-code: "https://github.com/thorstone25/qups" diff --git a/build/coverage.xml b/build/coverage.xml index 0977ff0..7e2f223 100644 --- a/build/coverage.xml +++ b/build/coverage.xml @@ -1,10 +1,10 @@ - + /home/thurston/sandbox-2/qups/ - + @@ -14,7 +14,7 @@ - + @@ -140,15 +140,15 @@ - - - + + + - + @@ -277,7 +277,7 @@ - + @@ -350,26 +350,26 @@ - + - - - - - + + + + + - + - + @@ -385,110 +385,110 @@ - + - - - - - - + + + + + + - - + + - - + + - + - - + + - + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - + + - - + + - + @@ -510,47 +510,47 @@ - + - - - - - - - + + + + + + + - + - + - - + + - - - - + + + + @@ -829,92 +829,92 @@ - - - - - - - - + + + + + + + + - - - - - - - - - - - + + + + + + + + + + + - + - - - - - - + + + + + + - + - - - - - - - + + + + + + + - + - - - - - + + + + + - - + + - - - - + + + + @@ -982,19 +982,19 @@ - - + + - - - - - + + + + + @@ -1008,7 +1008,7 @@ - + @@ -1034,68 +1034,68 @@ - - + + - - - - - - - - - + + + + + + + + + - + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + - + @@ -1443,7 +1443,7 @@ - + @@ -1499,44 +1499,44 @@ - - - + + + - + - + - + - + - + - - + + - + - - + + @@ -1565,9 +1565,9 @@ - - - + + + @@ -1582,281 +1582,281 @@ - - + + - + - - + + - - + + - + - - - - + + + + - + - - + + - - - + + + - + - + - + - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - - + + - - + + - - - + + + - - + + - - + + - - + + - - - + + + - - + + - - - + + + - - + + @@ -1890,165 +1890,199 @@ - + - - - - - - - - - + + + + + + - + + + + + + + - - - - - + + + + + - + - - - - - - + + + + + + + + + + + + + - + - - - + + + - - - - - - - - - - - + + + + + + + + + + + - - - - + + + + - - - - - + + + + + - - - - - - + + + + + + + + + + + + - - - + + + - + - + - + + + + + + + - - - + + + + - + - - - + + + - - - - - - + + + + + + + + + + + + + + + + + @@ -2906,28 +2940,28 @@ - - - - - - - - - + + + + + + + + + - - + + - + @@ -2937,24 +2971,24 @@ - - - - - - + + + + + + - + - + @@ -2967,20 +3001,20 @@ - + - + - - + + @@ -2988,9 +3022,9 @@ - - - + + + @@ -3003,107 +3037,107 @@ - + - - - + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - + - - + + - + - - - - - - - - + + + + + + + + - - + + - + - + - - - - - - - - + + + + + + + + - + @@ -3114,139 +3148,139 @@ - - + + - + - + - + - + - + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + - + - - - - + + + + - - - + + + - + - - - - - + + + + + - - - - - - - + + + + + + + - - + + - + @@ -3256,24 +3290,24 @@ - - - - - - + + + + + + - + - + @@ -3286,20 +3320,20 @@ - + - + - - + + @@ -3307,148 +3341,148 @@ - - - + + + - - - - + + + + - + - - - - - - + + + + + + - + - - + + - - - - - - - + + + + + + + - - - - - - - - - - - + + + + + + + + + + + - + - - + + - + - - - - - - - - - + + + + + + + + + - + - + - - - - - - - - - + + + + + + + + + - - + + - + @@ -3544,7 +3578,7 @@ - + @@ -3554,7 +3588,7 @@ - + @@ -3579,16 +3613,16 @@ - - + + - - - + + + @@ -3840,7 +3874,7 @@ - + @@ -3860,7 +3894,7 @@ - + @@ -3890,7 +3924,7 @@ - + @@ -4134,8 +4168,8 @@ - - + + @@ -4457,31 +4491,31 @@ - + - + - - - + + + - - - - - + + + + + @@ -4598,76 +4632,76 @@ - + - + - + - + - + - - - + + + - + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - + @@ -4798,38 +4832,38 @@ - + - + - + - + - + - - - - - - + + + + + + @@ -4839,120 +4873,120 @@ - - - - - - - - - - - - + + + + + + + + + + + + - + - - - - - + + + + + - - + + - + - - - - - - - - - + + + + + + + + + - - - + + + - - - - + + + + - - - - - - + + + + + + - - + + - - - + + + - - - - - - - + + + + + + + - - - - + + + + @@ -5011,17 +5045,17 @@ - + - + - + @@ -5036,13 +5070,13 @@ - + - + @@ -5059,16 +5093,16 @@ - - + + - - - + + + @@ -5311,7 +5345,7 @@ - + @@ -5323,13 +5357,13 @@ - + - + @@ -5441,9 +5475,9 @@ - - - + + + @@ -5674,31 +5708,31 @@ - + - + - - - + + + - - - - - + + + + + @@ -5799,76 +5833,76 @@ - + - + - + - + - + - - - + + + - + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - + @@ -5976,127 +6010,127 @@ - - - - - + + + + + - - - - - - + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - + + + + + - - + + - + - - - - - - - - - + + + + + + + + + - - - + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + - - - - + + + + @@ -6135,9 +6169,9 @@ - - - + + + @@ -6146,13 +6180,13 @@ - + - + @@ -6529,14 +6563,14 @@ - - + + - + @@ -6884,7 +6918,7 @@ - + @@ -6989,7 +7023,7 @@ - + @@ -6999,23 +7033,23 @@ - + - - - - - - - - - - - + + + + + + + + + + + @@ -7026,22 +7060,22 @@ - + - + - + - + - + @@ -7243,13 +7277,13 @@ - + - - + + @@ -7347,35 +7381,35 @@ - + - + - - - - - - - - - - - + + + + + + + + + + + - - - - + + + + @@ -7558,9 +7592,9 @@ - - - + + + @@ -7573,26 +7607,26 @@ - + - + - - + + - - + + @@ -7631,70 +7665,70 @@ - - - - - - + + + + + + - + - + - + - + - + - + - + - + - + - + - + @@ -7714,7 +7748,7 @@ - + @@ -7724,7 +7758,7 @@ - + @@ -7734,7 +7768,7 @@ - + @@ -7744,7 +7778,7 @@ - + @@ -7754,7 +7788,7 @@ - + @@ -7764,7 +7798,7 @@ - + @@ -7774,7 +7808,7 @@ - + @@ -7789,29 +7823,29 @@ - - - + + + - + - - - + + + - - + + @@ -7834,109 +7868,109 @@ - - - - - - - + + + + + + + - + - + - - - - - - - - - + + + + + + + + + - + - + - + - + - + - + - + - - - + + + - + - + - - - - - + + + + + - + @@ -7959,19 +7993,19 @@ - + - - - - - - + + + + + + @@ -7989,12 +8023,12 @@ - + - + @@ -8009,46 +8043,46 @@ - + - - - - + + + + - + - + - + - + - + - + @@ -8093,7 +8127,7 @@ - + @@ -8158,33 +8192,33 @@ - - - - + + + + - + - - - - - - + + + + + + - + @@ -8195,20 +8229,20 @@ - - + + - - - - - - + + + + + + @@ -8222,30 +8256,30 @@ - - + + - + - - - - - - - - - - + + + + + + + + + + @@ -8258,7 +8292,7 @@ - + @@ -8291,37 +8325,37 @@ - - - - + + + + - + - + - + - - + + - - - + + + @@ -8345,34 +8379,34 @@ - - - - - + + + + + - + - + - - - - - - + + + + + + @@ -8380,21 +8414,21 @@ - + - + - + - - - - - - - + + + + + + + @@ -8418,32 +8452,32 @@ - + - + - + - + - + - + @@ -8553,31 +8587,31 @@ - - - - + + + + - + - - - + + + - - - + + + @@ -8589,49 +8623,49 @@ - - - - - - + + + + + + - + - + - - - - - - + + + + + + - + - + - - - - - - - + + + + + + + @@ -8643,12 +8677,12 @@ - - - - - - + + + + + + @@ -8694,23 +8728,23 @@ - - - - + + + + - + - + @@ -8729,34 +8763,34 @@ - - - - - + + + + + - + - + - - - - - - + + + + + + @@ -8801,32 +8835,32 @@ - + - + - + - + - + - + @@ -8936,21 +8970,21 @@ - - - - + + + + - + - + @@ -8962,30 +8996,30 @@ - - - - - + + + + + - + - + - - - - - - + + + + + + @@ -9014,12 +9048,12 @@ - - - - - - + + + + + + @@ -9065,25 +9099,25 @@ - - - - + + + + - + - - - + + + @@ -9108,13 +9142,13 @@ - + - + @@ -9128,25 +9162,25 @@ - - - + + + - - - + + + - - - + + + @@ -9186,8 +9220,8 @@ - - + + @@ -9227,13 +9261,13 @@ - - - - - - - + + + + + + + @@ -9273,41 +9307,41 @@ - - + + - + - + - + - + - + - - - + + + @@ -9332,13 +9366,13 @@ - + - + @@ -9348,25 +9382,25 @@ - - - + + + - - - + + + - - - + + + @@ -9395,8 +9429,8 @@ - - + + @@ -9427,13 +9461,13 @@ - - - - - - - + + + + + + + @@ -9465,19 +9499,19 @@ - - - - - - + + + + + + - + @@ -9487,7 +9521,7 @@ - + @@ -9584,31 +9618,31 @@ - - - + + + - + - - - + + + - - - + + + @@ -9621,7 +9655,7 @@ - + @@ -10041,37 +10075,37 @@ - - - + + + - - + + - - + + - - - - + + + + - + @@ -10083,23 +10117,23 @@ - - + + - - + + - + - + @@ -10121,35 +10155,35 @@ - + - - + + - - - + + + - + - + @@ -10183,14 +10217,14 @@ - - + + - + @@ -10210,55 +10244,55 @@ - - + + - - + + - + - + - + - + - + - + - + @@ -10275,19 +10309,19 @@ - + - + - + @@ -10348,13 +10382,13 @@ - + - + @@ -10373,17 +10407,17 @@ - + - + - + @@ -10403,11 +10437,11 @@ - - - + + + - + @@ -10417,7 +10451,7 @@ - + @@ -10504,31 +10538,31 @@ - - - + + + - + - - - + + + - - - + + + @@ -10541,7 +10575,7 @@ - + @@ -10952,36 +10986,36 @@ - - - - - - - - + + + + + + + + - - + + - - - - + + + + - + @@ -10993,23 +11027,23 @@ - - + + - - + + - + - + @@ -11027,35 +11061,35 @@ - + - - + + - - - + + + - + - + @@ -11081,14 +11115,14 @@ - - + + - + @@ -11108,44 +11142,44 @@ - - + + - - + + - - - - + + + + - - + + - + - + @@ -11155,19 +11189,19 @@ - + - + - + @@ -11205,13 +11239,13 @@ - + - + @@ -11226,9 +11260,9 @@ - - - + + + @@ -11303,56 +11337,56 @@ - - - - + + + + - + - + - + - + - - - + + + - + - - - - + + + + @@ -11362,7 +11396,7 @@ - + @@ -11388,7 +11422,7 @@ - + @@ -11411,63 +11445,63 @@ - - - - - - - - + + + + + + + + - + - + - + - - - - + + + + - - - - - - - - + + + + + + + + - + - + @@ -11489,39 +11523,39 @@ - + - + - + - - + + - + - + @@ -11540,24 +11574,24 @@ - - + + - - - + + + - - - + + + @@ -11568,40 +11602,40 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - + + + - - - + + + @@ -11614,8 +11648,8 @@ - - + + @@ -11623,42 +11657,42 @@ - + - - - - + + + + - - + + - - - - - - + + + + + + - - - - + + + + @@ -11945,13 +11979,13 @@ - - - - - - - + + + + + + + @@ -11967,12 +12001,12 @@ - + - + @@ -12043,8 +12077,8 @@ - - + + @@ -12055,39 +12089,39 @@ - + - + - + - - + + - + - + @@ -12099,63 +12133,63 @@ - - + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - + + + @@ -12163,40 +12197,40 @@ - + - - - - - - - - + + + + + + + + - - - - - - + + + + + + - - - - + + + + @@ -12448,20 +12482,20 @@ - - - - - - - + + + + + + + - - + + @@ -12515,8 +12549,8 @@ - - + + @@ -12525,23 +12559,23 @@ - + - - - + + + - - + + @@ -12551,48 +12585,48 @@ - - - + + + - - + + - - - + + + - - - - - - + + + + + + - - - - - - - - - - - + + + + + + + + + + + @@ -12732,8 +12766,8 @@ - - + + @@ -12749,15 +12783,15 @@ - - - - - - - - - + + + + + + + + + @@ -12801,15 +12835,15 @@ - - - - - - - - - + + + + + + + + + @@ -12874,60 +12908,60 @@ - + - + - - - + + + - - + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + @@ -13049,8 +13083,8 @@ - - + + @@ -13058,15 +13092,15 @@ - - - - - - - - - + + + + + + + + + @@ -13094,15 +13128,15 @@ - - - - - - - - - + + + + + + + + + @@ -13162,35 +13196,35 @@ - + - - - + + + - + - - + + - + @@ -13200,37 +13234,37 @@ - - - + + + - - + + - - - - - - + + + + + + - - - - - - - + + + + + + + @@ -13385,7 +13419,7 @@ - + @@ -13417,26 +13451,26 @@ - + - + - - - - - - - - - - + + + + + + + + + + @@ -13518,65 +13552,65 @@ - - - + + + - - - + + + - + - - + + - + - - - + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + @@ -13714,7 +13748,7 @@ - + @@ -13726,18 +13760,18 @@ - - - - - - - - - - - - + + + + + + + + + + + + @@ -13812,66 +13846,66 @@ - - - + + + - - - + + + - - + + - - - + + + - + - - - + + + - - + + - + - - - - - + + + + + @@ -13924,56 +13958,56 @@ - - - - - + + + + + - - - + + + - - + + - - - + + + - - - - + + + + - - - - - - - - + + + + + + + + @@ -14020,45 +14054,45 @@ - + - + - + - + - + - + - + @@ -14068,11 +14102,11 @@ - - - - - + + + + + @@ -14080,21 +14114,21 @@ - - + + - + - + @@ -14106,37 +14140,37 @@ - - - + + + - - + + - - - - - - - + + + + + + + - - - - - - + + + + + + @@ -14203,45 +14237,45 @@ - - + + - - - - - - - + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + @@ -14307,45 +14341,45 @@ - + - + - + - + - + - + - + @@ -14355,11 +14389,11 @@ - - - - - + + + + + @@ -14367,21 +14401,21 @@ - - + + - + - + @@ -14389,29 +14423,29 @@ - - - + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + @@ -14464,34 +14498,34 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -14548,50 +14582,50 @@ - + - - + + - + - + - - - - + + + + - - - - - - + + + + + + - + @@ -14601,7 +14635,7 @@ - + @@ -14610,12 +14644,12 @@ - - - - - - + + + + + + @@ -14628,7 +14662,7 @@ - + @@ -14676,27 +14710,27 @@ - - + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + @@ -14736,17 +14770,17 @@ - - - + + + - + - + @@ -14807,168 +14841,168 @@ - - - + + + - - + + - - + + - + - + - + - - + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + - + - + - + - - + + - - - - - + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - + + - + - + - + - - - - + + + + - + - + @@ -14987,14 +15021,14 @@ - - + + - + @@ -15013,23 +15047,23 @@ - - - - - - + + + + + + - - - - - - + + + + + + @@ -15042,22 +15076,22 @@ - - - - - - - - - - + + + + + + + + + + - + @@ -15156,20 +15190,20 @@ - + - - - - - - + + + + + + - + @@ -15427,19 +15461,19 @@ - - + + - - - - - - + + + + + + @@ -15455,58 +15489,58 @@ - + - - - - - - - - - - - + + + + + + + + + + + - + - - - + + + - + - - + + - + @@ -15516,47 +15550,47 @@ - - - + + + - + - + - - + + - - - + + + - - - - - - + + + + + + @@ -16243,7 +16277,7 @@ - + @@ -16267,9 +16301,9 @@ - - - + + + @@ -16283,7 +16317,7 @@ - + @@ -16297,13 +16331,13 @@ - + - + @@ -16330,8 +16364,8 @@ - - + + @@ -16350,22 +16384,22 @@ - - - - - - + + + + + + - - - - - + + + + + @@ -16389,81 +16423,81 @@ - + - - + + - + - - + + - - - + + + - - - - + + + + - - - + + + - - - + + + - + - - - - + + + + - + @@ -16473,29 +16507,29 @@ - - - - + + + + - + - - - - - - - - + + + + + + + + @@ -16533,30 +16567,30 @@ - - - - - - - - + + + + + + + + - + - - - - + + + + @@ -16857,71 +16891,71 @@ - - - - + + + + - + - + - - - - - - + + + + + + - + - + - - - - - - + + + + + + - - - + + + - - - + + + @@ -16932,7 +16966,7 @@ - + @@ -16954,8 +16988,8 @@ - - + + @@ -16966,7 +17000,7 @@ - + @@ -16988,58 +17022,58 @@ - - + + - + - - + + - + - - - - + + + + - - - + + + - - + + - - + + @@ -17047,11 +17081,11 @@ - - - - - + + + + + @@ -17181,25 +17215,25 @@ - - - + + + - - - + + + - - + + @@ -17208,26 +17242,26 @@ - - + + - - - - + + + + - - - - + + + + @@ -17236,42 +17270,42 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - + - - + + - - - - + + + + @@ -17288,29 +17322,29 @@ - + - + - + - - - - - - - - - - - + + + + + + + + + + + @@ -17321,8 +17355,8 @@ - - + + @@ -17366,69 +17400,80 @@ - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - + - - + + - + - + - - - - - + + + + + - + - + - + - + - + - + @@ -17436,364 +17481,386 @@ - + - - - - - - - - - - - - + + + + + + + + + + + + - - + + - + - + + + + + + + - - - - - - - - - - - - - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + - - - - - - - - - + + + + + + + + + - + - + - + - + - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - + + - - + + - - - + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - + + + - + - + - - + + - - + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - + - - + + - - + + - - - - - - - - - + + + + + + + + + - - - - + + + + - + - + - - - - + + + + - - - - - - + + + + + + - + @@ -17803,7 +17870,7 @@ - + @@ -17812,12 +17879,12 @@ - - - - - - + + + + + + @@ -17830,7 +17897,7 @@ - + @@ -17874,27 +17941,27 @@ - - + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + @@ -17926,17 +17993,17 @@ - - - + + + - + - + @@ -17986,173 +18053,173 @@ - - - + + + - - + + - - + + - + - - - - - - - - - - - + + + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + - + - + - + - - + + - - - - - + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - + + - + - + - + - - - - + + + + - + - + @@ -18171,14 +18238,14 @@ - - + + - + @@ -18197,23 +18264,23 @@ - - - - - - + + + + + + - - - - - - + + + + + + @@ -18226,22 +18293,22 @@ - - - - - - - - - - + + + + + + + + + + - + @@ -18340,20 +18407,20 @@ - + - - - - - - + + + + + + - + @@ -18599,28 +18666,28 @@ - - - - - - + + + + + + - - - + + + - - - - - - + + + + + + @@ -18636,58 +18703,58 @@ - + - - - - - - - - - - - + + + + + + + + + + + - + - - - + + + - + - - + + - + @@ -18697,47 +18764,47 @@ - - - + + + - + - + - - + + - - - + + + - - - - - - + + + + + + @@ -19445,7 +19512,7 @@ - + @@ -19469,9 +19536,9 @@ - - - + + + @@ -19485,7 +19552,7 @@ - + @@ -19499,13 +19566,13 @@ - + - + @@ -19532,8 +19599,8 @@ - - + + @@ -19544,31 +19611,31 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - + + + + + @@ -19592,115 +19659,115 @@ - + - - + + - + - - + + - - - + + + - - - - + + + + - - - + + + - - - + + + - + - - - - + + + + - + - - - - - - - - - + + + + + + + + + - + - - - - - - - - + + + + + + + + @@ -19738,30 +19805,30 @@ - - - - - - - - + + + + + + + + - + - - - - + + + + @@ -20074,85 +20141,85 @@ - - - - - - - - + + + + + + + + - - - - + + + + - + - + - - - - - - + + + + + + - + - + - - - - - - + + + + + + - - - - - - - - - + + + + + + + + + - - - - + + + + @@ -20163,7 +20230,7 @@ - + @@ -20185,8 +20252,8 @@ - - + + @@ -20197,7 +20264,7 @@ - + @@ -20219,58 +20286,58 @@ - - + + - + - - + + - + - - - - + + + + - - - + + + - - + + - - + + @@ -20278,11 +20345,11 @@ - - - - - + + + + + @@ -20412,49 +20479,49 @@ - - - + + + - - - + + + - - + + - - + + - - - - + + + + - - - - + + + + @@ -20463,39 +20530,39 @@ - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + @@ -20512,29 +20579,29 @@ - + - + - + - - - - - - - - - - - + + + + + + + + + + + @@ -20543,8 +20610,8 @@ - - + + @@ -20582,406 +20649,441 @@ - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - + + + + - - - - - - - - - + + + + + + + + + - + - - + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - + + + + + - + - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - + - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + - - + + - - + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - + + - - + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - + - + - + - + - + - + - - + + - + @@ -20992,7 +21094,7 @@ - + @@ -21115,46 +21217,46 @@ - - - + + + - + - - - - - - + + + + + + - + - + - - - - + + + + @@ -21171,7 +21273,7 @@ - + @@ -21181,28 +21283,28 @@ - + - + - + - + - + @@ -21257,27 +21359,27 @@ - - - + + + - - + + - - + + - + @@ -21288,7 +21390,7 @@ - + @@ -21401,57 +21503,57 @@ - - - + + + - - - - - - - - + + + + + + + + - + - + - - - - + + + + - + - - + + - - - + + + @@ -21796,25 +21898,25 @@ - + - + - + - + @@ -21822,9 +21924,9 @@ - - - + + + @@ -21901,21 +22003,21 @@ - + - - + + - + @@ -21968,13 +22070,13 @@ - + - + @@ -21982,16 +22084,16 @@ - - - - + + + + - + @@ -22002,20 +22104,20 @@ - + - + - - + + @@ -22040,85 +22142,85 @@ - - - - - - + + + + + + - - - - - - - - - + + + + + + + + + - - - + + + - + - + - + - + - + - - + + - - + + - - + + @@ -22129,38 +22231,40 @@ - - - - - - - + + + + + + + - - - - - - + + + + + + - - + + - - + + + +