├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── ProgressBar.m ├── README.md ├── demos ├── basicUseCases.m ├── countProcessedBytes.m ├── disableBar.m ├── nestedProgressBars.m ├── nonUnicodeProgressBar.m ├── parallelSetup.m ├── passSuccessInfo.m ├── printInfoDuringRun.m └── titleBanner.m ├── images ├── example1.gif └── example2.gif ├── progress.m ├── tests ├── ProgressBar_test.m └── progress_test.m └── updateParallel.m /.gitignore: -------------------------------------------------------------------------------- 1 | *.m~ 2 | *.asv 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and 5 | this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [3.4.1] - 2024-12-16 8 | ### Fixed 9 | - Fixed a bug which lead to an error when trying to change the command line font 10 | on Windows. Thanks @SeanZhang99! 11 | 12 | ## [3.4.0] - 2023-09-03 13 | ### Added 14 | - Support for nested loops where the inner one is a parallel `parfor` loop. Thanks 15 | @tunakasif! 16 | 17 | ## [3.3.0] - 2021-02-28 18 | ### Added 19 | - Optional switch to completely disable the bar's progress and printing functionality 20 | via `IsActive`. If `false`, the progress bar is disabled. The default is `true`. 21 | 22 | ## [3.2.0] - 2020-11-13 23 | ### Added 24 | - Optional switch to override default MATLAB font in Windows by `OverrideDefaultFont` 25 | property. If `true` and while the bar is not released, MATLAB's code font will be 26 | changed programmatically to `Courier New` 27 | - Proper unit testing as a test-case class 28 | 29 | ### Changed 30 | - Demos should now be a bit prettier 31 | 32 | ### Fixed 33 | - Progress bar in non-finite and iteration mode (not having a konwn total number of 34 | iterations) will now show correct unit string `it` instead of toggling wrongly between 35 | `it` and `s` 36 | 37 | 38 | ## [3.1.1] - 2019-11-26 39 | ### Fixed 40 | - Updated CHANGELOG 41 | 42 | ### Added 43 | - MATHWORKS FileExchange banner in the README 44 | 45 | 46 | ## [3.1.0] - 2019-11-03 47 | ### Fixed 48 | - Moved setup-related method-calls to the setup phase of the object. In v3.0.0, the 49 | timer object was initialized in the constructor, leading to potential issues in 50 | computing the elapsed time and estimated time until completion. Also, user-set 51 | properties (like, e.g., the bar's title) could have had no influence while calling the 52 | step function due to the same reason. 53 | ### Changed 54 | - Renamed private properties 55 | - The class' version is now stated as a constant property 56 | - Made some properties constant and method static 57 | - Cleaned up comments 58 | - Updated README and CHANGELOG 59 | 60 | 61 | ## [3.0.0] - 2017-05-02 62 | ### Changed 63 | The whole class has been re-factored to be a **MATLAB System Object** 64 | - Since updating a progress bar is an iterative process with a setup/reset/release 65 | paradigm, this object type fits the purpose best 66 | - Due to the System Object class structure, multiple methods have been renamed and 67 | optional input arguments for the `step()` (formerly `update()`) method are now 68 | mandatory. See the example in `README.md`. 69 | - The bar's **title** is now mandatory and has a default string: `'Processing'`. Notable 70 | change is that if the title exceeds the length of 20 characters the title will act 71 | like a banner and cycle with a shift of 3 characters each time the bar is updated. 72 | This way, the progress bar can have a constant width (for now, 90 characters seem to 73 | fit many screens). 74 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause 2 | 3 | Copyright (c) 2024, Jens-Alrik Adrian 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 8 | met: 9 | 10 | 1. Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 13 | 2. Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in 15 | the documentation and/or other materials provided with the 16 | distribution. 17 | 18 | 3. Neither the name of the copyright holder nor the names of its 19 | contributors may be used to endorse or promote products derived 20 | from this software without specific prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 23 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 24 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 25 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 26 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 27 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 28 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 29 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 31 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 32 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | -------------------------------------------------------------------------------- /ProgressBar.m: -------------------------------------------------------------------------------- 1 | classdef ProgressBar < matlab.System 2 | %PROGRESSBAR A class to provide a convenient and useful progress bar 3 | % ------------------------------------------------------------------------- 4 | % This class mimics the design and some features of the TQDM 5 | % (https://github.com/tqdm/tqdm) progress bar in python. All optional 6 | % functionalities are set via name-value pairs in the constructor after the 7 | % argument of the total numbers of iterations used in the progress (which 8 | % can also be empty if unknown or even neglected if no name-value pairs are 9 | % passed). The central class' method is 'step()' to increment the 10 | % progress state of the object. 11 | % 12 | % Usage: pbar = ProgressBar() 13 | % pbar = ProgressBar(total) 14 | % pbar = ProgressBar(total, Name, Value) 15 | % 16 | % where 'total' is the total number of iterations. 17 | % 18 | % 19 | % ProgressBar Properties: 20 | % Total - the total number of iterations [default: []] 21 | % Title - the progress bar's title shown in front [default: 'Processing'] 22 | % Unit - the unit of the update process. Can either be 'Iterations' or 23 | % 'Bytes' [default: 'Iterations'] 24 | % UpdateRate - the progress bar's update rate in Hz. Defines the printing 25 | % update interval [default: 5 Hz] 26 | % 27 | % 28 | % ProgressBar Methods: 29 | % ProgressBar - class constructor 30 | % release - clean up and finish the progress bar's internal state 31 | % printMessage - print some infos during the iterations. Messages get 32 | % printed above the bar and the latter shifts one row down 33 | % setup - not needed in a common application. Tiny helper function when 34 | % setting up nested loops to print a parent bar before the first 35 | % update occured. When the inner loop takes long, a nasty white 36 | % space is shown in place of the parent bar until the first 37 | % update takes place. This function can be used as a remedy. 38 | % step - the central update method to increment the internal progress 39 | % state 40 | % 41 | % Author : J.-A. Adrian (JA) 42 | % 43 | 44 | 45 | properties (Constant) 46 | % Tag every timer with this to find it properly 47 | TIMER_TAG_NAME = 'ProgressBar'; 48 | VERSION = '3.4.1'; 49 | end 50 | 51 | properties (Nontunable) 52 | % Total number of iterations to compute progress and ETA 53 | Total; 54 | 55 | % Titel of the progress bar if desired. Shown in front of the bar 56 | Title = 'Processing'; 57 | 58 | % The visual printing rate in Hz. Default is 5 Hz 59 | UpdateRate = 5; 60 | 61 | % The unit of each update. Can be either 'Iterations' or 'Bytes'. 62 | % Default is 'Iterations'. 63 | Unit = 'Iterations'; 64 | 65 | % Directory in which the worker binary files are being saved when in 66 | % parallel mode. 67 | WorkerDirectory = tempdir; 68 | end 69 | 70 | properties (Logical, Nontunable) 71 | % Boolean whether to activate progress bar at all 72 | % useful for non-interactive / batch / hpc usage 73 | IsActive = true; 74 | 75 | % Boolean whether to use Unicode symbols or ASCII hash symbols (i.e. #) 76 | UseUnicode = true; 77 | 78 | % Boolean whether the progress bar is to be used in parallel computing setup 79 | IsParallel = false; 80 | 81 | % Boolean whether to override Windows' monospaced font to cure "growing bar" syndrome 82 | OverrideDefaultFont = false; 83 | end 84 | 85 | properties (Access = private) 86 | Bar = ''; 87 | IterationCounter = 0; 88 | 89 | NumWrittenCharacters = 0; 90 | FractionMainBlock; 91 | FractionSubBlock; 92 | 93 | HasTotalIterations = false; 94 | HasBeenUpdated = false; 95 | HasFiniteUpdateRate = true; 96 | HasItPerSecBelow1 = false; 97 | 98 | BlockCharacters; 99 | 100 | IsTimerRunning = false; 101 | 102 | TicObject; 103 | TimerObject; 104 | 105 | MaxBarWidth = 90; 106 | CurrentTitleState = ''; 107 | CurrentFont; 108 | end 109 | 110 | properties (Constant, Hidden) 111 | % The number of sub blocks in one main block of width of a character. 112 | % HTML 'left blocks' go in eigths -> 8 sub blocks in one main block 113 | NUM_SUB_BLOCKS = 8; 114 | 115 | % The number of characters the title string should shift each cycle 116 | NUM_CHARACTERS_SHIFT = 3; 117 | 118 | % The maximum length of the title string without banner cycling 119 | MAX_TITLE_LENGTH = 20; 120 | 121 | OVERRIDE_FONT_NAME = 'Courier New'; 122 | end 123 | 124 | properties (Access = private, Dependent) 125 | IsThisBarNested; 126 | end 127 | 128 | 129 | 130 | 131 | methods 132 | % Class Constructor 133 | function [obj] = ProgressBar(total, varargin) 134 | if nargin 135 | obj.Total = total; 136 | 137 | obj.setProperties(nargin-1, varargin{:}); 138 | end 139 | 140 | if ~isempty(obj.Total) 141 | obj.HasTotalIterations = true; 142 | end 143 | if isinf(obj.UpdateRate) 144 | obj.HasFiniteUpdateRate = false; 145 | end 146 | 147 | % check if prog. bar runs in deployed mode and if so, switch to 148 | % ASCII symbols and a smaller bar width 149 | if isdeployed 150 | obj.UseUnicode = false; 151 | obj.MaxBarWidth = 72; 152 | end 153 | 154 | % setup ASCII symbols if desired 155 | if obj.UseUnicode 156 | obj.BlockCharacters = ProgressBar.getUnicodeSubBlocks(); 157 | else 158 | obj.BlockCharacters = ProgressBar.getAsciiSubBlocks(); 159 | end 160 | end 161 | 162 | function [] = printMessage(obj, message, shouldPrintNextProgBar) 163 | %PRINTMESSAGE class method to print a message while prog bar running 164 | %---------------------------------------------------------------------- 165 | % This method lets the user print a message during the processing. A 166 | % normal fprintf() or disp() would break the bar so this method can be 167 | % used to print information about iterations or debug infos. 168 | % 169 | % Usage: obj.printMessage(obj, message) 170 | % obj.printMessage(obj, message, shouldPrintNextProgBar) 171 | % 172 | % Input: --------- 173 | % message - the message that should be printed to screen 174 | % shouldPrintNextProgBar - Boolean to define wether to 175 | % immidiately print another prog. bar 176 | % after print the success message. Can 177 | % be usefule when every iteration takes 178 | % a long time and a white space appears 179 | % where the progress bar used to be. 180 | % [default: shouldPrintNextProgBar = false] 181 | % 182 | 183 | % input parsing and validation 184 | narginchk(2, 3); 185 | 186 | if nargin < 3 || isempty(shouldPrintNextProgBar) 187 | shouldPrintNextProgBar = false; 188 | end 189 | validateattributes(shouldPrintNextProgBar, ... 190 | {'logical', 'numeric'}, ... 191 | {'scalar', 'binary', 'nonempty', 'nonnan'} ... 192 | ); 193 | 194 | % remove the current prog bar 195 | fprintf(1, ProgressBar.backspace(obj.NumWrittenCharacters)); 196 | 197 | % print the message and break the line 198 | fprintf(1, '\t'); 199 | fprintf(1, message); 200 | fprintf(1, '\n'); 201 | 202 | % reset the number of written characters 203 | obj.NumWrittenCharacters = 0; 204 | 205 | % if the next prog bar should be printed immideately do this 206 | if shouldPrintNextProgBar 207 | obj.printProgressBar(); 208 | end 209 | end 210 | 211 | function [yesNo] = get.IsThisBarNested(obj) 212 | % If there are more than one timer object with our tag, the current 213 | % bar must be nested 214 | yesNo = length(obj.getTimerList()) > 1; 215 | end 216 | end 217 | 218 | 219 | methods (Access = protected) 220 | function [] = validatePropertiesImpl(obj) 221 | valFunStrings = @(in) validateattributes(in, {'char'}, {'nonempty'}); 222 | valFunNumeric = @(in) validateattributes(in, ... 223 | {'numeric'}, ... 224 | {'scalar', 'positive', 'real', 'nonempty', 'nonnan'} ... 225 | ); 226 | valFunBoolean = @(in) validateattributes(in, ... 227 | {'logical', 'numeric'}, ... 228 | {'scalar', 'binary', 'nonnan', 'nonempty'} ... 229 | ); 230 | 231 | assert(ProgressBar.checkInputOfTotal(obj.Total)); 232 | 233 | assert(any(strcmpi(obj.Unit, {'Iterations', 'Bytes'}))); 234 | 235 | valFunStrings(obj.Title); 236 | valFunStrings(obj.WorkerDirectory); 237 | valFunNumeric(obj.UpdateRate); 238 | valFunBoolean(obj.UseUnicode); 239 | valFunBoolean(obj.IsParallel); 240 | end 241 | 242 | function [] = setupImpl(obj) 243 | if obj.IsActive 244 | % get a new tic object 245 | obj.TicObject = tic; 246 | 247 | % workaround for issue "Bar Gets Longer With Each Iteration" on windows systems 248 | s = settings; 249 | if obj.OverrideDefaultFont && ispc() 250 | % store current font to reset the code font back to this value in the release() method 251 | obj.CurrentFont = s.matlab.fonts.codefont.Name.ActiveValue; 252 | 253 | % change to Courier New which is shipped by every Windows distro since Windows 3.1 254 | s.matlab.fonts.codefont.Name.TemporaryValue = obj.OVERRIDE_FONT_NAME; 255 | end 256 | 257 | % add a new timer object with the standard tag name and hide it 258 | obj.TimerObject = timer(... 259 | 'Tag', obj.TIMER_TAG_NAME, ... 260 | 'ObjectVisibility', 'off' ... 261 | ); 262 | 263 | % if the bar should not be printed in every iteration setup the 264 | % timer to the desired update rate 265 | if obj.HasFiniteUpdateRate 266 | obj.setupTimer(); 267 | end 268 | 269 | % if 'Total' is known setup the bar correspondingly and compute 270 | % some constant values 271 | if obj.HasTotalIterations 272 | % initialize the progress bar and pre-compute some measures 273 | obj.setupBar(); 274 | obj.computeBlockFractions(); 275 | end 276 | 277 | obj.CurrentTitleState = obj.Title; 278 | if length(obj.Title) > obj.MAX_TITLE_LENGTH 279 | obj.CurrentTitleState = [obj.CurrentTitleState, ' -- ']; 280 | end 281 | 282 | % if this is a nested bar hit return 283 | if obj.IsThisBarNested 284 | fprintf(1, '\n'); 285 | end 286 | 287 | % if the bar is used in a parallel setup start the timer right now 288 | if obj.IsParallel 289 | obj.startTimer(); 290 | end 291 | obj.printProgressBar(); 292 | end 293 | end 294 | 295 | function [] = stepImpl(obj, stepSize, wasSuccessful, shouldPrintNextProgBar) 296 | %STEPIMPL class method to increment the object's progress state 297 | %---------------------------------------------------------------------- 298 | % This method is the central update function in the loop to indicate 299 | % the increment of the progress. Pass empty arrays for each input 300 | % argument if default is desired. 301 | % 302 | % Usage: obj.step(stepSize, wasSuccessful, shouldPrintNextProgBar) 303 | % 304 | % Input: --------- 305 | % stepSize - the size of the progress step when the method is 306 | % called. This can be used to pass the number of 307 | % processed bytes when using 'Bytes' as units. 308 | % [default: stepSize = 1] 309 | % wasSuccessful - Boolean to provide information about the 310 | % success of an individual iteration. If you pass 311 | % a 'false' a message will be printed stating the 312 | % current iteration was not successful. 313 | % [default: wasSuccessful = true] 314 | % shouldPrintNextProgBar - Boolean to define wether to 315 | % immidiately print another prog. bar 316 | % after print the success message. Can 317 | % be useful when every iteration takes 318 | % a long time and a white space appears 319 | % where the progress bar used to be. 320 | % [default: shouldPrintNextProgBar = false] 321 | % 322 | 323 | % input parsing and validating 324 | if isempty(shouldPrintNextProgBar) 325 | shouldPrintNextProgBar = false; 326 | end 327 | if isempty(wasSuccessful) 328 | wasSuccessful = true; 329 | end 330 | if isempty(stepSize) 331 | stepSize = 1; 332 | end 333 | 334 | validateattributes(stepSize, ... 335 | {'numeric'}, ... 336 | {'scalar', 'positive', 'real', 'nonnan', 'finite', 'nonempty'} ... 337 | ); 338 | validateattributes(wasSuccessful, ... 339 | {'logical', 'numeric'}, ... 340 | {'scalar', 'binary', 'nonnan', 'nonempty'} ... 341 | ); 342 | validateattributes(shouldPrintNextProgBar, ... 343 | {'logical', 'numeric'}, ... 344 | {'scalar', 'binary', 'nonnan', 'nonempty'} ... 345 | ); 346 | 347 | if obj.IsActive 348 | % increment the iteration counter 349 | obj.incrementIterationCounter(stepSize); 350 | 351 | % if the timer was stopped before, because no update was given, 352 | % start it now again. 353 | if ~obj.IsTimerRunning && obj.HasFiniteUpdateRate 354 | obj.startTimer(); 355 | end 356 | 357 | % if the iteration was not successful print a message saying so. 358 | if ~wasSuccessful 359 | infoMsg = sprintf('Iteration %i was not successful!', ... 360 | obj.IterationCounter); 361 | obj.printMessage(infoMsg, shouldPrintNextProgBar); 362 | end 363 | 364 | % when the bar should be updated in every iteration, do this with 365 | % each time calling update() 366 | if ~obj.HasFiniteUpdateRate 367 | obj.printProgressBar(); 368 | end 369 | 370 | % stop the timer after the last iteration if an update rate is 371 | % used. The first condition is needed to prevent the if-statement 372 | % to fail if obj.Total is empty. This happens when no total number 373 | % of iterations was passed / is known. 374 | if ~isempty(obj.Total) ... 375 | && obj.IterationCounter == obj.Total ... 376 | && obj.HasFiniteUpdateRate 377 | 378 | obj.stopTimer(); 379 | end 380 | end 381 | end 382 | 383 | function [] = releaseImpl(obj) 384 | % stop the timer 385 | if obj.IsTimerRunning 386 | obj.stopTimer(); 387 | end 388 | 389 | if obj.IsThisBarNested 390 | % when this prog bar was nested, remove it from the command 391 | % line and get back to the end of the parent bar. 392 | % +1 due to the line break 393 | fprintf(1, ProgressBar.backspace(obj.NumWrittenCharacters + 1)); 394 | elseif obj.IterationCounter && ~obj.IsThisBarNested 395 | % when a non-nested progress bar has been plotted, hit return 396 | fprintf(1, '\n'); 397 | end 398 | 399 | % delete the timer object 400 | delete(obj.TimerObject); 401 | 402 | % restore previously used font 403 | if obj.OverrideDefaultFont && ~isempty(obj.CurrentFont) 404 | s = settings; 405 | s.matlab.fonts.codefont.Name.TemporaryValue = obj.CurrentFont; 406 | end 407 | 408 | % if used in parallel processing delete all aux. files and clear 409 | % the persistent variables inside of updateParallel() 410 | if obj.IsParallel 411 | files = ProgressBar.findWorkerFiles(obj.WorkerDirectory); 412 | 413 | if ~isempty(files) 414 | delete(files{:}); 415 | end 416 | 417 | clear updateParallel; 418 | 419 | % rest some time to not flood the screen with the parent bar 420 | pause(0.1); 421 | end 422 | end 423 | 424 | 425 | function [] = computeBlockFractions(obj) 426 | % Compute the progress percentage of a single main and a single sub 427 | % block 428 | obj.FractionMainBlock = 1 / length(obj.Bar); 429 | obj.FractionSubBlock = obj.FractionMainBlock / obj.NUM_SUB_BLOCKS; 430 | end 431 | 432 | 433 | function [] = setupBar(obj) 434 | % Set up the growing bar part of the printed line by computing the 435 | % width of it 436 | 437 | [~, preBarFormat, postBarFormat] = obj.returnFormatString(); 438 | 439 | % insert worst case inputs to get (almost) maximum length of bar 440 | preBar = sprintf(... 441 | preBarFormat, ... 442 | blanks(min(length(obj.CurrentTitleState), obj.MAX_TITLE_LENGTH)), ... 443 | 100 ... 444 | ); 445 | postBar = sprintf(... 446 | postBarFormat, ... 447 | obj.Total, ... 448 | obj.Total, ... 449 | 10, 60, 60, 10, 60, 60, 1e2 ... 450 | ); 451 | 452 | lenBar = obj.MaxBarWidth - length(preBar) - length(postBar); 453 | 454 | obj.Bar = blanks(lenBar); 455 | end 456 | 457 | 458 | function [] = printProgressBar(obj) 459 | % This method removes the old and prints the current bar to the screen 460 | % and saves the number of written characters for the next iteration 461 | 462 | % remove old previous bar 463 | fprintf(1, ProgressBar.backspace(obj.NumWrittenCharacters)); 464 | 465 | formatString = obj.returnFormatString(); 466 | argumentList = obj.returnArgumentList(); 467 | 468 | % print new bar 469 | obj.NumWrittenCharacters = fprintf(1, ... 470 | formatString, ... 471 | argumentList{:} ... 472 | ); 473 | end 474 | 475 | 476 | function [format, preString, postString] = returnFormatString(obj) 477 | % This method returns the format string for the fprintf() function in 478 | % printProgressBar() 479 | 480 | % use the correct units 481 | if strcmp(obj.Unit, 'Bytes') 482 | unitString = 'K'; 483 | 484 | if obj.HasItPerSecBelow1 485 | fractionString = {'s', 'KB'}; 486 | else 487 | fractionString = {'KB', 's'}; 488 | end 489 | else 490 | unitString = 'it'; 491 | 492 | if obj.HasItPerSecBelow1 493 | fractionString = {'s', 'it'}; 494 | else 495 | fractionString = {'it', 's'}; 496 | end 497 | end 498 | 499 | % consider a growing bar if the total number of iterations is known 500 | % and consider a title if one is given. 501 | if obj.HasTotalIterations 502 | preString = '%s: %03.0f%% '; 503 | 504 | centerString = '|%s|'; 505 | 506 | postString = ... 507 | [ 508 | ' %i/%i', unitString, ... 509 | ' [%02.0f:%02.0f:%02.0f<%02.0f:%02.0f:%02.0f, %.2f ', ... 510 | fractionString{1}, '/', fractionString{2}, ']' 511 | ]; 512 | 513 | format = [preString, centerString, postString]; 514 | else 515 | preString = ''; 516 | postString = ''; 517 | 518 | format = [ 519 | '%s: %i', unitString, ' [%02.0f:%02.0f:%02.0f, %.2f ', ... 520 | fractionString{1}, '/', fractionString{2}, ']' 521 | ]; 522 | end 523 | end 524 | 525 | 526 | function [argList] = returnArgumentList(obj) 527 | % This method returns the argument list as a cell array for the 528 | % fprintf() function in printProgressBar() 529 | 530 | % elapsed time (ET) 531 | thisTimeSec = toc(obj.TicObject); 532 | etHoursMinsSecs = ProgressBar.convertTime(thisTimeSec); 533 | 534 | % mean iterations per second counted from the start 535 | iterationsPerSecond = obj.IterationCounter / thisTimeSec; 536 | 537 | if iterationsPerSecond < 1 538 | iterationsPerSecond = 1 / iterationsPerSecond; 539 | obj.HasItPerSecBelow1 = true; 540 | else 541 | obj.HasItPerSecBelow1 = false; 542 | end 543 | 544 | 545 | % consider the correct units 546 | scaledIteration = obj.IterationCounter; 547 | scaledTotal = obj.Total; 548 | if strcmp(obj.Unit, 'Bytes') 549 | % let's show KB 550 | scaledIteration = round(scaledIteration / 1000); 551 | scaledTotal = round(scaledTotal / 1000); 552 | iterationsPerSecond = iterationsPerSecond / 1000; 553 | end 554 | 555 | if obj.HasTotalIterations 556 | % 1 : Title 557 | % 2 : progress percent 558 | % 3 : progBar string 559 | % 4 : iterationCounter 560 | % 5 : Total 561 | % 6 : ET.hours 562 | % 7 : ET.minutes 563 | % 8 : ET.seconds 564 | % 9 : ETA.hours 565 | % 10: ETA.minutes 566 | % 11: ETA.seconds 567 | % 12: it/s 568 | 569 | % estimated time of arrival (ETA) 570 | [etaHoursMinsSecs] = obj.estimateETA(thisTimeSec); 571 | 572 | if obj.IterationCounter 573 | % usual case -> the iteration counter is > 0 574 | barString = obj.getCurrentBar; 575 | else 576 | % if startMethod() calls this method return the empty bar 577 | barString = obj.Bar; 578 | end 579 | 580 | argList = { 581 | obj.CurrentTitleState(... 582 | 1:min(length(obj.Title), obj.MAX_TITLE_LENGTH) ... 583 | ), ... 584 | floor(obj.IterationCounter / obj.Total * 100), ... 585 | barString, ... 586 | scaledIteration, ... 587 | scaledTotal, ... 588 | etHoursMinsSecs(1), ... 589 | etHoursMinsSecs(2), ... 590 | etHoursMinsSecs(3), ... 591 | etaHoursMinsSecs(1), ... 592 | etaHoursMinsSecs(2), ... 593 | etaHoursMinsSecs(3), ... 594 | iterationsPerSecond ... 595 | }; 596 | else 597 | % 1: Title 598 | % 2: iterationCounter 599 | % 3: ET.hours 600 | % 4: ET.minutes 601 | % 5: ET.seconds 602 | % 6: it/s 603 | 604 | argList = { 605 | obj.CurrentTitleState(... 606 | 1:min(length(obj.Title), obj.MAX_TITLE_LENGTH) ... 607 | ), ..., ... 608 | scaledIteration, ... 609 | etHoursMinsSecs(1), ... 610 | etHoursMinsSecs(2), ... 611 | etHoursMinsSecs(3), ... 612 | iterationsPerSecond ... 613 | }; 614 | end 615 | 616 | % cycle the bar's title 617 | obj.updateCurrentTitle(); 618 | end 619 | 620 | 621 | function [barString] = getCurrentBar(obj) 622 | % This method constructs the growing bar part of the printed line by 623 | % indexing the correct part of the blank bar and getting either a 624 | % Unicode or ASCII symbol. 625 | 626 | % set up the bar and the current progress as a ratio 627 | lenBar = length(obj.Bar); 628 | currProgress = obj.IterationCounter / obj.Total; 629 | 630 | % index of the current main block 631 | thisMainBlock = min(ceil(currProgress / obj.FractionMainBlock), lenBar); 632 | 633 | % index of the current sub block 634 | continuousBlockIndex = ceil(currProgress / obj.FractionSubBlock); 635 | thisSubBlock = mod(continuousBlockIndex - 1, obj.NUM_SUB_BLOCKS) + 1; 636 | 637 | % fix for non-full last blocks when steps are large: make them full 638 | obj.Bar(1:max(thisMainBlock-1, 0)) = ... 639 | repmat(obj.BlockCharacters(end), 1, thisMainBlock - 1); 640 | 641 | % return a full bar in the last iteration or update the current 642 | % main block 643 | if obj.IterationCounter == obj.Total 644 | obj.Bar = repmat(obj.BlockCharacters(end), 1, lenBar); 645 | else 646 | obj.Bar(thisMainBlock) = obj.BlockCharacters(thisSubBlock); 647 | end 648 | 649 | barString = obj.Bar; 650 | end 651 | 652 | 653 | function [etaHoursMinsSecs] = estimateETA(obj, elapsedTime) 654 | % This method estimates linearly the remaining time 655 | 656 | % the current progress as ratio 657 | progress = obj.IterationCounter / obj.Total; 658 | 659 | % the remaining seconds 660 | remainingSeconds = elapsedTime * ((1 / progress) - 1); 661 | 662 | % convert seconds to hours:mins:seconds 663 | etaHoursMinsSecs = ProgressBar.convertTime(remainingSeconds); 664 | end 665 | 666 | 667 | function [] = setupTimer(obj) 668 | % This method initializes the timer object if an upate rate is used 669 | 670 | obj.TimerObject.BusyMode = 'drop'; 671 | obj.TimerObject.ExecutionMode = 'fixedSpacing'; 672 | 673 | if ~obj.IsParallel 674 | obj.TimerObject.TimerFcn = @(~, ~) obj.timerCallback(); 675 | obj.TimerObject.StopFcn = @(~, ~) obj.timerCallback(); 676 | else 677 | obj.TimerObject.TimerFcn = @(~, ~) obj.timerCallbackParallel(); 678 | obj.TimerObject.StopFcn = @(~, ~) obj.timerCallbackParallel(); 679 | end 680 | updatePeriod = round(1 / obj.UpdateRate * 1000) / 1000; 681 | obj.TimerObject.Period = updatePeriod; 682 | end 683 | 684 | 685 | function [] = timerCallback(obj) 686 | % This method is the timer callback. If an update came in between the 687 | % last printing and now print a new prog bar, else stop the timer and 688 | % wait. 689 | if obj.HasBeenUpdated 690 | obj.printProgressBar(); 691 | else 692 | obj.stopTimer(); 693 | end 694 | 695 | obj.HasBeenUpdated = false; 696 | end 697 | 698 | 699 | function [] = timerCallbackParallel(obj) 700 | % find the aux. worker files 701 | [files, numFiles] = ProgressBar.findWorkerFiles(obj.WorkerDirectory); 702 | 703 | % if none have been written yet just print a progressbar and return 704 | if ~numFiles 705 | obj.printProgressBar(); 706 | 707 | return; 708 | end 709 | 710 | % read the status in every file 711 | results = zeros(numFiles, 1); 712 | for iFile = 1:numFiles 713 | fid = fopen(files{iFile}, 'rb'); 714 | 715 | if fid > 0 716 | results(iFile) = fread(fid, 1, 'uint64'); 717 | fclose(fid); 718 | end 719 | end 720 | 721 | % the sum of all files should be the current iteration 722 | obj.IterationCounter = sum(results); 723 | 724 | % print the progress bar 725 | obj.printProgressBar(); 726 | 727 | % if total is known and we are at the end stop the timer 728 | if ~isempty(obj.Total) && obj.IterationCounter == obj.Total 729 | obj.stopTimer(); 730 | end 731 | end 732 | 733 | 734 | function [] = startTimer(obj) 735 | % This method starts the timer object and updates the status bool 736 | 737 | start(obj.TimerObject); 738 | obj.IsTimerRunning = true; 739 | end 740 | 741 | 742 | function [] = stopTimer(obj) 743 | % This method stops the timer object and updates the status bool 744 | 745 | stop(obj.TimerObject); 746 | obj.IsTimerRunning = false; 747 | end 748 | 749 | 750 | function [] = incrementIterationCounter(obj, stepSize) 751 | % This method increments the iteration counter and updates the status 752 | % bool 753 | 754 | obj.IterationCounter = obj.IterationCounter + stepSize; 755 | 756 | obj.HasBeenUpdated = true; 757 | end 758 | 759 | 760 | function [list] = getTimerList(obj) 761 | % This function returns the list of all hidden timers which are tagged 762 | % with our default tag 763 | 764 | list = timerfindall('Tag', obj.TIMER_TAG_NAME); 765 | end 766 | 767 | 768 | function [] = updateCurrentTitle(obj) 769 | strTitle = obj.CurrentTitleState; 770 | 771 | if length(strTitle) > obj.MAX_TITLE_LENGTH 772 | strTitle = circshift(strTitle, -obj.NUM_CHARACTERS_SHIFT); 773 | 774 | obj.CurrentTitleState = strTitle; 775 | end 776 | end 777 | end 778 | 779 | methods (Static) 780 | function deleteAllTimers() 781 | delete(timerfindall('Tag', ProgressBar.TIMER_TAG_NAME)); 782 | end 783 | 784 | 785 | function [blocks] = getUnicodeSubBlocks() 786 | % This function returns the HTML 'left blocks' to construct the growing bar. The HTML 787 | % 'left blocks' range from 1 to 8 excluding the 'space'. 788 | 789 | blocks = [ 790 | char(9615), ... 791 | char(9614), ... 792 | char(9613), ... 793 | char(9612), ... 794 | char(9611), ... 795 | char(9610), ... 796 | char(9609), ... 797 | char(9608) ... 798 | ]; 799 | end 800 | 801 | function [blocks] = getAsciiSubBlocks() 802 | % This function returns the ASCII number signs (hashes) to construct the growing bar. 803 | % The HTML 'left blocks' range from 1 to 8 excluding the 'space'. 804 | 805 | blocks = repmat('#', 1, 8); 806 | end 807 | 808 | 809 | function [str] = backspace(numChars) 810 | % This function returns the desired numbers of backspaces to delete characters from the 811 | % current line 812 | 813 | str = repmat(sprintf('\b'), 1, numChars); 814 | end 815 | 816 | 817 | function [hoursMinsSecs] = convertTime(secondsIn) 818 | % This fast implementation to convert seconds to hours:mins:seconds using mod() stems 819 | % from http://stackoverflow.com/a/21233409 820 | 821 | hoursMinsSecs = floor(mod(secondsIn, [0, 3600, 60]) ./ [3600, 60, 1]); 822 | end 823 | 824 | 825 | function [isOk] = checkInputOfTotal(total) 826 | % This function is the input checker of the main constructor argument 'total'. It is ok 827 | % if it's empty but if not it must obey validateattributes. 828 | 829 | isTotalEmpty = isempty(total); 830 | 831 | if isTotalEmpty 832 | isOk = true; 833 | return; 834 | else 835 | validateattributes(total, ... 836 | {'numeric'}, ... 837 | {'scalar', 'integer', 'positive', 'real', 'nonnan', 'finite'} ... 838 | ); 839 | isOk = true; 840 | end 841 | end 842 | 843 | 844 | function [files, numFiles] = findWorkerFiles(workerDir) 845 | % This function returns file names and the number of files that were written by the 846 | % updateParallel() function if the prog. bar is used in a parallel setup. 847 | % 848 | % Input: workerDir - directory where the aux. files of the worker are saved 849 | % 850 | 851 | [pattern] = updateParallel(); 852 | 853 | files = dir(fullfile(workerDir, pattern)); 854 | files = {files.name}; 855 | 856 | files = cellfun(... 857 | @(filename) fullfile(workerDir, filename), ... 858 | files, ... 859 | 'uni', false ... 860 | ); 861 | numFiles = length(files); 862 | end 863 | end 864 | 865 | end 866 | 867 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![View MatlabProgressBar on File 2 | Exchange](https://www.mathworks.com/matlabcentral/images/matlab-file-exchange.svg)](https://de.mathworks.com/matlabcentral/fileexchange/57895-matlabprogressbar) 3 | 4 | # MatlabProgressBar 5 | 6 | - [MatlabProgressBar](#matlabprogressbar) 7 | - [Dependencies](#dependencies) 8 | - [Installation](#installation) 9 | - [Usage](#usage) 10 | - [Proposed Usage for Simple Loops](#proposed-usage-for-simple-loops) 11 | - [Extended Usage with all Features](#extended-usage-with-all-features) 12 | - [Parallel Toolbox Support](#parallel-toolbox-support) 13 | - [Known Issues](#known-issues) 14 | - [Flickering Bar or Flooding of the Command Window](#flickering-bar-or-flooding-of-the-command-window) 15 | - [The Bar Gets Longer With Each Iteration](#the-bar-gets-longer-with-each-iteration) 16 | - [Strange Symbols in the Progress Bar](#strange-symbols-in-the-progress-bar) 17 | - [Remaining Timer Objects in MATLAB's Background](#remaining-timer-objects-in-matlabs-background) 18 | - [Issues Concerning Parallel Processing](#issues-concerning-parallel-processing) 19 | - [Unit Tests](#unit-tests) 20 | - [License](#license) 21 | 22 | This project hosts the source code to the [original MATLAB FileExchange 23 | project](https://de.mathworks.com/matlabcentral/fileexchange/57895-matlabprogressbar) 24 | and is place of active development. 25 | 26 | A drawback in MATLAB's own `waitbar()` function is the lack of some functionalities and 27 | the loss of speed due to the rather laggy GUI updating process. Therefore, this MATLAB 28 | class aims to provide a smart progress bar in the command window and is optimized for 29 | progress information in simple iterations or large frameworks with full support of 30 | parallel *parfor* loops and asynchronous processing via *parfeval()* provided by the 31 | MATLAB Parallel Computing Toolbox. 32 | 33 | A design target was to mimic the best features of the progress bar 34 | [tqdm](https://github.com/tqdm/tqdm) for Python. Thus, this project features a 35 | Unicode-based bar and some numeric information about the current progress and the 36 | average iterations per second. 37 | 38 | Several projects exist on MATLAB's [File 39 | Exchange](https://www.mathworks.com/matlabcentral/fileexchange/?term=progress+bar) but 40 | none incorporates the feature set shown below. That's why I decided to start this 41 | project. 42 | 43 | ![Easy progress bar example](images/example2.gif) 44 | 45 | **Supported features include (or are planned)**: 46 | - [x] proper unit testing 47 | - [x] display the bar name as a ticker. That way, a fixed bar width could be used 48 | - [x] inherit from MATLAB System Object to gain benefits from the setup method 49 | - [ ] use [this new functionality](https://de.mathworks.com/help/distcomp/send.html) 50 | for the parallel implementation. Introduced in R2017a. 51 | - [x] TQDM Unicode blocks 52 | - [x] optional constructor switch for ASCII number signs (hashes) 53 | - those will be used if `ProgressBar()` is used in deploy mode (MATLAB Compiler) 54 | - [x] optional bar title 55 | - [x] optional visual update interval in Hz [defaults to 10 Hz] 56 | - [x] when no total number of iterations is passed the bar shows the elapsed time, the 57 | number of (elapsed) iterations and iterations/s 58 | - [x] nested bars (at the moment only one nested bar is supported [one parent, one 59 | child]) 60 | - [x] `printMessage()` method for debug printing (or the like) 61 | - [x] print an info when a run was not successful 62 | - [x] support another meaningful 'total of something' measure where the number of items 63 | is less meaningful (for example non-uniform processing time) such as total file size 64 | (processing multiple files with different file size). At the moment, the only 65 | alternative supported unit is `Bytes` 66 | - [x] when the internal updating process is faster than the actual updates via 67 | `update()`, the internal counter and printing of the process bar stops until the next 68 | update to save processing time 69 | - [x] linear ETA estimate over all last iterations 70 | - [x] support parfor loops provided by the Parallel Computing Toolbox 71 | - [x] show s/it if it/sec < 1 72 | - [x] override MATLAB's default non-UTF font if `OverrideDefaultFont` is `true`. This 73 | will switch the font for the command line to `Courier New` for the lifetime of the 74 | bar. Default is `false` 75 | - [x] disable the progress bar if `IsActive` is `false`. This will disable the 76 | functionality completely and can be used in situations in which the bar is not 77 | beneficial (e.g. if the bar is used in a sub-application of a processing cluster). 78 | Default is `true`. 79 | 80 | 81 | **Note**: 82 | Be sure to have a look at the [Known Issues](#known-issues) section for current known 83 | bugs and possible work-arounds. 84 | 85 | ## Dependencies 86 | 87 | No dependencies to toolboxes. 88 | 89 | The code has been tested with MATLAB R2016a, R2016b and R2020b on Windows 10, Xubuntu 90 | 16.04.2 LTS and Linux Mint 20. 91 | 92 | 93 | ## Installation 94 | 95 | Put the files `ProgressBar.m`, `progress.m` and `updateParallel.m` into your MATLAB path 96 | or the directory of your MATLAB project. 97 | 98 | 99 | ## Usage 100 | 101 | Detailed information and examples about all features of `ProgressBar` are stated in the 102 | demo scripts in the `./demos/` directory. 103 | 104 | ### Proposed Usage for Simple Loops 105 | The simplest use in `for`-loops is to use the `progress()` function. It wraps the main 106 | `ProgressBar` class and is intended to only support the usual progress bar. Be aware 107 | that functionalities like `printMessage()`, printing success information or a step size 108 | different to 1 are not supported with `progress.m`. Also, this only works for 109 | **non-parallel** loops. 110 | 111 | See the example below: 112 | ```matlab 113 | numIterations = 10e3; 114 | 115 | % create the loop using the progress() class 116 | for iIteration = progress(1:numIterations) 117 | % do some processing 118 | end 119 | ``` 120 | 121 | ![Example 2](images/example2.gif) 122 | 123 | ### Extended Usage with all Features 124 | The basic work flow is to instantiate a `ProgressBar` object and use either the `step()` 125 | method to update the progress state (MATLAB <= R2015b) or use the instantiated object 126 | directly as seen below. Refer to the method's help for information about input 127 | parameters. The shown call is the *default* call and sufficient. If you want to pass 128 | information about the step size, the iteration's success or if a new bar should be 129 | printed immediately (e.g. when iterations take long time) you can pass these information 130 | instead of empty matrices. 131 | 132 | All settings are done using *name-value* pairs in the constructor. It is **strongly 133 | encouraged** to call the object's `release()` method after the loop is finished to clean 134 | up the internal state and avoid possibly unrobust behavior of following progress bars. 135 | 136 | *Usage* 137 | `obj = ProgressBar(totalIterations, varargin)` 138 | 139 | A simple but quite common example looks like this: 140 | ```matlab 141 | numIterations = 10e3; 142 | 143 | % instantiate an object with an optional title and an update 144 | % rate of 5 Hz, i.e. 5 bar updates per seconds, to save 145 | % printing load. 146 | progBar = ProgressBar(... 147 | numIterations, ... 148 | 'Title', 'Awesome Computation' ... 149 | ); 150 | 151 | % begin the actual loop and update the object's progress 152 | % state 153 | for iIteration = 1:numIterations 154 | %%% do some processing here 155 | % ... 156 | 157 | progBar([], [], []); 158 | % or in releases <= R2015b 159 | % progBar.step([], [], []); 160 | end 161 | % call the 'release()' method to clean up 162 | progBar.release(); 163 | ``` 164 | 165 | ![Extended usage with the object method calls](images/example1.gif) 166 | 167 | ### Parallel Toolbox Support 168 | 169 | If you use MATLAB's Parallel Computing Toolbox, refer to the following example or the 170 | demo file `k_parallelSetup.m`. Tested parallel functionalities are `parfor` and 171 | `parfeval()` for asynchronous processing. 172 | 173 | ```matlab 174 | numIterations = 10e3; 175 | 176 | % Instantiate the object with the 'IsParallel' switch set to true 177 | progBar = ProgressBar(numIterations, ... 178 | 'IsParallel', true, ... 179 | 'Title', 'Parallel Processing' ... 180 | ); 181 | 182 | % ALWAYS CALL THE SETUP() METHOD FIRST!!! 183 | progBar.setup([], [], []); 184 | parfor iIteration = 1:numIterations 185 | pause(0.1); 186 | 187 | % USE THIS FUNCTION AND NOT THE STEP() METHOD OF THE OBJECT!!! 188 | updateParallel([], pwd); 189 | end 190 | progBar.release(); 191 | ``` 192 | 193 | As of `v3.4.0`, you can also nest the loops so that the inner one uses a `parfor` loop. 194 | 195 | 196 | ## Known Issues 197 | 198 | ### Flickering Bar or Flooding of the Command Window 199 | 200 | MATLAB's speed to print to the command window is actually pretty low. If the update rate 201 | of the progress bar is high the mentioned effects can occur. Try to reduce the update 202 | rate from the default 5 Hz to something lower (say 3 Hz) with the `'UpdateRate', 3` 203 | name-value pair. 204 | 205 | ### The Bar Gets Longer With Each Iteration 206 | 207 | There seems to be a problem with the default font `Monospaced` in Windows. If this 208 | behavior is problematic, change the font for the command window to a different 209 | monospaced font, preferably with proper Unicode support. 210 | 211 | If you do not want to or cannot change the font in the setting, you can set the class's 212 | `OverrideDefaultFont` to `true` while you are in the configuration phase. This will 213 | change MATLAB's coding font to `Courier New` for the duration in which the bar is alive 214 | (until the `release()` method is executed). After the object's lifetime, your previous 215 | font will be restored automatically. 216 | 217 | For convenience, this property can also be set in the `progress()` wrapper to always 218 | trigger for the wrapper if desired. 219 | 220 | Thanks [@GenosseFlosse](https://github.com/GenosseFlosse) for the fix! 221 | 222 | ### Strange Symbols in the Progress Bar 223 | 224 | The display of the updating progress bar is highly dependent on the **font** you use in 225 | the command window. Be sure to use a proper font that can handle Unicode characters. 226 | Otherwise be sure to always use the `'Unicode', false` switch in the constructor. 227 | 228 | ### Remaining Timer Objects in MATLAB's Background 229 | 230 | Sometimes, if the user cancels a loop in which a progress bar was used, the destructor 231 | is not called properly and the timer object remains in memory. This can lead to strange 232 | behavior of the next progress bar instantiated because it thinks it is nested. If you 233 | encounter strange behavior like wrong line breaks or disappearing progress bars after 234 | the bar has finished, just call the following static method to delete all remaining 235 | timer objects in memory which belong(ed) to progress bars and start over. 236 | 237 | ```matlab 238 | ProgressBar.deleteAllTimers(); 239 | ``` 240 | 241 | ### Issues Concerning Parallel Processing 242 | 243 | The work-flow when using the progress bar in a parallel setup is to instantiate the 244 | object with the `IsParallel` switch set to `true` and using the `updateParallel()` 245 | function to update the progress state instead of the `step()` method of the object. If 246 | this results in strange behavior check the following list. Generally, it is advisable to 247 | **first be sure that the executed code or functions in the parallel setup run without 248 | errors or warnings.** If not the execution may prevent the class destructor to properly 249 | clean up all files and timer objects. 250 | 251 | - are there remaining timer objects that haven't been deleted from canceled 252 | `for`/`parfor` loops or `parfeval()` calls when checking with `timerfindall('Tag', 253 | 'ProgressBar')`? 254 | - use `delete(timerfindall('Tag', 'ProgressBar'))` 255 | - does the progress exceed 100%? 256 | - try to call `clear all` or, specifically, `clear updateParallel` to clear the 257 | internal state (persistent variables) in the mentioned function. This should have 258 | been done by the class destructor but sometimes gets unrobust if there have been 259 | errors in parallel executed functions. 260 | - Also try to look into your temp directory (returned by `tempdir()`) if remaining 261 | `progbarworker_*` files exist. Delete those if necessary. 262 | 263 | 264 | **TL/DR**: 265 | `clear all` and `delete(timerfindall('Tag', 'ProgressBar'))` are your friend! Be sure 266 | that no files following the pattern `progbarworker_*` remain in the directory returned 267 | by `tempdir()`. 268 | 269 | ## Unit Tests 270 | You can run all available tests in the project directory by navigating into the `tests` 271 | folder and executing `runtests` in MATLAB. However, if you want to omit the parallel 272 | tests (e.g. you don't have the Parallel Toolbox installed), just execute 273 | 274 | ```matlab 275 | runtests Tag NonParallel 276 | ``` 277 | 278 | ## License 279 | 280 | The code is licensed under BSD 3-Clause as stated in the `LICENSE` file 281 | -------------------------------------------------------------------------------- /demos/basicUseCases.m: -------------------------------------------------------------------------------- 1 | % This script shows all currently implemented features one by one. 2 | % 3 | % Author: J.-A. Adrian (JA) 4 | % 5 | 6 | clear; 7 | close all; 8 | 9 | addpath('..'); 10 | 11 | numIterations = 25; 12 | 13 | 14 | %% Easy Setup WITH Known Number of Iterations 15 | % This should be your every-day solution if you don't care for additional feature and just want to 16 | % have a progress bar for your for-loop 17 | for iIteration = progress(1:numIterations) 18 | pause(0.3); 19 | end 20 | 21 | % You can do the same thing but alter any bar's property (learn about those later on). For example, 22 | % customize the shown bar title in the front. 23 | for iIteration = progress(1:numIterations, 'Title', 'New Title') 24 | pause(0.3); 25 | end 26 | 27 | 28 | %% Setup WITHOUT Known Number of Iterations 29 | % If you just want to keep track of iterations, use the main class and don't pass a total number of 30 | % iterations. Here, we instanciate a progress bar object and use it for iteration updates. 31 | b = ProgressBar(); 32 | 33 | counter = 0; 34 | while counter < numIterations 35 | counter = counter + 1; 36 | pause(0.3); 37 | 38 | b(1, [], []); 39 | end 40 | b.release(); 41 | 42 | 43 | % You can do the same thing but alter any bar's property (learn about those later on). For example, 44 | % customize the shown bar title in the front. 45 | b = ProgressBar([], 'Title', 'Test'); 46 | 47 | counter = 0; 48 | while counter < numIterations 49 | counter = counter + 1; 50 | pause(0.3); 51 | 52 | b(1, [], []); 53 | end 54 | b.release(); 55 | 56 | 57 | %% Custom Print Update Rate 58 | % The default update rate is 5 Hz. We can change it for smoother looks. 59 | numIterations = 5e5; 60 | 61 | % change the update rate to, e.g., 10 Hz 62 | b = ProgressBar(numIterations, ... 63 | 'UpdateRate', 10 ... 64 | ); 65 | 66 | for iIteration = 1:numIterations 67 | b(1, [], []); 68 | end 69 | b.release(); 70 | 71 | 72 | % Set the upate rate to infinity to print at every call of the progress bar 73 | updateRateHz = inf; 74 | numIterations = 100; 75 | 76 | % pass the number of iterations and the update cycle in Hz 77 | b = ProgressBar(numIterations, ... 78 | 'UpdateRate', updateRateHz ... 79 | ); 80 | 81 | for iIteration = 1:numIterations 82 | b(1, [], []); 83 | 84 | pause(0.1); 85 | end 86 | b.release(); 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /demos/countProcessedBytes.m: -------------------------------------------------------------------------------- 1 | % Demo of another counting unit. At this point, only 'Bytes' is supported as alternative. 2 | % 3 | % Author: J.-A. Adrian (JA) 4 | % 5 | 6 | clear; 7 | close all; 8 | 9 | addpath('..'); 10 | 11 | % set up some dummy file sizes and 'processing times' for each file 12 | dummyFile = {rand(1e3, 1), rand(5e2, 1), rand(1e5, 1), rand(1e5, 1)}; 13 | filePause = [1, 0.5, 3, 3]; 14 | 15 | numTotalBytes = sum(cellfun(@(x) size(x, 1), dummyFile)); 16 | 17 | 18 | %% Work with Size of Processed Bytes WITHOUT Knowledge of Total Bytes 19 | b = ProgressBar([], ... 20 | 'Unit', 'Bytes', ... 21 | 'Title', 'Test Bytes 1' ... 22 | ); 23 | 24 | for iFile = 1:length(dummyFile) 25 | buffer = dummyFile{iFile}; 26 | 27 | pause(filePause(iFile)); 28 | b(length(buffer), [], []); 29 | end 30 | b.release(); 31 | 32 | 33 | 34 | %% Work With Size of Processed Bytes WITH Knowledge of Total Bytes 35 | b = ProgressBar(numTotalBytes, ... 36 | 'Unit', 'Bytes', ... 37 | 'Title', 'Test Bytes 2' ... 38 | ); 39 | 40 | for iFile = 1:length(dummyFile) 41 | buffer = dummyFile{iFile}; 42 | 43 | pause(filePause(iFile)); 44 | b.step(length(buffer), [], []); 45 | end 46 | b.release(); 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /demos/disableBar.m: -------------------------------------------------------------------------------- 1 | % This script will show how the bar can be programmatically disabled. 2 | % 3 | % Author: J.-A. Adrian (JA) 4 | % 5 | 6 | clear; 7 | close all; 8 | 9 | addpath('..'); 10 | 11 | numIterations = 25; 12 | 13 | 14 | %% This Will Show the Bar 15 | for iIteration = progress(1:numIterations) 16 | pause(0.1); 17 | end 18 | 19 | 20 | %% This Will Disable the Bar 21 | for iIteration = progress(1:numIterations, 'IsActive', false) 22 | fprintf('New iteration...\n'); 23 | pause(0.1); 24 | end 25 | -------------------------------------------------------------------------------- /demos/nestedProgressBars.m: -------------------------------------------------------------------------------- 1 | % Demo of nested bars. At this point only one nested bar is supported 2 | % 3 | % Author: J.-A. Adrian (JA) 4 | % 5 | 6 | 7 | addpath('..'); 8 | 9 | numOuterIterations = 3; 10 | numInnerIterations = 15; 11 | 12 | 13 | %% Nested Bars without Inner Update Rate 14 | % be sure to set the update rate to inf to disable a timed printing of the bar! 15 | b1 = ProgressBar(numOuterIterations, ... 16 | 'UpdateRate', inf, ... 17 | 'Title', 'Loop 1' ... 18 | ); 19 | 20 | % helper method to print a first progress bar before the inner loop starts. 21 | % This prevents a blank line until the first b1.step() is called. 22 | b1.setup([], [], []); 23 | for iOuterIteration = 1:numOuterIterations 24 | b2 = ProgressBar(numInnerIterations, ... 25 | 'UpdateRate', inf, ... 26 | 'Title', 'Loop 2' ... 27 | ); 28 | b2.setup([], [], []); 29 | 30 | for jInnerIteration = 1:numInnerIterations 31 | b2(1, [], []); 32 | 33 | pause(0.1); 34 | end 35 | b2.release(); 36 | 37 | b1(1, [], []); 38 | end 39 | b1.release(); 40 | 41 | 42 | 43 | %% Nested Bars WITH Inner Update Rate 44 | numInnerIterations = 50e3; 45 | 46 | % be sure to set the update rate to inf to disable a timed printing of the bar! 47 | b1 = ProgressBar(numOuterIterations, ... 48 | 'UpdateRate', inf, ... 49 | 'Title', 'Loop 1' ... 50 | ); 51 | 52 | b1.setup([], [], []); 53 | for iOuterIteration = 1:numOuterIterations 54 | % this progress can have an update rate! 55 | b2 = ProgressBar(numInnerIterations, ... 56 | 'UpdateRate', 5, ... 57 | 'Title', 'Loop 2' ... 58 | ); 59 | b2.setup([], [], []); 60 | 61 | for jInnerIteration = 1:numInnerIterations 62 | b2.step(1, [], []); 63 | end 64 | b2.release(); 65 | 66 | b1.step(1, [], []); 67 | end 68 | b1.release(); 69 | 70 | 71 | -------------------------------------------------------------------------------- /demos/nonUnicodeProgressBar.m: -------------------------------------------------------------------------------- 1 | % Demo of using ASCII hashes instead of the fancy Unicode blocks. 2 | % 3 | % Author: J.-A. Adrian (JA) 4 | % 5 | 6 | clear; 7 | close all; 8 | 9 | addpath('..'); 10 | 11 | numIterations = 100; 12 | 13 | 14 | %% Demo of Not Using Unicode Characters 15 | b = ProgressBar(numIterations, ... 16 | 'UseUnicode', false, ... 17 | 'Title', 'ASCII' ... 18 | ); 19 | 20 | for iIteration = 1:numIterations 21 | pause(0.1); 22 | 23 | b([], [], []); 24 | end 25 | b.release(); 26 | 27 | -------------------------------------------------------------------------------- /demos/parallelSetup.m: -------------------------------------------------------------------------------- 1 | % Demo of the parallel functionality using a parfor loop. This script may throw errors if you don't 2 | % own the Parallel Processing Toolbox. 3 | % 4 | % Author: J.-A. Adrian (JA) 5 | % 6 | 7 | clear; 8 | close all; 9 | 10 | addpath('..'); 11 | 12 | numIterations = 500; 13 | 14 | if isempty(gcp('nocreate')) 15 | parpool(); 16 | end 17 | 18 | 19 | %% Without Knowledge of Total Number of Iterations 20 | % Instantiate the object with the 'IsParallel' switch set to true and save 21 | % the aux. files in the default directory (tempdir) 22 | b = ProgressBar([], ... 23 | 'IsParallel', true, ... 24 | 'Title', 'Parallel 1' ... 25 | ); 26 | 27 | % ALWAYS CALL THE SETUP() METHOD FIRST!!! 28 | b.setup([], [], []); 29 | parfor iIteration = 1:numIterations 30 | pause(0.1); 31 | 32 | % USE THIS FUNCTION AND NOT THE STEP() METHOD OF THE OBJECT!!! 33 | updateParallel(); 34 | end 35 | b.release(); 36 | 37 | 38 | %% With Knowledge of Total Number of Iterations 39 | % Instantiate the object with the 'Parallel' switch set to true and save 40 | % the aux. files in the current working directory (pwd) 41 | b = ProgressBar(numIterations, ... 42 | 'IsParallel', true, ... 43 | 'WorkerDirectory', pwd(), ... 44 | 'Title', 'Parallel 2' ... 45 | ); 46 | 47 | % ALWAYS CALL THE SETUP() METHOD FIRST!!! 48 | b.setup([], [], []); 49 | parfor iIteration = 1:numIterations 50 | pause(0.1); 51 | 52 | % USE THIS FUNCTION AND NOT THE STEP() METHOD OF THE OBJECT!!! 53 | updateParallel([], pwd); 54 | end 55 | b.release(); 56 | 57 | 58 | -------------------------------------------------------------------------------- /demos/passSuccessInfo.m: -------------------------------------------------------------------------------- 1 | % Demo of the success bool of the update() method. This can be used to print failure messages during 2 | % the loop. 3 | % 4 | % Author: J.-A. Adrian (JA) 5 | % 6 | 7 | clear; 8 | close all; 9 | 10 | addpath('..'); 11 | 12 | numIterations = 1e2; 13 | 14 | 15 | %% Pass Success Information of the Current Iteration 16 | b = ProgressBar(numIterations, ... 17 | 'Title', 'Test Success' ... 18 | ); 19 | 20 | % throw the dice to generate some booleans. This parameters produce a 21 | % success rate of 95% 22 | wasSuccessful = logical(binornd(1, 0.95, numIterations, 1)); 23 | for iIteration = 1:numIterations 24 | pause(0.1); 25 | 26 | b([], wasSuccessful(iIteration), []); 27 | end 28 | b.release(); 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /demos/printInfoDuringRun.m: -------------------------------------------------------------------------------- 1 | % Demo of the printMessage() method. In 3 iterations an info message is printed. 2 | % 3 | % Author: J.-A. Adrian (JA) 4 | % 5 | 6 | clear; 7 | close all; 8 | 9 | addpath('..'); 10 | 11 | numIterations = 1e2; 12 | 13 | 14 | %% Simple Setup and Print a Message in Desired Iterations 15 | b = ProgressBar(numIterations, ... 16 | 'Title', 'Progress' ... 17 | ); 18 | 19 | for iIteration = 1:numIterations 20 | b(1, [], []); 21 | 22 | if iIteration == 30 || iIteration == 50 || iIteration == 70 23 | b.printMessage(sprintf('Hello! @Iteration %i', iIteration)); 24 | end 25 | 26 | pause(0.1); 27 | end 28 | b.release(); 29 | 30 | -------------------------------------------------------------------------------- /demos/titleBanner.m: -------------------------------------------------------------------------------- 1 | % If the title is chosen very long it will work as a banner, rotating each step a bit further. 2 | % 3 | % Author: J.-A. Adrian (JA) 4 | % 5 | 6 | clear; 7 | close all; 8 | 9 | addpath('..'); 10 | 11 | numIterations = 50; 12 | 13 | %% Short Title 14 | b = ProgressBar(numIterations, ... 15 | 'Title', 'Short' ... 16 | ); 17 | for iIteration = 1:numIterations 18 | pause(0.1); 19 | 20 | b(1, [], []); 21 | end 22 | b.release(); 23 | 24 | %% Long Title 25 | numIterations = 100; 26 | 27 | b = ProgressBar(numIterations, ... 28 | 'Title', 'A Long Long Long Title' ... 29 | ); 30 | for iIteration = 1:numIterations 31 | pause(0.1); 32 | 33 | b(1, [], []); 34 | end 35 | b.release(); 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /images/example1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JAAdrian/MatlabProgressBar/0e4acf5f3656d4c772552fdd01c7a93043457586/images/example1.gif -------------------------------------------------------------------------------- /images/example2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JAAdrian/MatlabProgressBar/0e4acf5f3656d4c772552fdd01c7a93043457586/images/example2.gif -------------------------------------------------------------------------------- /progress.m: -------------------------------------------------------------------------------- 1 | classdef progress < handle 2 | %PROGRESS Wrapper class to provide an iterator object for loop creation 3 | % ------------------------------------------------------------------------- 4 | % This class provides the possibility to create an iterator object in 5 | % MATLAB to make the handling of ProgressBar() even easier. The following 6 | % example shows the usage. Although no ProgressBar() is called by the user, 7 | % a progress bar is shown. The input arguments are the same as for 8 | % ProgressBar(), so please refer to the documentation of ProgressBar(). 9 | % 10 | % Note that this implementation is slower than the conventional 11 | % ProgressBar() class since the subsref() method is called with 12 | % non-optimized values in every iteration. 13 | % 14 | % ========================================================================= 15 | % Example: 16 | % 17 | % for k = progress(1:100) 18 | % % do some processing 19 | % end 20 | % 21 | % Or with additional name-value pairs: 22 | % 23 | % for k = progress(1:100, 'Title', 'Computing') 24 | % % do some processing 25 | % end 26 | % 27 | % ========================================================================= 28 | % 29 | % progress Properties: 30 | % none 31 | % 32 | % progress Methods: 33 | % progress - class constructor 34 | % 35 | % 36 | % Author : J.-A. Adrian (JA) 37 | % 38 | 39 | 40 | properties (Access = private) 41 | IterationList; 42 | ProgressBar; 43 | end 44 | 45 | methods 46 | % Class Constructor 47 | function obj = progress(in, varargin) 48 | if ~nargin 49 | return; 50 | end 51 | 52 | obj.IterationList = in; 53 | 54 | % pass all varargins to ProgressBar() 55 | obj.ProgressBar = ProgressBar(length(in), varargin{:}); 56 | end 57 | 58 | % Class Destructor 59 | function delete(obj) 60 | % call the destructor of the ProgressBar() object 61 | if ~isempty(obj.ProgressBar) 62 | obj.ProgressBar.release(); 63 | end 64 | end 65 | 66 | function [varargout] = subsref(obj, S) 67 | % This method implements the subsref method and only calls the update() 68 | % method of ProgressBar. The actual input 'S' is passed to the default 69 | % subsref method of the class of obj.IterationList. 70 | 71 | obj.ProgressBar.step([], [], []); 72 | varargout = {subsref(obj.IterationList, S)}; 73 | end 74 | 75 | function [m, n] = size(obj) 76 | % This method implements the size() function for the progress() class. 77 | 78 | [m, n] = size(obj.IterationList); 79 | end 80 | end 81 | end 82 | 83 | -------------------------------------------------------------------------------- /tests/ProgressBar_test.m: -------------------------------------------------------------------------------- 1 | classdef ProgressBar_test < matlab.unittest.TestCase 2 | %PROGRESSBAR_TEST Unit test for ProgressBar.m 3 | % ------------------------------------------------------------------------- 4 | % Run it by calling 'runtests()' 5 | % or specifically 'runtests('ProgressBar_test')' 6 | % 7 | % Author : J.-A. Adrian (JA) 8 | % 9 | 10 | 11 | properties (Constant) 12 | DEFAULT_SEED = 123; 13 | end 14 | 15 | properties 16 | UnitName = "ProgressBar"; 17 | Seed; 18 | end 19 | 20 | 21 | methods (TestClassSetup) 22 | function setClassRng(testCase) 23 | testCase.Seed = rng(); 24 | testCase.addTeardown(@rng, testCase.Seed); 25 | 26 | rng(testCase.DEFAULT_SEED); 27 | end 28 | 29 | function addPath(testCase) 30 | defaultPath = matlabpath(); 31 | 32 | myLocation = fileparts(mfilename('fullpath')); 33 | addpath(fullfile(myLocation, '..')); 34 | 35 | testCase.addTeardown(@() matlabpath(defaultPath)); 36 | end 37 | end 38 | 39 | methods (TestMethodSetup) 40 | function setMethodRng(testCase) 41 | rng(testCase.DEFAULT_SEED); 42 | end 43 | end 44 | 45 | methods (TestMethodTeardown) 46 | function deleteRogueTimers(~) 47 | delete(timerfindall('Tag', ProgressBar.TIMER_TAG_NAME)); 48 | end 49 | end 50 | 51 | 52 | 53 | methods (Test, TestTags = {'NonParallel'}) 54 | function testTimerDeletion(testCase) 55 | unit = testCase.getUnit(); 56 | 57 | tagName = unit.TIMER_TAG_NAME; 58 | timer('Tag', tagName); 59 | testCase.verifyNotEmpty(timerfindall('Tag', tagName)); 60 | 61 | unit.deleteAllTimers(); 62 | testCase.verifyEmpty(timerfindall('Tag', tagName)); 63 | end 64 | 65 | 66 | function testUnicodeBlocks(testCase) 67 | unit = testCase.getUnit(); 68 | 69 | blocks = unit.getUnicodeSubBlocks(); 70 | testCase.verifyEqual(blocks, '▏▎▍▌▋▊▉█'); 71 | end 72 | 73 | 74 | function testAsciiBlocks(testCase) 75 | unit = testCase.getUnit(); 76 | 77 | blocks = unit.getAsciiSubBlocks(); 78 | testCase.verifyEqual(blocks, '########'); 79 | end 80 | 81 | 82 | function testBackspaces(testCase) 83 | unit = testCase.getUnit(); 84 | 85 | backspaces = unit.backspace(3); 86 | testCase.verifyEqual(backspaces, sprintf('\b\b\b')); 87 | end 88 | 89 | 90 | function testTimeConversion(testCase) 91 | unit = testCase.getUnit(); 92 | 93 | testCase.verifyEqual(unit.convertTime(0), [0, 0, 0]); 94 | testCase.verifyEqual(unit.convertTime(30), [0, 0, 30]); 95 | testCase.verifyEqual(unit.convertTime(60), [0, 1, 0]); 96 | testCase.verifyEqual(unit.convertTime(60*60), [1, 0, 0]); 97 | end 98 | 99 | 100 | function checkBarLengthInput(testCase) 101 | unit = testCase.getUnit(); 102 | 103 | testCase.verifyEqual(unit.checkInputOfTotal([]), true); 104 | testCase.verifyEqual(unit.checkInputOfTotal(10), true); 105 | 106 | testCase.verifyError(@() unit.checkInputOfTotal('char'), 'MATLAB:invalidType'); 107 | testCase.verifyError(@() unit.checkInputOfTotal(-1), 'MATLAB:expectedPositive'); 108 | testCase.verifyError(@() unit.checkInputOfTotal([1, 1]), 'MATLAB:expectedScalar'); 109 | testCase.verifyError(@() unit.checkInputOfTotal(1j), 'MATLAB:expectedInteger'); 110 | testCase.verifyError(@() unit.checkInputOfTotal(1.5), 'MATLAB:expectedInteger'); 111 | testCase.verifyError(@() unit.checkInputOfTotal(inf), 'MATLAB:expectedInteger'); 112 | testCase.verifyError(@() unit.checkInputOfTotal(nan), 'MATLAB:expectedInteger'); 113 | end 114 | 115 | 116 | function findWorkerFiles(testCase) 117 | unit = testCase.getUnit(); 118 | 119 | pattern = updateParallel(); 120 | testCase.assertEmpty(dir(pattern)); 121 | 122 | workerFilename = [pattern(1:end-1), 'test']; 123 | fid = fopen(workerFilename, 'w'); 124 | fclose(fid); 125 | 126 | foundFiles = unit.findWorkerFiles(pwd()); 127 | testCase.verifyEqual(length(foundFiles), 1); 128 | testCase.verifyEqual(foundFiles, {fullfile(pwd(), workerFilename)}); 129 | 130 | delete(workerFilename); 131 | end 132 | 133 | 134 | function barHasDefaults(testCase) 135 | unit = testCase.getUnit(); 136 | 137 | testCase.verifyEqual(unit.TIMER_TAG_NAME, 'ProgressBar'); 138 | testCase.verifyEmpty(unit.Total); 139 | testCase.verifyEqual(unit.UpdateRate, 5); 140 | testCase.verifyEqual(unit.Unit, 'Iterations'); 141 | testCase.verifyTrue(unit.UseUnicode); 142 | testCase.verifyFalse(unit.IsParallel); 143 | testCase.verifyFalse(unit.OverrideDefaultFont); 144 | end 145 | 146 | 147 | function canSetBarTotal(testCase) 148 | unit = testCase.getUnit(10); 149 | 150 | testCase.verifyEqual(unit.Total, 10); 151 | end 152 | 153 | 154 | function canPrintSimpleBar(testCase) 155 | unit = testCase.getUnit(); 156 | 157 | firstBar = evalc('unit([], [], [])'); 158 | pause(0.5); 159 | secondBar = evalc('unit([], [], [])'); 160 | 161 | testCase.verifyTrue(contains(firstBar, 'Processing')); 162 | testCase.verifyTrue(contains(firstBar, '1it')); 163 | 164 | testCase.verifyTrue(contains(secondBar, '2it')); 165 | unit.release(); 166 | end 167 | 168 | 169 | function canPrintBarWithTotal(testCase) 170 | unit = testCase.getUnit(2); 171 | 172 | firstBar = evalc('unit([], [], [])'); 173 | testCase.verifyTrue(contains(firstBar, 'Processing')); 174 | testCase.verifyTrue(contains(firstBar, '050%')); 175 | testCase.verifyTrue(contains(firstBar, '1/2')); 176 | 177 | secondBar = evalc('unit([], [], [])'); 178 | testCase.verifyTrue(contains(secondBar, '100%')); 179 | testCase.verifyTrue(contains(secondBar, '2/2')); 180 | 181 | unit.release(); 182 | end 183 | 184 | 185 | function canPrintLongTitle(testCase) 186 | unit = testCase.getUnit(2); 187 | unit.Title = 'This is a long title string'; 188 | 189 | firstBar = evalc('unit([], [], [])'); 190 | secondBar = evalc('unit([], [], [])'); 191 | unit.release(); 192 | 193 | testCase.verifyNotEqual(firstBar, secondBar); 194 | end 195 | 196 | 197 | function canRunWithoutUpdateTimer(testCase) 198 | unit = testCase.getUnit(2, 'UpdateRate', inf); 199 | 200 | firstBar = evalc('unit([], [], [])'); 201 | secondBar = evalc('unit([], [], [])'); 202 | unit.release(); 203 | 204 | testCase.verifyTrue(contains(firstBar, '1/2it')); 205 | testCase.verifyTrue(contains(secondBar, '2/2it')); 206 | end 207 | 208 | 209 | function canBeDisabled(testCase) 210 | unit = testCase.getUnit(2, 'IsActive', false); 211 | 212 | firstBar = evalc('unit([], [], [])'); 213 | secondBar = evalc('unit([], [], [])'); 214 | unit.release(); 215 | 216 | testCase.verifyEmpty(firstBar); 217 | testCase.verifyEmpty(secondBar); 218 | end 219 | end 220 | 221 | 222 | methods (Test, TestTags = {'Parallel'}) 223 | function canRunInParallel(testCase) 224 | numIterations = 100; 225 | 226 | unit = testCase.getUnit(numIterations, ... 227 | 'IsParallel', true, ... 228 | 'WorkerDirectory', pwd() ... 229 | ); 230 | 231 | init = evalc('unit.setup([], [], []);'); 232 | parfor iIteration = 1:numIterations 233 | pause(0.1); 234 | updateParallel([], pwd()); 235 | end 236 | unit.release(); 237 | 238 | testCase.verifyTrue(contains(init, '000%')); 239 | testCase.verifyTrue(contains(init, sprintf('0/%dit', numIterations))); 240 | testCase.verifyEmpty(timerfindall('Tag', 'ProgressBar')); 241 | 242 | pause(0.5); 243 | files = unit.findWorkerFiles(pwd()); 244 | testCase.verifyEmpty(files); 245 | end 246 | end 247 | 248 | 249 | 250 | methods 251 | function [unit] = getUnit(testCase, total, varargin) 252 | % Factory to get a bar object with desired 'total' 253 | 254 | if nargin < 2 255 | total = []; 256 | end 257 | 258 | unitHandle = str2func(testCase.UnitName); 259 | unit = unitHandle(total, varargin{:}); 260 | end 261 | end 262 | 263 | 264 | end 265 | -------------------------------------------------------------------------------- /tests/progress_test.m: -------------------------------------------------------------------------------- 1 | classdef progress_test < matlab.unittest.TestCase 2 | %PROGRESS_TEST Unit test for progress.m 3 | % ------------------------------------------------------------------------- 4 | % Run it by calling 'runtests()' 5 | % or specifically 'runtests('progress_test')' 6 | % 7 | % Author : J.-A. Adrian (JA) 8 | % 9 | 10 | 11 | properties (Constant) 12 | DEFAULT_SEED = 123; 13 | end 14 | 15 | properties (Access = public) 16 | Seed; 17 | end 18 | 19 | 20 | 21 | methods (TestClassSetup) 22 | function setClassRng(testCase) 23 | testCase.Seed = rng(); 24 | testCase.addTeardown(@rng, testCase.Seed); 25 | 26 | rng(testCase.DEFAULT_SEED); 27 | end 28 | 29 | function addPath(testCase) 30 | defaultPath = matlabpath(); 31 | 32 | myLocation = fileparts(mfilename('fullpath')); 33 | addpath(fullfile(myLocation, '..')); 34 | 35 | testCase.addTeardown(@() matlabpath(defaultPath)); 36 | end 37 | end 38 | 39 | methods (TestMethodSetup) 40 | function setMethodRng(testCase) 41 | rng(testCase.DEFAULT_SEED); 42 | end 43 | end 44 | 45 | methods (TestMethodTeardown) 46 | function deleteRogueTimers(~) 47 | delete(timerfindall('Tag', ProgressBar.TIMER_TAG_NAME)); 48 | end 49 | end 50 | 51 | 52 | 53 | methods (Test, TestTags = {'NonParallel'}) 54 | function earlyExit(testCase) 55 | obj = progress(); 56 | 57 | testCase.verifyClass(obj, 'progress'); 58 | testCase.verifyEmpty(properties(obj)); 59 | 60 | methodsAreImplemented = contains(methods(obj), {'progress', 'size', 'subsref'}); 61 | testCase.verifyEqual(sum(methodsAreImplemented), 3); 62 | end 63 | 64 | 65 | function simpleCall(testCase) 66 | str = evalc('for k = progress(1); end;'); 67 | 68 | testCase.verifyTrue(contains(str, '100%')); 69 | testCase.verifyTrue(contains(str, '1/1it')); 70 | end 71 | 72 | 73 | function disabledProgress(testCase) 74 | str = evalc('for k = progress(1, ''IsActive'', false); end;'); 75 | 76 | testCase.verifyEmpty(str); 77 | end 78 | end 79 | 80 | end 81 | -------------------------------------------------------------------------------- /updateParallel.m: -------------------------------------------------------------------------------- 1 | function [pattern] = updateParallel(stepSize, workerDirName) 2 | %UPDATEPARALLEL Update function when ProgressBar is used in parallel setup 3 | % ------------------------------------------------------------------------- 4 | % This function replaces the update() method of the ProgressBar() class 5 | % when a progress in a parfor loop should be displayed. The function writes 6 | % by default to a temp file in the local temp dir. Each worker will call a 7 | % copy of this function and if a persistent file name variable is not yet 8 | % set a unique file name will be generated and a binary file will be 9 | % initialized. Each worker remembers its own file name to write to and will 10 | % update its own current progress and write it to file. The ProgressBar() 11 | % class will handle the management of all worker files. 12 | % 13 | % 14 | % Usage: [pattern] = updateParallel(stepSize, workerDirName) 15 | % 16 | % Input: --------- 17 | % stepSize - the size of the progress step when the function is 18 | % called. This can be used to pass the number of 19 | % processed bytes when using 'Bytes' as units. If 20 | % bytes are used be sure to pass only integer values. 21 | % [default: stepSize = 1] 22 | % workerDirName - directory where the worker aux. files will be 23 | % saved. This can be specified for debug purposes 24 | % or if multiple progress bars in a parallel 25 | % setup would get in each other's way since all 26 | % have the same file pattern and would distract 27 | % each progress bar's progress state. 28 | % [default: workerDirName = tempdir()] 29 | % 30 | % Output: --------- 31 | % filePattern - the common beginning of every file name before the 32 | % unique part begins. This is an auxiliary function 33 | % output which is used by the ProgressBar() class. 34 | % Typically not be of interest for the user. The 35 | % variable is only returned if no input arguments were 36 | % passed! 37 | % 38 | % 39 | % 40 | % Author: J.-A. Adrian (JA) 41 | % 42 | 43 | 44 | % some constants 45 | persistent workerFileName; 46 | filePattern = 'progbarworker_'; 47 | 48 | % input parsing and validation 49 | narginchk(0, 2); 50 | 51 | if nargin < 2 || isempty(workerDirName) 52 | workerDirName = tempdir; 53 | end 54 | if nargin <1 || isempty(stepSize) 55 | stepSize = 1; 56 | end 57 | if ~nargin && nargout 58 | pattern = [filePattern, '*']; 59 | 60 | return; 61 | end 62 | 63 | validateattributes(stepSize, ... 64 | {'numeric'}, ... 65 | {'scalar', 'positive', 'integer', 'real', 'nonnan', ... 66 | 'finite', 'nonempty'} ... 67 | ); 68 | validateattributes(workerDirName, {'char'}, {'nonempty'}); 69 | 70 | 71 | 72 | % if the function is called the first time the persistent variable is 73 | % initialized and the worker file is created. The condition is skipped in 74 | % the following calls. 75 | if isempty(workerFileName) 76 | uuid = char(java.util.UUID.randomUUID); 77 | workerFileName = fullfile(workerDirName, [filePattern, uuid]); 78 | 79 | fid = fopen(workerFileName, 'wb'); 80 | fwrite(fid, 0, 'uint64'); 81 | fclose(fid); 82 | end 83 | 84 | % this part is executed every time the function is called: 85 | % open the binary file and increment the existing progress with stepSize 86 | fid = fopen(workerFileName, 'r+b'); 87 | if fid > 0 88 | status = fread(fid, 1, 'uint64'); 89 | 90 | fseek(fid, 0, 'bof'); 91 | fwrite(fid, status + stepSize, 'uint64'); 92 | 93 | fclose(fid); 94 | end 95 | 96 | --------------------------------------------------------------------------------