├── 3D reconstruction
├── vec.m
├── soft.m
├── linear_gradient_b.m
├── power_iters.m
├── read_tiff_stack.m
├── tv3d_iso_Haar.m
├── MiniscopeFISTA.m
├── Miniscope_shift_varying_recon.m
└── Miniscope_3d_shift_varying_main.m
├── LICENSE
├── README.md
└── Adaptive Stitching with Nanoscribe
├── TipSlicerUpdated.py
└── Adaptive-Stitching-Nanoscribe.ipynb
/3D reconstruction/vec.m:
--------------------------------------------------------------------------------
1 | function y = vec(x)
2 | y=x(:);
--------------------------------------------------------------------------------
/3D reconstruction/soft.m:
--------------------------------------------------------------------------------
1 | function y = soft(x,T)
2 |
3 | y = sign(x).*max(abs(x) - T, 0);
4 | %y = y./(y+T) .* x;
5 |
6 |
7 |
--------------------------------------------------------------------------------
/3D reconstruction/linear_gradient_b.m:
--------------------------------------------------------------------------------
1 | function [f, g] = linear_gradient_b(x,A,At,b)
2 | % Generalized gradient step for ||Ax-b|| cost function
3 | % Takes in variable, x, linear operator, A, and adjoint, At. Does not
4 | % require passing in Atb
5 | %
6 | % outputs:
7 | % g: Gradient. Does not need to be a vector.
8 | % f: Cost function value
9 |
10 | u = A(x);
11 | g = At(u-b);
12 | f = norm(u(:)-b(:));
--------------------------------------------------------------------------------
/3D reconstruction/power_iters.m:
--------------------------------------------------------------------------------
1 | function mu_out = power_iters(A,sz)
2 | %A = @(x)Aadj_3d(A3d(x));
3 | %sz = [Ny, Nx, Nz];
4 | bk = gpuArray(single(randn(sz)));
5 | k = 0;
6 | maxiter = 100;
7 | mu = gpuArray(zeros(1));
8 | resid = 1e10;
9 | while resid > .0001 && k<=maxiter
10 | k = k+1;
11 | Abk = A(bk);
12 | mu(k) = transpose(conj(bk(:)))*Abk(:)/(norm(bk(:))^2);
13 | if k >= 2
14 | resid = abs(mu(k) - mu(k-1));
15 | else
16 | resid = 1e10;
17 | end
18 | bknorm = norm(bk(:));
19 | bk = Abk/bknorm;
20 | fprintf('iter %i \t Eigenvalue %.6f \t residual %.6f \n',k,mu,resid)
21 | end
22 | mu_out = mu(end);
--------------------------------------------------------------------------------
/3D reconstruction/read_tiff_stack.m:
--------------------------------------------------------------------------------
1 | function stack = read_tiff_stack(path,varargin)
2 | %stack = read_tiff_stack(path,downsample_ratio,list_of_images)
3 | %list_of_images is a vector of indices to read out. if empty, read all.
4 |
5 |
6 |
7 | info = imfinfo(path);
8 | num_images_in = numel(info);
9 |
10 | if nargin>1
11 | ds = varargin{1};
12 | if nargin > 2
13 | k_list = varargin{2};
14 | else
15 | k_list = 1:num_images_in;
16 | end
17 | else
18 | ds = 1;
19 | end
20 | num_planes = length(k_list);
21 | if info(1).SamplesPerPixel == 1
22 |
23 | stack = zeros(info(1).Height/ds, info(1).Width/ds,num_planes);
24 | else
25 | stack = zeros(info(1).Height/ds, info(1).Width/ds,info(1).SamplesPerPixel,num_planes);
26 | end
27 | n = 0;
28 | for k = k_list
29 | n = n+1;
30 | if info(1).SamplesPerPixel == 1
31 | stack(:,:,n) = imresize(imread(path, k, 'Info', info),1/ds,'box');
32 | else
33 | stack(:,:,:,n) = imresize(imread(path, k, 'Info', info),1/ds,'box');
34 | end
35 |
36 | end
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2020, Waller Lab
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | 3. Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/3D reconstruction/tv3d_iso_Haar.m:
--------------------------------------------------------------------------------
1 | function y = tv3d_iso_Haar(x, tau, alpha)
2 |
3 | % Private functions here
4 | % circshift does circular shifting
5 | % indexing: x(5:10), 1 indexed. Use x(5:4:end-6) to index in strides of 4
6 | % to the 6th-to-last element
7 | D = 3;
8 | gamma = 1; %step size
9 | thresh = sqrt(2) * 2 * D * tau * gamma;
10 | y = zeros(size(x), 'like', x);
11 | for axis = 1 : 3
12 | if axis == 3
13 | t_scale = alpha;
14 | else
15 | t_scale = 1;
16 | end
17 | y = y + iht3(ht3(x, axis, false, thresh*t_scale), axis, false);
18 | y = y + iht3(ht3(x, axis, true, thresh*t_scale), axis, true);
19 | end
20 | y = y / (2 * D);
21 | return
22 |
23 | function w = ht3(x, ax, shift, thresh)
24 | s = size(x);
25 | w = zeros(s, 'like', x);
26 | C = 1 / sqrt(2);
27 | if shift
28 | x = circshift(x, -1, ax);
29 | end
30 | m = floor(s(ax) / 2);
31 | if ax == 1
32 | w(1:m, :, :) = C * (x(2:2:end, :, :) + x(1:2:end, :, :)); % use diff or circhisft?
33 | w((m + 1):end, :, :) = hs_soft(C * (x(2:2:end, :, :) - x(1:2:end, :, :)), thresh);
34 | %w((m + 1):end, :, :) = hs_soft(w((m + 1):end, :, :), thresh);
35 | elseif ax == 2
36 | w(:, 1:m, :) = C * (x(:, 2:2:end, :) + x(:, 1:2:end, :));
37 | w(:, (m + 1):end, :) = hs_soft(C * (x(:, 2:2:end, :) - x(:, 1:2:end, :)), thresh);
38 | %w(:, (m + 1):end, :) = hs_soft(w(:, (m + 1):end, :), thresh);
39 | else
40 | w(:, :, 1:m) = C * (x(:, :, 2:2:end) + x(:, :, 1:2:end));
41 | w(:, :, (m + 1):end) = hs_soft(C * (x(:, :, 2:2:end) - x(:, :, 1:2:end)), thresh);
42 | %w(:, :, (m + 1):end) = hs_soft(w(:, :, (m + 1):end), thresh);
43 | end
44 | return
45 |
46 | function y = iht3(w, ax, shift)
47 | s = size(w);
48 | y = zeros(s, 'like', w);
49 | C = 1 / sqrt(2);
50 | m = floor(s(ax) / 2);
51 | if ax == 1
52 | y(1:2:end, :, :) = C * (w(1:m, :, :) - w((m + 1):end, :, :));
53 | y(2:2:end, :, :) = C * (w(1:m, :, :) + w((m + 1):end, :, :));
54 | elseif ax == 2
55 | y(:, 1:2:end, :) = C * (w(:, 1:m, :) - w(:, (m + 1):end, :));
56 | y(:, 2:2:end, :) = C * (w(:, 1:m, :) + w(:, (m + 1):end, :));
57 | else
58 | y(:, :, 1:2:end) = C * (w(:, :, 1:m) - w(:, :, (m + 1):end));
59 | y(:, :, 2:2:end) = C * (w(:, :, 1:m) + w(:, :, (m + 1):end));
60 | end
61 | if shift
62 | y = circshift(y, 1, ax);
63 | end
64 | return
65 |
66 | function threshed = hs_soft(x,tau)
67 |
68 | threshed = max(abs(x)-tau,0);
69 | threshed = threshed.*sign(x);
70 | return
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |

2 |
3 | # [Miniscope3D](https://waller-lab.github.io/Miniscope3D/)
4 |
5 |
6 |
7 | ## Paper
8 | [Miniscope3D: optimized single-shot miniature 3D fluorescence microscopy](https://www.nature.com/articles/s41377-020-00403-7)
9 |
10 | Please cite the following paper when using this code or data:
11 |
12 |
13 | ```
14 | Yanny, Kyrollos, et al. "Miniscope3D: optimized single-shot miniature 3D fluorescence microscopy." Light: Science & Applications 9.1 (2020): 1-13.
15 |
16 | @article{yanny2020miniscope3d,
17 | title={Miniscope3D: optimized single-shot miniature 3D fluorescence microscopy},
18 | author={Yanny, Kyrollos and Antipa, Nick and Liberti, William and Dehaeck, Sam and Monakhova, Kristina and Liu, Fanglin Linda and Shen, Konlin and Ng, Ren and Waller, Laura},
19 | journal={Light: Science \& Applications},
20 | volume={9},
21 | number={1},
22 | pages={1--13},
23 | year={2020},
24 | publisher={Nature Publishing Group}
25 | }
26 |
27 | ```
28 |
29 |
30 | ## Contents
31 |
32 | 1. [Data](#Data)
33 | 2. [Setup](#Setup)
34 | 3. [Description](#Description)
35 |
36 | ## Data
37 | Sample data and PSFs (needed to run the code) can be found [here](https://drive.google.com/drive/folders/1E9Rt8edPrkn80qfUbDhEnLme98MPtQ2w?usp=sharing)
38 |
39 | This includes the following files:
40 | * calibration.mat - includes the calibratated point spread function, filter function, and wavelength list
41 | * four sample raw measurements
42 |
43 |
44 | ## Setup
45 | Clone this project using:
46 | ```
47 | git clone https://github.com/Waller-Lab/Miniscope3D.git
48 | ```
49 | ### 1) 3D Reconstruction
50 | First, download the sample data folder mentioned above. This contains PSF components and weights needed for the shift-varying forward model as well as waterbear raw data.
51 |
52 | Then, open the Miniscope_3d_shift_varying_main.m file in the 3D Reconstruction folder. This file is the main file that runs the reconstruction. You first need to edit the psf_path variable to the folder path where you downloaded the weights and components (SVD_2_5um_PSF_5um_1_ds4_components.mat and SVD_2_5um_PSF_5um_1_ds4_weights.mat).
53 |
54 | Then, run the file, a UI will show up asking you to choose a measurement file and background file. The measurement file is the data you want to deconvolve (e.g. waterbear raw data) and the background is a background image captured before taking the data. This background is subtracted from the data file (to account for back reflections)
55 |
56 | ### 2) Adaptive Stitching with Nanoscribe
57 | This code performs adaptive stitching for randomly spaced microlenses that are multifocal and have aberrations added to them. The adaptive stitching is needed for the microlenses to be correctly 3D printed with Nanoscribe. The code places the stitching artifacts at the boundaries of the microlenses and thus maximizes optical quality.
58 |
59 | The demonstration here will be for the 36 multifocal microlenses with astigmatism and tilt added. This is the phase mask used in the paper. The code also provides the necessary .gwl files needed for the print-job on Nanoscribe
60 |
61 |
62 |
63 | ## Description
64 | This repository contains code for the Miniscope3D paper. The code includes: 1) 3D Reconstruction with Shif-Varying model (matlab). This code reconstructs 3D volumes from a single 2D image. 2) Adaptive Stitching with Nanoscribe (python). This code performs the adaptive stitching needed to 3D print the phase mask with Nanoscribe. We might be adding Microlens phase mask optimization (python) in the future.
65 |
66 |
--------------------------------------------------------------------------------
/3D reconstruction/MiniscopeFISTA.m:
--------------------------------------------------------------------------------
1 | function [out,varargout] = MiniscopeFISTA(GradErrHandle,ProxFunc,xk,options)
2 |
3 | % Out = MiniscopeFISTA(GradErrHanle,ProxHandle,AxyTxy0,measurement,options)
4 | %
5 | % GradErrHandle: handle for function that computes error and gradient at
6 | % each step
7 | %
8 | % ProxFunc: handle for function that does projection step
9 | %
10 | % xk : initial guess for volume
11 | %
12 | % options : structure with optimization options.
13 | %
14 | % Nick Antipa and Kyrollos Yanny 2020
15 |
16 | if ~isfield(options,'convTol')
17 | options.convTol = 1e-9;
18 | end
19 | if ~isfield(options,'residTol')
20 | options.residTol = 1e-2;
21 | end
22 | if ~isfield(options,'xsize')
23 | options.xsize = size(A,2);
24 | end
25 | if ~isfield(options,'momentum')
26 | options.momentum = 'nesterov';
27 | end
28 | if ~isfield(options,'disp_figs')
29 | options.disp_figs = 0;
30 | end
31 | if ~isfield(options,'restarting')
32 | options.restarting = 0;
33 | end
34 | if ~isfield(options,'print_interval')
35 | options.print_interval = 1;
36 | end
37 | if ~isfield(options,'color_map')
38 | options.color_map = 'parula';
39 | end
40 | if ~isfield(options,'save_progress')
41 | options.save_progress = 0;
42 | end
43 | if ~isfield(options,'restart_interval')
44 | options.restart_interval = 0;
45 | end
46 | if ~isfield(options,'disp_crop')
47 | options.disp_crop = @(x)x;
48 | end
49 | if ~isfield(options,'disp_prctl')
50 | options.disp_prctl = 99.999;
51 | end
52 | if options.save_progress
53 | if ~isfield(options,'save_progress')
54 | options.progress_file = 'prox_progress.avi';
55 | end
56 | if exist(options.progress_file,'file')
57 | overwrite_mov = input('video file exists. Overwrite? y to overwrite, n to abort.');
58 | if strcmpi(overwrite_mov,'n')
59 | new_mov_name = input('input new name (no extension): ');
60 | options.progress_file = [new_mov_name,'.avi'];
61 | end
62 | end
63 | options.vidObj = VideoWriter(options.progress_file);
64 | open(options.vidObj);
65 | end
66 |
67 | % Initialize
68 | step_num = 0;
69 | fun_val = zeros([options.maxIter,1],'like',xk);
70 | tk = ones(1,'like',xk);
71 | yk = xk;
72 | f = 1e12*ones(1,'like',xk);
73 | tic
74 | while (step_num < options.maxIter) && (f>options.residTol)
75 |
76 | step_num = step_num+1;
77 | [f_kp1, g] = GradErrHandle(yk);
78 | fun_val(step_num) = f_kp1;
79 | [x_kp1, norm_x] = ProxFunc(yk-options.stepsize*g);
80 | fun_val(step_num) = fun_val(step_num)+norm_x; %To do: this should be tau*norm_x?
81 |
82 | % Compute momentum terms
83 | t_kp1 = (1+sqrt(1+4*tk^2))/2;
84 | beta_kp1 = (tk-1)/t_kp1;
85 | restart = (yk(:)-x_kp1(:))'*vec(x_kp1 - xk);
86 | yk = x_kp1+beta_kp1*(x_kp1 - xk);
87 |
88 | if step_num == 1
89 | if options.known_input
90 | fprintf('Iteration \t objective \t ||x|| \t momentum \t MSE \t PSNR\n');
91 | else
92 | fprintf('Iter\t ||Ax-b|| \t ||x|| \t Obj \t sparsity \t momentum \t elapsed time\n');
93 | end
94 | end
95 | if restart<0 && mod(step_num,options.restart_interval)==0
96 | fprintf('reached momentum reset interval\n')
97 | restart = Inf;
98 | end
99 |
100 | if ~mod(step_num,options.print_interval)
101 |
102 | if options.known_input
103 | fprintf('%i\t %6.4e\t %6.4e\t %.3f\t %6.4e\t %.2f dB\n',...
104 | step_num,f,norm_x,tk,...
105 | norm(options.xin(:) - yk(:)),...
106 | psnr(gather(yk),options.xin,255));
107 | else
108 | telapse = toc;
109 | fprintf('%i\t%6.4e\t%6.4e\t%6.4e\t%6.4e\t%.3f\t%.4f\n',...
110 | step_num,f,norm_x,fun_val(step_num),...
111 | nnz(x_kp1)/numel(x_kp1)*100,tk,telapse)
112 | end
113 | tic
114 | end
115 |
116 | if ~mod(step_num,options.disp_fig_interval)
117 | if options.disp_figs
118 | draw_figures(yk,options);
119 | end
120 |
121 | if options.save_progress
122 |
123 | frame = getframe(options.fighandle);
124 | writeVideo(options.vidObj,frame);
125 |
126 | end
127 | end
128 |
129 | if restart>0 && options.restarting
130 | tk = 1;
131 | fprintf('restarting momentum \n')
132 | yk = x_kp1;
133 | else
134 | tk = t_kp1;
135 | end
136 | xk = x_kp1;
137 | f = f_kp1;
138 | if abs(restart)=options.maxIter
151 | fprintf('Reached max number of iterations. Stopping. \n');
152 | end
153 |
154 | out = yk;
155 |
156 | if nargout>1
157 | varargout{1} = fun_val;
158 | end
159 |
160 | draw_figures(out,options)
161 |
162 | if options.save_progress
163 | close(options.vidObj);
164 | end
165 |
166 | return
167 |
168 |
169 | function draw_figures(xk,options)
170 | set(0,'CurrentFigure',options.fighandle)
171 | if numel(options.xsize)==2
172 | imagesc(options.disp_crop(xk))
173 | axis image
174 | colorbar
175 | colormap(options.color_map);
176 | %caxis(gather([prctile(xk(:),.1) prctile(xk(:),90)]))
177 | elseif numel(options.xsize)==3
178 | xk = gather(xk);
179 | set(0,'CurrentFigure',options.fighandle)
180 | subplot(1,3,1)
181 |
182 | im1 = squeeze(max(xk,[],3));
183 | imagesc(im1);
184 | hold on
185 | axis image
186 | colormap parula
187 | %colorbar
188 | caxis([0 prctile(im1(:),options.disp_prctl)])
189 | set(gca,'fontSize',6)
190 | axis off
191 | hold off
192 | set(0,'CurrentFigure',options.fighandle)
193 | subplot(1,3,2)
194 | im2 = squeeze(max(xk,[],1));
195 | imagesc(im2);
196 | hold on
197 | %axis image
198 | colormap parula
199 | %colorbar
200 | set(gca,'fontSize',8)
201 | caxis([0 prctile(im2(:),options.disp_prctl)])
202 | axis off
203 | hold off
204 | drawnow
205 | set(0,'CurrentFigure',options.fighandle)
206 | subplot(1,3,3)
207 | im3 = squeeze(max(xk,[],2));
208 | imagesc(im3);
209 | hold on
210 | %axis image
211 | colormap parula
212 | colorbar
213 | set(gca,'fontSize',8)
214 | caxis([0 prctile(im3(:),options.disp_prctl)]);
215 | axis off
216 | hold off
217 | elseif numel(options.xsize)==1
218 | plot(xk)
219 | end
220 | drawnow
221 |
222 |
--------------------------------------------------------------------------------
/3D reconstruction/Miniscope_shift_varying_recon.m:
--------------------------------------------------------------------------------
1 | % Read in PSF
2 | % This is designed to work with measurements in a folder, then in a
3 | % parallel folder, save the recons (i.e. measurements../recons/)
4 |
5 |
6 |
7 | %psf_path = 'D:\Kyrollos\RandoscopeNanoscribe\RandoscopeNanoscribe\Miniscope3D\psf_svd_12comps_23z_240xy_20190619';
8 | %psf_path = 'D:\Antipa\Randoscopev2_PSFs\Data_8_21_2019\SVD_2_5um_PSF_20um_1';
9 |
10 | psf_path = 'D:\Antipa\Randoscopev2_PSFs\20190912_recalibration\SVD_2p5_um_PSF_5um_1_green_channel';
11 | %psf_path = 'T:\Antipa\Randoscopev2_PSFs\20190912_recalibration\SVD_2p5_um_PSF_5um_1_green_channel';
12 | comps_path = [psf_path,'\SVD_2_5um_PSF_5um_1_ds2_components_green_SubAvg.mat'];
13 | weights_path = [psf_path,'\SVD_2_5um_PSF_5um_1_ds2_weights_interp_green_SubAvg.mat'];
14 |
15 | %%
16 | %comps_path = [psf_path,'/SVD_2_5um_PSF_5um_1_ds2_components_green_NoFro.mat'];
17 | %weights_path = [psf_path,'/SVD_2_5um_PSF_5um_1_ds2_weights_interp_green_NoFro.mat'];
18 | fprintf('loading components\n')
19 |
20 | h_in = load(comps_path);
21 | fprintf('done.\nLoading weights\n')
22 | weights_in = load(weights_path);
23 | fprintf('done loading PSF data\n')
24 | %%
25 |
26 | %Get names of files/paths
27 | [meas_name,data_path,~] = uigetfile('*.*','Select measurement','T:\Randoscope\RandoscopeV2_data');
28 | dots = strfind(meas_name,'.');
29 | fext = meas_name(dots(end):end);
30 | if strcmpi(fext,'.tif')
31 | [bg_name, bg_path,~] = uigetfile('*.*',['Select background for ',meas_name],fullfile([data_path,'../']));
32 | else
33 | bg_name = 'NONE';
34 | bg_path = 'NONE';
35 | end
36 |
37 | meas_path = [data_path,meas_name];
38 | bg_path = [bg_path,bg_name];
39 |
40 | if strcmpi(fext,'.tif')
41 | ome = strfind(meas_name,'.ome');
42 | bg_ome = strfind(bg_name,'.ome');
43 | meta_name = [meas_name(1:ome-1),'_metadata.txt'];
44 | bg_meta_name = [bg_name(1:bg_ome-1),'_metadata.txt'];
45 |
46 | % Inline function to open a metadata file, convert to characters then
47 | % decode (it's in json format).
48 | parse_json = @(x)jsondecode(transpose(fread(fopen(x),'*char')));
49 |
50 | % Construct full system paths to images
51 | meta_path = [data_path,meta_name];
52 | bg_meta_path = [bg_path,bg_meta_name];
53 |
54 |
55 | % Get json data from measurement and background images
56 |
57 | file_info = parse_json(meta_path);
58 | bg_info = parse_json(bg_meta_path);
59 |
60 | params.data_format = file_info.FrameKey_0_0_0.x50890959_DataFormat;
61 | params.bg_format = bg_info.FrameKey_0_0_0.x50890959_DataFormat;
62 | params.ds_raw = file_info.FrameKey_0_0_0.Binning;
63 | params.ds_bg = bg_info.FrameKey_0_0_0.Binning;
64 | elseif strcmpi(fext,'.mat')
65 | params.data_format = 'mat';
66 | params.bg_format = 'NONE';
67 | params.ds_raw = 4;
68 | mat_var_name = 'vid_bgrm_ds';
69 | meta_path = 'NONE';
70 | file_info='NONE';
71 | bg_info='NONE';
72 | end
73 |
74 |
75 |
76 |
77 | params.demosaic = contains(lower(params.data_format),'raw');
78 | params.demosaic_bg = contains(lower(params.bg_format),'raw');
79 | params.bg_name = bg_name;
80 | params.meas_path = meas_path;
81 | params.meta_path = meta_path;
82 | params.bg_path = bg_path;
83 | params.meas_info = file_info;
84 | params.bg_info = bg_info;
85 | %%
86 | %Waterbear_20190905\waterbear_big_lastone_20_3_30ms';
87 | %bg_path = 'D:\Randoscope\RandoscopeV2_data\Waterbear_20190905\waterbear_big_lastone_bck_20_3_30ms_1';
88 | %= 'waterbear_big_lastone_3_MMStack_Default.ome.tif';
89 | %bg_name = 'waterbear_big_lastone_bck_20_3_30ms_1_MMStack_Default.ome.tif';
90 |
91 |
92 |
93 |
94 |
95 | %for zd = 9
96 |
97 | %data_path = 'Z:\kyrollos\RandoscopeNanoscribe\Nanoscribe_pdms\Data_8_21_2019\real_res_target_10um_1'; %<--folder where the measurements are
98 | %bg_path = 'Z:\kyrollos\RandoscopeNanoscribe\Nanoscribe_pdms\Data_8_21_2019\bck_real_res_target_10um_1';
99 |
100 |
101 | params.data_tiff_format = 'time'; %Use 'time' if tiff stacks are at the same location over time, use 'z' if they are z stacks'
102 | params.tiff_color = 2; %use 'rgb' or 'mono'. Use number (1,2,3) for r,g, or b only
103 | params.meas_depth = 82; %If using 3D tiff or list of files, which slice was processed?
104 |
105 | params.ds_z = 1; %z downsampling ratio
106 | params.meas_bias = 0;
107 |
108 | params.ds = 4; % Global downsampling ratio (i.e.final image-to-sensor ratio)
109 | params.ds_psf = 2; %PSf downsample ratio (how much to further downsample -- if preprocessing included downsampling, use 1)
110 | params.ds_meas = params.ds/params.ds_raw; % How much to further downsample measurement?
111 | params.z_range = 1:44; %Must be even number!! Range of z slices to be solved for. If this is a scalar, 2D. Use this for subsampling z also (e.g. 1:4:... to do every 4th image)
112 | params.rank = 12;
113 | useGpu = 1; %cannot fit ds=2 on gpu unless we limit z range!!!!
114 | params.psf_norm = 'fro'; %Use max, slice, fro, or none
115 |
116 | %meas_name = ['real_res_target_10um_1_MMStack_Img_',num2str(params.meas_depth),'_000_000.ome.tif']; %<--- name of measurement
117 | %bg_name = ['bck_real_res_target_10um_1_MMStack_Img_',num2str(params.meas_depth),'_000_000.ome.tif'];
118 |
119 |
120 |
121 |
122 |
123 | % Make sure h and weights are in order y,x,z,rank
124 | fprintf('permuting PSF data\n')
125 | h = permute(h_in.comps_out(:,:,1:params.rank,params.z_range),[1,2,4,3]);
126 | weights = permute(weights_in.weights_out(:,:,1:params.rank,params.z_range),[1,2,4,3]);
127 | fprintf('Done permuting. Resampling PSF\n');
128 |
129 | %clear h_in;
130 | %clear weights_in;
131 | h = single(imresize(squeeze(h),1/params.ds_psf,'box'));
132 | weights = single(imresize(squeeze(weights),1/params.ds_psf,'box'));
133 |
134 | % Normalize weights to have maximum sum through rank of 1
135 | weights_norm = max(sum(weights(size(weights,1)/2,size(weights,2)/2,:,:),4),[],3);
136 | weights = weights/weights_norm;
137 | fprintf('Done. PSF ready!\n')
138 | %clear h_permute;
139 | %clear weights_permute;
140 |
141 | %%
142 | switch lower(params.psf_norm)
143 | case('max')
144 | h = h/max(h(:));
145 | case('none')
146 | case('fro')
147 | h = h/norm(vec(h));
148 | case('slice')
149 | for sl = 1:Nz
150 | slice_norm = norm(h(:,:,sl,1),'fro');
151 | for cp = 1:Nr
152 | h(:,:,sl,cp) = h(:,:,sl,cp)/slice_norm;
153 | end
154 | end
155 | end
156 |
157 | H = fft2(ifftshift(ifftshift(h,1),2));
158 | Hconj = conj(H);
159 | if useGpu
160 | H = gpuArray(H);
161 | Hconj = gpuArray(Hconj);
162 | weights = gpuArray(weights);
163 | end
164 |
165 |
166 | % Read in data
167 |
168 | %%
169 | init_style = 'loaded'; %Use 'loaded' to load initialization, 'zeros' to start from scratch. Admm will run 2D deconv, then replicate result to all time points
170 | im_tag = 'rank1_soft_of_TV_TEST';
171 | if strcmpi(params.data_format,'mat')
172 | data_in = load(meas_path,mat_var_name);
173 | data_in = data_in.(mat_var_name);
174 | end
175 |
176 |
177 | for meas_slice = 1:33
178 |
179 | params.meas_slice = meas_slice; %Slices to load from tiff stack. If 'all' used, it will average.
180 | %params.meas_slice = 'all';
181 | if ~strcmpi(params.data_format,'mat')
182 | switch lower(params.data_tiff_format)
183 | case('z')
184 | data_raw = double(read_tiff_stack(meas_path,params.ds_meas,params.meas_depth));
185 | bg_in = double(read_tiff_stack(bg_path,params.ds_meas,params.meas_depth));
186 | case('time')
187 | bg_raw = read_tiff_stack(bg_path,1);
188 | if strcmpi(params.meas_slice,'all')
189 | data_raw = mean(double(read_tiff_stack(meas_path,1)),4); %Average out the time variable
190 | if params.demosaic
191 | data_demos = imresize(double(demosaic(uint16(data_in),'grbg')),params.ds_raw/params.ds,'box');
192 | else
193 | data_demos = imresize(data_raw,params.ds_raw/params.ds,'box');
194 | end
195 | else
196 | data_in = read_tiff_stack(meas_path,1,params.meas_slice);
197 |
198 |
199 | if params.demosaic
200 | data_demos = imresize(double(demosaic(uint16(data_in),'grbg')),params.ds_raw/params.ds,'box');
201 | %grbg
202 | else
203 | data_demos = imresize(mean(data_in,4),params.ds_raw/params.ds,'box');
204 | end
205 | end
206 | if params.demosaic_bg
207 | bg_in = imresize(double(demosaic(uint16(mean(bg_raw,3)),'grbg')),params.ds_bg/params.ds,'box');
208 | else
209 | bg_in = imresize(mean(double(bg_raw),4),params.ds_bg/params.ds,'box');
210 | end
211 |
212 | % data_raw = data_raw(:,:,:,1);
213 |
214 | end
215 |
216 | if strcmpi(params.tiff_color,'rgb')
217 | data = mean(data_demos,3);
218 | bg = mean(bg_in,3); %Average out color. Change to (:,:,color) to select one channel
219 | elseif isnumeric(params.tiff_color)
220 | data = data_demos(:,:,params.tiff_color);
221 | bg = bg_in(:,:,params.tiff_color);
222 | end
223 | else
224 | data = data_in(:,:,meas_slice);
225 | bg = 0;
226 | end
227 |
228 |
229 | data = data - bg - params.meas_bias;
230 | b = data/max(data(:));
231 |
232 |
233 | % data_r = data_in(:,:,1);
234 | % data_g = data_in(:,:,2);
235 | % data_b = data_in(:,:,3);
236 |
237 |
238 | %Nx = size(h,2);
239 | %Ny = size(h,1);
240 | if numel(size(h)) == 3
241 | [Ny, Nx, Nr] = size(h);
242 | Nz = 1;
243 | else
244 | [Ny, Nx, Nz, Nr] = size(h);
245 | end
246 |
247 | %define crop and pad operators to handle 2D fft convolution
248 | pad2d = @(x)padarray(x,[size(h,1)/2,size(h,2)/2],0,'both');
249 | ccL = size(h,2)/2+1;
250 | ccU = 3*size(h,2)/2;
251 | rcL = size(h,1)/2+1;
252 | rcU = 3*size(h,1)/2;
253 |
254 | %cc = gpuArray((size(h,2)/2+1):(3*size(h,2)/2));
255 | %rc = gpuArray((size(h,1)/2+1):(3*size(h,1)/2));
256 | crop2d = @(x)x(rcL:rcU,ccL:ccU);
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 | if strcmpi(init_style, 'zeros')
265 | xinit = zeros(Ny, Nx, Nz);
266 | elseif strcmpi(init_style,'loaded')
267 | if ~exist('xinit')
268 | xinit = zeros(Ny,Nx,Nz);
269 | else
270 | xinit = xhat_out(:,:,:);
271 | end
272 | elseif strcmpi(init_style,'admm')
273 | xinit_2d = gpuArray(single(zeros(Ny, Nx, 3)));
274 |
275 | for n = 1:3
276 | xinit_2d(:,:,n) = admm2d_solver(gpuArray(single(b(:,:,n))), gpuArray(single(h(:,:,n))),[],.001);
277 |
278 | imagesc(2*xinit_2d/max(xinit_2d(:)))
279 | end
280 | end
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 | options.color_map = 'parula';
289 |
290 |
291 |
292 | options.convTol = 15e-4;
293 |
294 | %options.xsize = [256,256];
295 | options.maxIter = 2000;
296 | options.residTol = 5e-5;
297 | options.momentum = 'nesterov';
298 | options.disp_figs = 1;
299 | options.disp_fig_interval = 40; %display image this often
300 | if Nz == 1
301 | options.xsize = [Ny, Nx];
302 | else
303 | options.xsize=[Ny, Nx, Nz];
304 | end
305 | options.print_interval = 20;
306 |
307 | figure(2)
308 | clf
309 | imagesc(b)
310 | axis image
311 |
312 | h1 = figure(1);
313 | clf
314 | options.fighandle = h1;
315 | nocrop = @(x)x;
316 | options.known_input = 0;
317 |
318 |
319 |
320 |
321 |
322 | large = 0;
323 | if Nz > 1
324 | if large == 0
325 | A = @(x)A_svd_3d(x, weights,H);
326 |
327 | Aadj = @(y)A_adj_svd_3d(y, weights, Hconj);
328 | else
329 | weights=gpuArray(weights);
330 | H = gpuArray(H);
331 | Hconj = gpuArray(Hconj);
332 | b = gpuArray(single(b));
333 | A = @(x)A_svd_3d_large(x,weights,H);
334 | Aadj = @(y)A_adj_svd_3d_large(y, weights, Hconj);
335 | end
336 | elseif Nz == 1
337 | A = @(x)A_svd(H, weights, x, nocrop);
338 | Aadj = @(y)A_adj_svd(Hconj,weights,y,nocrop);
339 | end
340 |
341 |
342 |
343 |
344 | %options.stepsize = .1e-3; for ds=4
345 | if params.ds == 4
346 | if strcmpi(params.psf_norm ,'fro')
347 | if Nz == 18
348 | options.stepsize = 3e-3;
349 | elseif Nz == 12
350 | options.stepsize = .4e-2;
351 | fprintf('foo\n')
352 | elseif Nz == 14
353 | options.stepsize = 4e-3;
354 | elseif Nz == 20
355 | if params.rank == 12
356 | options.stepsize = 3e-3;
357 | elseif params.rank == 8
358 | options.stepsize = 1e-3;
359 | elseif params.rank == 18
360 | options.stepsize = 4e-3;
361 |
362 |
363 | end
364 | elseif Nz>20
365 | options.stepsize = .014; %015 is nice?
366 | end
367 |
368 | else
369 | options.stepsize = 3e-6;
370 | end
371 |
372 | elseif params.ds == 2
373 | options.stepsize = 0.7e-3;
374 | end
375 | %.3e-4 is good for waterbears
376 | params.tau1 = options.stepsize*.3e-3; %was 0.5e-7 %.000005 works pretty well for v1 camera, .0002 for v2
377 | params.tau_soft = options.stepsize * 3e-3;
378 | tau_iso = (.25e-4);
379 | params.z_tv_weight = 1; %z weighting in anisotropic TV
380 | tau2 = .001 %Auxilliary
381 | TVnorm3d = @(x)sum(sum(sum(abs(x))));
382 |
383 |
384 | if useGpu
385 |
386 | grad_handle = @(x)linear_gradient_b(x, A, Aadj, gpuArray(single(b)));
387 |
388 | params.tau1 = gpuArray(params.tau1);
389 | params.tau_soft = gpuArray(params.tau_soft);
390 | tau_iso = gpuArray(tau_iso);
391 | params.z_tv_weight = gpuArray(params.z_tv_weight);
392 | options.stepsize = gpuArray(options.stepsize);
393 |
394 | else
395 | if ~large
396 | grad_handle = @(x)linear_gradient_b(x, A, Aadj, single(b));
397 | else
398 | grad_handle = @(x)linear_gradient_large(x,A,Aadj,gpuArray(single(b)));
399 | end
400 |
401 | end
402 |
403 | %Prox
404 | %prox_handle = @(x)deal(x.*(x>=0), abs(sum(sum(sum(x(x<0))))));
405 |
406 |
407 |
408 | %prox_handle = @(x)deal(1/3*(x.*(x>=0) + soft(x, tau2) + tv3dApproxHaar(x, params.tau1)), TVnorm3d(x));
409 |
410 |
411 |
412 |
413 | if ~strcmpi(params.data_format,'mat')
414 | if Nz>1
415 | prox_handle = @(x)deal(1/2*(max(x,0) + (tv3d_iso_Haar((x), params.tau1, params.z_tv_weight))), params.tau1*TVnorm3d(x));
416 | elseif Nz == 1
417 | prox_handle = @(x)deal(.5*tv2d_aniso_haar(x,params.tau1*options.stepsize) + ...
418 | .5*max(x,0), params.tau1*options.stepsize*TVnorm(x));
419 | end
420 | else
421 | % prox_handle = @(x)deal(.5*(soft(x,params.tau_soft) +...
422 | % tv3d_iso_Haar(x, params.tau1, params.z_tv_weight)), ...
423 | % params.tau1*TVnorm3d(x));
424 | prox_handle = @(x)deal(soft(tv3d_iso_Haar(x, params.tau1, params.z_tv_weight),params.tau_soft), ...
425 | params.tau1*TVnorm3d(x));
426 | %prox_handle=@(x)deal(soft(x,params.tau_soft),params.tau_soft*sum(abs(vec(x))));
427 | end
428 | TVpars.epsilon = 1e-7;
429 | TVpars.MAXITER = 100;
430 | TVpars.alpha = .3;
431 | %prox_handle = @(x)deal(hsvid_TV3DFista(x, tau_iso, 0, 10, TVpars) , hsvid_TVnorm3d(x));
432 |
433 | if strcmpi(init_style, 'zeros')
434 | xinit = zeros(Ny, Nx, Nz);
435 |
436 | end
437 |
438 | if useGpu
439 |
440 | TVpars.epsilon = gpuArray(TVpars.epsilon);
441 | TVpars.MAXITER = gpuArray(TVpars.MAXITER);
442 | TVpars.alpha = gpuArray(TVpars.alpha);
443 | xinit = gpuArray(single(xinit));
444 | success = false;
445 | while success == false %This shouldn't be necessary, but it deals with restarting when GPU runs OOM
446 | try
447 | [xhat, f2] = MiniscopeFISTA(grad_handle,prox_handle,xinit,options);
448 | success = true;
449 | catch
450 | success = false;
451 | end
452 | end
453 |
454 | else
455 | if large
456 | xinit = gpuArray(xinit);
457 | end
458 | [xhat, f2] = MiniscopeFISTA(grad_handle,prox_handle,xinit,options);
459 | end
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 | datestamp = datetime;
468 | tiff_string = sprintf('%03d',meas_slice);
469 | date_string = datestr(datestamp,'yyyy-mmm-dd_HHMMSS');
470 | save_str = ['../recons/',date_string,'_',meas_name(1:end-4),'_',im_tag,'_',tiff_string];
471 | full_path = fullfile(data_path,save_str);
472 | mkdir(full_path);
473 |
474 |
475 |
476 | imout = gather(xhat/prctile(xhat(:),100*(numel(xhat)-10)/numel(xhat))); %Saturate only 10 pixels
477 | xhat_out = gather(xhat);
478 |
479 | params.tau1 = gather(params.tau1);
480 | params.tau_soft = gather(params.tau_soft);
481 | imbase = meas_name(1:end-4);
482 | mkdir([full_path, '/png/']);
483 | filebase = [full_path, '/png/', imbase];
484 | f_out = gather(f2);
485 | out_names = {};
486 | for n= 1:size(imout,3)
487 | out_names{n} = [filebase,'_',sprintf('Z_%.3i_T_',params.z_range(n)),...
488 | tiff_string,'_',im_tag,'.png'];
489 | imwrite(imout(:,:,n),out_names{n});
490 | fprintf('writing image %i of %i\n',n,size(xhat,3))
491 | end
492 |
493 | fprintf('zipping...\n')
494 | zip([full_path, '/png/', imbase],out_names)
495 | fprintf('done zipping\n')
496 |
497 | fprintf('writing .mat\n')
498 | options.fighandle = []
499 |
500 | save([full_path,'/',meas_name(1:end-4),'_',date_string,'_',im_tag,'_',tiff_string,'.mat'], 'tau_iso','TVpars','xhat_out', 'options', 'comps_path','weights_path', 'b','params')
501 | fprintf('done writing .mat\n')
502 | % gpuDevice(1)
503 | clear xhat
504 | clear f2
505 |
506 |
507 | if params.ds == 2
508 | gpuDevice(1)
509 | end
510 | end
511 | %end
512 |
513 | %%
514 | % imagesc(brain_recon.xhat(:,:,10))
515 | % axis image
--------------------------------------------------------------------------------
/3D reconstruction/Miniscope_3d_shift_varying_main.m:
--------------------------------------------------------------------------------
1 | % Read in PSF
2 | % This is designed to work with measurements in a folder, then in a
3 | % parallel folder, save the recons (i.e. measurements../recons/)
4 |
5 |
6 |
7 | %psf_path = 'D:\Kyrollos\RandoscopeNanoscribe\RandoscopeNanoscribe\Miniscope3D\psf_svd_12comps_23z_240xy_20190619';
8 | %psf_path = 'D:\Antipa\Randoscopev2_PSFs\Data_8_21_2019\SVD_2_5um_PSF_20um_1';
9 |
10 | psf_path = 'D:\Antipa\Randoscopev2_PSFs\20190912_recalibration\SVD_2p5_um_PSF_5um_1_green_channel';
11 | %psf_path = 'T:\Antipa\Randoscopev2_PSFs\20190912_recalibration\SVD_2p5_um_PSF_5um_1_green_channel';
12 | comps_path = [psf_path,'\SVD_2_5um_PSF_5um_1_ds2_components_green_SubAvg.mat'];
13 | weights_path = [psf_path,'\SVD_2_5um_PSF_5um_1_ds2_weights_interp_green_SubAvg.mat'];
14 |
15 | %%
16 | %comps_path = [psf_path,'/SVD_2_5um_PSF_5um_1_ds2_components_green_NoFro.mat'];
17 | %weights_path = [psf_path,'/SVD_2_5um_PSF_5um_1_ds2_weights_interp_green_NoFro.mat'];
18 | fprintf('loading components\n')
19 |
20 | h_in = load(comps_path);
21 | fprintf('done.\nLoading weights\n')
22 | weights_in = load(weights_path);
23 | fprintf('done loading PSF data\n')
24 |
25 |
26 | %%
27 |
28 | %Get names of files/paths
29 | [meas_name,data_path,~] = uigetfile('*.*','Select measurement','T:\Randoscope\RandoscopeV2_data');
30 | dots = strfind(meas_name,'.');
31 | fext = meas_name(dots(end):end);
32 | if strcmpi(fext,'.tif')
33 | [bg_name, bg_path,~] = uigetfile('*.*',['Select background for ',meas_name],fullfile([data_path,'../']));
34 | else
35 | bg_name = 'NONE';
36 | bg_path = 'NONE';
37 | end
38 |
39 |
40 |
41 |
42 |
43 |
44 | meas_path = [data_path,meas_name];
45 | bg_path = [bg_path,bg_name];
46 |
47 | if strcmpi(fext,'.tif')
48 | ome = strfind(meas_name,'.ome');
49 | bg_ome = strfind(bg_name,'.ome');
50 | meta_name = [meas_name(1:ome-1),'_metadata.txt'];
51 | bg_meta_name = [bg_name(1:bg_ome-1),'_metadata.txt'];
52 |
53 | % Inline function to open a metadata file, convert to characters then
54 | % decode (it's in json format).
55 | parse_json = @(x)jsondecode(transpose(fread(fopen(x),'*char')));
56 |
57 | % Construct full system paths to images
58 | meta_path = [data_path,meta_name];
59 | bg_meta_path = [bg_path,bg_meta_name];
60 |
61 |
62 | % Get json data from measurement and background images
63 |
64 | file_info = parse_json(meta_path);
65 | bg_info = parse_json(bg_meta_path);
66 |
67 | params.data_format = file_info.FrameKey_0_0_0.x50890959_DataFormat;
68 | params.bg_format = bg_info.FrameKey_0_0_0.x50890959_DataFormat;
69 | params.ds_raw = file_info.FrameKey_0_0_0.Binning;
70 | params.ds_bg = bg_info.FrameKey_0_0_0.Binning;
71 | elseif strcmpi(fext,'.mat')
72 | params.data_format = 'mat';
73 | params.bg_format = 'NONE';
74 | params.ds_raw = 4;
75 | mat_var_name = 'vid_bgrm_ds';
76 | meta_path = 'NONE';
77 | file_info='NONE';
78 | bg_info='NONE';
79 |
80 | end
81 |
82 |
83 |
84 |
85 | params.demosaic = contains(lower(params.data_format),'raw');
86 | params.demosaic_bg = contains(lower(params.bg_format),'raw');
87 | params.bg_name = bg_name;
88 | params.meas_path = meas_path;
89 | params.meta_path = meta_path;
90 | params.bg_path = bg_path;
91 | params.meas_info = file_info;
92 | params.bg_info = bg_info;
93 | %%
94 | %Waterbear_20190905\waterbear_big_lastone_20_3_30ms';
95 | %bg_path = 'D:\Randoscope\RandoscopeV2_data\Waterbear_20190905\waterbear_big_lastone_bck_20_3_30ms_1';
96 | %= 'waterbear_big_lastone_3_MMStack_Default.ome.tif';
97 | %bg_name = 'waterbear_big_lastone_bck_20_3_30ms_1_MMStack_Default.ome.tif';
98 |
99 |
100 |
101 |
102 |
103 | %for zd = 9
104 |
105 | %data_path = 'Z:\kyrollos\RandoscopeNanoscribe\Nanoscribe_pdms\Data_8_21_2019\real_res_target_10um_1'; %<--folder where the measurements are
106 | %bg_path = 'Z:\kyrollos\RandoscopeNanoscribe\Nanoscribe_pdms\Data_8_21_2019\bck_real_res_target_10um_1';
107 |
108 |
109 | params.data_tiff_format = 'time'; %Use 'time' if tiff stacks are at the same location over time, use 'z' if they are z stacks'
110 | params.tiff_color = 2; %use 'rgb' or 'mono'. Use number (1,2,3) for r,g, or b only
111 | params.meas_depth = 82; %If using 3D tiff or list of files, which slice was processed?
112 |
113 | params.ds_z = 1; %z downsampling ratio
114 | params.meas_bias = 0;
115 |
116 | params.ds = 4; % Global downsampling ratio (i.e.final image-to-sensor ratio)
117 | params.ds_psf = 2; %PSf downsample ratio (how much to further downsample -- if preprocessing included downsampling, use 1)
118 | params.ds_meas = params.ds/params.ds_raw; % How much to further downsample measurement?
119 | params.z_range = 1:44; %Must be even number!! Range of z slices to be solved for. If this is a scalar, 2D. Use this for subsampling z also (e.g. 1:4:... to do every 4th image)
120 | params.rank = 12;
121 | useGpu = 1; %cannot fit ds=2 on gpu unless we limit z range!!!!
122 | params.psf_norm = 'fro'; %Use max, slice, fro, or none
123 |
124 | %meas_name = ['real_res_target_10um_1_MMStack_Img_',num2str(params.meas_depth),'_000_000.ome.tif']; %<--- name of measurement
125 | %bg_name = ['bck_real_res_target_10um_1_MMStack_Img_',num2str(params.meas_depth),'_000_000.ome.tif'];
126 |
127 |
128 |
129 |
130 |
131 | % Make sure h and weights are in order y,x,z,rank
132 | fprintf('permuting PSF data\n')
133 | h = permute(h_in.comps_out(:,:,1:params.rank,params.z_range),[1,2,4,3]);
134 | weights = permute(weights_in.weights_out(:,:,1:params.rank,params.z_range),[1,2,4,3]);
135 | fprintf('Done permuting. Resampling PSF\n');
136 |
137 | %clear h_in;
138 | %clear weights_in;
139 | h = single(imresize(squeeze(h),1/params.ds_psf,'box'));
140 | weights = single(imresize(squeeze(weights),1/params.ds_psf,'box'));
141 |
142 | % Normalize weights to have maximum sum through rank of 1
143 | weights_norm = max(sum(weights(size(weights,1)/2,size(weights,2)/2,:,:),4),[],3);
144 | weights = weights/weights_norm;
145 | fprintf('Done. PSF ready!\n')
146 | %clear h_permute;
147 | %clear weights_permute;
148 |
149 | %%
150 | switch lower(params.psf_norm)
151 | case('max')
152 | h = h/max(h(:));
153 | case('none')
154 | case('fro')
155 | h = h/norm(vec(h));
156 | case('slice')
157 | for sl = 1:Nz
158 | slice_norm = norm(h(:,:,sl,1),'fro');
159 | for cp = 1:Nr
160 | h(:,:,sl,cp) = h(:,:,sl,cp)/slice_norm;
161 | end
162 | end
163 | end
164 |
165 | H = fft2(ifftshift(ifftshift(h,1),2));
166 | Hconj = conj(H);
167 | if useGpu
168 | H = gpuArray(H);
169 | Hconj = gpuArray(Hconj);
170 | weights = gpuArray(weights);
171 | end
172 |
173 |
174 | % Read in data
175 |
176 | %%
177 | init_style = 'zeros'; %Use 'loaded' to load initialization, 'zeros' to start from scratch. Admm will run 2D deconv, then replicate result to all time points
178 | im_tag = 'rank1_soft_of_TV_TEST';
179 | if strcmpi(params.data_format,'mat')
180 | data_in = load(meas_path,mat_var_name);
181 | data_in = data_in.(mat_var_name);
182 | end
183 |
184 |
185 | for meas_slice = 1:33
186 |
187 | params.meas_slice = meas_slice; %Slices to load from tiff stack. If 'all' used, it will average.
188 | %params.meas_slice = 'all';
189 | if ~strcmpi(params.data_format,'mat')
190 | switch lower(params.data_tiff_format)
191 | case('z')
192 | data_raw = double(read_tiff_stack(meas_path,params.ds_meas,params.meas_depth));
193 | bg_in = double(read_tiff_stack(bg_path,params.ds_meas,params.meas_depth));
194 | case('time')
195 | bg_raw = read_tiff_stack(bg_path,1);
196 | if strcmpi(params.meas_slice,'all')
197 | data_raw = mean(double(read_tiff_stack(meas_path,1)),4); %Average out the time variable
198 | if params.demosaic
199 | data_demos = imresize(double(demosaic(uint16(data_in),'grbg')),params.ds_raw/params.ds,'box');
200 | else
201 | data_demos = imresize(data_raw,params.ds_raw/params.ds,'box');
202 | end
203 | else
204 | data_in = read_tiff_stack(meas_path,1,params.meas_slice);
205 |
206 |
207 | if params.demosaic
208 | data_demos = imresize(double(demosaic(uint16(data_in),'grbg')),params.ds_raw/params.ds,'box');
209 | %grbg
210 | else
211 | data_demos = imresize(mean(data_in,4),params.ds_raw/params.ds,'box');
212 | end
213 | end
214 | if params.demosaic_bg
215 | bg_in = imresize(double(demosaic(uint16(mean(bg_raw,3)),'grbg')),params.ds_bg/params.ds,'box');
216 | else
217 | bg_in = imresize(mean(double(bg_raw),4),params.ds_bg/params.ds,'box');
218 | end
219 |
220 | % data_raw = data_raw(:,:,:,1);
221 |
222 | end
223 |
224 | if strcmpi(params.tiff_color,'rgb')
225 | data = mean(data_demos,3);
226 | bg = mean(bg_in,3); %Average out color. Change to (:,:,color) to select one channel
227 | elseif isnumeric(params.tiff_color)
228 | data = data_demos(:,:,params.tiff_color);
229 | bg = bg_in(:,:,params.tiff_color);
230 | end
231 | else
232 | data = data_in(:,:,meas_slice);
233 | bg = 0;
234 | end
235 |
236 |
237 | data = data - bg - params.meas_bias;
238 | b = data/max(data(:));
239 |
240 |
241 | % data_r = data_in(:,:,1);
242 | % data_g = data_in(:,:,2);
243 | % data_b = data_in(:,:,3);
244 |
245 |
246 | %Nx = size(h,2);
247 | %Ny = size(h,1);
248 | if numel(size(h)) == 3
249 | [Ny, Nx, Nr] = size(h);
250 | Nz = 1;
251 | else
252 | [Ny, Nx, Nz, Nr] = size(h);
253 | end
254 |
255 | %define crop and pad operators to handle 2D fft convolution
256 | pad2d = @(x)padarray(x,[size(h,1)/2,size(h,2)/2],0,'both');
257 | ccL = size(h,2)/2+1;
258 | ccU = 3*size(h,2)/2;
259 | rcL = size(h,1)/2+1;
260 | rcU = 3*size(h,1)/2;
261 |
262 | %cc = gpuArray((size(h,2)/2+1):(3*size(h,2)/2));
263 | %rc = gpuArray((size(h,1)/2+1):(3*size(h,1)/2));
264 | crop2d = @(x)x(rcL:rcU,ccL:ccU);
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 | if strcmpi(init_style, 'zeros')
273 | xinit = zeros(Ny, Nx, Nz);
274 | elseif strcmpi(init_style,'loaded')
275 | if ~exist('xinit')
276 | xinit = zeros(Ny,Nx,Nz);
277 | else
278 | xinit = xhat_out(:,:,:);
279 | end
280 | elseif strcmpi(init_style,'admm')
281 | xinit_2d = gpuArray(single(zeros(Ny, Nx, 3)));
282 |
283 | for n = 1:3
284 | xinit_2d(:,:,n) = admm2d_solver(gpuArray(single(b(:,:,n))), gpuArray(single(h(:,:,n))),[],.001);
285 |
286 | imagesc(2*xinit_2d/max(xinit_2d(:)))
287 | end
288 | end
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 | options.color_map = 'parula';
297 |
298 |
299 |
300 | options.convTol = 15e-4;
301 |
302 | %options.xsize = [256,256];
303 | options.maxIter = 2000;
304 | options.residTol = 5e-5;
305 | options.momentum = 'nesterov';
306 | options.disp_figs = 1;
307 | options.disp_fig_interval = 40; %display image this often
308 | if Nz == 1
309 | options.xsize = [Ny, Nx];
310 | else
311 | options.xsize=[Ny, Nx, Nz];
312 | end
313 | options.print_interval = 20;
314 |
315 | figure(2)
316 | clf
317 | imagesc(b)
318 | axis image
319 |
320 | h1 = figure(1);
321 | clf
322 | options.fighandle = h1;
323 | nocrop = @(x)x;
324 | options.known_input = 0;
325 |
326 |
327 |
328 |
329 |
330 | large = 0;
331 | if Nz > 1
332 | if large == 0
333 | A = @(x)A_svd_3d(x, weights,H);
334 |
335 | Aadj = @(y)A_adj_svd_3d(y, weights, Hconj);
336 | else
337 | weights=gpuArray(weights);
338 | H = gpuArray(H);
339 | Hconj = gpuArray(Hconj);
340 | b = gpuArray(single(b));
341 | A = @(x)A_svd_3d_large(x,weights,H);
342 | Aadj = @(y)A_adj_svd_3d_large(y, weights, Hconj);
343 | end
344 | elseif Nz == 1
345 | A = @(x)A_svd(H, weights, x, nocrop);
346 | Aadj = @(y)A_adj_svd(Hconj,weights,y,nocrop);
347 | end
348 |
349 |
350 |
351 |
352 | %options.stepsize = .1e-3; for ds=4
353 | if params.ds == 4
354 | if strcmpi(params.psf_norm ,'fro')
355 | if Nz == 18
356 | options.stepsize = 3e-3;
357 | elseif Nz == 12
358 | options.stepsize = .4e-2;
359 | fprintf('foo\n')
360 | elseif Nz == 14
361 | options.stepsize = 4e-3;
362 | elseif Nz == 20
363 | if params.rank == 12
364 | options.stepsize = 3e-3;
365 | elseif params.rank == 8
366 | options.stepsize = 1e-3;
367 | elseif params.rank == 18
368 | options.stepsize = 4e-3;
369 |
370 |
371 | end
372 | elseif Nz>20
373 | options.stepsize = .014; %015 is nice?
374 | end
375 |
376 | else
377 | options.stepsize = 3e-6;
378 | end
379 |
380 | elseif params.ds == 2
381 | options.stepsize = 0.7e-3;
382 | end
383 | %.3e-4 is good for waterbears
384 | params.tau1 = options.stepsize*.3e-3; %was 0.5e-7 %.000005 works pretty well for v1 camera, .0002 for v2
385 | params.tau_soft = options.stepsize * 3e-3;
386 | tau_iso = (.25e-4);
387 | params.z_tv_weight = 1; %z weighting in anisotropic TV
388 | tau2 = .001 %Auxilliary
389 | TVnorm3d = @(x)sum(sum(sum(abs(x))));
390 |
391 |
392 | if useGpu
393 |
394 | grad_handle = @(x)linear_gradient_b(x, A, Aadj, gpuArray(single(b)));
395 |
396 | params.tau1 = gpuArray(params.tau1);
397 | params.tau_soft = gpuArray(params.tau_soft);
398 | tau_iso = gpuArray(tau_iso);
399 | params.z_tv_weight = gpuArray(params.z_tv_weight);
400 | options.stepsize = gpuArray(options.stepsize);
401 |
402 | else
403 | if ~large
404 | grad_handle = @(x)linear_gradient_b(x, A, Aadj, single(b));
405 | else
406 | grad_handle = @(x)linear_gradient_large(x,A,Aadj,gpuArray(single(b)));
407 | end
408 |
409 | end
410 |
411 | %Prox
412 | %prox_handle = @(x)deal(x.*(x>=0), abs(sum(sum(sum(x(x<0))))));
413 |
414 |
415 |
416 | %prox_handle = @(x)deal(1/3*(x.*(x>=0) + soft(x, tau2) + tv3dApproxHaar(x, params.tau1)), TVnorm3d(x));
417 |
418 |
419 |
420 |
421 | if ~strcmpi(params.data_format,'mat')
422 | if Nz>1
423 | prox_handle = @(x)deal(1/2*(max(x,0) + (tv3d_iso_Haar((x), params.tau1, params.z_tv_weight))), params.tau1*TVnorm3d(x));
424 | elseif Nz == 1
425 | prox_handle = @(x)deal(.5*tv2d_aniso_haar(x,params.tau1*options.stepsize) + ...
426 | .5*max(x,0), params.tau1*options.stepsize*TVnorm(x));
427 | end
428 | else
429 | % prox_handle = @(x)deal(.5*(soft(x,params.tau_soft) +...
430 | % tv3d_iso_Haar(x, params.tau1, params.z_tv_weight)), ...
431 | % params.tau1*TVnorm3d(x));
432 | prox_handle = @(x)deal(soft(tv3d_iso_Haar(x, params.tau1, params.z_tv_weight),params.tau_soft), ...
433 | params.tau1*TVnorm3d(x));
434 | %prox_handle=@(x)deal(soft(x,params.tau_soft),params.tau_soft*sum(abs(vec(x))));
435 | end
436 | TVpars.epsilon = 1e-7;
437 | TVpars.MAXITER = 100;
438 | TVpars.alpha = .3;
439 | %prox_handle = @(x)deal(hsvid_TV3DFista(x, tau_iso, 0, 10, TVpars) , hsvid_TVnorm3d(x));
440 |
441 | if strcmpi(init_style, 'zeros')
442 | xinit = zeros(Ny, Nx, Nz);
443 |
444 | end
445 |
446 | if useGpu
447 |
448 | TVpars.epsilon = gpuArray(TVpars.epsilon);
449 | TVpars.MAXITER = gpuArray(TVpars.MAXITER);
450 | TVpars.alpha = gpuArray(TVpars.alpha);
451 | xinit = gpuArray(single(xinit));
452 | success = false;
453 | while success == false %This shouldn't be necessary, but it deals with restarting when GPU runs OOM
454 | try
455 | [xhat, f2] = MiniscopeFISTA(grad_handle,prox_handle,xinit,options);
456 | success = true;
457 | catch
458 | success = false;
459 | end
460 | end
461 |
462 | else
463 | if large
464 | xinit = gpuArray(xinit);
465 | end
466 | [xhat, f2] = MiniscopeFISTA(grad_handle,prox_handle,xinit,options);
467 | end
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 | datestamp = datetime;
476 | tiff_string = sprintf('%03d',meas_slice);
477 | date_string = datestr(datestamp,'yyyy-mmm-dd_HHMMSS');
478 | save_str = ['../recons/',date_string,'_',meas_name(1:end-4),'_',im_tag,'_',tiff_string];
479 | full_path = fullfile(data_path,save_str);
480 | mkdir(full_path);
481 |
482 |
483 |
484 | imout = gather(xhat/prctile(xhat(:),100*(numel(xhat)-10)/numel(xhat))); %Saturate only 10 pixels
485 | xhat_out = gather(xhat);
486 |
487 | params.tau1 = gather(params.tau1);
488 | params.tau_soft = gather(params.tau_soft);
489 | imbase = meas_name(1:end-4);
490 | mkdir([full_path, '/png/']);
491 | filebase = [full_path, '/png/', imbase];
492 | f_out = gather(f2);
493 | out_names = {};
494 | for n= 1:size(imout,3)
495 | out_names{n} = [filebase,'_',sprintf('Z_%.3i_T_',params.z_range(n)),...
496 | tiff_string,'_',im_tag,'.png'];
497 | imwrite(imout(:,:,n),out_names{n});
498 | fprintf('writing image %i of %i\n',n,size(xhat,3))
499 | end
500 |
501 | fprintf('zipping...\n')
502 | zip([full_path, '/png/', imbase],out_names)
503 | fprintf('done zipping\n')
504 |
505 | fprintf('writing .mat\n')
506 | options.fighandle = []
507 |
508 | save([full_path,'/',meas_name(1:end-4),'_',date_string,'_',im_tag,'_',tiff_string,'.mat'], 'tau_iso','TVpars','xhat_out', 'options', 'comps_path','weights_path', 'b','params')
509 | fprintf('done writing .mat\n')
510 | % gpuDevice(1)
511 | clear xhat
512 | clear f2
513 |
514 |
515 | if params.ds == 2
516 | gpuDevice(1)
517 | end
518 | end
519 | %end
520 |
521 | %%
522 | % imagesc(brain_recon.xhat(:,:,10))
523 | % axis image
--------------------------------------------------------------------------------
/Adaptive Stitching with Nanoscribe/TipSlicerUpdated.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import numpy as np
3 | import numpy.random as npRand
4 | import matplotlib.pyplot as plt
5 | import matplotlib.colors as colcol
6 | import matplotlib.cm as cmx
7 | import cv2
8 | import tables
9 | import scipy.ndimage as im
10 | from scipy.spatial import Voronoi
11 | import os
12 | import glob
13 | import re
14 | import scipy.signal as signal
15 |
16 | def squareSlice(X,Y,Z,extraParams):
17 | amp,period,heightOff=extraParams
18 | ResMat=(amp*np.cos(X*2.0*np.pi/period)+amp*np.cos(Y*2.0*np.pi/period)+amp+heightOff)>Z
19 | return ResMat
20 |
21 | #Z=sin(X)+sin(-X/2+sqrt(3)/2*Y) + sin(-X/2-sqrt(3)/2*Y)
22 | # minimum Z = heightOff
23 | # max Z = heightOff + 4.5*amp
24 | def hexagonalSlice(X,Y,Z,extraParams):
25 | amp,period,heightOff=extraParams
26 | ResMat=(amp*np.cos(X*2.0*np.pi/period)+amp*np.cos((-0.5*X+np.sqrt(3.0)/2.0*Y)*2.0*np.pi/period)+amp*np.cos((-X/2.0-np.sqrt(3.0)/2.0*Y)*2.0*np.pi/period)+1.5*amp+heightOff)>Z
27 | return ResMat
28 |
29 | def polarSineSlice(X,Y,Z,extraParams):
30 | xc,yc,period,amp,offset=extraParams
31 | D=np.sqrt((X-xc)**2+(Y-yc)**2)*2.0*np.pi/period
32 | ResMat=amp*np.cos(D)+offset>Z
33 | return ResMat
34 |
35 | # height falls off as the sum of the distances to the two focal points of an ellipse
36 | # smallAxis determines the scale and is the distance from centre to edge - perpendicular to separating line,
37 | # maxHeight scales the height
38 | def ellipticalConeSlice(X,Y,Z,extraParams):
39 | centreX,centreY,sepDist,sepAngle,smallAxis,maxHeight=extraParams
40 | centre=np.array([centreX,centreY])
41 | cosang=np.array([np.cos(np.deg2rad(sepAngle)),np.sin(np.deg2rad(sepAngle))])
42 | f1=centre+sepDist/2.0 * cosang
43 | f2=centre-sepDist/2.0 * cosang
44 | D=np.sqrt((f1[0]-X)**2+(f1[1]-Y)**2)+np.sqrt((f2[0]-X)**2+(f2[1]-Y)**2)
45 | heightOff=sepDist/np.cos(np.arctan(2*smallAxis/sepDist))
46 | ResMat=maxHeight*(heightOff-D)/(heightOff-sepDist)>Z
47 | return ResMat
48 |
49 | # will create a X,Y & Z matrix based on the given stlMesh
50 | def getMeshXYZ(stlMesh,hashing,slicing):
51 | # now determine the extent of the figure, in order to create X,Y and Z axes
52 | xext=stlMesh[:,:,0].max()-stlMesh[:,:,0].min()
53 | yext=stlMesh[:,:,1].max()-stlMesh[:,:,1].min()
54 | zext=stlMesh[:,:,2].max()-stlMesh[:,:,2].min()
55 | maxd=max(xext,yext)
56 | maxd=max(maxd,zext)
57 |
58 | Xmins=stlMesh[:,:,0].min(axis=1)
59 | XMI=Xmins.min()
60 | Xmaxs=stlMesh[:,:,0].max(axis=1)
61 | XMA=Xmaxs.max()
62 | Ymins=stlMesh[:,:,1].min(axis=1)
63 | YMI=Ymins.min()
64 | Ymaxs=stlMesh[:,:,1].max(axis=1)
65 | YMA=Ymaxs.max()
66 | Zmins=stlMesh[:,:,2].min(axis=1)
67 | ZMI=Zmins.min()
68 | Zmaxs=stlMesh[:,:,2].max(axis=1)
69 | ZMA=Zmaxs.max()
70 |
71 | offsetXY=5*hashing
72 | offsetZ=2*slicing
73 |
74 | # should set the range to match the hashing resolution! otherwise discrepancy with grid used later
75 | # assuming the grid goes through zero => each border should be a multiple of het hashing
76 | nrxmin=np.int(np.round((XMI-offsetXY)/hashing))
77 | nrxmax=np.int(np.round((XMA+offsetXY)/hashing))+1
78 | nrymin=np.int(np.round((YMI-offsetXY)/hashing))
79 | nrymax=np.int(np.round((YMA+offsetXY)/hashing))+1
80 |
81 | X,Y=np.meshgrid(np.arange(nrxmin*hashing,nrxmax*hashing,hashing),np.arange(nrymin*hashing,nrymax*hashing,hashing))
82 | Z=np.arange(ZMI,ZMA+offsetZ,slicing) # should start from zero
83 |
84 | # print('Maximum extent: {} with {} slices'.format(maxd,len(Z)))
85 | if (maxd<1):
86 | print('Warning, max extent is <1: {}'.format(maxd))
87 |
88 | return X,Y,Z
89 |
90 | def loadStl(stlName,mulDimension=1.,centred=True,zToZero=True,flipZ=False):
91 | from stl import mesh
92 | mm = mesh.Mesh.from_file(stlName)
93 | mm2=mm.vectors*mulDimension
94 |
95 | if (centred):
96 | xCentre=(mm2[:,:,0].min()+mm2[:,:,0].max())/2.0
97 | yCentre=(mm2[:,:,1].min()+mm2[:,:,1].max())/2.0
98 | mm2[:,:,0]=mm2[:,:,0]-xCentre
99 | mm2[:,:,1]=mm2[:,:,1]-yCentre
100 | if (flipZ):
101 | mm2[:,:,2]=mm2[:,:,2].max()-mm2[:,:,2]
102 | if (zToZero):
103 | mm2[:,:,2]=mm2[:,:,2]-mm2[:,:,2].min()
104 |
105 | Zmins=mm2[:,:,2].min(axis=1)
106 | Zmaxs=mm2[:,:,2].max(axis=1)
107 |
108 | return mm2,Zmins,Zmaxs
109 |
110 |
111 | # complete slicing into a hdf5 file
112 | # first load the stl with loadStl, this also gives the Zmins and Zmaxs values
113 | # when giving X & Y smaller than total size STL => not possible to get a good infill => use inFillMaster (hdf5-stack) with smaller resolution possibly
114 | def stlToStack(mesh,h5name,X,Y,Z,Zmins,Zmaxs,inFillMaster='',writingMask=np.ones((1,1)),epsilon=0.001,onlyShell=False):
115 | zslices=len(Z)
116 | height=X.shape[0]
117 | width=X.shape[1]
118 |
119 | if (writingMask.shape[0]==1):
120 | writingMask=np.ones(X.shape)
121 |
122 |
123 | compFilt=tables.Filters(complevel=3)
124 | with tables.open_file(h5name,filters=compFilt,mode="w") as h5file:
125 | Array3D=h5file.create_carray("/",'data',tables.BoolAtom(),(zslices,height,width),filters=compFilt)
126 | ArrayX=h5file.create_carray("/",'X',tables.Float32Atom(),(height,width))
127 | ArrayY=h5file.create_carray("/",'Y',tables.Float32Atom(),(height,width))
128 | ArrayZ=h5file.create_carray("/",'Z',tables.Float32Atom(),(zslices,))
129 | ArrayX[:]=X.copy()
130 | ArrayY[:]=Y.copy()
131 | ArrayZ[:]=Z.copy()
132 | h5file.root.data.attrs.GalvoCentre=[X[0].mean(),Y[:,0].mean(),Z.min()]
133 | h5file.root.data.attrs.PiezoCentre=[X[0].min(),Y[:,0].min(),Z.min()]
134 |
135 | if (inFillMaster!=''):
136 | with tables.open_file(inFillMaster,mode='r') as infill:
137 | Xglob=infill.root.X[:]
138 | Yglob=infill.root.Y[:]
139 | Zglob=infill.root.Z[:]
140 | Mglob=infill.root.data
141 |
142 | doCarving=False
143 | dXg=Xglob[0,1]-Xglob[0,0]
144 | dX=X[0,1]-X[0,0]
145 | dZg=Zglob[1]-Zglob[0]
146 | dZ=Z[1]-Z[0]
147 |
148 | if ((dX==dXg) and (dZ==dZg)):
149 | doCarving=True
150 |
151 | for i in range(len(Z)):
152 |
153 | if (doCarving):
154 | zind=np.searchsorted(Zglob,Z[i]) # check if it shouldn't be -1
155 | if (zind>=len(Zglob)):
156 | zind=len(Zglob)-1
157 | tempS=carvePiece(X,Y,Xglob,Yglob,Mglob[zind])
158 | Array3D[i,:,:]=tempS*writingMask
159 | else:
160 | # print(i)
161 | try:
162 | tempS=singleSliceStl(mesh,Zmins,Zmaxs,Z[i],X,Y,epsilon,onlyShell=True)
163 | zind=np.searchsorted(Zglob,Z[i])
164 | if (zind>=len(Zglob)):
165 | zind=len(Zglob)-1
166 | G=Mglob[zind][:]
167 | Ger=im.binary_erosion(G,iterations=3) # perhaps keep on eroding until number connected components changes? Or extract skeleton? => should reduce in-fill time?
168 | nrLabs,Labs=cv2.connectedComponents(Ger.astype(np.uint8))
169 | # print('First erosion: ',nrLabs)
170 | oldNrLabs=nrLabs
171 | if (nrLabs>1):
172 | while (oldNrLabs==nrLabs):
173 | Ger=im.binary_erosion(Ger,iterations=3)
174 | nrLabs,Labs=cv2.connectedComponents(Ger.astype(np.uint8))
175 | # print('Next erosion: ',nrLabs)
176 | tempS=fillInFromTemplate(tempS,X,Y,Ger,Xglob,Yglob)
177 | Array3D[i,:,:]=tempS*writingMask
178 | except:
179 | print('Problem with slice {} at position {}'.format(i,[X[0].mean(),Y[:,0].mean(),Z.min()]))
180 | # now just take the slice below the current one.
181 | if (i>0):
182 | Array3D[i,:,:]=Array3D[i-1,:,:]
183 | else:
184 | for i in range(len(Z)):
185 | # print(i)
186 | #try:
187 | Array3D[i,:,:]=writingMask*singleSliceStl(mesh,Zmins,Zmaxs,Z[i],X,Y,epsilon,onlyShell=onlyShell)
188 | #except:
189 | # now just take the slice below the current one. I could perhaps take the average of above vs below
190 | # print('Problem with slice {}'.format(i))
191 | #Array3D[i,:,:]=singleSliceStl(mm2,mm.normals*mulDimension,Zmins,Zmaxs,Z[i-1],X,Y,epsilon) # Not a perfect solution, but better than nothing!!
192 | return
193 |
194 |
195 | # will start with the simplest mask and complexify when needed (=> to function pointer probably)
196 | def applyStackMask(stackName,multMatrix):
197 | with tables.open_file(stackName,mode='a') as H:
198 | D=H.root.data
199 | for i in range(len(D)):
200 | D[i,:multMatrix.shape[0],:multMatrix.shape[1]]=D[i,:multMatrix.shape[0],:multMatrix.shape[1]]*multMatrix
201 | return
202 |
203 |
204 | # give as input already the loaded stl file with the mesh and the normals separately
205 | def singleSliceStl(mesh,Zmins,Zmaxs,Zslice,X,Y,epsilon=0.001,onlyShell=False,debug=False):
206 |
207 | lines=[]
208 | gI=np.argwhere(np.logical_and((Zmaxs>=Zslice),(Zmins<=Zslice)))[:,0]
209 | triangs=mesh[gI]
210 |
211 | T=np.zeros((X.shape[0],X.shape[1]),dtype=np.uint8) # this will be the filled in image
212 |
213 | for triang in triangs:
214 | dists=triang[:,2]-Zslice
215 | copoints=np.argwhere(np.abs(dists)1)):
218 | # print('In copoints situation',copoints.shape)
219 | # if (len(copoints)==2):
220 | tArray=triang[copoints[:,0]]
221 |
222 | if (len(copoints)!=2):
223 | tArray=np.row_stack((tArray,triang[copoints[0,0]]))
224 | #print(tArray)
225 |
226 | lines.append(tArray)
227 |
228 | if ((len(tArray)<3)):
229 | pp=triang[copoints[:,0]]
230 |
231 | else: # for in-plane triangles, add the centre point as a seed point
232 | vv=tArray[:3,:2]
233 | yy=convertToPixels(X,Y,vv)
234 | cv2.fillConvexPoly(T,yy.reshape(-1,1,2),255)
235 |
236 | else:
237 | ps=[]
238 | if (len(copoints)==1):
239 | poi=triang[copoints[:,0]]
240 | ps.append([poi[0,0],poi[0,1],Zslice])
241 |
242 |
243 | if (dists[0]*dists[1]<0):
244 | # print('Case 1')
245 | x1,y1,z1=triang[0,:]
246 | x2,y2,z2=triang[1,:]
247 | ts=(Zslice-z1)/(z2-z1)
248 | xs=(x2-x1)*ts+x1
249 | ys=(y2-y1)*ts+y1
250 | ps.append([xs,ys,Zslice])
251 |
252 | if (dists[1]*dists[2]<0):
253 | # print('Case 2')
254 | x1,y1,z1=triang[1,:]
255 | x2,y2,z2=triang[2,:]
256 | ts=(Zslice-z1)/(z2-z1)
257 | xs=(x2-x1)*ts+x1
258 | ys=(y2-y1)*ts+y1
259 | ps.append([xs,ys,Zslice])
260 |
261 | if (dists[0]*dists[2]<0):
262 | # print('Case 3')
263 | x1,y1,z1=triang[0,:]
264 | x2,y2,z2=triang[2,:]
265 | ts=(Zslice-z1)/(z2-z1)
266 | xs=(x2-x1)*ts+x1
267 | ys=(y2-y1)*ts+y1
268 | ps.append([xs,ys,Zslice])
269 |
270 | if (len(ps)==0):
271 | print('Freaky!!! Not case 1, 2 or 3')
272 |
273 | if (len(ps)>0):
274 | pp=np.asarray(ps)
275 | # print(pp)
276 | lines.append(pp)
277 |
278 |
279 |
280 | li=[(convertToPixels(X,Y,lines[ind][:,:2])).reshape(-1,1,2) for ind in range(len(lines))]
281 | li3=[ np.round(litem).astype(np.int32) for litem in li]
282 | res=cv2.polylines(T,li3,False,255,lineType=8)
283 |
284 | if (onlyShell):
285 | if (debug):
286 | return T,gI,lines
287 | else:
288 | return T
289 |
290 | if (T.sum()!=0):
291 | T=colourStlShell(T)
292 | return T
293 |
294 | def colourStlShell(TT):
295 | nrLabs,Labs=cv2.connectedComponents(TT)
296 | im,cnts,hier = cv2.findContours(TT, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
297 | hier=hier[0]
298 |
299 | bigTree=[]
300 | gen=np.argwhere(hier[:,3]==-1)[:,0]
301 |
302 | thisGen,nextGen=checkGeneration(gen,hier,cnts,Labs)
303 | bigTree.append(thisGen)
304 | while (len(nextGen)>0):
305 | thisGen,nextGen=checkGeneration(nextGen,hier,cnts,Labs)
306 | bigTree.append(thisGen)
307 |
308 | N=np.zeros((TT.shape))
309 | genNr=0
310 | for gen in bigTree:
311 | if (genNr%2==0): # should be white
312 | # print('Should paint white: {}'.format(gen))
313 | for gg in gen:
314 | cv2.drawContours(N,cnts,gg,1,-1)
315 | genNr=genNr+1
316 | else:
317 | # print('Should paint black: {}'.format(gen))
318 | for gg in gen:
319 | cv2.drawContours(N,cnts,gg,0,-1)
320 | for gg in gen:
321 | cv2.drawContours(N,cnts,gg,1,1)
322 | genNr=genNr+1
323 | return N
324 |
325 | def checkGeneration(gen,hier,cnts,Labs):
326 | thisGen=gen
327 | nextGen=[]
328 |
329 | findex=0
330 | while (findexXglob.shape[1]-1):
387 | endOffX=carveX[1]-Xglob.shape[1]+1
388 | carveX[1]=Xglob.shape[1]-1
389 | #print(carveX,startOffX,endOffX)
390 |
391 |
392 | carveY=ncoord[:,1]
393 | startOffY=0
394 | endOffY=0
395 | if (carveY[0]<0):
396 | startOffY=-carveY[0]
397 | carveY[0]=0
398 | if (carveY[1]>Xglob.shape[0]-1):
399 | endOffY=carveY[1]-Xglob.shape[0]+1
400 | carveY[1]=Xglob.shape[0]-1
401 | #print(carveY,startOffY,endOffY)
402 |
403 | M=np.zeros(Xloc.shape)
404 | M[startOffY:M.shape[0]-endOffY,startOffX:M.shape[1]-endOffX]=Mslice[carveY[0]:carveY[1]+1,carveX[0]:carveX[1]+1]
405 | return M
406 |
407 |
408 | def fillInFromTemplate(I,Xloc,Yloc,Template,X,Y):
409 | Mask=np.zeros((I.shape[0]+2,I.shape[1]+2),dtype=np.uint8)
410 | Mask[1:-1,1:-1]=I[:]
411 | TestIm=np.zeros(I.shape,np.uint8)
412 | gI=np.where(Template>0)
413 | Xnon=X[gI]
414 | Ynon=Y[gI]
415 | ggI=np.where(np.logical_and(YnonYloc.min(),np.logical_and(Xnon>Xloc.min(),XnontesBig[:,:-1])
480 | edgeXEnd=(tesBig[:,1:]0):
544 | startInd=max(0,sliceInd-amShells)
545 | stopInd=min(len(sliceIndList),sliceInd+amShells+1)
546 | extInds=sliceIndList[startInd:stopInd]
547 | Rstack=np.zeros((len(extInds),D.shape[1],D.shape[2]),dtype=bool)
548 | for jj in np.arange(len(extInds)):
549 | Rstack[jj]=D[extInds[jj]][:]
550 |
551 | extraStart=0
552 | extend=False
553 | if (sliceIndlen(sliceIndList)-1):
557 | extend=True
558 | if (extend):
559 | #print(startInd,stopInd,extraStart,extraStart+len(Rstack),len(Rstack))
560 | Rstack2=np.zeros((2*amShells+1,Rstack.shape[1],Rstack.shape[2]),dtype=bool)
561 | Rstack2[extraStart:extraStart+len(Rstack)]=Rstack
562 | Rstack=Rstack2
563 |
564 | RR=Rstack[amShells]
565 | if (RR.sum()==0):
566 | return sliceList,sliceCodes,sliceZList,sliceDist
567 |
568 | Xr=X.copy()
569 | Yr=Y.copy()
570 | erosionKernel=disk(hatchStep)
571 |
572 | # First do in-plane shells
573 | for i in range(amShells):
574 | if (RR.sum()>0):
575 | if (len(contApprox)==1):
576 | gSlice=getShell(RR.astype(np.uint8),Xr,Yr,zslice,contApprox[0],random=randomShell)
577 | else:
578 | if (i==0):
579 | gSlice=getShell(RR.astype(np.uint8),Xr,Yr,zslice,contApprox[0],random=randomShell)
580 | else:
581 | gSlice=getShell(RR.astype(np.uint8),Xr,Yr,zslice,contApprox[1],random=randomShell)
582 | sliceList.append(gSlice)
583 | sliceCodes.append(1) # 1 is for shell
584 | sliceZList.append(zslice)
585 | nDist=0
586 | for gArr in gSlice:
587 | nDist=nDist+np.sum(np.sqrt(np.sum((gArr[1:]-gArr[:-1])**2,axis=1)))
588 | sliceDist.append(nDist)
589 | if ((i==0) and (divider>1)): # should reduce resolution at this stage if necessary
590 | Rstack=Rstack[:,::divider,::divider]
591 | RR=Rstack[amShells]
592 | Xr=X[::divider,::divider]
593 | Yr=Y[::divider,::divider]
594 | #RR=im.binary_erosion(RR,iterations=iterations)
595 | RR=im.binary_erosion(RR,structure=erosionKernel)
596 | if (RR.sum()==0):
597 | return sliceList,sliceCodes,sliceZList,sliceDist
598 |
599 | # now check if some floors/ceilings need to be printed.
600 | # Rstack=Rstack[:,::iterations,::iterations]
601 | # RR=RR[::iterations,::iterations]
602 | # Xr=Xr[::iterations,::iterations]
603 | # Yr=Yr[::iterations,::iterations]
604 |
605 | rotAngle=-1
606 | if (len(np.atleast_1d(atAngles))>1):
607 | rotAngle=atAngles[sliceInd%len(atAngles)]
608 | else:
609 | if (atAngles==-1):
610 | rotAngle=np.random.randint(0,360)
611 | else:
612 | rotAngle=atAngles
613 |
614 |
615 | if (doTopBottom): # if you are doing shell + solid => better to switch of top-bottom.
616 | inds=np.arange(len(Rstack))
617 | inds=np.delete(inds,amShells)
618 | Cmin=Rstack[inds].min(axis=0)
619 |
620 |
621 | FillIn=np.logical_and(RR,np.logical_not(Cmin))
622 | if (FillIn.sum()>0):
623 | # now hatch these ceilings/floors without coarsening..
624 | gHatch,gDist=getHatching(FillIn.astype(np.uint8),Xr,Yr,zslice,angle=rotAngle,subdiv=hatchStep,crossed=True)
625 | sliceList.append(gHatch)
626 | sliceCodes.append(3) # 3 is for solid (straight lines with overlap), this is used for bottom/top hatching as well.
627 | sliceZList.append(zslice)
628 | sliceDist.append(gDist)
629 |
630 | RR=np.logical_and(RR,np.logical_not(FillIn))
631 |
632 | else: # no shells, only do scaffolds => Solid mode...
633 | Xr=X.copy()
634 | Yr=Y.copy()
635 | rotAngle=-1
636 | if (len(np.atleast_1d(atAngles))>1):
637 | rotAngle=atAngles[slicInd%len(atAngles)]
638 | else:
639 | if (atAngles==-1):
640 | rotAngle=np.random.randint(0,360)
641 | else:
642 | rotAngle=atAngles
643 |
644 | #print('Will try to hatch')
645 | # Finally, hatch in a coarse way the remaining material => Scaffolding
646 | if (scaffoldStep>0):
647 | if (RR.sum()>0):
648 | gHatch,gDist=getHatching(RR.astype(np.uint8),Xr,Yr,zslice,angle=rotAngle,subdiv=scaffoldStep)
649 | if (len(gHatch)>0):
650 | sliceList.append(gHatch)
651 | sliceCodes.append(2) # 2 is for scaffolding (straight lines with no hatching, doubles slicing)
652 | sliceZList.append(zslice)
653 | sliceDist.append(gDist)
654 |
655 | return sliceList,sliceCodes,sliceZList,sliceDist
656 |
657 | #~ def getFullShellScaffold(h5name,amShells,scaffoldJump,atAngles=[0,90,180,270],contApprox=['SIMPLE'],divider=1,iterations=1,randomShell=True,doTopBottom=True):
658 | #~ h5=tables.open_file(h5name,mode='r')
659 |
660 | #~ X=h5.root.X[:]
661 | #~ Y=h5.root.Y[:]
662 | #~ Z=h5.root.Z[:]
663 | #~ D=h5.root.data
664 |
665 | #~ allSliceList=[]
666 | #~ allSliceCodes=[]
667 | #~ allSliceZList=[]
668 | #~ allSliceDistList=[]
669 | #~ for slicInd in range(0,len(Z),1):
670 | #~ # for slicInd in range(0,65,1):
671 | #~ sList,sCodes,sZs,newDists=getSliceOfShellScaffold(D,X,Y,Z,slicInd,amShells,scaffoldJump,atAngles,contApprox,divider,iterations,randomShell,doTopBottom)
672 | #~ if (len(sCodes)==0):
673 | #~ continue
674 | #~ for j in range(len(sList)):
675 | #~ allSliceList.append(sList[j])
676 | #~ allSliceCodes.append(sCodes[j])
677 | #~ allSliceZList.append(sZs[j])
678 | #~ # print(slicInd,j,len(sList),len(newDists),sum(newDists))
679 | #~ allSliceDistList.append(newDists[j])
680 | #~ h5.close()
681 | #~ return allSliceList,allSliceCodes,allSliceZList,allSliceDistList
682 |
683 | # amShells gives the amount of contours to include
684 | # scaffoldStep gives the amount of lines in the stack to skip while making the solid infill => total dist = hatching of stack * scaffoldStep
685 | # hatchStep is similar to the above, but now for the distance between contours and the distance for the top and bottom 'shells'
686 | # codeSpeeds is typically a list of three speeds for the three line-types: contour, top/bottom shell, scaffold or inner infill
687 | # codeIntens is a list giving the intensities to go with the above speeds
688 | # atAngles: between each slicing layer, the view will be rotated by the next angle to get better uniformity
689 | # sliceIndexList: when empty all slices in the stack are done, otherwise only the indices in this list are used. => Variable slicing distance!
690 | # contApprox: contour approximation technique => see opencv documentation
691 | # divider: reduce image stack size after first contour extraction to speed up execution; not really used anymore
692 | # randomShell: whether to start at a random position along the contour
693 | # doTopBottom: when writing contours or shells, should he also make upper/lower shells? Switch off when in solid mode
694 | # writeHeader: make own gwl header or avoid when writing one of the blocks in a big stitch
695 | # writeColourH5: can also create a colour-coded hdf5-file for visualisation of the instructions
696 | #
697 | # returns the lengths written per coded line-type: combined with the codeSpeeds, this allows for an easy time-estimation for the structure
698 |
699 | # a simple Zscaler function giving 1 as an intensity multiplier independent of input height
700 | def noBaseReflection(zheight):
701 | return 1.0
702 | def stackToGwl(stackName,gwlName,amShells,scaffoldStep,hatchStep,atAngles=[0,90,180,270],codeSpeeds=[10000,10000,10000],codeIntens=[80,80,80],sliceIndexList=[],contApprox=['L1'],divider=1,randomShell=True,doTopBottom=True,writeHeader=True,writeColourH5=False,interfacePos=-1,ZscalerFunc=noBaseReflection):
703 | with tables.open_file(stackName,mode='r') as h5:
704 | X=h5.root.X[:]
705 | Y=h5.root.Y[:]
706 | Z=h5.root.Z[:]
707 | D=h5.root.data
708 | GC=h5.root.data.attrs.GalvoCentre
709 |
710 | allSliceList=[]
711 | allSliceCodes=[]
712 | allSliceZList=[]
713 | allSliceDistList=[]
714 | if (len(sliceIndexList)==0):
715 | sliceIndexList=range(0,len(Z))
716 |
717 | for slicInd in np.arange(len(sliceIndexList)):
718 | sList,sCodes,sZs,newDists=getSliceOfShellScaffold(D,X,Y,Z,sliceIndexList,slicInd,amShells,scaffoldStep,hatchStep,atAngles,contApprox,divider,randomShell,doTopBottom)
719 | if (len(sCodes)==0):
720 | continue
721 | for j in range(len(sList)):
722 | allSliceList.append(sList[j])
723 | allSliceCodes.append(sCodes[j])
724 | allSliceZList.append(sZs[j])
725 | # print(slicInd,j,len(sList),len(newDists),sum(newDists))
726 | allSliceDistList.append(newDists[j])
727 |
728 | writeGwlFile(gwlName,allSliceList,allSliceCodes,allSliceZList,codeSpeeds,codeIntens,stagePosition=GC,writeHeader=writeHeader,interfacePos=interfacePos,debug=True,ZscalerFunc=ZscalerFunc)
729 | if (writeColourH5):
730 | Z2=Z[sliceIndexList]
731 | writeH5ColourGwl(gwlName[:-4]+'_col.h5',allSliceList,allSliceCodes,allSliceZList,X,Y,Z2)
732 | return getCodeLengths(allSliceDistList,allSliceCodes)
733 |
734 | def getCodeLengths(allSliceDistList,allSliceCodes):
735 | if (len(allSliceCodes)!=0):
736 | amCodes=max(np.unique(allSliceCodes))
737 | codeLengths=np.zeros(amCodes)
738 | try:
739 | for i in range(len(allSliceDistList)):
740 | codeLengths[allSliceCodes[i]-1]=codeLengths[allSliceCodes[i]-1]+allSliceDistList[i]
741 | except:
742 | print('Problem with codelength calculation!')
743 | print(allSliceDistList)
744 | print(allSliceCodes)
745 | return codeLengths
746 | else:
747 | return [0]
748 |
749 |
750 |
751 | def writeGwlHeader(F,interfacePos,CodeSpeeds,CodeIntensities):
752 | F.write('GalvoScanMode\r')
753 | F.write('ContinuousMode\r\r')
754 | # F.write('ConnectPointsOff\r') # not used for GT in continuous mode
755 | F.write('InvertZAxis 1\r') # DiLL configuration
756 | F.write('% DefocusFactor 0.6\r') # for air objective
757 | F.write('TiltCorrectionOff\r') # not available with GT
758 | F.write('StageVelocity 200\r')
759 | F.write('PowerScaling 1\r')
760 | F.write('GalvoAcceleration 2\r\r')
761 |
762 | F.write('var $folderNumber = 1\r\r')
763 |
764 | F.write('% 1 is for shell, 2 for interior and 3 for top/bottom\r')
765 | for j in range(len(CodeSpeeds)):
766 | F.write('var $Speed{} = {}\r'.format(j+1,CodeSpeeds[j]))
767 | F.write('var $Power{} = {}\r'.format(j+1,CodeIntensities[j]))
768 | # F.write('local $block = 0\r')
769 |
770 |
771 | F.write('\r')
772 | F.write('DebugModeOn\r')
773 | F.write('TimeStampOn\r')
774 |
775 | F.write ('var $perSliceDebug = 0\r')
776 | F.write ('var $perBlockDebug = 1\r')
777 | F.write ('var $writePics = 1\r\r')
778 |
779 | F.write('XOffset 0\r') # Will use offsets later so initialise here
780 | F.write('YOffset 0\r')
781 | F.write('ZOffset 0\r\r') # Can add 3.5 here for a proper interface positioning
782 |
783 | F.write('var $doTiltCorrection = 0 % When >0, a correction for the interface location will be used instead of FindInterfaceAt\r')
784 | F.write('var $tiltdX=0 % The coefficient a in the fitted plane formula: Z=ax+by+c\r')
785 | F.write('var $tiltdY=0 % The coefficent b in the above formula\r')
786 | F.write('var $tiltInterface=3 % The equivalent FindInterfaceAt location\r')
787 | F.write('var $tiltMargin=20 % This is the amount of +- correction that will be allowed\r\r')
788 | F.write('if $doTiltCorrection>0 \r')
789 | F.write(' local $tiltInt=$tiltMargin+$tiltInterface\r')
790 | F.write(' FindInterfaceAt $tiltInt\r')
791 | F.write(' ZDrivePosition\r')
792 | F.write(' AddZOffset $tiltMargin\r')
793 | F.write('end\r\r')
794 |
795 |
796 |
797 | F.write('var $interfacePos = {} % if >=0 will be done for each block => clashes with TiltCorrection!!\r'.format(interfacePos))
798 | F.write('var $piezoInterf = 10 % piezo will be moved here to do the FindInterface\r')
799 | F.write('var $piezoWrite = 150 % afterwards it will come back here to write the block\r\r')
800 |
801 | F.write('var $currX = 0\r') # This will be used for relative positioning of different stitching blocks or arrays
802 | F.write('var $currY = 0\r')
803 | F.write('var $currZ = 0\r\r')
804 |
805 | F.write('% Write out the static parameters to the log file\r')
806 | F.write('ShowParameter\r')
807 | F.write('MessageOut "Speed1 %d Speed2 %d Speed3 %d" #($Speed1,$Speed2,$Speed3)\r')
808 | F.write('MessageOut "Power1 %d Power2 %d Power3 %d" #($Power1,$Power2,$Power3)\r')
809 | F.write('MessageOut "tiltMargin %d tiltInterface %d interfacePos %d" #($tiltMargin,$tiltInterface,$interfacePos)\r\r')
810 | F.write('% ----------- End of header -----------\r\r')
811 | return
812 |
813 |
814 |
815 | # Writes to GWL file
816 | # The adaptation of intensity on Z-position will be handled here.
817 | # stagePosition is used for stitching multiple blocks. Will move stage to this position.
818 | # In the header the current location is initialised to (0,0,0) and will then move stage to given location.
819 | # X,Y and ZOffsets are next incremented to this stagePosition, so as to centre the coordinate system for Galvowriting.
820 | # so make sure that all coordinates are reachable when the coordinates are centered on this stagePosition
821 | # interfacePos <0 implies that FindInterfaceAt is not called for this block, if >=0 it is called with the given value
822 | def writeGwlFile(filename,allSliceList,allSliceCodes,allSliceZList,CodeSpeeds=[10000,10000,10000],CodeIntensities=[80,80,80],stagePosition=[0,0,0],interfacePos=-1,ZscalerFunc=noBaseReflection,writeHeader=True,debug=True):
823 | with open(filename,mode="w") as F:
824 | if (writeHeader):
825 | writeGwlHeader(F,interfacePos,CodeSpeeds,CodeIntensities)
826 |
827 |
828 | # first handle relative movement of stage if necessary
829 | # also adapt X,Y and ZOffset parameters to have properly centered Galvo-coordinates for Nanoscribe
830 | # even if inside the gwl blocks, the original coordinate system is maintained.
831 | F.write('NewStructure\r')
832 | F.write('% The following commands are used for relative positioning.\r')
833 | F.write('% If you change them for manual positioning, do not forget to change (remove) the AddX(Y or Z)Offset values as well\r')
834 | F.write('local $locX = {:.0f}\r'.format(stagePosition[0])) # target values
835 | F.write('local $locY = {:.0f}\r'.format(stagePosition[1]))
836 | F.write('local $locZ = {:.0f}\r\r'.format(stagePosition[2]))
837 |
838 | F.write('local $xmov = $locX-$currX\r')
839 | F.write('MoveStageX $xmov\r')
840 | F.write('AddXOffset -$xmov\r')
841 | F.write('local $ymov = $locY-$currY\r')
842 | F.write('MoveStageY $ymov\r')
843 | F.write('AddYOffset -$ymov\r\r')
844 |
845 | F.write('local $zmov = $locZ-$currZ\r')
846 | # F.write('AddZDrivePosition $zmov\r')
847 | F.write('AddZOffset -$zmov\r\r')
848 |
849 | F.write('% Now find the interface if $interfacePos >=0\r')
850 | F.write('if $interfacePos >= 0 \r')
851 | F.write(' PiezoGotoY $piezoInterf \r')
852 | F.write(' FindInterfaceAt $interfacePos \r')
853 | F.write(' PiezoGotoY $piezoWrite \r')
854 | F.write(' ZDrivePosition\r')
855 | F.write(' set $currZ=0 % after a findinterface I am at zero again.\r')
856 | F.write('end\r\r')
857 |
858 | F.write('local $zmov = $locZ-$currZ\r')
859 | F.write('AddZDrivePosition $zmov\r')
860 | # F.write('AddZOffset -$zmov\r\r')
861 |
862 | F.write('set $currX=$locX\r')
863 | F.write('set $currY=$locY\r')
864 | F.write('set $currZ=$locZ\r\r')
865 |
866 |
867 |
868 | F.write('% Now handle the interface tilt correction => clashes with FindInterfaceAt\r')
869 | F.write('local $tiltCorr = 0\r')
870 | F.write('if $doTiltCorrection >0 \r')
871 | F.write(' set $tiltCorr = $tiltdX*$currX+$tiltdY*$currY\r')
872 | F.write(' AddZOffset $tiltCorr\r')
873 | F.write('end\r\r')
874 |
875 | F.write('% Now write out all the relevant numbers to the log file \r')
876 | F.write('MessageOut "tiltCorr %_2f " #($tiltCorr)\r')
877 | F.write('MessageOut "locX %_2f locY %_2f locZ %_2f" #($locX,$locY,$locZ)\r')
878 |
879 |
880 | Zscaler=1.0
881 | pointCounter=0
882 | lineCounter=0
883 | oldCode=-1
884 | oldZ=-1
885 | zCount=len(np.unique(allSliceZList))
886 | zIndex=0 # a counter to allow following the slice progress
887 | writeSliceInfo=False
888 | for i in np.arange(len(allSliceList)):
889 | sliceArray=allSliceList[i]
890 | zz=allSliceZList[i]
891 | cod=allSliceCodes[i] # can modify speed etc based on this code
892 | if ((cod!=oldCode) or (zz!=oldZ)):
893 | if (debug and (zz!=oldZ)):
894 | # first write old slice info
895 | F.write('if $perSliceDebug>0 \r')
896 | F.write(' MessageOut "Finished slice {0:05d} out of {1:d} for block %05d" #($folderNumber)\r'.format(zIndex,zCount))
897 | F.write(' if $writePics>0 \r')
898 | F.write(' Wait 0.1\r')
899 | filenam='PicPerSlice/Pic_Block_%05d_{0:05d}.tif'.format(zIndex) # I could also add the timestamp in the name or location (in case of stitching)
900 | F.write(' CapturePhoto "{}" #($folderNumber)\r'.format(filenam))
901 | F.write(' end\r')
902 |
903 |
904 | zIndex=zIndex+1
905 | F.write(' MessageOut "Started slice {0:05d} out of {1:d} for block %05d" #($folderNumber)\r'.format(zIndex,zCount))
906 | F.write('end\r\r')
907 |
908 | F.write('ScanSpeed $Speed{}\r'.format(cod))
909 | F.write('LaserPower $Power{}\r'.format(cod))
910 | Zscaler=ZscalerFunc(zz)
911 | F.write('MultLaserPower {0:.2f}\r\r'.format(Zscaler))
912 |
913 |
914 | oldCode=cod
915 | oldZ=zz
916 |
917 | for j in np.arange(len(sliceArray)):
918 | for k in np.arange(len(sliceArray[j])):
919 | if (sliceArray[j].shape[1]==3):
920 | F.write('{0:.2f} {1:.2f} {2:.2f}\r'.format(sliceArray[j][k,0],sliceArray[j][k,1],sliceArray[j][k,2]))
921 | elif (sliceArray[j].shape[1]==4):
922 | F.write('{0:.2f} {1:.2f} {2:.2f} {3:.2f}\r'.format(sliceArray[j][k,0],sliceArray[j][k,1],sliceArray[j][k,2],sliceArray[j][k,3]))
923 | pointCounter=pointCounter+1
924 | F.write("write\r\r")
925 | lineCounter=lineCounter+1
926 |
927 | if (debug):
928 | F.write('if $perSliceDebug>0 \r')
929 | F.write('MessageOut "Finished slice {0:05d} out of {1:d} for block %05d" #($folderNumber)\r'.format(zIndex,zCount))
930 | F.write(' if $writePics>0 \r')
931 | F.write(' Wait 0.1\r')
932 | filenam='PicPerSlice/Pic_Block_%05d_{0:05d}.tif'.format(zIndex) # I could also add the timestamp in the name or location (in case of stitching)
933 | F.write(' CapturePhoto "{}" #($folderNumber)\r'.format(filenam))
934 | F.write(' end\r')
935 | F.write('end\r\r')
936 |
937 | if (writeHeader): # if you don't want a header, than it is likely that you are not writing a single block => save the log only once!
938 | messfile='MyLogfile Block%d' # should make this name more unique in case of stitching but cannot make new folder
939 | if (debug):
940 | F.write('SaveMessages "{}" #($folderNumber)\r'.format(messfile))
941 |
942 | F.write('% Now remove the interface tilt correction again, if applied\r')
943 | F.write('if $doTiltCorrection >0 \r')
944 | F.write(' AddZOffset -$tiltCorr\r')
945 | F.write('end\r\r')
946 |
947 | return pointCounter,lineCounter
948 |
949 | # Writes an empty GWL file, used for interface tilt determination
950 | def writeEmptyGwlFile(filename,stagePosition=[0,0,0],interfacePos=0,writeHeader=True,debug=True):
951 | with open(filename,mode="w") as F:
952 | if (writeHeader):
953 | writeGwlHeader(F,interfacePos,[0],[0])
954 |
955 |
956 | # first handle relative movement of stage if necessary
957 | # also adapt X,Y and ZOffset parameters to have properly centered Galvo-coordinates for Nanoscribe
958 | # even if inside the gwl blocks, the original coordinate system is maintained.
959 | F.write('% The following commands are used for relative positioning.\r')
960 | F.write('% If you change them for manual positioning, do not forget to change (remove) the AddX(Y or Z)Offset values as well\r')
961 | F.write('local $locX = {:.0f}\r'.format(stagePosition[0])) # target values
962 | F.write('local $locY = {:.0f}\r'.format(stagePosition[1]))
963 | F.write('local $locZ = {:.0f}\r\r'.format(stagePosition[2]))
964 |
965 | F.write('local $xmov = $locX-$currX\r')
966 | F.write('MoveStageX $xmov\r')
967 | F.write('AddXOffset -$xmov\r')
968 | F.write('local $ymov = $locY-$currY\r')
969 | F.write('MoveStageY $ymov\r')
970 | F.write('AddYOffset -$ymov\r')
971 | F.write('local $zmov = $locZ-$currZ\r')
972 | F.write('AddZDrivePosition $zmov\r')
973 | F.write('AddZOffset -$zmov\r')
974 |
975 | F.write('set $currX=$locX\r')
976 | F.write('set $currY=$locY\r')
977 | F.write('set $currZ=$locZ\r\r')
978 |
979 | # handle using FindInterfaceAt
980 | F.write('if $interfacePos >= 0 \r')
981 | F.write(' PiezoGotoY $piezoInterf \r')
982 | F.write(' FindInterfaceAt $interfacePos \r')
983 | F.write(' PiezoGotoY $piezoWrite \r')
984 | F.write(' ZDrivePosition\r')
985 | F.write('end\r\r')
986 |
987 |
988 | return
989 |
990 | def writeGwlForRegex(outGwlName,filePattern,codeSpeeds=[10000,10000,10000],codeIntensities=[80,80,80],interfacePos=-1,debug=True):
991 | fp=os.path.split(filePattern)
992 | if (fp[0]==''):
993 | fnames,fnumbs=findFiles(filePattern)
994 | else:
995 | fnames,fnumbs=findFiles(fp[1],fp[0])
996 | with open(outGwlName,mode="w") as F:
997 | writeGwlHeader(F,interfacePos,codeSpeeds,codeIntensities)
998 |
999 | blockCounter=0
1000 | for ff in fnames:
1001 | F.write('if $perBlockDebug>0 \r')
1002 | F.write(' MessageOut "Starting block {0:d} out of {1:d}"\r'.format(blockCounter,len(fnames)))
1003 | F.write('end \r')
1004 | #F.write('include {}\r'.format(os.path.split(ff)[1])) # perhaps use relpath first and replace / with \
1005 | tempPath=os.path.relpath(ff)
1006 | startP=tempPath.find('/')
1007 | F.write('include {}\r'.format(tempPath[startP+1:].replace('/','\\')))
1008 |
1009 | F.write('if $perBlockDebug>0 \r')
1010 | F.write(' MessageOut "Finished block {0:d} out of {1:d}"\r'.format(blockCounter,len(fnames)))
1011 | F.write(' if $writePics>0 \r')
1012 | filenam='PicPerBlock/Block_%05d.tif'
1013 | F.write(' Wait 1\r')
1014 | F.write(' CapturePhoto "{}" #($folderNumber)\r'.format(filenam))
1015 | F.write(' end\r')
1016 |
1017 | F.write('end \r\r\r')
1018 |
1019 | F.write('set $folderNumber=$folderNumber+1\r')
1020 | blockCounter=blockCounter+1
1021 |
1022 | if (debug):
1023 | F.write('SaveMessages "MyLogfile"\r')
1024 |
1025 | return
1026 |
1027 |
1028 |
1029 |
1030 |
1031 |
1032 | # other good colourmaps 'viridis', 'magma','plasma','inferno', 'coolwarm', 'rainbow','spectral'
1033 | def writePointCloud(allSliceList,allSliceCodes,filename,divider=10,colorName='coolwarm',goodCodes=[0,1,2,3,4]):
1034 | #just modified the input parameters
1035 | pointCounter=0
1036 | pointList=np.zeros((0,3))
1037 | pointList=[]
1038 | for i in np.arange(len(allSliceList)):
1039 | cod=allSliceCodes[i] # can modify speed etc based on this code
1040 | if (cod not in goodCodes):
1041 | continue
1042 | sliceArray=allSliceList[i]
1043 | for contour in sliceArray:
1044 | for pp in contour:
1045 | if (np.random.randint(divider)==0):
1046 | # pointList=np.append(pointList,contour,axis=0)
1047 | pointList.append(pp)
1048 |
1049 | Pts=np.asarray(pointList)
1050 |
1051 | Hs=np.unique(np.asarray(Pts[:,2]))
1052 |
1053 | amPoints=len(Pts)
1054 | F=open(filename,mode='w')
1055 | F.write('# .PCD v.7 - Point Cloud Data file format\n')
1056 | F.write('VERSION .7\n')
1057 | F.write('FIELDS x y z rgb\n')
1058 | F.write('SIZE 4 4 4 4\n')
1059 | F.write('TYPE F F F I\n')
1060 | F.write('COUNT 1 1 1 1\n')
1061 | F.write('WIDTH {}\n'.format(amPoints))
1062 | F.write('HEIGHT 1\n')
1063 | F.write('VIEWPOINT 0 0 0 1 0 0 0\n')
1064 | F.write('POINTS {}\n'.format(amPoints))
1065 | F.write('DATA ascii\n')
1066 |
1067 | mappie = plt.get_cmap(colorName)
1068 | cNorm=colcol.Normalize(vmin=Hs.min(),vmax=Hs.max())
1069 | scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=mappie)
1070 | cc=scalarMap.to_rgba(Hs)
1071 | rgb = (((cc[:,0]*255).astype(int) << 16) + ((cc[:,1]*255).astype(int) << 8) + (cc[:,2]*255).astype(int))
1072 |
1073 |
1074 | for pp in Pts:
1075 | gi=np.searchsorted(Hs,pp[2]) # only 1 height per slice
1076 | col=rgb[gi]
1077 | F.write('{:.4f} {:.4f} {:.4f} {:}\n'.format(pp[0],pp[1],pp[2],col))
1078 |
1079 | F.close()
1080 | return amPoints
1081 |
1082 | # other good colourmaps 'viridis','magma','plasma','inferno','rainbow','spectral'
1083 | def writePointCloud2(allSliceList,allSliceCodes,allSliceZList,filename,divider=10,colorName='coolwarm'):
1084 | zmin=np.min(allSliceZList)
1085 | zmax=np.max(allSliceZList)
1086 |
1087 |
1088 | F=open(filename,mode='w')
1089 | F.write('# .PCD v.7 - Point Cloud Data file format\n')
1090 | F.write('VERSION .7\n')
1091 | F.write('FIELDS x y z rgb\n')
1092 | F.write('SIZE 4 4 4 4\n')
1093 | F.write('TYPE F F F I\n')
1094 | F.write('COUNT 1 1 1 1\n')
1095 | F.write('WIDTH ')
1096 | writeLoc1=F.tell()
1097 | filler=' ' # this should be larger than amount of points
1098 | F.write(filler+'\n')
1099 | F.write('HEIGHT 1\n')
1100 | F.write('VIEWPOINT 0 0 0 1 0 0 0\n')
1101 | F.write('POINTS ')
1102 | writeLoc2=F.tell()
1103 | F.write(filler+'\n')
1104 | F.write('DATA ascii\n')
1105 |
1106 | Hs=np.arange(zmin,zmax,0.1)
1107 | mappie = plt.get_cmap(colorName)
1108 | cNorm=colcol.Normalize(vmin=Hs.min(),vmax=Hs.max())
1109 | scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=mappie)
1110 | cc=scalarMap.to_rgba(Hs)
1111 | rgb = (((cc[:,0]*255).astype(int) << 16) + ((cc[:,1]*255).astype(int) << 8) + (cc[:,2]*255).astype(int))
1112 |
1113 | pointCounter=0
1114 | for i in np.arange(len(allSliceList)):
1115 | cod=allSliceCodes[i] # can modify speed etc based on this code
1116 | if (cod!=2):
1117 | continue
1118 | sliceArray=allSliceList[i]
1119 | gi=np.searchsorted(Hs,sliceArray[0][0,2]) # only 1 height per slice
1120 | col=rgb[gi]
1121 | for contour in sliceArray:
1122 | for pp in contour:
1123 | if (np.random.randint(divider)==0):
1124 | F.write('{:.4f} {:.4f} {:.4f} {:}\n'.format(pp[0],pp[1],pp[2],col))
1125 | pointCounter=pointCounter+1
1126 |
1127 | F.close()
1128 |
1129 | amPoints='{}'.format(pointCounter)
1130 | neededSpaces=len(filler)-len(amPoints)
1131 | F=open(filename,mode='r+')
1132 |
1133 | F.seek(writeLoc1)
1134 | F.write(amPoints)
1135 | for i in np.arange(neededSpaces):
1136 | F.write(' ')
1137 | F.write('\n')
1138 |
1139 | F.seek(writeLoc2)
1140 | F.write(amPoints)
1141 | for i in np.arange(neededSpaces):
1142 | F.write(' ')
1143 | F.write('\n')
1144 | F.close()
1145 |
1146 | return pointCounter
1147 |
1148 | # X, Y & Z are ranges with their values. Will make a 2d matrix of X & Y myself
1149 | def writeEmptyStack(h5name,X,Y,Z,compression=True):
1150 | zslices=len(Z)
1151 | Xm,Ym=np.meshgrid(X,Y)
1152 | height=Xm.shape[0]
1153 | width=Xm.shape[1]
1154 |
1155 | compFilt=None
1156 | if compression:
1157 | compFilt=tables.Filters(complevel=3)
1158 |
1159 | with tables.open_file(h5name,filters=compFilt,mode="w") as h5file:
1160 | #Array3D=h5file.createCArray("/",'data',tables.UInt8Atom(),(amount,height,width),filters=compFilt)
1161 | Array3D=h5file.create_carray("/",'data',tables.BoolAtom(),(zslices,height,width),filters=compFilt)
1162 | ArrayX=h5file.create_carray("/",'X',tables.Float32Atom(),(height,width))
1163 | ArrayY=h5file.create_carray("/",'Y',tables.Float32Atom(),(height,width))
1164 | ArrayZ=h5file.create_carray("/",'Z',tables.Float32Atom(),(zslices,))
1165 | ArrayX[:]=Xm.copy()
1166 | ArrayY[:]=Ym.copy()
1167 | ArrayZ[:]=Z.copy()
1168 |
1169 | return
1170 |
1171 | def formulaToStack(h5name,X,Y,Z,sliceFormula,extraParams=[],writingMask=np.ones((1,1)),compression=True):
1172 | zslices=len(Z)
1173 | height=X.shape[0]
1174 | width=X.shape[1]
1175 |
1176 | if (writingMask.shape[0]==1):
1177 | writingMask=np.ones(X.shape)
1178 |
1179 | compFilt=None
1180 | if compression:
1181 | compFilt=tables.Filters(complevel=3)
1182 |
1183 | with tables.open_file(h5name,filters=compFilt,mode="w") as h5file:
1184 | #Array3D=h5file.createCArray("/",'data',tables.UInt8Atom(),(amount,height,width),filters=compFilt)
1185 | Array3D=h5file.create_carray("/",'data',tables.BoolAtom(),(zslices,height,width),filters=compFilt)
1186 | ArrayX=h5file.create_carray("/",'X',tables.Float32Atom(),(height,width))
1187 | ArrayY=h5file.create_carray("/",'Y',tables.Float32Atom(),(height,width))
1188 | ArrayZ=h5file.create_carray("/",'Z',tables.Float32Atom(),(zslices,))
1189 | ArrayX[:]=X.copy()
1190 | ArrayY[:]=Y.copy()
1191 | ArrayZ[:]=Z.copy()
1192 | for i in range(len(Z)):
1193 | Array3D[i,:,:]=writingMask*sliceFormula(X,Y,Z[i],extraParams)
1194 | # Still add GalvoCentre and PiezoCentre attributes
1195 | h5file.root.data.attrs.GalvoCentre=[X[0].mean(),Y[:,0].mean(),Z.min()]
1196 | h5file.root.data.attrs.PiezoCentre=[X[0].min(),Y[:,0].min(),Z.min()]
1197 | return
1198 |
1199 | def formulaToStack2(h5name,X,Y,Z,sliceFormula,maskFormula,extraParams,compression=True):
1200 | zslices=len(Z)
1201 | height=X.shape[0]
1202 | width=X.shape[1]
1203 |
1204 | compFilt=None
1205 | if compression:
1206 | compFilt=tables.Filters(complevel=3)
1207 |
1208 | with tables.open_file(h5name,filters=compFilt,mode="w") as h5file:
1209 | #Array3D=h5file.createCArray("/",'data',tables.UInt8Atom(),(amount,height,width),filters=compFilt)
1210 | Array3D=h5file.create_carray("/",'data',tables.BoolAtom(),(zslices,height,width),filters=compFilt)
1211 | ArrayX=h5file.create_carray("/",'X',tables.Float32Atom(),(height,width))
1212 | ArrayY=h5file.create_carray("/",'Y',tables.Float32Atom(),(height,width))
1213 | ArrayZ=h5file.create_carray("/",'Z',tables.Float32Atom(),(zslices,))
1214 | ArrayX[:]=X.copy()
1215 | ArrayY[:]=Y.copy()
1216 | ArrayZ[:]=Z.copy()
1217 | for i in range(len(Z)):
1218 | mask=maskFormula(X,Y,Z[i],extraParams)
1219 | Array3D[i,:,:]=mask*sliceFormula(X,Y,Z[i],extraParams)
1220 | # Still add GalvoCentre and PiezoCentre attributes
1221 | h5file.root.data.attrs.GalvoCentre=[X[0].mean(),Y[:,0].mean(),Z.min()]
1222 | h5file.root.data.attrs.PiezoCentre=[X[0].min(),Y[:,0].min(),Z.min()]
1223 | return
1224 |
1225 | def writeH5ColourGwl(h5name,allSliceList,allSliceCodes,allSliceZList,X,Y,Z,compression=True,zprecision=0.05):
1226 | zslices=len(Z)
1227 | height,width=X.shape
1228 | hashing=X[0,1]-X[0,0]
1229 | compFilt=None
1230 | if compression:
1231 | compFilt=tables.Filters(complevel=3)
1232 |
1233 | with tables.open_file(h5name,filters=compFilt,mode="w") as h5file:
1234 |
1235 | Array3D=h5file.create_carray("/",'data',tables.UInt8Atom(),(zslices,height,width*3),filters=compFilt)
1236 | ArrayX=h5file.create_carray("/",'X',tables.Float32Atom(),(height,width))
1237 | ArrayY=h5file.create_carray("/",'Y',tables.Float32Atom(),(height,width))
1238 | ArrayZ=h5file.create_carray("/",'Z',tables.Float32Atom(),(zslices,))
1239 | ArrayX[:]=X.copy()
1240 | ArrayY[:]=Y.copy()
1241 | ArrayZ[:]=Z.copy()
1242 | h5file.root.data.set_attr('PIXFORMAT',b'RGB8') # so it can be visualised by gigaviewer
1243 |
1244 | currZ=-1
1245 | T=np.zeros((X.shape[0],X.shape[1],3),dtype=np.uint8)
1246 | pos=0
1247 | colours=[(255,0,0),(0,255,0),(0,0,255),(120,120,120)]
1248 | for i in range(len(allSliceZList)):
1249 | if (np.abs(allSliceZList[i]-currZ)>zprecision): # first time at this height => do initialisation
1250 | pos=np.searchsorted(Z,allSliceZList[i]-zprecision)
1251 | #print(i,allSliceZList[i],currZ,pos)
1252 | Array3D[pos,:,:]=T.reshape((T.shape[0],T.shape[1]*3)) # now write old T (for that one is finished now), pos 0 will be overwritten but ok
1253 | T=np.zeros((X.shape[0],X.shape[1],3),dtype=np.uint8) # make new T ready for next layers
1254 | currZ=allSliceZList[i]
1255 |
1256 | # NEED to accumulate all images on a given height (add colours not replace)!
1257 |
1258 | profs=allSliceList[i]
1259 | li=[(convertToPixels(X,Y,profs[ind][:,:2])).reshape(-1,1,2) for ind in range(len(profs))]
1260 | #li=[profs[ind][:,:2].reshape(-1,1,2) for ind in range(len(profs))]
1261 |
1262 | #li2=(li-X.min())/hashing # Only correct when X.min() = Y.min() => should substract differently for X and Y
1263 | li3=[ np.round(litem).astype(np.int32) for litem in li]
1264 | # li=[((profs[ind][:,:2]-X.min())/(X.max()-X.min())*X.shape[0]).astype(np.int32).reshape(-1,1,2) for ind in range(len(profs))]
1265 | res=cv2.polylines(T,li3,False,colours[allSliceCodes[i]-1])
1266 | Array3D[pos,:,:]=T.reshape((T.shape[0],T.shape[1]*3)) # don't forget last slice
1267 |
1268 | def disk(radius, dtype=np.uint8):
1269 | """Generates a flat, disk-shaped structuring element.
1270 | A pixel is within the neighborhood if the euclidean distance between
1271 | it and the origin is no greater than radius.
1272 | Parameters
1273 | ----------
1274 | radius : int
1275 | The radius of the disk-shaped structuring element.
1276 | Other Parameters
1277 | ----------------
1278 | dtype : data-type
1279 | The data type of the structuring element.
1280 | Returns
1281 | -------
1282 | selem : ndarray
1283 | The structuring element where elements of the neighborhood
1284 | are 1 and 0 otherwise.
1285 | """
1286 | L = np.arange(-radius, radius + 1)
1287 | X, Y = np.meshgrid(L, L)
1288 | return np.array((X ** 2 + Y ** 2) <= radius ** 2, dtype=dtype)
1289 |
1290 |
1291 | ### Typical regex would be "drops0ts(\d+).dat", placing the brackets will make the sort based on captured integer
1292 | def findFiles(regex,dirName=os.curdir,isFloat=False):
1293 | filenames=glob.glob(os.path.join(os.path.abspath(dirName),"*"))
1294 | if len(filenames) == 0:
1295 | print("No pictures found")
1296 | return (0,None)
1297 | # two steps, first get a list of those filenames matching the given regex
1298 | newList=[]
1299 | numList=[]
1300 | for gfile in filenames:
1301 | mat=re.search(regex,gfile)
1302 | if (mat):
1303 | newList.append(gfile)
1304 | if (len(mat.groups())>0):
1305 | if (isFloat):
1306 | nr=float(mat.groups()[-1])
1307 | else:
1308 | nr=int(mat.groups()[-1])
1309 | numList.append(nr)
1310 |
1311 | # now sort the files according to their number. Always fitting regex \d+.dat to extract number for each file
1312 | if (len(numList)>0):
1313 | sI=np.argsort(numList)
1314 | sortF= [newList[i] for i in sI]
1315 | else:
1316 | newList.sort()
1317 | sortF=newList
1318 |
1319 | return sortF,np.sort(numList)
1320 |
1321 | def getSquareStitchCentres(T,writeRad,X,Y):
1322 | dX=X[0,1]-X[0,0]
1323 | dist_pix=int(round(2*writeRad/dX))
1324 | xx=np.arange(X.min(),X.max(),2*writeRad)
1325 | yy=np.arange(Y.min(),Y.max(),2*writeRad)
1326 | NX,NY=np.meshgrid(xx,yy)
1327 | pp=np.column_stack((NX.ravel(),NY.ravel()))
1328 | #NC_pix=FormulaSlicer.convertToPixels(X,Y,NC)
1329 |
1330 | #pp=calcHexCenters(amY,amX,writeRad)
1331 | # now only keep those centres which have some work to do
1332 | regions, vertices = makeVoronoiPolygons(pp)
1333 | goodRegions=[]
1334 | goodPoints=[]
1335 | for i in np.arange(len(pp)):
1336 | polygon = vertices[regions[i]]
1337 | MM=makeVoronoiMask(polygon,X,Y)
1338 | if ((MM*T).sum()!=0):
1339 | goodPoints.append(pp[i])
1340 | goodRegions.append(regions[i])
1341 |
1342 | hexPoints=np.asarray(goodPoints)
1343 | return hexPoints
1344 |
1345 | def getHexStitchCentres(T,writeRad,X,Y):
1346 | amX=int(round((X.max()-X.min())*1.2/(2*writeRad)))
1347 | amY=int(round((Y.max()-Y.min())*1.4/(2*writeRad)))
1348 | pp=calcHexCenters(amY,amX,writeRad)
1349 | # now only keep those centres which have some work to do
1350 | regions, vertices = makeVoronoiPolygons(pp)
1351 | goodRegions=[]
1352 | goodPoints=[]
1353 | for i in np.arange(len(pp)):
1354 | polygon = vertices[regions[i]]
1355 | MM=makeVoronoiMask(polygon,X,Y)
1356 | if ((MM*T).sum()!=0):
1357 | goodPoints.append(pp[i])
1358 | goodRegions.append(regions[i])
1359 |
1360 | hexPoints=np.asarray(goodPoints)
1361 | return hexPoints
1362 |
1363 | # this will return the coords of the circle centers in a tesselation
1364 | def calcHexCenters(nrows,ncols,R):
1365 | offx=R*np.sqrt(3)
1366 | offy=1.5*R
1367 | offExtra=np.sqrt(3)*R/2.0
1368 |
1369 | coordList=[]
1370 |
1371 | # draw circumscribing circles
1372 | for i in np.arange(ncols):
1373 | for j in np.arange(nrows):
1374 | if np.logical_and(j%2==1,i==0):
1375 | cx=i*offx-j%2*offExtra
1376 | cy=j*offy
1377 | coordList.append((cx,cy))
1378 | cx=i*offx+j%2*offExtra
1379 | cy=j*offy
1380 | coordList.append((cx,cy))
1381 |
1382 | coords=np.asarray(coordList)
1383 | centerx=(coords[:,0].max()+coords[:,0].min())/2.0
1384 | centery=(coords[:,1].max()+coords[:,1].min())/2.0
1385 | centCoord=coords-[centerx,centery]
1386 |
1387 | return centCoord
1388 |
1389 | def getOptimalStitchCentres(T,VoteRadius,WriteRadius,X,Y):
1390 | dPix=X[0,1]-X[0,0]
1391 | VoteRad_pix=int(round(VoteRadius/dPix))
1392 | WriteRad_pix=int(round(WriteRadius/dPix))
1393 |
1394 | In=T.copy()
1395 | centList=[]
1396 | while(In.sum()!=0):
1397 | Vote=getVoteFullCircle(In,VoteRad_pix)#,oldT,oldVote)
1398 | cp,Out=getVoteCentre(In,Vote,WriteRad_pix)
1399 | diff=(In-Out).sum()
1400 | if (diff==0): # something went wrong with differential voting => go back to absolute voting
1401 | print('Something went wrong with voting')
1402 | In=Out.copy()
1403 | centList.append(cp)
1404 | Cents=np.asarray(centList)
1405 | Xc=X[Cents[:,1],Cents[:,0]]
1406 | Yc=Y[Cents[:,1],Cents[:,0]]
1407 | points=np.column_stack((Xc,Yc)).astype(np.float64)
1408 | return points
1409 |
1410 | def getOptimalStitchCentres_Bulk(T,VoteRadius,WriteRadius,X,Y):
1411 | dPix=X[0,1]-X[0,0]
1412 | VoteRad_pix=int(round(VoteRadius/dPix))
1413 | WriteRad_pix=int(round(WriteRadius/dPix))
1414 |
1415 | In=T.copy()
1416 | centList=[]
1417 | while(In.sum()!=0):
1418 | Vote=getVoteFullCircle_Bulk(In,VoteRad_pix)#,oldT,oldVote)
1419 | cp,Out=getVoteCentre(In,Vote,WriteRad_pix)
1420 | diff=(In-Out).sum()
1421 | if (diff==0): # something went wrong with differential voting => go back to absolute voting
1422 | print('Something went wrong with voting')
1423 | In=Out.copy()
1424 | centList.append(cp)
1425 | Cents=np.asarray(centList)
1426 | Xc=X[Cents[:,1],Cents[:,0]]
1427 | Yc=Y[Cents[:,1],Cents[:,0]]
1428 | points=np.column_stack((Xc,Yc)).astype(np.float64)
1429 | return points
1430 |
1431 | def getVoteFullCircle(T,VoteRad):
1432 | Disc=disk(VoteRad)
1433 | # E=cv2.Canny(T.astype(np.uint8),1,1)
1434 | Sx=cv2.Sobel(T.astype(np.uint8),cv2.CV_64F,1,0)
1435 | Sy=cv2.Sobel(T.astype(np.uint8),cv2.CV_64F,0,1)
1436 | SS,Sang=cv2.cartToPolar(Sx,Sy)
1437 | E=np.where(SS>1,1,0)
1438 | RE=signal.fftconvolve(E.astype(np.uint8),Disc,'same')
1439 | return RE/Disc.sum()
1440 |
1441 | # apply gaussian filter on disk. But make sure that you are not voting outside of original disk
1442 | # => should probably rescale the disk. => make smaller disk in large enough matrix and apply
1443 | # gaussian on that.
1444 | def getVoteFullCircle_gaus(T,VoteRad,gaus):
1445 | Disc=disk(VoteRad)
1446 | # E=cv2.Canny(T.astype(np.uint8),1,1)
1447 | Sx=cv2.Sobel(T.astype(np.uint8),cv2.CV_64F,1,0)
1448 | Sy=cv2.Sobel(T.astype(np.uint8),cv2.CV_64F,0,1)
1449 | SS,Sang=cv2.cartToPolar(Sx,Sy)
1450 | E=np.where(SS>1,1,0)
1451 | RE=signal.fftconvolve(E.astype(np.uint8),Disc,'same')
1452 | return RE/Disc.sum()
1453 |
1454 | def getVoteFullCircle_Bulk(T,VoteRad):
1455 | Disc=disk(VoteRad)
1456 | # E=cv2.Canny(T.astype(np.uint8),1,1)
1457 | #~ Sx=cv2.Sobel(T.astype(np.uint8),cv2.CV_64F,1,0)
1458 | #~ Sy=cv2.Sobel(T.astype(np.uint8),cv2.CV_64F,0,1)
1459 | #~ SS,Sang=cv2.cartToPolar(Sx,Sy)
1460 | #~ E=np.where(SS>1,1,0)
1461 | RE=signal.fftconvolve(T.astype(np.uint8),Disc,'same')
1462 | return RE/Disc.sum()
1463 |
1464 |
1465 | # This will cast votes in a circle of VoteRadius around all contourpoints
1466 | def getVoteFullCircle_old(T2,VoteRad,T=np.zeros((1,1)),Vote=np.zeros((1,1))):
1467 | if (T.sum()==0):
1468 | P=np.zeros(T2.shape,dtype=np.uint8)
1469 | R=cv2.findContours(T2.astype(np.uint8),cv2.RETR_LIST,cv2.CHAIN_APPROX_NONE)
1470 | Res=np.zeros(T2.shape,dtype=np.int32)
1471 | for CC in R[1]:
1472 | CC=CC.reshape((-1,2))
1473 |
1474 | for cc in CC:
1475 | P[:]=0
1476 | # cc=CC[150]
1477 | P=cv2.circle(P,(cc[0],cc[1]),VoteRad,1,-1)
1478 | #Pb=cv2.GaussianBlur(P,(kernel,kernel),0)
1479 | Res=Res+P
1480 | return Res
1481 | else:
1482 | try:
1483 | Tdif=T-T2
1484 | Cdif=cv2.findContours((Tdif).astype(np.uint8),cv2.RETR_LIST,cv2.CHAIN_APPROX_NONE)
1485 | Cold=cv2.findContours((T).astype(np.uint8),cv2.RETR_LIST,cv2.CHAIN_APPROX_NONE)
1486 |
1487 |
1488 | AllC_old=Cold[1][0].reshape((-1,2))
1489 | for nC in Cold[1][1:]:
1490 | AllC_old=np.row_stack((AllC_old,nC.reshape((-1,2))))
1491 | #print(AllC_old.shape)
1492 |
1493 | #print(T2.sum(),len(Cdif[1]),Tdif.sum())
1494 | AllC_dif=Cdif[1][0].reshape((-1,2))
1495 | for nC in Cdif[1][1:]:
1496 | AllC_dif=np.row_stack((AllC_dif,nC.reshape((-1,2))))
1497 |
1498 |
1499 | P=np.zeros(T.shape,dtype=np.uint8)
1500 | Sub=np.zeros(T.shape,dtype=np.int32)
1501 | Add=np.zeros(T.shape,dtype=np.int32)
1502 | for difE in AllC_dif:
1503 | dis=np.abs(AllC_old-difE)
1504 | if (len(np.argwhere(dis[:,0]+dis[:,1]<0.5))>0):
1505 | P[:]=0
1506 | P=cv2.circle(P,(difE[0],difE[1]),VoteRad,1,-1)
1507 | Sub=Sub+P
1508 | else:
1509 | P[:]=0
1510 | P=cv2.circle(P,(difE[0],difE[1]),VoteRad,1,-1)
1511 | Add=Add+P
1512 |
1513 | NewVote=Vote.astype(np.int32)+Add-Sub
1514 | except: # something went wrong with the differential voting system => use classic voting!
1515 | NewVote=getVoteFullCircle(T2,VoteRad)
1516 | return NewVote
1517 |
1518 | # This will take the original matrix + the vote => find the elected
1519 | # centre point and substract a zone with WriteRadius from the material matrix T => return T2
1520 | def getVoteCentre(T,Vote,WriteRad):
1521 | mm=np.argmax(Vote*T)
1522 | #mm=np.argmax(Vote) # While sometimes better, it can get stuck (happened for free-standing)
1523 | xmax=mm%Vote.shape[1]
1524 | ymax=np.int(mm/Vote.shape[1])
1525 | R2=np.zeros(T.shape,dtype=np.float)
1526 | R2=cv2.circle(R2,(xmax,ymax),WriteRad,1,-1)
1527 | T2=np.where(T-R2>0,1,0)
1528 | return np.array([xmax,ymax]),T2
1529 |
1530 |
1531 | def makeVoronoiPolygons(points):
1532 | vor = Voronoi(points)
1533 | regions, vertices = voronoi_finite_polygons_2d(vor)
1534 | return regions,vertices
1535 |
1536 | def voronoi_finite_polygons_2d(vor, radius=None):
1537 | """
1538 | Reconstruct infinite voronoi regions in a 2D diagram to finite
1539 | regions.
1540 |
1541 | Parameters
1542 | ----------
1543 | vor : Voronoi
1544 | Input diagram
1545 | radius : float, optional
1546 | Distance to 'points at infinity'.
1547 |
1548 | Returns
1549 | -------
1550 | regions : list of tuples
1551 | Indices of vertices in each revised Voronoi regions.
1552 | vertices : list of tuples
1553 | Coordinates for revised Voronoi vertices. Same as coordinates
1554 | of input vertices, with 'points at infinity' appended to the
1555 | end.
1556 |
1557 | """
1558 |
1559 | if vor.points.shape[1] != 2:
1560 | raise ValueError("Requires 2D input")
1561 |
1562 | new_regions = []
1563 | new_vertices = vor.vertices.tolist()
1564 |
1565 | center = vor.points.mean(axis=0)
1566 | if radius is None:
1567 | radius = vor.points.ptp().max()*2
1568 |
1569 | # Construct a map containing all ridges for a given point
1570 | all_ridges = {}
1571 | for (p1, p2), (v1, v2) in zip(vor.ridge_points, vor.ridge_vertices):
1572 | all_ridges.setdefault(p1, []).append((p2, v1, v2))
1573 | all_ridges.setdefault(p2, []).append((p1, v1, v2))
1574 |
1575 | # Reconstruct infinite regions
1576 | for p1, region in enumerate(vor.point_region):
1577 | vertices = vor.regions[region]
1578 |
1579 | if all([v >= 0 for v in vertices]):
1580 | # finite region
1581 | new_regions.append(vertices)
1582 | continue
1583 |
1584 | # reconstruct a non-finite region
1585 | ridges = all_ridges[p1]
1586 | new_region = [v for v in vertices if v >= 0]
1587 |
1588 | for p2, v1, v2 in ridges:
1589 | if v2 < 0:
1590 | v1, v2 = v2, v1
1591 | if v1 >= 0:
1592 | # finite ridge: already in the region
1593 | continue
1594 |
1595 | # Compute the missing endpoint of an infinite ridge
1596 |
1597 | t = vor.points[p2] - vor.points[p1] # tangent
1598 | t /= np.linalg.norm(t)
1599 | n = np.array([-t[1], t[0]]) # normal
1600 |
1601 | midpoint = vor.points[[p1, p2]].mean(axis=0)
1602 | direction = np.sign(np.dot(midpoint - center, n)) * n
1603 | far_point = vor.vertices[v2] + direction * radius
1604 |
1605 | new_region.append(len(new_vertices))
1606 | new_vertices.append(far_point.tolist())
1607 |
1608 | # sort region counterclockwise
1609 | vs = np.asarray([new_vertices[v] for v in new_region])
1610 | c = vs.mean(axis=0)
1611 | angles = np.arctan2(vs[:,1] - c[1], vs[:,0] - c[0])
1612 | new_region = np.array(new_region)[np.argsort(angles)]
1613 |
1614 | # finish
1615 | new_regions.append(new_region.tolist())
1616 |
1617 | return new_regions, np.asarray(new_vertices)
1618 |
1619 | def convertToPixels(X,Y,realList):
1620 | X_start = X[0,0]
1621 | X_delta = X[0,1]-X[0,0]
1622 | Y_start = Y[0,0]
1623 | Y_delta = Y[1,0]-Y[0,0]
1624 |
1625 | if (realList.shape[1]==2):
1626 | CoordX_pix=(np.round((realList[:,0]-X_start)/X_delta)).astype(np.int)
1627 | CoordY_pix=(np.round((realList[:,1]-Y_start)/Y_delta)).astype(np.int)
1628 | coords_pix=np.column_stack((CoordX_pix,CoordY_pix))
1629 | return coords_pix
1630 | elif (realList.shape[1]==1):
1631 | return (np.round((realList)/X_delta)).astype(np.int) # can only assume X_delta and Y_delta are the same
1632 |
1633 |
1634 | def makeVoronoiMask(polygon,X,Y):
1635 | M=np.zeros(X.shape,dtype=np.uint8)
1636 | # sssx=[np.argmin(np.abs(xx-ppp[0])) for ppp in polygon]
1637 | # sssy=[np.argmin(np.abs(yy-ppp[1])) for ppp in polygon]
1638 | # kj=np.column_stack((sssx,sssy)) # or other order!!
1639 | kj=convertToPixels(X,Y,polygon)
1640 | M=cv2.fillPoly(M,[kj.astype(np.int32)],1,100)
1641 | return M
1642 |
1643 | def makeCircularMask(centre,radius,xx,yy):
1644 | M=np.zeros((len(yy),len(xx)),dtype=np.uint8)
1645 | kj=convertToPixels(X,Y,np.asarray(centre))
1646 | dpix=xx[1]-xx[0]
1647 | rad_pix=int(round(radius/dpix))
1648 | M=cv2.circle(M,(kj[0],kj[1]),rad_pix,1,-1)
1649 | return M
1650 |
1651 | def getInfill(points,writeRadius,T,X,Y):
1652 | regions, vertices = makeVoronoiPolygons(points)
1653 | myL=[]
1654 | for i in np.arange(len(points)):
1655 | polygon = vertices[regions[i]]
1656 | MM=makeVoronoiMask(polygon,X,Y)
1657 | myMaterial=(MM*T).sum()
1658 | myL.append(myMaterial)
1659 |
1660 | return np.asarray(myL)
1661 |
1662 | def writeTSPfile(filename,pp):
1663 | import scipy.spatial.distance as dist
1664 | DD=dist.pdist(pp,'cityblock')
1665 |
1666 |
1667 | F=open(filename,mode='w')
1668 | F.write('NAME: {}\n'.format(filename[:-4]))
1669 | F.write('TYPE: TSP\n')
1670 | F.write('COMMENT: Nanoscribe Blocks in Ham Path\n')
1671 | F.write('DIMENSION: {}\n'.format(len(pp)+1))
1672 | F.write('EDGE_WEIGHT_TYPE: EXPLICIT\n')
1673 | F.write('EDGE_WEIGHT_FORMAT: UPPER_ROW\n')
1674 | F.write('DISPLAY_DATA_TYPE: NO_DISPLAY\n')
1675 | F.write('EDGE_WEIGHT_SECTION\n')
1676 |
1677 | pcounter=0
1678 | st=''
1679 | for cc in np.arange(len(pp)): # start with a zero line to transform into ham path problem
1680 | st=st+'0 '
1681 | F.write(st+'\n')
1682 |
1683 | for c in np.arange(len(pp)-1,0,-1):
1684 | st=''
1685 | for i in np.arange(c):
1686 | st=st+'{:d} '.format(int(DD[pcounter]))
1687 | pcounter=pcounter+1
1688 | F.write(st+'\n')
1689 |
1690 |
1691 | F.write('EOF')
1692 |
1693 | F.close()
1694 | return
1695 |
1696 | def readTSPfile(filename):
1697 | Sol=open(filename,mode='r').readlines()
1698 | OrdList=[]
1699 | for line in Sol[1:]:
1700 | els=line.split()
1701 | for el in els:
1702 | OrdList.append(int(el))
1703 | OO=np.asarray(OrdList)
1704 | if (OO[0]==0):
1705 | OO=OO[1:]-1 # cut away the fictitious first point
1706 | else:
1707 | print('First point is not the fictitious one: investigate')
1708 | return OO
1709 |
1710 | def TSPnearest(pp):
1711 | import scipy.spatial.distance as dist
1712 | DD=dist.pdist(pp,'cityblock')
1713 | Dmat=dist.squareform(DD)
1714 | order=(np.argsort(Dmat,axis=1))[:,1:] # cut out the first point as it is always the diagonal point
1715 |
1716 | pathList=[]
1717 | distList=[]
1718 |
1719 | for stp in np.arange(len(pp)):
1720 | visList=[stp]
1721 | dist=0
1722 | currP=stp
1723 | while (len(visList)0,1,0)
1765 | goodLabs=np.unique(RemMask*LL)[1:]
1766 |
1767 | T2=np.where(T-RemMask>0,1,0)
1768 | return np.array([xmax,ymax]),T2,goodLabs,crossing
1769 |
1770 | def makeFreeMaterialMatrix(Tloc,seeds):
1771 | TestIm=np.zeros(Tloc.shape,np.uint8)
1772 | Mask=np.zeros((Tloc.shape[0]+2,Tloc.shape[1]+2),dtype=np.uint8)
1773 | Mask[1:-1,1:-1]=Tloc[:]
1774 | Mask=1-Mask
1775 | for seed in seeds:
1776 | rr,TestIm,Mask,rect=cv2.floodFill(TestIm,Mask,(seed[0].astype(np.int32),seed[1].astype(np.int32)),1)
1777 | return TestIm
1778 |
1779 | def getOptimalFreestandingStitchCentres(T,VoteRadius,WriteRadius,X,Y,closing=0):
1780 | dPix=X[0,1]-X[0,0]
1781 | VoteRad_pix=int(round(VoteRadius/dPix))
1782 | WriteRad_pix=int(round(WriteRadius/dPix))
1783 |
1784 | L,n=im.label(T,np.ones((3,3)))
1785 | posLabels=[]
1786 | for i in np.arange(1,n+1):
1787 | Lcoords=np.argwhere(L==i)
1788 | meanLabel=Lcoords.mean(axis=0)
1789 | posLabels.append([meanLabel[1],meanLabel[0],i])
1790 | Pos=np.round(np.asarray(posLabels)).astype(np.int)
1791 | #print(Pos.shape,X.shape)
1792 | realPos=np.column_stack((X[0][Pos[:,0]],Y[:,0][Pos[:,1]]))
1793 | if (len(Pos)==0):
1794 | print('Big problem with algorithm, check debugoutput')
1795 | return L,posLabels,T
1796 |
1797 |
1798 | In=T.copy()
1799 | if (closing==0):
1800 | Voters=In.copy()
1801 | else:
1802 | #Voters=im.binary_closing(In,iterations=closing).astype(np.uint8)
1803 | Voters=im.binary_dilation(In,iterations=closing).astype(np.uint8)
1804 |
1805 | centList=[]
1806 | labPosList=[]
1807 | labsList=[]
1808 | #VoteList=[]
1809 | while(In.sum()!=0):
1810 | Vote=getVoteFullCircle(Voters,VoteRad_pix)
1811 | # VoteList.append(Vote)
1812 | cp,Out,goodLabs,crossing=getVoteCentre_freestanding(In,L,Vote,WriteRad_pix,True)
1813 | diff=(In-Out).sum()
1814 | if (diff==0): # something went wrong with differential voting => go back to absolute voting
1815 | print('Something wrong with voting, will do plan B')
1816 | TempIn=np.where(L==crossing[0],1,0)
1817 | Vote=getVoteFullCircle(TempIn,VoteRad_pix)
1818 | cp,Out,goodLabs,crossing=getVoteCentre_freestanding(In,L,Vote,WriteRad_pix,True)
1819 | diff=(In-Out).sum()
1820 | if (diff==0):
1821 | print('Do not know what to do, will abort')
1822 | return In,Voters,Vote
1823 | break
1824 | In=Out.copy()
1825 | if (closing==0):
1826 | Voters=In.copy()
1827 | else:
1828 | Voters=im.binary_closing(In,iterations=closing).astype(np.uint8)
1829 | centList.append(cp)
1830 | labPosList.append(realPos[goodLabs-1])
1831 | labsList.append(goodLabs)
1832 | Cents=np.asarray(centList)
1833 | Xc=X[Cents[:,1],Cents[:,0]]
1834 | Yc=Y[Cents[:,1],Cents[:,0]]
1835 | points=np.column_stack((Xc,Yc)).astype(np.float64)
1836 | return points,labPosList,labsList#,VoteList,Voters
1837 |
--------------------------------------------------------------------------------
/Adaptive Stitching with Nanoscribe/Adaptive-Stitching-Nanoscribe.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Adaptive Stitching\n",
8 | "This code performs adaptive stitching for randomly spaced microlenses that are multifocal and have aberrations added to them. The adaptive stitching is needed for the microlenses to be correctly 3D printed with Nanoscribe. The code places the stitching artifacts at the boundaries of the microlenses and thus maximizes optical quality. \n",
9 | "\n",
10 | "The demonstration here will be for the 36 multifocal microlenses with astigmatism and tilt added. This was used in the paper: **Miniscope3D: optimized single-shot miniature 3D fluorescence microscopy**"
11 | ]
12 | },
13 | {
14 | "cell_type": "code",
15 | "execution_count": 1,
16 | "metadata": {},
17 | "outputs": [
18 | {
19 | "name": "stdout",
20 | "output_type": "stream",
21 | "text": [
22 | "Using matplotlib backend: agg\n",
23 | "Populating the interactive namespace from numpy and matplotlib\n"
24 | ]
25 | }
26 | ],
27 | "source": [
28 | "%pylab\n",
29 | "%matplotlib inline\n",
30 | "import matplotlib.pyplot as plt\n",
31 | "import TipSlicerUpdated\n",
32 | "import random\n",
33 | "import matplotlib.pyplot as plt\n",
34 | "import numpy as np\n",
35 | "import os\n",
36 | "import scipy.ndimage.morphology as im"
37 | ]
38 | },
39 | {
40 | "cell_type": "code",
41 | "execution_count": 2,
42 | "metadata": {},
43 | "outputs": [],
44 | "source": [
45 | "# this is the internal potential calculation for a single lens extracted for reuse in the masking def\n",
46 | "def AberPotential(X,Y,z,params):\n",
47 | " xpos,ypos,rlist,r_lenslet,height,aperR,indexPoint,Z=params\n",
48 | " Xn = X/1800.\n",
49 | " Yn = Y/1800.\n",
50 | " n=indexPoint\n",
51 | " Xnzern=Xn- xpos[n]/1800.\n",
52 | " Ynzern=Yn- ypos[n]/1800.\n",
53 | " r = np.sqrt(np.square(Xnzern) + np.square(Ynzern))\n",
54 | " Z2 = Z[n,0] * 2.*Xnzern\n",
55 | " Z3 = Z[n,1] * 2.*Ynzern\n",
56 | " Z5 = Z[n,2] * 2.*np.sqrt(6.)*Xnzern*Ynzern\n",
57 | " Z6 = Z[n,3] * np.sqrt(6.)*(Xnzern**2-Ynzern**2)\n",
58 | " #Z12 = Z[n,4] * np.sqrt(10.)*(Xnzern**2-Ynzern**2)*(4*r**2-3)\n",
59 | " #Z13 = Z[n,5] * 2*np.sqrt(10.)*Xnzern*Ynzern*(4*r**2-3)\n",
60 | " #Z23 = Z[n,6] * 2*np.sqrt(14.)*Xnzern*Ynzern*(15*r**4-20*r**2+6)\n",
61 | " #Z24 = Z[n,7] * np.sqrt(14.)*(Xnzern**2-Ynzern**2)*(15*r**4-20*r**2+6)\n",
62 | " ZW = Z5+Z6+Z2+Z3#+Z12+Z13+Z23+Z24\n",
63 | " sph1 = ZW+np.real(np.sqrt(0j+rlist[n]**2 - (X-xpos[n])**2 - (Y-ypos[n])**2))-np.real(np.sqrt(0j+rlist[n]**2-r_lenslet**2))-(z-height)\n",
64 | " return sph1\n",
65 | "\n",
66 | "\n",
67 | "def sliceFormulaAber(X,Y,z,params):\n",
68 | " xpos,ypos,rlist,r_lenslet,height,aperR,indexPoint,aber=params\n",
69 | " aper=np.sqrt(X**2+Y**2) <= aperR\n",
70 | " Z=aber\n",
71 | " if z<=height: \n",
72 | " return aper\n",
73 | " else: \n",
74 | " T = np.zeros(len(X))\n",
75 | " inds=range(len(xpos))\n",
76 | " if (indexPoint>-1):\n",
77 | " inds=[indexPoint]\n",
78 | " for n in inds:\n",
79 | " sph1=AberPotential(X,Y,z,[xpos,ypos,rlist,r_lenslet,height,aperR,n,aber])\n",
80 | " T = np.maximum(T,sph1)\n",
81 | " T=(T>0)\n",
82 | " return (T)*aper\n",
83 | " \n",
84 | "def maskFormulaAber(X,Y,z,params):\n",
85 | " xpos,ypos,rlist,r_lenslet,height,aperR,indexPoint,aber=params\n",
86 | " aper=np.sqrt(X**2+Y**2) <= aperR\n",
87 | " if (z<=height):\n",
88 | " z=height \n",
89 | " DD=np.zeros((len(xpos),X.shape[0],X.shape[1]))\n",
90 | " for i in np.arange(len(DD)):\n",
91 | " DD[i]=-AberPotential(X,Y,z,[xpos,ypos,rlist,r_lenslet,height,aperR,i,aber])*aper\n",
92 | " Div=np.argmin(DD,axis=0)\n",
93 | " if (indexPoint==-1):\n",
94 | " return Div\n",
95 | " else:\n",
96 | " return np.where(Div==indexPoint,1,0)*aper\n",
97 | "\n",
98 | "# extract the centres from the structure itself\n",
99 | "def getCentresAber(X,Y,z,params):\n",
100 | " xpos,ypos,rlist,r_lenslet,height,aperR,indexPoint,aber=params\n",
101 | " aper=np.sqrt(X**2+Y**2) <= aperR\n",
102 | " centList=[]\n",
103 | " for i in np.arange(len(xpos)):\n",
104 | " DD=-AberPotential(X,Y,z,[xpos,ypos,rlist,r_lenslet,height,aperR,i,aber])\n",
105 | " xc,yc=np.unravel_index(np.argmin(DD*aper),DD.shape)\n",
106 | " centList.append([yc,xc])\n",
107 | " Cents=np.asarray(centList)\n",
108 | " return Cents"
109 | ]
110 | },
111 | {
112 | "cell_type": "markdown",
113 | "metadata": {},
114 | "source": [
115 | "# Define microlens array parameters"
116 | ]
117 | },
118 | {
119 | "cell_type": "code",
120 | "execution_count": 3,
121 | "metadata": {},
122 | "outputs": [],
123 | "source": [
124 | "#all units are in microns\n",
125 | "r_lenslet=200 #average diameter of each microlens\n",
126 | "samples = (1800,1800) #how many samples\n",
127 | "xgrng = np.array((-900, 900)) #physical dimension of the microlens array in microns (1.8 mm here)\n",
128 | "xgrng2 = np.array((-1800, 1800)) #in microns\n",
129 | "ygrng=xgrng\n",
130 | "ygrng2=xgrng2\n",
131 | "\n",
132 | "\n",
133 | "yg = np.linspace(ygrng[0], ygrng[1], samples[0])\n",
134 | "xg=yg\n",
135 | "px=xg[1] - xg[0]\n",
136 | "py=px\n",
137 | "X,Y= np.meshgrid(xg,yg)\n",
138 | "\n",
139 | "yg2 = np.linspace(ygrng2[0], ygrng2[1], samples[0])\n",
140 | "xg2=yg2\n",
141 | "px2=xg2[1] - xg2[0]\n",
142 | "py2=px2\n",
143 | "X2,Y2= np.meshgrid(xg2,yg2)\n",
144 | "\n",
145 | "Xn = X2/np.max(X2)\n",
146 | "Yn = Y2/np.max(Y2)\n",
147 | "\n",
148 | "# xposition list in mm\n",
149 | "xpos=np.array([ 0.43641585, 0.29636857, 0.21274264, 0.6394599 , 0.65087426,\n",
150 | " -0.49792665, -0.11775845, 0.59266114, -0.1886654 , -0.45093933,\n",
151 | " -0.06002499, 0.49039292, 0.18374896, 0.32612935, 0.5355058 ,\n",
152 | " -0.07172677, 0.75601995, -0.27811506, -0.24367642, -0.22418435,\n",
153 | " 0.22841486, -0.67916805, -0.6805813 , -0.00778827, 0.8232382 ,\n",
154 | " -0.30767533, 0.19155966, 0.46077853, 0.10799856, -0.23324046,\n",
155 | " -0.4479409 , 0.13673823, -0.42627445, -0.02470369, -0.46301848,\n",
156 | " -0.6288782 ], dtype=float32)\n",
157 | "# yposition list in mm\n",
158 | "ypos=np.array([-0.0961623 , -0.590606 , 0.07416802, 0.08558229, -0.2063948 ,\n",
159 | " 0.22398773, -0.66743815, -0.4089036 , -0.48776114, -0.01421136,\n",
160 | " 0.48614654, 0.47054946, -0.29256105, -0.4106429 , -0.5639799 ,\n",
161 | " 0.6772651 , 0.26205382, 0.5754221 , 0.10239594, -0.2096889 ,\n",
162 | " 0.47864714, -0.39620727, 0.18052123, 0.17819166, 0.07138762,\n",
163 | " -0.68207985, -0.7970757 , 0.18449856, -0.4582133 , 0.37643924,\n",
164 | " 0.50502807, 0.66083544, -0.18756227, -0.07771606, -0.5855127 ,\n",
165 | " -0.00837374], dtype=float32)\n",
166 | "#radius of curvature in mm\n",
167 | "rlist=np.array([ 6.306523 , 6.453186 , 16.322765 , 7.4996486, 12.613046 ,\n",
168 | " 5.780979 , 12.064652 , 6.032326 , 17.342937 , 6.6068335,\n",
169 | " 7.1150513, 19.8205 , 8.408697 , 11.09948 , 7.9282 ,\n",
170 | " 9.91025 , 8.161383 , 13.87435 , 7.3022895, 11.561958 ,\n",
171 | " 14.604579 , 9.249567 , 9.568518 , 6.767976 , 5.903979 ,\n",
172 | " 7.707972 , 18.499134 , 10.672577 , 6.937175 , 6.1663775,\n",
173 | " 15.415944 , 10.277296 , 8.671469 , 8.951194 , 5.663 ,\n",
174 | " 13.213667 ],dtype=float32) \n",
175 | "\n",
176 | "#zernike list in mm\n",
177 | "zernlist=np.array([[-3.03727644e-03, 3.45289009e-03, -8.50316486e-04,\n",
178 | " -9.94477305e-04],\n",
179 | " [ 2.08902825e-03, -5.25590964e-03, -5.57003869e-03,\n",
180 | " 1.66087621e-03],\n",
181 | " [-1.54656521e-03, -7.22034927e-03, -1.47716681e-04,\n",
182 | " 1.33255846e-03],\n",
183 | " [-6.46650698e-03, -1.09545095e-03, -1.48787047e-03,\n",
184 | " 2.08820638e-04],\n",
185 | " [ 4.51811217e-03, -1.51357264e-03, 1.89111437e-04,\n",
186 | " 3.22496460e-04],\n",
187 | " [-1.23638997e-03, 1.33150117e-03, -5.25358878e-03,\n",
188 | " 9.76655632e-03],\n",
189 | " [ 4.36959649e-03, -7.47654727e-03, 3.64801125e-03,\n",
190 | " -1.86670839e-03],\n",
191 | " [-1.29632989e-03, -8.81011318e-03, -2.17094715e-03,\n",
192 | " 5.12582343e-03],\n",
193 | " [-3.18442448e-03, -7.02016230e-04, -7.80638075e-04,\n",
194 | " 8.95439996e-04],\n",
195 | " [ 1.33037404e-03, -4.07017302e-03, 1.83174980e-03,\n",
196 | " 4.63185087e-03],\n",
197 | " [ 6.37175108e-04, 3.62162525e-03, -1.38857099e-03,\n",
198 | " -1.69407239e-03],\n",
199 | " [ 4.18911048e-04, 2.99920258e-03, -6.04745280e-03,\n",
200 | " -2.02366011e-03],\n",
201 | " [ 7.84681237e-04, 2.57954700e-03, 8.30374379e-03,\n",
202 | " -5.63086197e-03],\n",
203 | " [-9.27667075e-04, 7.60378689e-03, 5.11155883e-03,\n",
204 | " -2.85442872e-03],\n",
205 | " [ 3.69066279e-03, 1.44687016e-03, 3.64636071e-03,\n",
206 | " -1.35618821e-03],\n",
207 | " [ 3.47679667e-03, 9.66886338e-03, 6.05431778e-05,\n",
208 | " 2.10903143e-03],\n",
209 | " [-7.06169615e-03, 1.05272122e-02, -2.37144646e-03,\n",
210 | " 2.14290898e-03],\n",
211 | " [-3.13660316e-03, 7.06096133e-03, -8.40417400e-04,\n",
212 | " -2.18926976e-03],\n",
213 | " [ 7.11004948e-03, 2.00309485e-04, 1.43178422e-02,\n",
214 | " 9.40302107e-03],\n",
215 | " [ 5.73336380e-03, 4.81330231e-03, -6.11291872e-03,\n",
216 | " -5.15368767e-03],\n",
217 | " [-2.86504888e-04, -1.82941454e-04, 8.74243851e-04,\n",
218 | " 1.06786657e-03],\n",
219 | " [-4.43635369e-03, 3.36597041e-05, -3.35925579e-04,\n",
220 | " -2.17877914e-06],\n",
221 | " [-4.91749262e-03, 5.81631484e-03, 6.35021715e-04,\n",
222 | " 1.07655076e-04],\n",
223 | " [-7.96477543e-04, 1.53168791e-03, 7.15573411e-03,\n",
224 | " -7.31880311e-03],\n",
225 | " [ 5.98072400e-03, 1.42488685e-02, -8.81085114e-04,\n",
226 | " -4.13347734e-03],\n",
227 | " [-4.52623982e-03, -2.22414453e-03, 2.17066554e-05,\n",
228 | " -1.89099193e-03],\n",
229 | " [-3.83377355e-03, 6.57709083e-04, 6.44097291e-03,\n",
230 | " 1.02351094e-03],\n",
231 | " [-3.02775938e-04, 2.83448049e-03, 1.72653550e-03,\n",
232 | " -2.10265117e-03],\n",
233 | " [ 1.72412605e-03, -8.58071493e-04, 6.27913105e-04,\n",
234 | " -4.08340804e-03],\n",
235 | " [ 2.68175127e-03, -1.63576938e-03, -9.31847002e-03,\n",
236 | " -1.93845030e-04],\n",
237 | " [-5.83293755e-03, 1.95736508e-03, -2.00207127e-04,\n",
238 | " -1.04814884e-03],\n",
239 | " [ 3.55686457e-03, 7.30788289e-03, -9.41661303e-04,\n",
240 | " 4.80500096e-03],\n",
241 | " [ 6.76476862e-04, -2.14314481e-04, -7.66097510e-04,\n",
242 | " -2.71079480e-03],\n",
243 | " [-3.65834520e-03, 7.38649350e-03, 1.80285275e-02,\n",
244 | " -1.13024702e-02],\n",
245 | " [-7.70746637e-03, -1.72380230e-03, 5.40864607e-03,\n",
246 | " -1.24156917e-03],\n",
247 | " [-4.90240427e-03, -6.24271634e-04, 2.45964224e-03,\n",
248 | " -1.91784347e-04]],dtype=float32)\n",
249 | " \n",
250 | "\n",
251 | "\n",
252 | "Nlenslets=36 #number of microlenses\n",
253 | "#convert from mm to microns\n",
254 | "xpos=np.reshape(xpos,(36))*1000\n",
255 | "ypos=np.reshape(ypos,(36))*1000\n",
256 | "rlist=np.reshape(rlist,(36))*1000\n",
257 | "zernlist=zernlist*1000\n",
258 | "\n",
259 | "height=50 #how much of support material to be printed under the phase mask (in microns)\n",
260 | "aperR=900 #radius of phase mask (collection of microlenses)"
261 | ]
262 | },
263 | {
264 | "cell_type": "code",
265 | "execution_count": 4,
266 | "metadata": {},
267 | "outputs": [],
268 | "source": [
269 | "X3,Y3=np.meshgrid(np.arange(-1000,1000),np.arange(-1000,1000))"
270 | ]
271 | },
272 | {
273 | "cell_type": "markdown",
274 | "metadata": {},
275 | "source": [
276 | "# Let's visualize our microlens array after adaptive stitching"
277 | ]
278 | },
279 | {
280 | "cell_type": "code",
281 | "execution_count": 8,
282 | "metadata": {},
283 | "outputs": [
284 | {
285 | "data": {
286 | "text/plain": [
287 | ""
288 | ]
289 | },
290 | "execution_count": 8,
291 | "metadata": {},
292 | "output_type": "execute_result"
293 | },
294 | {
295 | "data": {
296 | "image/png": "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\n",
297 | "text/plain": [
298 | ""
299 | ]
300 | },
301 | "metadata": {
302 | "needs_background": "light"
303 | },
304 | "output_type": "display_data"
305 | }
306 | ],
307 | "source": [
308 | "z=50 # this is for visualization only, this the height in microns where you want to see the slice\n",
309 | "\n",
310 | "lens=-1 # putting this to a number between 0 and Nlenslets will show you what each block does\n",
311 | " # setting it to -1 will show all lenses..\n",
312 | "clf()\n",
313 | "Back=sliceFormulaAber(X2,Y2,z,[xpos,ypos,rlist,r_lenslet,height,aperR,-1,zernlist])\n",
314 | "T=sliceFormulaAber(X2,Y2,z,[xpos,ypos,rlist,r_lenslet,height,aperR,lens,zernlist])\n",
315 | "Mask=maskFormulaAber(X2,Y2,z,[xpos,ypos,rlist,r_lenslet,height,aperR,lens,zernlist])\n",
316 | "Res=T*(Mask)\n",
317 | "Back=np.where(Back==0,NaN,Back)\n",
318 | "imshow(Back+Res)\n",
319 | "\n"
320 | ]
321 | },
322 | {
323 | "cell_type": "markdown",
324 | "metadata": {},
325 | "source": [
326 | "## Now get the Centres of the lenses (most negative dist) as xpos, ypos not ideal"
327 | ]
328 | },
329 | {
330 | "cell_type": "code",
331 | "execution_count": 9,
332 | "metadata": {},
333 | "outputs": [],
334 | "source": [
335 | "# Note that there might be a small dependency on the height, taken 53 here but this can be changed..\n",
336 | "Cents=getCentresAber(X2,Y2,51,[xpos,ypos,rlist,r_lenslet,height,aperR,-1,zernlist])\n",
337 | "CCentsX=X2[0,Cents[:,0]]\n",
338 | "CCentsY=Y2[Cents[:,1],0]"
339 | ]
340 | },
341 | {
342 | "cell_type": "code",
343 | "execution_count": 10,
344 | "metadata": {},
345 | "outputs": [
346 | {
347 | "data": {
348 | "text/plain": [
349 | "(-810.9505280711508, 939.0216787103948, -876.3868816008894, 860.3779877709837)"
350 | ]
351 | },
352 | "execution_count": 10,
353 | "metadata": {},
354 | "output_type": "execute_result"
355 | },
356 | {
357 | "data": {
358 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAQsAAAD4CAYAAAD7JMNRAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8vihELAAAACXBIWXMAAAsTAAALEwEAmpwYAAAWyUlEQVR4nO3df7AdZX3H8fe3icSKAoFEmvLDGxTtBJ1qCJTWHzWA/IjWOK3D4DgFkSljipYfbdNQ5kYH/jHRFmV0sChY6VBj/FUdBiuBXJ3pdBJIMPwIELkJiMQAQRA7tYVGv/3jeU6y9+ace/ee3T3n2d3Pa2bn7nnO5u7uPbnf++yzz36/5u6IiEznt4Z9ACJSDwoWIpKLgoWI5KJgISK5KFiISC6zh30AecybN89HRkaGfRgirbB169Zn3X3+5PZaBIuRkRG2bNky7MMQaQUz+0m3dl2GiEguChYikouChYjkUkqwMLMrzGy7mT1oZl81s5eb2UIz22xm42b2NTM7JG47J74ej++PlHEMIlKtwsHCzI4B/gpY4u5vBGYB5wNrgOvc/XXA88DF8Z9cDDwf26+L24lI4sq6DJkN/LaZzQZeAewBTge+Ed//CvC+uL48via+f4aZWUnHIWVbuxbGxia2jY2FdmmVwsHC3XcDnwaeIASJF4CtwC/cfV/c7EngmLh+DPDT+G/3xe2Pmvx9zewSM9tiZlv27t1b9DClX6ecAueddyBgjI2F16ecMtzjkoEr4zJkLqG3sBD4XeBQ4Jyi39fdb3T3Je6+ZP78g+aHyKAsXQrr14cAsXp1+Lp+fWiXVinjMuRM4DF33+vu/wd8C3grcES8LAE4Ftgd13cDxwHE9w8Hfl7CcUhVli6FFSvg2mvDVwWKViojWDwBnGZmr4hjD2cADwFjwPvjNhcC34nr342vie9vdGXgSdvYGNxwA4yOhq+TxzCkFcoYs9hMGKi8F3ggfs8bgb8DrjSzccKYxE3xn9wEHBXbrwRWFT0GqVBnjGL9erjmmgOXJAoYrWN1+KO+ZMkS17MhQ7J2bRjMzF56jI3BPffAypX1249My8y2uvuSye2awSlTW7ny4DGKpUvL/wXWXZfk1eKpU2mB7F2XFSvC2IjuuiRFPQtJh+66JE3BQtKhuy5JU7CQNOiuS/IULFLS5ucw7rln4hhFZwzjnnuGe1yyn4JFStp8R2BQd12kb7obkhLdEZCEqWeRGt0RkEQpWKRGdwQkUQoWKdEdAUmYgkVKdEdAEqYHyURkAj1IJiKFKFiISC4KFiKSS1lFho4ws2+Y2SNm9rCZ/aGZHWlmG8zs0fh1btzWzOz6WGTofjNbXMYxiEi1yupZfBb4d3f/PeD3gYcJ6fLucvcTgbs4kD7vXODEuFwC3FDSMYhIhcooBXA48A5ijk13f8ndf8HEYkKTiwzd4sEmQhbwBUWPQ0SqVUbPYiGwF/iymf3IzL5kZocCR7v7nrjNU8DRcX1/kaEoW4BoPxUZEklLGcFiNrAYuMHd3wL8N5MydsdU/zOa0KEiQyJpKSNYPAk8GUsCQCgLsBh4unN5Eb8+E9/fX2QoyhYgEpFElVE35Cngp2b2htjUKTKULSY0ucjQBfGuyGnAC5nLFRFJVFn5LD4G3GpmhwC7gIsIgWi9mV0M/AQ4L257O7AMGAd+FbcVSZdqmgAlBQt33wYcNJec0MuYvK0Dl5ax34HQfxTpZDDrPOSXfTq4RTSDczptTnUngSrJAwoW09N/FAFlMEPBIp+2/Udpc5bxXpTBTMEil7b9R9Gl10TKYBa4e/LLySef7EOzcaP7vHnha7fXTdU5z9HRdpzvVNasOfj8N24M7Q0EbPEuv4fKlDWdNt8NWb06XHqNjoa/qNIKvTJlKVhId52ut+qXtI7S6kl+KV2ja7A1GQoWcrCUsoxrsDUZugyR9OmSaKB0GSL11bZ5LolSsJD0tW2eS6IULCRtKQ22tpyChaQtpcHWltMAp4hMoAFOESmktGBhZrNidu/b4uuFZrY5FhP6WsyihZnNia/H4/sjZR2DiFSnzJ7FZYTiQh1rgOvc/XXA88DFsf1i4PnYfl3cTkQSV1b5wmOBdwNfiq8NOJ2Q6RsOLjLUKT70DeCMuL2IJKysnsVngJXAb+Lro4BfuPu++DpbSGh/kaH4/gtx+wlUZEgkLWWUL3wP8Iy7by3hePZzFRkSSUoZPYu3Au81s8eBdYTLj88Saph2sodnCwntLzIU3z8c+HkJx9FeejKz/mrwGZZRZOgqdz/W3UeA84GN7v5BYAx4f9xscpGhTvGh98ft05/skTI9mVl/dfgMu6XP6ncB3gncFtdPAO4mFBP6OjAntr88vh6P758w3fcdalq9ulAavPpL5DOkR1q9oefXzLMoWOQ0Oho+0tHRYR+J9CuBz7BXsNAMzqbQk5n1l/hnqGDRBHoys/5q8BkqWDSBnsysvxp8hnrqVEQm0FOnIlKIgoWI5KJgISK5KFiISC4KFiKSi4KFiOSiYCEiudQ/WNTg0V6RJqh/sBj0o70KTtJS9Q8WnWmx550Hq1cfmF9fVT3MOuQdEKlA/YMFDLZw7qCDkwyeeo9dNSNYDPrRXlX1bjb1HrvrluRiJgshn+YY8BCwHbgsth8JbAAejV/nxnYDridkyrofWDzdPqZMftPJLtTJKjT5dRUSyWgkFWrxZ0xVmbKABZ1feOBVwI+BRcBaYFVsXwWsievLgO/FoHEasHm6fUwZLNasOfiD3LgxtFdhGMEpBYP+OacggaxVw1BZsDjoG4bEvO8CdgAL/EBA2RHX/wn4QGb7/dv1WpJKq9fGXxr39gVJ9SyqDRbACPAEcBihyFCn3TqvgduAt2XeuwtY0uV7XQJsAbYcf/zxlf+AJIe2/AK1LTBO0itYlFkY+ZXAN4HL3f2X2ffiAcwoy46ryFB62jKwW4OsVbmVeWenWwSZ6QK8DPg+cGWmrZmXIW3Wlp5Fk/TRS6KqnkUsanwT8LC7/2PmrWwxoclFhi6w4DTgBXffU/Q4pGI1SCgrXZQ4L6is8oV/DpxuZtvisgz4JPAuM3sUODO+Brgd2EW4dfpF4C9LOAapWpO65m1T0uWjEvaKNF2nV7hiRZi0OE3PQgl7RdqoxMtHBQuRJivx8lGXIU21dm14liHb3RwbC/9JVq4c3nFJ8nQZ0jZ6GEpKNnvYByAVyd4yyzmwJTIV9SyarC0zLmUgFCyabNB5PiRoaPIcBYum0ozL4WnoeJGCRVNpxuXwNDT1om6dilRl9eowXjQ6Gnp3NaFbpyKD1MDxIgULkbI1dLxIwUKkbA0dL9KYhYhMoDELESlEwUJ6a+jkIunP0IKFmZ1jZjvMbNzMVg3rOGQKDZ1cJP0ZSrAws1nA54FzCQWJPmBmi4ZxLDKFhk4ukv4Mq2dxKjDu7rvc/SVgHbB8SMciU9HDaBINK1gcA/w08/rJ2LafmV1iZlvMbMvevXvzf2ddZ5ergZOLpD/JDnB6v0WGdJ1dnoZOLpL+DCtY7CZUX+84NrYVl8J1dlN6Nw2dXCR96lZ5qOqFkKFrF7AQOAS4Dzip1/Z9VSQbZgXsltfKlHqj6lqnMwxQ+4CPEkoePgysd/ftpe1g2NfZKfRu6qgpPbKm6hZBUltm1LNI6a/6MHs3dZTSZ9dipNSzqFQq19nD7t3U0TB6ZOrN5NctgqS21K6Kuv5CFjPIHpk+q4PQmp5FClLp3dTRoHtkGl/Kr1sESW2pXc9C+jPMv/IaX9oP9SwkecPqkWl8KRclv5F2y85SXbr04NctpOQ3It1ofCk39SxEZAL1LESkEAULEclFwaLpNENRSqJg0XTK7yElmT3sA5CKZWcorlgR5hG0+Lag9E89izZQHk0pgYJFG2iGopRAwaJKKQwuKo+mlKRQsDCzT5nZI2Z2v5l928yOyLx3VSwgtMPMzs60t6e4UAqDi5qhKGXp9nRZ3gU4C5gd19cAa+L6IkJezTmEPJs7gVlx2QmcwIHcm4um20+tnzrtPDk5Otr6PAlSD1Tx1Km73+EhnybAJkKWbggFg9a5+4vu/hgwTigs1L7iQhpclIYoc8ziw8D34nqvIkLTFhfq6LvIUGo0uCgNMW2wMLM7zezBLsvyzDZXA/uAW8s6MO+3yFBKNLgo/UphcHySaYOFu5/p7m/ssnwHwMw+BLwH+GC83oHeRYSqKy6UIg0uSr9SGByfrNtARt4FOAd4CJg/qf0kJg5w7iIMbs6ouFBnqfUAp0i/hjQ4To8BzqLTvT8XA8IGMwPY5O4fcfftZrY+BpJ9wKXu/msAM+sUF5oF3OxlFhcSaZLs4Pjo6NAHx5X8RtKydm3oamd/McbGwqXbypXDO65h6Fx6DPiZHiW/kXpI8Vp9GBIcHFewkLSojkeQ4OC4LkMkTatXH7hWv+aaYR9Nq+gyROpDE9mSpGAhaUnwWl0CBQtJS9Fr9QRnPjaFgoWkZeXKgwczly7Nf9tUd1Mqoxyc0izKOVoZ9SykeZQWoBIKFtI8uptSCQULaRbdTamMgoU0S4IzH5tCMzhFZALN4BSRQhQsRCQXBQsRyaWUYGFmf21mbmbz4mszs+tjIaH7zWxxZtsLzezRuFxYxv41xVekeoWDhZkdRyg29ESm+VzgxLhcAtwQtz0S+DjwB4QaIh83s7lFj0FTfEWqV0bP4jpgJZC9rbIcuCXm/9wEHGFmC4CzgQ3u/py7Pw9sICT9LUYJU0QqV7TW6XJgt7vfN+mtwRcZ0hRfkUpN+yCZmd0J/E6Xt64G/p5wCVI6d78RuBHCPItp/8HkKb5LlypgiJRo2mDh7md2azezNxHqf9wXywAcC9xrZqcydZGhd05q/0Efxz1RdopvJ0joUkSkVH1fhrj7A+7+ancfcfcRwiXFYnd/CvgucEG8K3Ia8IK77yHUCznLzObGgc2zYlsxmuIrUrmq8lncDiwjVE//FXARgLs/Z2bXAp3f4mvc/bnCe+uWGEWXISKlKm1SVuxhPBvX3d0vdffXuvub3H1LZrub3f11cflyWfuXGmjLfJiGnqdmcMrgtGU+TFPPs1sB1NQWFUZukCEV+x24Gp8nPQojq2chg9WW+TANPE8FCxmstqS8a+B5KljUUV0H0NqS8q6h56lgUUd1HUBry3yYhp6n0urVVSdAqDaGlExp9ZqmgQNokjYFi7pq4ACapE3Boo4aOoAmaVOwqKOGDqDVXl3vUuWkYFFHRSuNSzXqepcqJ1VRFylLwyu4q2chUqYG36VSsBApU4PvUilYiJSl4Xepyqgb8jEze8TMtpvZ2kz7VbHI0A4zOzvTfk5sGzezVUX3P3ANH/GWApp+l6rbc+t5F2ApcCcwJ75+dfy6CLgPmENI6rsTmBWXncAJwCFxm0XT7SepfBadPAWd/ASTX4vUHD3yWRS9G7IC+KS7vxgDzzOxfTmwLrY/ZmbjhApkAOPuvgvAzNbFbR8qeByD0/ARb5Feil6GvB54u5ltNrMfmlnnhvLgiwwNUoNHvEV6mTZYmNmdZvZgl2U5YZ7GkcBpwN8C6y0WESnK3W909yXuvmT+/PllfMvyNHjEu9Y0nlSpvosMAZjZCuBb8TrnbjP7DTCP3kWGmKK9HlTQKF2dGZSdzyL7WUlhRS9D/o0wyImZvZ4waPksocjQ+WY2x8wWEqqp302oF3KimS00s0OA8+O29dH0Ee86U4HsShUd4LwZuNnMHgReAi6MvYztZraeMHC5D7jU3X8NYGYfJVQhmwXc7O7bCx7DYKmgUdqy40mjo/pcSqRMWdIsyiBWmDJlSfM1fAblsClYSHNoPKlSugwRkQl0GSIihShYpECTiaQGFCxS0PB0bNIMSquXAj2cJjWgnkUq9HCaJE7BIhV6OE0Sp2CRAk0mkhpQsEiBJhNJDWhSlohMoElZko/mfEgPChYykeZ8SA+aZyETac6H9KCehRxMcz6ki0LBwszebGabzGxbzMR9amw3M7s+FhK638wWZ/7NhWb2aFwuLHoCUoEq5nxoLKT+uhUTybsAdwDnxvVlwA8y698DjJD5e3NsPxLYFb/Ojetzp9tPUkWGmq6qIkoqzlQb9CgyVPQyxIHD4vrhwM/i+nLglrjvTcARZrYAOBvY4O7PufvzwAbgnILHIGWqas6HkunWXtEBzsuB75vZpwmXNH8U20spMgRcAnD88ccXPEzJrcqExEqmW2tFiwytAK5w9+OAK4CbyjowT7nIkPRHz7/UWtEiQ7cAl8WXXwe+FNd7FRnaDbxzUvsPch+t1JeKM9Ve0TGLnwF/HNdPBx6N698FLoh3RU4DXnD3PYR6IWeZ2VwzmwucFduk6fT8S+0VHbP4C+CzZjYb+F/iGANwO+GOyDjwK+AiAHd/zsyuJVQmA7jG3Z8reAxSByrOVHuFgoW7/wdwcpd2By7t8W9uJlQyE5Ea0QxOEclFwUJEclGwEJFcFCxEJJdaZMoys73AT6bYZB7w7IAOJ6V9D3v/bd33sPdf9b5f4+4HzYSsRbCYjplt8S5pwJq+72Hvv637Hvb+h7VvXYaISC4KFiKSS1OCxY0t3few99/WfQ97/0PZdyPGLESkek3pWYhIxRQsRCSX2gULM/taTBC8zcweN7NtsX3EzP4n894XMv/mZDN7ICYQvt7MrM99f8LMdmf2sSzz3lXx++8ws7Mz7efEtnEzW1XgvD9lZo/EBMjfNrMjBnXeXY6llHOaZh/HmdmYmT1kZtvN7LLYPuPPoM/9Px5/dtvMbEtsO9LMNsRk0xtimoUpE1T3sd83ZM5tm5n90swuH9R5T6lbYs66LMA/AKvj+gjwYI/t7iYkDjZCIuFz+9zfJ4C/6dK+CLgPmAMsBHYCs+KyEzgBOCRus6jPfZ8FzI7ra4A1gzrvSd+ztHOaZj8LgMVx/VXAj+PPeUafQYH9Pw7Mm9S2FlgV11dlPoOuCapL+lk/BbxmUOc91VK7nkVH/Ct5HvDVabZbABzm7ps8/HRvAd5X8uEsB9a5+4vu/hghj8epcRl3913u/hKwLm47Y+5+h7vviy83EbKM9VTheZd2TlNx9z3ufm9c/y/gYXrka416fQZlWg58Ja5/hQM/z14Jqos6A9jp7lPNXh7EeQM1vAzJeDvwtLs/mmlbaGY/MrMfmtnbY9sxhMTAHT2TBOf00djVvLnTDaWEBMUz9GHCX7KOQZx3R1Xn1JOZjQBvATbHppl8Bv1y4A4z22oheTTA0R4yvkH4i390RfvuOJ+JfwwHcd49JRksbOokwR0fYOIPcg9wvLu/BbgS+FczO4wZmmbfNwCvBd4c9/cP/Z5jH/vubHM1sA+4NTaVct6pMrNXAt8ELnf3X1LxZ5DxNndfDJwLXGpm78i+GXtrlc07MLNDgPcSctvC4M67pyRrnfoUSYIBLKTx+1MyWbrc/UXgxbi+1cx2Aq8nJAnOdtk7yYP72nfmGL4I3BZf9kpQzBTtM963mX0IeA9wRvzPWtp5z8BU51oqM3sZIVDc6u7fAnD3pzPv5/0MZszdd8evz5jZtwld+6fNbIG774mXGc9Use/oXODezvkO6rynVMVASNULoTDRDye1zScO7BAG33YDR8bXkwf6lvW53wWZ9SsI14oAJzFxkGkXYXBqdlxfyIHBwJMKnPNDwPxBn/ek/ZV2TtPsxwjjLJ8p8hn0ue9DgVdl1v8z/vw/xcQBzrVx/d1MHOC8u4TzXwdcNMjznvaYqvimVS/APwMfmdT2Z8B2YBtwL/AnmfeWAA8SRoo/R5y52sd+/wV4ALifkME8+wFeHb//DjJ3HQgj5T+O711d4JzHCdem2+LyhUGdd5djKeWcptnH2wjd/Psz57ysn8+gj32fEH8B74s/26tj+1HAXYQs9ndyICgb8Pm47weAJQXP/VDg58DhRf7vlb1oureI5JLkAKeIpEfBQkRyUbAQkVwULEQkFwULEclFwUJEclGwEJFc/h+4hcakr5+7GwAAAABJRU5ErkJggg==\n",
359 | "text/plain": [
360 | ""
361 | ]
362 | },
363 | "metadata": {
364 | "needs_background": "light"
365 | },
366 | "output_type": "display_data"
367 | }
368 | ],
369 | "source": [
370 | "clf()\n",
371 | "plot(CCentsX,CCentsY,'rx')\n",
372 | "plt.axis('image')"
373 | ]
374 | },
375 | {
376 | "cell_type": "markdown",
377 | "metadata": {},
378 | "source": [
379 | "# Now start with Nanoscribe file"
380 | ]
381 | },
382 | {
383 | "cell_type": "code",
384 | "execution_count": 11,
385 | "metadata": {},
386 | "outputs": [],
387 | "source": [
388 | "optPoints=np.column_stack((CCentsX,CCentsY))"
389 | ]
390 | },
391 | {
392 | "cell_type": "code",
393 | "execution_count": 14,
394 | "metadata": {},
395 | "outputs": [
396 | {
397 | "name": "stdout",
398 | "output_type": "stream",
399 | "text": [
400 | "11402.334630350195\n"
401 | ]
402 | },
403 | {
404 | "data": {
405 | "image/png": "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\n",
406 | "text/plain": [
407 | ""
408 | ]
409 | },
410 | "metadata": {
411 | "needs_background": "light"
412 | },
413 | "output_type": "display_data"
414 | }
415 | ],
416 | "source": [
417 | "# let's optimize the path for printing\n",
418 | "Ord,NNd=TipSlicerUpdated.TSPnearest(optPoints)\n",
419 | "plot(optPoints[Ord][:,0],optPoints[Ord][:,1],'-')\n",
420 | "print(NNd)"
421 | ]
422 | },
423 | {
424 | "cell_type": "markdown",
425 | "metadata": {},
426 | "source": [
427 | "### First check different FOV + Blocks to see if FOV big enough"
428 | ]
429 | },
430 | {
431 | "cell_type": "code",
432 | "execution_count": 15,
433 | "metadata": {},
434 | "outputs": [],
435 | "source": [
436 | "i=0\n",
437 | "Fov=400 # This is important! How big of a fov in microns you are trying to print before the stage moves (can get away with up to 400)\n",
438 | "hatching=0.2 \n",
439 | "am=np.int(np.round(Fov/hatching))\n",
440 | "z=50"
441 | ]
442 | },
443 | {
444 | "cell_type": "code",
445 | "execution_count": 18,
446 | "metadata": {},
447 | "outputs": [
448 | {
449 | "name": "stdout",
450 | "output_type": "stream",
451 | "text": [
452 | "Working on 22\n"
453 | ]
454 | }
455 | ],
456 | "source": [
457 | "xc=optPoints[Ord[i],0]\n",
458 | "yc=optPoints[Ord[i],1]\n",
459 | "\n",
460 | "xx=np.linspace(xc-Fov/2.0,xc+Fov/2.0,am)\n",
461 | "yy=np.linspace(yc-Fov/2.0,yc+Fov/2.0,am)\n",
462 | "Xloc,Yloc=np.meshgrid(xx,yy)\n",
463 | "print('Working on {}'.format(Ord[i]))\n",
464 | "\n",
465 | "#Back=sliceFormulaAber(Xloc,Yloc,z,[xpos,ypos,rlist,r_lenslet,height,aperR,-1,zernlist])\n",
466 | "T=sliceFormulaAber(Xloc,Yloc,z,[xpos,ypos,rlist,r_lenslet,height,aperR,Ord[i],zernlist])\n",
467 | "Mask=maskFormulaAber(Xloc,Yloc,z,[xpos,ypos,rlist,r_lenslet,height,aperR,Ord[i],zernlist])\n",
468 | "Res=T*(Mask)\n",
469 | "#Back=np.where(Back==0,NaN,Back)\n",
470 | "#imshow(Back+Res)\n",
471 | "#print(i)\n",
472 | "#i=i+1"
473 | ]
474 | },
475 | {
476 | "cell_type": "code",
477 | "execution_count": 22,
478 | "metadata": {},
479 | "outputs": [],
480 | "source": [
481 | "# this name will define the sub-folder that will be written to\n",
482 | "rootName='MicroAberStitch_36lenses_pdms_01_500fov_corrected'\n"
483 | ]
484 | },
485 | {
486 | "cell_type": "markdown",
487 | "metadata": {},
488 | "source": [
489 | "# Let's write the files need for the Nanoscribe printer\n",
490 | "This will take some time to run"
491 | ]
492 | },
493 | {
494 | "cell_type": "code",
495 | "execution_count": 23,
496 | "metadata": {},
497 | "outputs": [
498 | {
499 | "name": "stdout",
500 | "output_type": "stream",
501 | "text": [
502 | "Writing block 0\n",
503 | "Writing block 1\n",
504 | "Writing block 2\n",
505 | "Writing block 3\n",
506 | "Writing block 4\n",
507 | "Writing block 5\n",
508 | "Writing block 6\n",
509 | "Writing block 7\n",
510 | "Writing block 8\n",
511 | "Writing block 9\n",
512 | "Writing block 10\n",
513 | "Writing block 11\n",
514 | "Writing block 12\n",
515 | "Writing block 13\n",
516 | "Writing block 14\n",
517 | "Writing block 15\n",
518 | "Writing block 16\n",
519 | "Writing block 17\n",
520 | "Writing block 18\n",
521 | "Writing block 19\n",
522 | "Writing block 20\n",
523 | "Writing block 21\n",
524 | "Writing block 22\n",
525 | "Writing block 23\n",
526 | "Writing block 24\n",
527 | "Writing block 25\n",
528 | "Writing block 26\n",
529 | "Writing block 27\n",
530 | "Writing block 28\n",
531 | "Writing block 29\n",
532 | "Writing block 30\n",
533 | "Writing block 31\n",
534 | "Writing block 32\n",
535 | "Writing block 33\n",
536 | "Writing block 34\n",
537 | "Writing block 35\n"
538 | ]
539 | }
540 | ],
541 | "source": [
542 | "#%%%time\n",
543 | "# STEP 1: Write out the H5 stacks, intermediate format that is equivalent to a sliced STL\n",
544 | "# => only changes if you change parameters here (slicing,hashing,writing height)\n",
545 | "# => for other variations (number of shells etc..) you don't have to rerun this\n",
546 | "\n",
547 | "FovZ=54.120638 # should be maximum size of total element, used for slice determination\n",
548 | "height=50\n",
549 | "roughSlicing=2\n",
550 | "fineSlicing=0.1\n",
551 | "zzbase=np.arange(0,height,roughSlicing) # speed up writing of base\n",
552 | "zzlenses=np.arange(height,FovZ,fineSlicing)\n",
553 | "zz=np.append(zzbase,zzlenses)\n",
554 | "\n",
555 | "\n",
556 | "Fov=500 # 600 is quite a lot, but using the 25x, could be possible. \n",
557 | " # diameter of writing circle, using the Galvo and the 25x, 400µm is definitely writeable, \n",
558 | " # it just implies the voxel might get deformed by optical aberrations of the objective\n",
559 | " # => you can expect some quality decrease but no doubt still better than having a stitching line..\n",
560 | "\n",
561 | "hatching=0.2 #should be 0.2\n",
562 | "am=np.int(np.round(Fov/hatching)) \n",
563 | "\n",
564 | "\n",
565 | "\n",
566 | "#overlapXY_multiplier=3 # this is how much the different stitching blocks would overlap (this x hatching). \n",
567 | "# the masks are not overlapping for the moment, if we want this, needs to be implemented in masking function..\n",
568 | "\n",
569 | "if (not os.path.exists(rootName)):\n",
570 | " os.mkdir(rootName)\n",
571 | "if (not os.path.exists(rootName+'/h5data')):\n",
572 | " os.mkdir(rootName+'/h5data')\n",
573 | "if (not os.path.exists(rootName+'/data')):\n",
574 | " os.mkdir(rootName+'/data')\n",
575 | "genBlockName=rootName+'/h5data/'+rootName+'_Block_{:05d}.h5'\n",
576 | "genGwlName=rootName+'/data/'+rootName+'_Block_{:05d}.gwl'\n",
577 | "genRegex=rootName+'/data/.+gwl'\n",
578 | "fullGwlName=rootName+'/'+rootName+'.gwl'\n",
579 | "\n",
580 | "for i in np.arange(len(Ord)):\n",
581 | "#for i in np.arange(5):\n",
582 | " # now make coordinate systems\n",
583 | " print('Writing block {}'.format(i))\n",
584 | " xc=optPoints[Ord[i],0]\n",
585 | " yc=optPoints[Ord[i],1]\n",
586 | "\n",
587 | " xx=np.linspace(xc-Fov/2.0,xc+Fov/2.0,am)\n",
588 | " yy=np.linspace(yc-Fov/2.0,yc+Fov/2.0,am)\n",
589 | " Xloc,Yloc=np.meshgrid(xx,yy)\n",
590 | " \n",
591 | " # make specific mask\n",
592 | "# MM=maskFormula(Xloc,Yloc,z,maskParams)\n",
593 | " # if you need overlap, do binary_dilation on the mask\n",
594 | "# MM=im.binary_dilation(MM,iterations=overlapXY_multiplier)\n",
595 | " \n",
596 | " # with these coordinate systems, apply formulaToStack\n",
597 | " blockName=genBlockName.format(i)\n",
598 | " \n",
599 | " extras=[xpos,ypos,rlist,r_lenslet,height,aperR,Ord[i],zernlist]\n",
600 | " Mask=maskFormulaAber(Xloc,Yloc,50.1,[xpos,ypos,rlist,r_lenslet,height,aperR,Ord[i],zernlist])\n",
601 | " \n",
602 | " # if you need overlap between the masks, apply some dilation here..\n",
603 | " Mask=im.binary_dilation(Mask,iterations=3)\n",
604 | " \n",
605 | " TipSlicerUpdated.formulaToStack(blockName,Xloc,Yloc,zz,sliceFormulaAber,writingMask=Mask,extraParams=extras)\n",
606 | " \n",
607 | " "
608 | ]
609 | },
610 | {
611 | "cell_type": "code",
612 | "execution_count": 24,
613 | "metadata": {},
614 | "outputs": [
615 | {
616 | "name": "stdout",
617 | "output_type": "stream",
618 | "text": [
619 | "Writing block 0\n",
620 | "Writing block 1\n",
621 | "Writing block 2\n",
622 | "Writing block 3\n",
623 | "Writing block 4\n",
624 | "Writing block 5\n",
625 | "Writing block 6\n",
626 | "Writing block 7\n",
627 | "Writing block 8\n",
628 | "Writing block 9\n",
629 | "Writing block 10\n",
630 | "Writing block 11\n",
631 | "Writing block 12\n",
632 | "Writing block 13\n",
633 | "Writing block 14\n",
634 | "Writing block 15\n",
635 | "Writing block 16\n",
636 | "Writing block 17\n",
637 | "Writing block 18\n",
638 | "Writing block 19\n",
639 | "Writing block 20\n",
640 | "Writing block 21\n",
641 | "Writing block 22\n",
642 | "Writing block 23\n",
643 | "Writing block 24\n",
644 | "Writing block 25\n",
645 | "Writing block 26\n",
646 | "Writing block 27\n",
647 | "Writing block 28\n",
648 | "Writing block 29\n",
649 | "Writing block 30\n",
650 | "Writing block 31\n",
651 | "Writing block 32\n",
652 | "Writing block 33\n",
653 | "Writing block 34\n",
654 | "Writing block 35\n"
655 | ]
656 | }
657 | ],
658 | "source": [
659 | "#%%%time\n",
660 | "# STEP 2: Write out GWL files based on H5 files from above\n",
661 | "\n",
662 | "amShells=5\n",
663 | "ScaffStep=2 # for solid infill => 2*hatching is spacing here\n",
664 | "hatchStep=1 # for contour distance and top-bottom infill, not used if amShells==1 and doTopBottom=False\n",
665 | "\n",
666 | "\n",
667 | "# the definition below will be used to increase the writing laser power when the slicing is larger\n",
668 | "# below the base height, the intensity will be ? times larger, above it the nominal one will be used.\n",
669 | "def intensityMultiplier(z):\n",
670 | " if (z