Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
fcb2d0e
cleared old example data, fixed addpath function
ashkanpakzad Nov 11, 2022
ac6299a
deleted old documentation
ashkanpakzad Nov 13, 2022
743bcfa
now tries lung lobe classification and restored lobe colour order
ashkanpakzad Nov 13, 2022
fffbc6c
asserts for loops and gpu use more robust
ashkanpakzad Nov 13, 2022
1f03f4e
classify by region generation
ashkanpakzad Dec 5, 2022
54b2162
can now plot histograms by region
ashkanpakzad Dec 5, 2022
3491985
added workflow for clinical airways
ashkanpakzad Dec 5, 2022
06366e3
added csv method for saving patches
ashkanpakzad Dec 5, 2022
c284372
exportorthopatches now saves everything to tar
ashkanpakzad Dec 5, 2022
1c2c382
updated patch saving
ashkanpakzad Dec 5, 2022
ee8b4b5
added gpu flag to batch runs
ashkanpakzad Dec 5, 2022
a70c084
added loop check
ashkanpakzad Dec 5, 2022
fa9fdfd
Merge branch 'dev' of github.com:ashkanpakzad/AirQuant into dev
ashkanpakzad Dec 5, 2022
028905b
try to run, skip if fail in batch
ashkanpakzad Dec 6, 2022
1fa8e89
Merge branch 'dev' of github.com:ashkanpakzad/AirQuant into dev
ashkanpakzad Dec 6, 2022
394d913
made histogram tolerant if region field incomplete
ashkanpakzad Dec 8, 2022
15b4228
Merge branch 'dev' of github.com:ashkanpakzad/AirQuant into dev
ashkanpakzad Dec 8, 2022
dcb2809
added overwrite flag to batch flow
ashkanpakzad Dec 15, 2022
bd77adf
big speedup in patches, use reduced volume
ashkanpakzad Jan 7, 2023
1a0f2e6
updated bronchus intermedius classification
ashkanpakzad Feb 6, 2023
9de2715
Merge branch 'dev' of github.com:ashkanpakzad/AirQuant into dev
ashkanpakzad Feb 6, 2023
c6a679f
corrected lims indexing
ashkanpakzad Feb 6, 2023
a430df5
3d plots can be gauss smooth
ashkanpakzad Feb 9, 2023
f71503d
lobe placement standardised in plot
ashkanpakzad Feb 9, 2023
ad968d7
updated airwayCNN to version with seg
ashkanpakzad Feb 24, 2023
deba0ee
added catch for - ellipse params
ashkanpakzad Feb 24, 2023
edf6708
-ve ellipse impossible, fixed cnn inference io bloat
ashkanpakzad Feb 24, 2023
ce6ad5d
fixed bug caused by variable refactor
ashkanpakzad May 10, 2023
9adb824
input affine now only needs to be close to orthogonal
ashkanpakzad May 10, 2023
7e8d157
updated lobe classification for LUL/LLML errors
ashkanpakzad May 10, 2023
33b9c7f
try catch now reads errors for all tubes func
ashkanpakzad May 10, 2023
deec04c
removed external dependency measure methods
ashkanpakzad May 10, 2023
1774863
default prune, save patch preview, util fig funcs
ashkanpakzad May 11, 2023
3dee2ce
export graph lobe classifications
ashkanpakzad May 16, 2023
7e874f5
make lobe dataset
ashkanpakzad May 16, 2023
fdbd6a7
fix typo in make lobe dataset
ashkanpakzad May 16, 2023
ffa8a22
wf now saves a summary per case
ashkanpakzad May 17, 2023
f94582c
added initialisation by carina search
ashkanpakzad May 19, 2023
d10fe7c
fixed temp results output test
ashkanpakzad May 21, 2023
6aaad9f
creating temp branch, will retire later
Aug 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ docs/_*
old/
*.mat
*.ipynb_checkpoints
*.nii
*.nii.gz
16 changes: 15 additions & 1 deletion AirQuantAddPath.m
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
end

if nargout > 0
varargout{1} = AirQuantDir
varargout{1} = AirQuantDir;
end
% configure expected paths for AirQuant
results_dir = fullfile(AirQuantDir, 'results');
Expand All @@ -42,4 +42,18 @@
mkdir(results_dir)
end

function a = AQstruct2array(s)
% AQSTRUCT2ARRAY Convert structure with doubles to an array.
% Incase struct2array is not available.
%
% From
% https://uk.mathworks.com/matlabcentral/answers/1717910-was-struct2array-removed-from-matlab
%

% Convert structure to cell
c = struct2cell(s);
% Construct an array
a = [c{:}];

end
end
Empty file added data/airquant/.empty
Empty file.
Binary file removed data/example/github_demo_raw.nii.gz
Binary file not shown.
Binary file removed data/example/github_demo_seg.nii.gz
Binary file not shown.
Binary file removed data/example/github_demo_seg_PTKskel.nii.gz
Binary file not shown.
2 changes: 1 addition & 1 deletion library/AirQuant.m
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function toNii(obj, filename, options)
if isprop(obj,'sourceinfo')
info = obj.sourceinfo;
elseif isprop(obj,'network')
info = obj.network.sourceinfo;
info = obj.network.header;
end

if exist('info','var')
Expand Down
10 changes: 5 additions & 5 deletions library/dataio/CropVol.m
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@
% incase lower lims are < index 1, set to min possible
lims = varargin{2};
for ii = 1:3
if lims(ii) < 1
lims(ii) = 1;
if lims(ii,1) < 1
lims(ii,1) = 1;
end
end
% incase upper lims are > size dim, set to max possible
j = 1;
for ii = 4:6
if lims(ii) > s(j)
lims(ii) = s(j);
for ii = 1:3
if lims(ii,2) > s(j)
lims(ii,2) = s(j);
end
j = j+1;
end
Expand Down
9 changes: 8 additions & 1 deletion library/dataio/ReorientVolume.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@
%

% get affine matrix
aff_raw_RAS = meta.Transform.T;
aff_raw_RAS_loaded = meta.Transform.T;
% set values that are basically 0 to 0
zero_tol = aff_raw_RAS_loaded < 1e-6 & aff_raw_RAS_loaded > -1e-6;
aff_raw_RAS = aff_raw_RAS_loaded;
aff_raw_RAS(zero_tol) = 0;
if any(aff_raw_RAS_loaded ~= aff_raw_RAS)
warning('Oblique orientation, precision loss within 1e-6')
end
% remove origin information
aff_raw_RAS(4,1:3) = [0,0,0];
% remove spacing information
Expand Down
98 changes: 89 additions & 9 deletions library/graph/skel_2_digraph.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
function [digraphout, glinkout, gnode] = skel_2_digraph(skel, method)
function [digraphout, glinkout, gnode, isloops] = skel_2_digraph(skel, method)
% Generate digraph from skeleton.
%
% Generate digraph from skeleton using skel2graph library.
Expand Down Expand Up @@ -36,16 +36,34 @@
if nargin < 2
method = 'topnode';
end

isloops = 0;

[gadj,gnode,glink] = Skel2Graph3D(skel,1);
% choose originating node using chosen method


% Create digraph with edges in both directions, loop through
% branches and remove opposing direction to originating node.
G = digraph(gadj);
bins = conncomp(G);

edges_og = [[glink.n1]', [glink.n2]'];
edges_rev = [[glink.n2]', [glink.n1]'];
weights = cellfun(@numel, {glink.point});
weights = [weights'; weights'];
edges_twoway = [edges_og; edges_rev];
Edgetable = table(edges_twoway,weights,'VariableNames',{'EndNodes','Weight'});
G = digraph(Edgetable);
nodelist = 1:numnodes(G);

G_loop_check = digraph(gadj);
if length(glink) ~= height(G_loop_check.Edges)/2
warning('Skeleton appears to contain multiple edges between the same nodes. There may be unexpected behaviour')
isloops = 1;
end

[bins,binsize] = conncomp(G);
originnode = zeros(max(bins),1);

if isnumeric(method)
assert(length(unique(bins)) < 2, ['Can only be used with one ' ...
'connected component.'])
Expand All @@ -56,21 +74,77 @@

elseif strcmp(method,'topnode')
% use most superiour node as origin for each subgraph
originnode = zeros(max(bins),1);
for ii = 1:max(bins)
binbool = (bins==ii);
nodelist = 1:numnodes(G);
binidx = nodelist(binbool);
gnodeii = gnode(binidx);
[~, binorigin] = max([gnodeii.comz]);
originnode(ii) = binidx(binorigin);
end

elseif strcmp(method,'carina')
% airway specific method - largest cc first
[~, I] = max(binsize);
binbool = (bins==I);
binidx = nodelist(binbool);

g = subgraph(G, binidx);
gnodeI=gnode(binidx);
%%% identfy carina by most central position
pos = [[gnodeI.comx]', [gnodeI.comy]', [gnodeI.comz]'];
center = size(skel)/2;
centralist_node=zeros(length(pos),1);
for i=1:length(pos)
centralist_node(i) = norm(center-pos(i,:));
end
% rank centralist_node - minimise
[~, ~, centralist_node_rank] = unique(centralist_node);

%%% identify carina by greatest node sum weight
weightiest_node = centrality(g,'outdegree',...
'importance',g.Edges.Weight);

% rank weightiest - maximise
[~,~,weightiest_node_rank_rev] = unique(weightiest_node);
weightiest_node_rank = max(weightiest_node_rank_rev) - ...
weightiest_node_rank_rev+1;

%%% identify carina by graph closeness (stucturally central)
closest_node = centrality(g,'outcloseness');
[~,~,closest_node_rank_rev] = unique(closest_node);
closest_node_rank = max(closest_node_rank_rev) - ...
closest_node_rank_rev+1;

%%% determine carina by rank of all 3 measures
all_ranks = centralist_node_rank+weightiest_node_rank+closest_node_rank;
[~, carina_node] = min(all_ranks);

%%% identify origin by most superior neighbor
%%% determine trachea by superior pos node to carina
% pos of carina neighbors
carina_neighbors = predecessors(g, carina_node);
neighbor_pos = pos(carina_neighbors,:);
[~, neighbor_pos_idx] = max(neighbor_pos(:,3));
binorigin=carina_neighbors(neighbor_pos_idx);
originnode(I) = binidx(binorigin);

% remaining uses topnode
for ii = 1:max(bins)
if ii == I
continue
end
binbool = (bins==ii);
binidx = nodelist(binbool);
gnodeii = gnode(binidx);
[~, binorigin] = max([gnodeii.comz]);
originnode(ii) = binidx(binorigin);
end
else
error('Invalid method')
end


% BF search from carina node to distal for each disconnected graph.
% BF search from origin node to distal for each disconnected graph.
allnode_discovery = cell(max(bins),1);
for ii = 1:max(bins)
allnode_discovery{ii} = bfsearch(G,originnode(ii));
Expand Down Expand Up @@ -130,7 +204,7 @@
end
labels = [1:length(glink)]';
Edgetable = table(edges,weights,labels,'VariableNames',{'EndNodes', 'Weight', 'Label'});

digraphout = digraph(Edgetable);
% add node properties from Gnode
digraphout.Nodes.comx(:) = [gnode(:).comx];
Expand All @@ -139,6 +213,7 @@
digraphout.Nodes.ep(:) = [gnode(:).ep];
digraphout.Nodes.label(:) = [1:length(gnode)]';

try
% BFS per graph in digraph
% first convert digraph to graph to get CCs
bins = conncomp(digraphout,'Type','weak','OutputForm','cell');
Expand All @@ -158,12 +233,17 @@
E = [E; sub_E];
end

assert(length(E)==length(glink),'Number of edges in new edge BFS not expected.')
assert(length(E)==length(glink))

% convert E indicies to glink indicies
E_glink = digraphout.Edges.Label(E);

% reorder glink
glinkout = glink(E_glink);


catch
glinkout = glink;
warning('Failed to reorder edges by BFS. Likely due to presence loops.')
end

end
15 changes: 10 additions & 5 deletions library/perpinterp/PlaneInterpVol.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
function out_plane = PlaneInterpVol(vol, voxdim, point, normal, options)
function out_plane = PlaneInterpVol(whole_vol, voxdim, whole_point, normal, options)
% short desc
%
% long desc
Expand Down Expand Up @@ -28,9 +28,9 @@

% Construct vol grid
arguments
vol
whole_vol
voxdim {mustBeNumeric}
point (3,1) {mustBeNumeric}
whole_point (3,1) {mustBeNumeric}
normal (3,1) {mustBeNumeric}
options.plane_sz (1,1) = 40
options.sample_sz (1,1) = 0.5
Expand All @@ -39,6 +39,8 @@
options.offgrid_val (1,1) {mustBeNumeric} = 0
end

[vol,point] = min_tube_vol(whole_vol,whole_point,voxdim,options.plane_sz);

image_sz = single(size(vol));
[x_domain , y_domain , z_domain] = ...
meshgrid(1:image_sz(2),1:image_sz(1),1:image_sz(3));
Expand All @@ -51,9 +53,12 @@
plane_grid = Grids_coords_for_plane(basis_vecs(:,3),...
basis_vecs(:,2), point, options.plane_sz, options.sample_sz);


if options.gpu
assert(strcmp(options.method,'linear'),'Can only use "linear" method with gpu optimisation.')
assert(license('test','distrib_computing_toolbox'),' "Parallel Computing Toolbox required for gpu optimisation.')
assert(strcmp(options.method,'linear'),'Can only use "linear" method with gpu optimisation.')
verstruct = struct2cell(ver);
assert(contains([verstruct{:}], 'Parallel Computing Toolbox'), "Parallel Computing Toolbox required for gpu optimisation. Otherwise set gpu=0")


% use gpu
x_domain = gpuArray(x_domain);
Expand Down
39 changes: 39 additions & 0 deletions library/perpinterp/min_tube_vol.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
function [min_vol,min_point] = min_tube_vol(vol,point,zooms,plane_size)
% Returns the minimum volume required t make perpendicular slices of
% tubes.

arguments
vol (:,:,:)
point (3,1)
zooms (3,1)
plane_size (1,1)
end

% identify indices of bounding box
min_BB_mm = point - plane_size/2;
max_BB_mm = point + plane_size/2;

% transform to index
min_BB = floor(min_BB_mm./zooms);
max_BB = ceil(max_BB_mm./zooms);

% check BB does not exceed vol extremes
for ii = 1:length(min_BB)
if min_BB(ii) < 1
min_BB(ii) = 1;
end

maxdim = size(vol,ii);
if max_BB(ii) > maxdim
max_BB(ii) = maxdim;
end
end

% transform vol
min_vol = vol(min_BB(1):max_BB(1),min_BB(2):max_BB(2),...
min_BB(3):max_BB(3));

% transform point, plus zooms due to index 1
min_point = point - min_BB.*zooms+zooms;
end

11 changes: 0 additions & 11 deletions library/util/AQstruct2array.m

This file was deleted.

13 changes: 13 additions & 0 deletions library/util/default_dock_fig.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
function default_dock_fig(flag)
% set default behaviour for new figures to be docked.
if nargin < 1
flag = 1;
end

if flag == 1
set(0,'DefaultFigureWindowStyle','docked')
else
set(0,'DefaultFigureWindowStyle','normal')
end

end
16 changes: 16 additions & 0 deletions library/util/fig_save.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
function fig_save(fig_handle,path,ext)
% saves figure as both .mat and .png (determined by ext).
%
% path must not have an ext.
%

if nargin < 3
ext = '.png';
end

% save as MATLAB proprietary .fig
savefig(fig_handle, path)
% save as image file
exportgraphics(fig_handle, strcat(path,ext));
end

Loading