Last active
February 15, 2024 10:42
-
-
Save raacampbell/0346d13864888c864e36e84cd6297035 to your computer and use it in GitHub Desktop.
bake with hack for re-acquiring slice with a different laser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function sectionInd = bake(obj,varargin) | |
% Runs an automated anatomy acquisition using the currently attached parameter file | |
% | |
% function BT.bake('Param1',val1,'Param2',val2,...) | |
% | |
% | |
% Inputs (optional param/val pairs) | |
% 'leaveLaserOn' - If true, the laser is not switched off when acquisition finishes. | |
% This setting can also be supplied by setting BT.leaveLaserOn. If | |
% not supplied, this built-in value is used. | |
% | |
% 'sliceLastSection' - If false, the last section of the whole acquisition is not cut | |
% off the block. This setting can also be supplied by setting | |
% BT.leaveLaserOn. If not supplied, this built-in value is used. | |
% | |
% | |
% Outputs | |
% sectionInd - the index of the main bake loop. If the loop didn't start, this will be 0. | |
% | |
% Rob Campbell - Basel, Feb, 2017 | |
% | |
% See also BT.runTileScan | |
sectionInd = 0; | |
obj.currentTilePosition=1; % so if there is an error before the main loop we don't turn off the laser. | |
% If we are completely not ready to proceed, bail out right away | |
if ~obj.isScannerConnected | |
fprintf('No scanner connected.\n') | |
return | |
end | |
%Parse the optional input arguments | |
params = inputParser; | |
params.CaseSensitive = false; | |
params.addParameter('leaveLaserOn', obj.leaveLaserOn, @(x) islogical(x) || x==1 || x==0) | |
params.addParameter('sliceLastSection', obj.sliceLastSection, @(x) islogical(x) || x==1 || x==0) | |
params.parse(varargin{:}); | |
obj.leaveLaserOn=params.Results.leaveLaserOn; | |
obj.sliceLastSection=params.Results.sliceLastSection; | |
% ---------------------------------------------------------------------------- | |
%Check whether the acquisition is likely to fail in some way | |
% First record the current scanner settings. Just in case they are not up to date. | |
% TODO -- When the recipe is written (below in the main for loop) the recordScannerSettings | |
% method is run anyway. So we should not need it here. There is a BUG that sometimes | |
% causes the Z voxel size to be wrong: to be what was used during the preview. But it's | |
% unclear how this is happening since the system is already running an acquisition with the | |
% correct parameters by the time we end up in the for loop. We leave this line here for | |
% now as it should not cause any problems, but it is unlikely to solve anything. | |
obj.recipe.recordScannerSettings | |
[acqPossible,msg]=obj.checkIfAcquisitionIsPossible(true); %true to indicate this is a bake | |
if ~acqPossible | |
obj.messageString = msg; | |
return | |
end | |
%Define an anonymous function to nicely print the current time | |
currentTimeStr = @() datestr(now,'yyyy/mm/dd HH:MM:SS'); | |
fprintf('Setting up acquisition of sample %s\n',obj.recipe.sample.ID) | |
% Remove any attached file logger objects. We will add one per physical section. | |
% Reset properties in preparation for acquisition | |
obj.detachLogObject | |
obj.currentTileSavePath=[]; | |
obj.sectionCompletionTimes=[]; | |
obj.acquisitionInProgress=true; | |
obj.acquisitionState='bake'; | |
obj.abortAcqNow=false; % This and the following property can't be on before we've even started | |
obj.abortAfterSectionComplete=false; | |
% Assign cleanup function, which is in private directory | |
tidy = onCleanup(@() bakeCleanupFun(obj)); | |
%---------------------------------------------------------------------------------------- | |
fprintf('\n\n\n ------>> Starting To Bake <<------ \n\n\n') | |
obj.acqLogWriteLine( sprintf('%s -- STARTING NEW ACQUISITION\n',currentTimeStr() ) ) | |
% Report to screen and the log file how much disk space is currently available | |
msg = obj.reportAcquisitionSize; | |
obj.acqLogWriteLine(msg) | |
if ~isempty(obj.laser) | |
obj.acqLogWriteLine(sprintf('Using laser: %s\n', obj.laser.readLaserID)) | |
end | |
% Print the version number and name of the scanning software | |
obj.acqLogWriteLine(sprintf('Acquiring with: %s\n', obj.scanner.getVersion)) | |
try | |
G=BakingTray.utils.getGitInfo; | |
obj.acqLogWriteLine(sprintf('Using BakingTray version %s from branch %s\n', G.hash, G.branch)) | |
catch | |
obj.acqLogWriteLine('Failed to extract git commit info for logging\n') | |
end | |
% Report laser settings and power in mW (if this is possible to do) | |
obj.acqLogWriteLine(sprintf('Acquiring sample at a laser power of %d nm ', obj.laser.readWavelength)) | |
laserPowerInmw = obj.scanner.returnLaserPowerInmW; | |
if isnan(laserPowerInmw) | |
obj.acqLogWriteLine(sprintf('(power in mW is not calibrated in ScanImage)\n\n')) | |
else | |
obj.acqLogWriteLine(sprintf('with %d mW at the sample\n\n', laserPowerInmw)) | |
end | |
% Set the watchdog timer on the laser to 40 minutes. The laser | |
% will switch off after this time if it heard nothing back from bake. | |
% e.g. if the computer reboots spontaneously, the laser will turn off 40 minutes later. | |
if ~isempty(obj.laser) | |
wDogSeconds = 40*60; | |
obj.laser.setWatchDogTimer(wDogSeconds); | |
end | |
%Log the current time to the recipe | |
obj.recipe.Acquisition.acqStartTime = currentTimeStr(); | |
% auto-ROI stuff if the user has selected this. Note that after the following if statement | |
% we have populated obj.currentTilePattern. This myuste be done before arming the scanner, as scanner arming | |
% requires us to know how many tiles will be imaged. | |
if strcmp(obj.recipe.mosaic.scanmode,'tiled: auto-ROI') | |
obj.currentSectionNumber = obj.recipe.mosaic.sectionStartNum; % TODO -- not tested with auto-ROI resume | |
fprintf('Bake is in auto-ROI mode. Setting currentSectionNumber to 1 and getting first ROIs:\n') | |
obj.populateCurrentTilePattern; | |
fprintf('Starting auto-ROI acquisition with a grid of %d tiles\n', ... | |
length(obj.currentTilePattern)) | |
% Write the auto-ROI parameters as a yaml file in the sample directory | |
BakingTray.yaml.WriteYaml(fullfile(obj.sampleSavePath,'autoROI_settings.yml'), autoROI.readSettings); | |
elseif strcmp(obj.recipe.mosaic.scanmode,'tiled: manual ROI') | |
obj.populateCurrentTilePattern; | |
end | |
% Reset flag to true, so FINISHED file is made when the loop exits (unless user chooses otherwise). | |
obj.completeAcquisitionOnBakeLoopExit=true; | |
% LOOP THROUGH ALL SECTIONS AND TILE SCAN | |
for sectionInd=1:obj.recipe.mosaic.numSections | |
obj.currentSectionNumber = sectionInd+obj.recipe.mosaic.sectionStartNum-1; % This is the current physical section | |
fprintf('\n\n%s\n * Section %d\n\n',repmat('-',1,70),obj.currentSectionNumber) % Print a line across the CLI | |
if obj.currentSectionNumber<0 | |
fprintf('WARNING: BT.bake is setting the current section number to less than 0\n') | |
end | |
if ~obj.defineSavePath % Define directories into which we will save data. Create if needed. | |
% (Detailed warnings are produced by defineSavePath method) | |
disp('Acquisition stopped: save path not defined'); | |
return | |
end | |
tLine = sprintf('%s -- STARTING section number %d (%d of %d) at z=%0.4f in directory %s\n',... | |
currentTimeStr() ,obj.currentSectionNumber, sectionInd, obj.recipe.mosaic.numSections, obj.getZpos, ... | |
strrep(obj.currentTileSavePath,'\','\\') ); | |
obj.acqLogWriteLine(tLine) | |
startAcq=now; | |
if ~isempty(obj.laser) | |
% Record laser status before section | |
obj.acqLogWriteLine(sprintf('laser status: %s\n', obj.laser.returnLaserStats)) | |
end | |
if obj.saveToDisk | |
obj.scanner.setUpTileSaving; | |
%Add logger object to the above directory | |
logFilePath = fullfile(obj.currentTileSavePath,'acquisition_log.txt'); | |
obj.attachLogObject(bkFileLogger(logFilePath)) | |
end % if obj.saveToDisk | |
% If we are in auto-ROI mode, ensure that only the desired channel is being displayed | |
% This is also done in obj.getThreshold, but repeating it here ensures the user can't | |
% alter the channel during acquisition. | |
if strcmp(obj.recipe.mosaic.scanmode,'tiled: auto-ROI') | |
obj.scanner.setChannelsToDisplay(obj.autoROI.channel); | |
end | |
% For syncAndCrunch to be happy we need to write the currently | |
% displayed channels. A bit of hack, but it's easiest solution | |
% for now. Alternative would be to have S&C rip it out of the | |
% TIFF header. (TODO) | |
if sectionInd==1 && strcmp(obj.scanner.scannerID,'ScanImage via SIBT') | |
scanSettings.hChannels.channelDisplay = obj.scanner.hC.hChannels.channelDisplay; | |
saveSettingsTo = fileparts(fileparts(obj.currentTileSavePath)); %Save to sample root directory | |
save(fullfile(saveSettingsTo,'scanSettings.mat'), 'scanSettings') | |
end | |
% Write the full recipe to disk | |
if sectionInd==1 | |
obj.recipe.writeFullRecipeForAcquisition(obj.sampleSavePath); | |
end | |
% ===> Now the scanning runs <=== | |
% HACKY LOOP ADDED HERE | |
for ii=1:2 | |
% TODO: OPEN LASER SHUTTER 1 AND CLOSE LASER SHUTTER 1. | |
% | |
% Set the file name. | |
obj.scanner.hC.hScan2D.logFileStem = sprintf(obj.scanner.returnTileFname, ii); | |
% 2023/02/15, RAAC, move arm scanner after log file stem is set | |
if ~obj.scanner.armScanner | |
disp('FAILED TO START -- COULD NOT ARM SCANNER') | |
return | |
end | |
runTileScanSuccess = obj.runTileScan; | |
end | |
%NOTE: if you are using the auto-ROI, the second acquisition is the one it will | |
%use to run this | |
if runTileScanSuccess.success == false % if runTileScan failed, we quit | |
if isempty(runTileScanSuccess.msg) | |
fprintf('\n--> BT.runTileScan returned false. QUITTING BT.bake\n\n') | |
return | |
elseif strcmp(runTileScanSuccess.msg,'initpreviewfailed') | |
fprintf('\n--> BT.runTileScan failed to make preview image. Likely sample vanished. Stop and mark as finished. QUITTING BT.bake\n\n') | |
obj.scanner.abortScanning; | |
makeFinished(obj.sampleSavePath) | |
return | |
end | |
end | |
% We will use this later to decide whether to cut. This test asks if the positionArray is complete | |
% so we don't cut if tiles are missing. We test here because the position array is modified before | |
% cutting can happen. | |
if obj.tilesRemaining==0 | |
sectionCompleted=true; | |
else | |
sectionCompleted=false; | |
end | |
% ===> Tile scan finished <=== | |
%If requested, save the current preview stack to disk | |
if exist(obj.logPreviewImageDataToDir,'dir') | |
try | |
fname=sprintf('%s_section_%d_%s.mat', ... | |
obj.recipe.sample.ID, ... | |
obj.currentSectionNumber, ... | |
datestr(now,'yyyy_mm_dd')); | |
fname = fullfile(obj.logPreviewImageDataToDir,fname); | |
fprintf('SAVING PREVIEW IMAGE TO: %s\n',fname) | |
imData=obj.lastPreviewImageStack; | |
save(fname,'imData') | |
catch | |
fprintf('Failed to save preview stack to log dir\n') | |
end | |
end | |
% Save the downsampled tile cache to the rawData directory if this is appropriate | |
if obj.keepAllDownSampledTiles | |
fprintf('Saving the tile cache from the last section\n') | |
tileCache = obj.allDownsampledTilesOneSection; | |
cacheFname = fullfile(obj.currentTileSavePath,'tileCache.mat'); | |
save(cacheFname,'tileCache') | |
end | |
% Now we save to full scan settings by stripping data from a tiff file. | |
% If this is the first pass through the loop and we're using ScanImage, dump | |
% the settings to a file. TODO: eventually we need to decide what to do with other | |
% scan systems and abstract this code. | |
if sectionInd==1 && strcmp(obj.scanner.scannerID,'ScanImage via SIBT') | |
d=dir(fullfile(obj.currentTileSavePath,'*.tif')); | |
if ~isempty(d) | |
tmp_fname = fullfile(obj.currentTileSavePath,d(end).name); | |
TMP=scanimage.util.opentif(tmp_fname); | |
scanSettings = TMP.SI; | |
saveSettingsTo = fileparts(fileparts(obj.currentTileSavePath)); %Save to sample root directory | |
fprintf('Saving ScanImage settings file to %s\n', saveSettingsTo) | |
save(fullfile(saveSettingsTo,'scanSettings.mat'), 'scanSettings') | |
end | |
end | |
if obj.abortAcqNow | |
fprintf('BT.bake: BT.abortAcq is true. Stopping acquisition.\n') | |
break | |
end | |
% If the laser is off-line for some reason (e.g. lack of modelock, we quit | |
% so we don't cut and the sample is safe. | |
if obj.isLaserConnected | |
[isReady,msg]=obj.laser.isReady; | |
if ~isReady | |
% Otherwise pause and check it's really down before carrying on | |
pause(3) | |
[isReady,msg]=obj.laser.isReady; | |
end | |
if ~isReady | |
msg = sprintf('LASER NOT RUNNING (Section %d): %s\n', obj.currentSectionNumber, msg); | |
obj.acqLogWriteLine(msg); | |
msg = sprintf('%s\BakingTray trying to recover it.\n',msg); | |
obj.slack(msg); | |
obj.laser.turnOn | |
pause(3) | |
obj.laser.openShutter | |
pause(2) | |
for ii=1:15 | |
if obj.laser.isReady | |
obj.acqLogWriteLine('LASER RECOVERED\n'); | |
obj.slack('BakingTray managed to recover the laser.'); | |
break | |
end | |
pause(10) | |
end | |
end | |
if ~isReady | |
msg = sprintf('*** STOPPING ACQUISITION DUE TO LASER: %s ***\n',msg); | |
obj.slack(msg) | |
fprintf(msg) | |
obj.acqLogWriteLine(msg) | |
return | |
end | |
end | |
% If too many channels are being displayed, fix this before carrying on | |
chanDisp=obj.scanner.getChannelsToDisplay; | |
if length(chanDisp)>1 && isa(obj.scanner,'SIBT') | |
fprintf('Setting chan display to %d only in BT.bake\n', chanDisp(end)) | |
obj.scanner.setChannelsToDisplay(chanDisp(end)); | |
end | |
% If the user is running auto-ROI, we now re-calculated the bounding boxes. The method call | |
% to getNextROIs does this and also updates currentTilePattern. | |
if strcmp(obj.recipe.mosaic.scanmode,'tiled: auto-ROI') | |
% Save the pStack file (TODO -- should we leave this here?) | |
pStack_fname = fullfile(obj.currentTileSavePath, 'sectionPreview.mat'); | |
sectionPreview = obj.autoROI.previewImages; | |
sectionPreview = rmfield(sectionPreview,'recipe'); | |
save(pStack_fname,'sectionPreview') | |
success = obj.getNextROIs; | |
if ~success %If getNextROIs failed | |
% Bail out gracefully if no tissue was found | |
msg = sprintf('Found no tissue in Section %d during Bake. Quitting acquisition.', ... | |
obj.currentSectionNumber); | |
obj.acqLogWriteLine(sprintf('%s -- %s\n', currentTimeStr(), msg)) | |
fprintf('\n*** %s ***\n\n',msg) | |
obj.slack(msg) | |
% Assume the acquisition is supposed to have finished this way | |
% TODO -- this could be a setting | |
makeFinished(obj.sampleSavePath) | |
return | |
end | |
% Save to disk the whole auto-ROI structure | |
autoROI_fname = fullfile(obj.pathToSectionDirs,obj.autoROIstats_fname); | |
autoROI_stats = obj.autoROI; | |
if ~isa(obj.scanner,'dummyScanner') | |
save(autoROI_fname,'autoROI_stats') | |
end | |
else | |
% Wipe the last two columns of the position array. These save the actual stage | |
% positions. This is necessary for the acquisition resume to work properly. | |
obj.positionArray(:,end-1:end)=nan; | |
end | |
% Cut the sample if necessary | |
if sectionCompleted | |
%Mark the section as complete | |
fname=fullfile(obj.currentTileSavePath,'COMPLETED'); | |
fid=fopen(fname,'w+'); | |
fprintf(fid,'COMPLETED'); | |
fclose(fid); | |
obj.acqLogWriteLine(sprintf('%s -- acquired %d tile positions in %s\n',... | |
currentTimeStr(), obj.currentTilePosition-1, prettyTime((now-startAcq)*24*60^2)) ); | |
if sectionInd<obj.recipe.mosaic.numSections || obj.sliceLastSection | |
%But don't slice if the user asked for an abort and sliceLastSection is false | |
if obj.abortAfterSectionComplete && ~obj.sliceLastSection | |
% pass | |
else | |
obj.sliceSample; | |
end | |
end | |
else | |
fprintf('Still waiting for %d tiles. Not cutting. Aborting.\n',obj.tilesRemaining) | |
obj.scanner.abortScanning; | |
return | |
end | |
obj.detachLogObject %Close the log file that writes to the section directory | |
if ~isempty(obj.laser) | |
% Record laser status after section | |
obj.acqLogWriteLine(sprintf('laser status: %s\n', obj.laser.returnLaserStats)) | |
end | |
elapsedTimeInSeconds=(now-startAcq)*24*60^2; | |
obj.acqLogWriteLine(sprintf('%s -- FINISHED section number %d, section completed in %s\n',... | |
currentTimeStr(), obj.currentSectionNumber, prettyTime(elapsedTimeInSeconds) )); | |
obj.sectionCompletionTimes(end+1)=elapsedTimeInSeconds; | |
if obj.abortAfterSectionComplete | |
%TODO: we could have a GUI come up that allows the user to choose if they want this happen. | |
obj.acqLogWriteLine(sprintf('%s -- BT.bake received "abortAfterSectionComplete". Not turning off the laser.\n',currentTimeStr() )); | |
obj.leaveLaserOn=true; | |
break | |
end | |
%%disp(' *** PRESS RETURN FOR NEXT SECTION *** '); pause | |
end % for sectionInd=1:obj.recipe.mosaic.numSections | |
if strcmp(obj.recipe.mosaic.scanmode,'tiled: auto-ROI') && isa(obj.scanner,'dummyScanner') | |
save(autoROI_fname,'autoROI_stats') | |
end | |
fprintf('Finished data acquisition\n') | |
if obj.scanner.isAcquiring | |
msg = ('FORCING ABORT: SOMETHING WENT WRONG--too many tiles were defined for some reason or data were not acquired.'); | |
disp(msg) | |
obj.acqLogWriteLine( sprintf('%s -- %s\n',currentTimeStr(), msg) ); | |
obj.scanner.abortScanning; | |
end | |
% Create an empty "FINISHED" file, which will trigger stitching from syncAndCrunch. | |
% It is the default to create the file. The only likely way it will not be created is if the user | |
% chooses not to when the stop the acquisition early. | |
if obj.completeAcquisitionOnBakeLoopExit | |
obj.acqLogWriteLine(sprintf('%s -- FINISHED AND COMPLETED ACQUISITION\n',currentTimeStr() )); | |
makeFinished(obj.sampleSavePath) | |
end | |
end %close of bake | |
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
function makeFinished(sampleSavePath) | |
%Create an empty finished file | |
fid=fopen(fullfile(sampleSavePath,'FINISHED'), 'w'); | |
fclose(fid); | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
move arm scanner after the file name step is set