Skip to content
Open
  •  
  •  
  •  
98 changes: 98 additions & 0 deletions LUMO_toolbox/DOTHUB_LUMOcomputeRotations.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
function angle_deg = DOTHUB_LUMOcomputeRotations(xy_original, xy_rotated, plotFlag)
% This function estimates the rotation of source A from the loaded 2D coordinates
% after comparing the 2D coordinates of the original (fixed position) and rotated (loaded) tiles.
% The function does the following:
% 1. Find the centroid for the original and rotated sources (A,B,C),
% treating them as triangles.
% 2. Calculate the vector length from the centroid to the reference
% vertex (source A) for both triangles.
% 3. Compute the cosine of the angle between the two vectors, return the
% angle in radians and convert it into degrees.
%
% ######################## INPUTS #########################################
%
% xy_original : The 2D coordinates of the original ABC sources,
% making a triangle.
% xy_rotaded : The 2D coordinates of the rotated (loaded) ABC
% sources, making a triangle.
% plotFlag : A boolean value to enable a visualisation of the
% original and rotated vectors
%
% ####################### OUTPUTS #########################################
% angle_deg : The estimated degree of rotation
%
% ####################### Dependencies ####################################
%
% #########################################################################
% GL, UCL, April 2024

% #########################################################################

% extract x, y original and rotated
x_original = xy_original(:, 1);
y_original = xy_original(:, 2);
x_rotated = xy_rotated(:, 1);
y_rotated = xy_rotated(:, 2);

% Find centroid for original and rotated coordinates
xor_centroid = mean(x_original);
yor_centroid = mean(y_original);
xrot_centroid = mean(x_rotated);
yrot_centroid = mean(y_rotated);

% Calculate vector components from centroid to source 1 (reference source)
vx_original = x_original(1) - xrot_centroid;
vy_original = y_original(1) - yrot_centroid;
vx_rotated = x_rotated(1) - xrot_centroid;
vy_rotated = y_rotated(1) - yrot_centroid;

%% Plot
if plotFlag
figure(1)
scatter(x_original, y_original, 'r');
hold on
scatter(xor_centroid, yor_centroid, 'black');
% Plot the vector from centroid to source 1
quiver(xor_centroid, yor_centroid, vx_original, vy_original, 0, 'red', 'LineWidth', 2);
hold on
% Overlay the rotated triangle
scatter(x_rotated, y_rotated, 'blue');
hold on
scatter(xrot_centroid, yrot_centroid, 'blue');
% Plot the vector from centroid to rotated source
quiver(xrot_centroid, yrot_centroid, vx_rotated, vy_rotated, 0, 'blue', 'LineWidth', 2);
hold off
end

%%
% Compute the dot product of vectors v1 and v2
dot_product = dot([vx_original, vy_original], [vx_rotated, vy_rotated]);

% Establish if rotation is clockwise or anti-clockwise
if vx_rotated < vx_original
% counter-clockwise rotation
rotation_direction = 'left-side';
elseif vx_rotated > vx_original
% clockwise rotation
rotation_direction = 'right-side';
else
rotation_direction = '180';
end

% Compute the magnitudes of vectors v1 and v2
magnitude_v1 = norm([vx_original, vy_original]);
magnitude_v2 = norm([vx_rotated, vy_rotated]);

% Compute the cosine of the angle between v1 and v2
cos_angle = dot_product / (magnitude_v1 * magnitude_v2);

% Compute the angle in radians
angle_rad = acos(cos_angle);

% Convert the angle from radians to degrees
angle_deg = rad2deg(angle_rad);

% Adjust for rotation on the right-side of original vector
if strcmp(rotation_direction, 'right-side')
angle_deg = 360 - angle_deg;
end
96 changes: 83 additions & 13 deletions LUMO_toolbox/DOTHUB_LUMOcreate2DSD.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
tmp = inputdlg('Enter number of LUMO tiles...','Tile number');
nTiles = str2num(tmp{1});
end

xspace = 17.5;
yspace = 15.1505;
ng = 12;
Expand Down Expand Up @@ -49,9 +50,9 @@
uit.ColumnWidth = {60,60,60};
uit.ColumnEditable = true;
uit.CellEditCallback = @updatePlot;
uit.Position = [0.05*ss(1) 0.05*ss(2) 0.15*ss(1) 0.75*ss(2)];
uit.Position = [0.025*ss(1) 0.05*ss(2) 0.15*ss(1) 0.85*ss(2)];
ax = uiaxes(fig);
ax.Position = [0.25*ss(1) 0.25*ss(2) 0.7*ss(1) 0.60*ss(2)];
ax.Position = [0.25*ss(1) 0.25*ss(2) 0.7*ss(1) 0.60*ss(2)]; % position of the Grid
bt = uibutton(fig,'Text','FINISHED');
bt.Position = [0.85*ss(1) 0.05*ss(2) 0.1*ss(1) 0.075*ss(2)];
bt.ButtonPushedFcn = @SaveandQuit;
Expand All @@ -60,6 +61,11 @@
bt2.Position = [0.25*ss(1) 0.05*ss(2) 0.1*ss(1) 0.075*ss(2)];
bt2.ButtonPushedFcn = @RotateGrid;

% Add button to load 2D layout from existing file
bt3 = uibutton(fig, 'Text', 'Load');
bt3.Position = [0.35*ss(1) 0.05*ss(2) 0.1*ss(1) 0.075*ss(2)];
bt3.ButtonPushedFcn = @Load2Dfile;

[source_array, detector_array] = getTileOptodePos_Callback;


Expand Down Expand Up @@ -117,6 +123,8 @@ function updatePlot(ssrc,event)
uit.Data(:,1:2) = centers(:,1:2);
uit.Data(:,3) = orientations;
[source_array, detector_array] = getTileOptodePos_Callback;

ax.Position = [0.2*ss(1) 0.2*ss(2) 0.8*ss(1) 0.75*ss(2)]; % position of the Grid
end

function [source_array, detector_array] = getTileOptodePos_Callback
Expand Down Expand Up @@ -148,21 +156,33 @@ function updatePlot(ssrc,event)
hold(ax,'on');

%plot grid
plot(ax,grid(:,1),grid(:,2),'k.','markersize',10);hold on;
plot(ax,grid(:,1),grid(:,2),'.','markersize',10, 'Color', [.7 .7 .7]);
hold on;
%plot array
plot(ax,source_array(:,1),source_array(:,2),'r.','markersize',30);
plot(ax,detector_array(:,1),detector_array(:,2),'b.','markersize',30);
for i = 1:size(source_array,1)
text(ax,source_array(i,1)+3,source_array(i,2),['S' num2str(i)],'color','r','FontSize',8);
end
for i = 1:size(detector_array,1)
text(ax,detector_array(i,1)+3,detector_array(i,2),['D' num2str(i)],'color','b','FontSize',8);
end
plot(ax,source_array(:,1),source_array(:,2),'r.','markersize',25);
plot(ax,detector_array(:,1),detector_array(:,2), 'b.','markersize',25);

% for i = 1:size(source_array,1)
% text(ax,source_array(i,1)+3,source_array(i,2),['S' num2str(i)],'color','r','FontSize',8);
% end

% add the number of tile anchored to Source3
source_counter = 1;
for n_source=3:3:size(source_array,1)
text(ax,source_array(n_source,1),source_array(n_source,2)+4,['T' num2str(source_counter)],'color','k','FontSize', 15);
source_counter = source_counter + 1;
end

% remove text of detectors
% for i = 1:size(detector_array,1)
% text(ax,detector_array(i,1)+3,detector_array(i,2),['D' num2str(i)],'color','b','FontSize',8);
% end
linend = source_array(1:3:end,[1 2]);
linstart = detector_array(3:4:end,[1 2]);

for i = 1:size(linend,1);
line(ax,[linstart(i,1) linend(i,1)],[linstart(i,2) linend(i,2)],'color','g','LineWidth',2);hold on;
plot(ax,linstart(i,1),linstart(i,2),'g.','MarkerSize',20);
line(ax,[linstart(i,1) linend(i,1)],[linstart(i,2) linend(i,2)],'color','#00DB4C','LineWidth',3);hold on;
% plot(ax,linstart(i,1),linstart(i,2),'g.','MarkerSize',20);
end

tmp = [source_array(:,1); detector_array(:,1)];
Expand Down Expand Up @@ -196,6 +216,56 @@ function RotateGrid(~,~)
getTileOptodePos_Callback;
end

function Load2Dfile(~,~)
% choose a file using a graphical file picker window
[file, path] = uigetfile('*.*', 'Select a file');
fname = fullfile(path, file);
layout_2D = load(fname, "-mat"); % the loaded structure

% Warning if the loaded file contains a different number from
% intended nTiles
loaded_nTiles = size(layout_2D.SD.DetPos, 1)/4;
if loaded_nTiles ~= nTiles
warning_msg = sprintf('The loaded SD files contains %d tiles. Initial nTiles input was %d', loaded_nTiles, nTiles);
uiwait(warndlg(warning_msg));

quest = {'Do you want to continue?'};
dlgtitle = 'Warning';
fieldsize = [1 45];
answer = questdlg(quest, dlgtitle, 'Yes', 'No', 'Cancel');

switch answer
case 'No'
error_msg = 'The number of loaded tiles does not match the initial nTiles input';
uiwait(errordlg(error_msg));
error('The number of loaded tiles does not match the initial nTiles input');
end
end

% extract the x, y values every 3 row (corresponding to the central
% detector
detCounter = 1;
for i = 3:4:size(detector_array, 1)
uit.Data(detCounter, 1:2) = layout_2D.SD.DetPos(i, 1:2);

detCounter = detCounter + 1;
end
updatePlot; % update the plot

srcCounter = 1;
for i=3:3:size(source_array,1)
source_original = source_array(i-2:i, :);
source_rotated = layout_2D.SD.SrcPos(i-2:i, :);
% Extract the degree of the rotated angle
angle_deg = DOTHUB_LUMOcomputeRotations(source_original, source_rotated, 0);
% update the data and the plot
uit.Data(srcCounter, 3) = angle_deg;
srcCounter = srcCounter + 1;
end
updatePlot;

end

function SaveandQuit(~,~)

SD_2Dtmp.SrcPos = source_array;
Expand Down
113 changes: 69 additions & 44 deletions LUMO_toolbox/DOTHUB_LUMOpolhemus2SD3D.m
Original file line number Diff line number Diff line change
Expand Up @@ -41,34 +41,62 @@

%Manage Inputs ###########################################################
%#########################################################################

% Variables
slg_Flag = -1; % flag to determine whether the user has selected SLGs
LG_length_Flag = 0; % flag to determine if the user uses fixed LGs
FLG_infant = 12.94; % TOTAL offset of the infant fixed light guide used when offsetting the Polhemus values
FLG_adult = 18.75; % TOTAL offset of the adult fixed light guide used when offsetting the Polhemus values

% Physical length of the movable light guide component. DO NOT CHANGE.
short_SLG_full_l = 3.2; % fully extended movable light guide for a short SLG
medium_SLG_full_l = 5.24;
long_SLG_full_l = 9.13;

% The compression value (compression in % / 100)
% Note: These values correspond to the average compression values
% obtained across subjects (N=3).
short_SLG_compression = 0.32;
medium_SLG_compression = 0.14;
long_SLG_compression = 0.14;

% Length of the movable light guide(in mm) after being compressed
short_SLG_compression_l = short_SLG_compression * short_SLG_full_l;
medium_SLG_compression_l = medium_SLG_compression * medium_SLG_full_l;
long_SLG_compression_l = long_SLG_compression * long_SLG_full_l;

% Calculate the SLGs offset
% offset = fully extended movable light guide - compression length
short_SLG_offset = short_SLG_full_l - short_SLG_compression_l;
medium_SLG_offset = medium_SLG_full_l - medium_SLG_compression_l;
long_SLG_offset = long_SLG_full_l - long_SLG_compression_l;

if ~exist('posCSVFileName','var')
[file,path] = uigetfile('*.csv','Select Polhemus data set (.csv)','MultiSelect','on');
posCSVFileName = fullfile(path,file);
end

mixedFlag = 0;
if ~exist('infantFlag','var')
answer = questdlg('Which light-guides were used in this array?','Select light guide type','ADULT','INFANT','MIXED','ADULT');
if strcmp(answer,'INFANT')
infantFlag = 1;
elseif strcmp(answer,'ADULT')
infantFlag = 0;
elseif strcmp(answer,'MIXED')
mixedFlag = 1;
else
return
answer = questdlg('Which light-guides were used in this array?','Select light guide type','ADULT','INFANT','SLGs','ADULT');
switch answer
case 'INFANT'
LG_length_Flag = 0;
case 'ADULT'
LG_length_Flag = 1;
case 'SLGs'
LG_length_Flag = 2;
end
elseif isempty(infantFlag)
answer = questdlg('Which light-guides were used in this array?','Select light guide type','ADULT','INFANT','MIXED','ADULT');
if strcmp(answer,'INFANT')
infantFlag = 1;
elseif strcmp(answer,'ADULT')
infantFlag = 0;
elseif strcmp(answer,'MIXED')
mixedFlag = 1;
else
return
end

% SLG selection
if LG_length_Flag == 2
answer = questdlg('What length of SLGs did you use?', 'Select SLG Length', 'SHORT', 'MEDIUM', 'LONG', 'MEDIUM');
switch answer
case 'SHORT'
slg_Flag = 0;
case 'MEDIUM'
slg_Flag = 1;
case 'LONG'
slg_Flag = 2;
end
end

if ~exist('saveFlag','var')
Expand All @@ -84,7 +112,7 @@
ext = '.csv';
end
posCSVFileName = fullfile(path,[name ext]);
SD3DFileName = fullfile(path,[name '.SD3D']);
SD3DFileName = fullfile(path,[(strcat(name, '_', answer)) '.SD3D']);

%Wavelengths
wavelength1 = 735;
Expand Down Expand Up @@ -118,27 +146,24 @@
nTiles = size(PolPoints,1)/3;
end

%Define offsets vector
if mixedFlag
prompt = 'Enter nTile space-separated values of 1 (Adult) and 0 (Infant)';
dlgtitle = 'Light guide specification';
dims = [1 45];
answer = inputdlg(prompt,dlgtitle,dims);
answer = str2num(cell2mat(answer));
if isempty(answer)
error('No light guide specification provided')
else
offset = zeros(nTiles,1);
offset(answer==1) = 18.75;
offset(answer==0) = 12.94;
if any(offset==0)
error('Error in light guide specification')
end
end
elseif infantFlag==1
offset = ones(nTiles,1)*12.94; %Infant offset length (mm)
elseif infantFlag==0
offset = ones(nTiles,1)*18.75; %Adult offset length (mm)
%Define the offsets vector
if LG_length_Flag==2 % 2=SLG

switch slg_Flag
case 0
offset = ones(nTiles,1)*(FLG_adult + short_SLG_offset); %short SLG offset (mm)
case 1
offset = ones(nTiles,1)*(FLG_adult + medium_SLG_offset); %medium SLG offset (mm)
case 2
offset = ones(nTiles,1)*(FLG_adult + long_SLG_offset); %long SLG offset (mm)
end
else
switch LG_length_Flag
case 0
offset = ones(nTiles,1)*FLG_infant; %Infant offset length (mm)
case 1
offset = ones(nTiles,1)*FLG_adult; %Adult offset length (mm)
end
end

%Translate and Rotate so that Iz is at 0 0 0, Nz is at 0 y 0, Ar and Al have same z
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version='1.0' encoding='UTF-8'?>
<Info />
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version='1.0' encoding='UTF-8'?>
<Info location="lumo.log" type="File" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version='1.0' encoding='UTF-8'?>
<Info />
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version='1.0' encoding='UTF-8'?>
<Info location="recordingdata.toml" type="File" />
Loading