Skip to content

Commit f612560

Browse files
authored
Merge pull request #575 from bls337/main
Add a status line and progress bar to the acquisition tab
2 parents ef4c100 + 7b91d99 commit f612560

6 files changed

Lines changed: 617 additions & 0 deletions

File tree

‎src/main/java/org/micromanager/lightsheetmanager/gui/tabs/AcquisitionTab.java‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.micromanager.lightsheetmanager.gui.tabs.acquisition.PositionPanel;
1616
import org.micromanager.lightsheetmanager.gui.tabs.acquisition.SavePanel;
1717
import org.micromanager.lightsheetmanager.gui.tabs.acquisition.SlicePanel;
18+
import org.micromanager.lightsheetmanager.gui.tabs.acquisition.StatusPanel;
1819
import org.micromanager.lightsheetmanager.gui.tabs.acquisition.TimePointsPanel;
1920
import org.micromanager.lightsheetmanager.gui.tabs.acquisition.DurationPanel;
2021
import org.micromanager.lightsheetmanager.gui.tabs.acquisition.VolumePanel;
@@ -49,6 +50,9 @@ public class AcquisitionTab extends Panel implements ListeningPanel, SettingsLis
4950
// durations
5051
private DurationPanel pnlDurations_;
5152

53+
// where the run is
54+
private StatusPanel pnlStatus_;
55+
5256
// time points
5357
private TimePointsPanel pnlTimePoints_;
5458
private CheckBox cbxUseTimePoints_;
@@ -113,6 +117,7 @@ private void createUserInterface() {
113117
);
114118

115119
pnlDurations_ = new DurationPanel(model_);
120+
pnlStatus_ = new StatusPanel(model_);
116121
pnlVolumeSettings_ = new VolumePanel(model_);
117122

118123
// switch between these two panels
@@ -219,6 +224,8 @@ private void createUserInterface() {
219224
pnlCenter.add(pnlChannelTable_, "wrap");
220225
pnlCenter.add(new JLabel("Acquisition Mode:"), "split 2");
221226
pnlCenter.add(cmbAcquisitionModes_, "");
227+
// as wide as the channel table, so a long message wraps instead of widening the column
228+
pnlCenter.add(pnlStatus_, "newline, w 280!");
222229

223230
pnlRight_.add(pnlVolumeSettings_, "growx, wrap");
224231
pnlRight_.add(pnlSliceSettings_, "growx, wrap");
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package org.micromanager.lightsheetmanager.gui.tabs.acquisition;
2+
3+
import org.micromanager.lightsheetmanager.LightSheetManager;
4+
import org.micromanager.lightsheetmanager.gui.components.Panel;
5+
import org.micromanager.lightsheetmanager.model.acquisitions.AcquisitionEngine;
6+
import org.micromanager.lightsheetmanager.model.acquisitions.RunProgress;
7+
8+
import javax.swing.JLabel;
9+
import javax.swing.JProgressBar;
10+
import javax.swing.SwingConstants;
11+
import javax.swing.Timer;
12+
import java.util.Objects;
13+
14+
/**
15+
* The status line of the acquisition tab: where the run is, how long until the next time point
16+
* and how the last run ended, with a bar of the images so far.
17+
*
18+
* <p>Polls the engine on the interface thread while the panel is displayable, so the
19+
* acquisition's threads never touch Swing, and a run started from a script shows too.
20+
*/
21+
public class StatusPanel extends Panel {
22+
23+
// often enough that the countdown steps once a second
24+
private static final int POLL_MS = 250;
25+
26+
// the bar's resolution
27+
private static final int BAR_STEPS = 1000;
28+
29+
private final AcquisitionEngine engine;
30+
private final JLabel lblStatus;
31+
private final JProgressBar barImages;
32+
private final JLabel lblImages;
33+
private final Timer timer;
34+
35+
public StatusPanel(final LightSheetManager model) {
36+
super("Status");
37+
engine = Objects.requireNonNull(model).acquisitions();
38+
39+
// the bar takes the width the image count leaves
40+
setMigLayout("", "[grow, fill][]", "");
41+
42+
lblStatus = new JLabel();
43+
// two lines high, so a long message wraps instead of resizing the panel
44+
lblStatus.setVerticalAlignment(SwingConstants.TOP);
45+
final int lineHeight = lblStatus.getFontMetrics(lblStatus.getFont()).getHeight();
46+
47+
barImages = new JProgressBar(0, BAR_STEPS);
48+
// the count goes beside the bar: some themes paint text on a bar in nearly the color of
49+
// its filled part
50+
lblImages = new JLabel();
51+
52+
add(lblStatus, "span 2, wmin 0, h " + 2 * lineHeight + "!, wrap");
53+
add(barImages, "wmin 0");
54+
add(lblImages, "");
55+
56+
timer = new Timer(POLL_MS, e -> refresh());
57+
refresh();
58+
}
59+
60+
@Override
61+
public void addNotify() {
62+
super.addNotify();
63+
timer.start();
64+
}
65+
66+
@Override
67+
public void removeNotify() {
68+
// the window is being disposed: a running timer would keep the panel alive
69+
timer.stop();
70+
super.removeNotify();
71+
}
72+
73+
/**
74+
* Shows the engine's progress as it is now. The timer calls this while the panel is showing.
75+
* A label set to the text it already shows is neither laid out again nor repainted.
76+
*/
77+
public void refresh() {
78+
final RunProgress now = engine.progress();
79+
// html, so a message longer than the panel wraps onto the second line
80+
lblStatus.setText("<html>" + now.describe() + "</html>");
81+
final long expected = now.imagesExpected();
82+
barImages.setValue(expected == 0 ? 0
83+
: (int) (BAR_STEPS * Math.min(now.images(), expected) / expected));
84+
lblImages.setText(now.describeImages());
85+
}
86+
}

‎src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngine.java‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ public abstract class AcquisitionEngine implements AcquisitionManager, MMAcquist
7474
// true while finish() restores the system after a run, for the stop and abort log lines
7575
private volatile boolean cleaningUp_ = false;
7676

77+
final RunProgressTracker tracker_ = new RunProgressTracker(() -> this.datastore_);
78+
7779
private final AutofocusAdapter autofocus_;
7880

7981
// visibility: written on the acquisition thread, read on the edt when a window asks to close
@@ -296,6 +298,13 @@ public boolean isRunning() {
296298
return acquisitionRequested_;
297299
}
298300

301+
/**
302+
* A snapshot of where the run in flight is, or of how the last run ended.
303+
*/
304+
public RunProgress progress() {
305+
return tracker_.now();
306+
}
307+
299308
/**
300309
* Records whether a series is waiting between time points, for the stop and abort log lines.
301310
*
@@ -425,6 +434,8 @@ private Future<?> requestRun(boolean testAcquisition) {
425434
model_.logging().reportError("Acquisition is already running.");
426435
return;
427436
}
437+
// in the task: the executor runs one at a time, so this cannot reach a run in flight
438+
tracker_.start();
428439

429440
// marker data
430441
long runId = -1; // set at START, so STOP is logged only after a START
@@ -477,6 +488,8 @@ private Future<?> requestRun(boolean testAcquisition) {
477488
model_.logging().reportError(e, "Error during acquisition cleanup");
478489
} finally {
479490
cleaningUp_ = false;
491+
// before the acquisition is let go: it says how the run ended
492+
tracker_.end(currentAcquisition_, stopRequested_);
480493
// must ALWAYS run: if currentAcquisition_ is left set, every future
481494
// acquisition is rejected until the plugin restarts
482495
currentAcquisition_ = null;
@@ -488,6 +501,8 @@ private Future<?> requestRun(boolean testAcquisition) {
488501
// free the datastore so a large store isn't kept in memory (matches MM's
489502
// AcqEngJAdapter.onAcquisitionEnded); also what the save guard checks to skip aborted/empty runs
490503
datastore_ = null;
504+
// after the run is let go, so the log cannot hold it
505+
tracker_.logEnd(studio_.logs());
491506
// LSM-ACQ-STOP in the innermost finally: fires on completion, error, abort, throwing finish()
492507
if (runId != -1) {
493508
final long elapsedMs = (System.nanoTime() - startNs) / 1_000_000L;

‎src/main/java/org/micromanager/lightsheetmanager/model/acquisitions/AcquisitionEngineScape.java‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,11 @@ boolean run() {
352352
// never wait on an Acquisition that was not finished: the wait is unbounded
353353
return false;
354354
}
355+
tracker_.show(new RunProgress.Source(progressShape(settings, cameraNames), 0, 0L,
356+
tracker_::imagesInDataset, runSchedule(settings, currentAcquisition_)));
355357
currentAcquisition_.waitForCompletion();
358+
// the dataset is complete, so its count is final
359+
tracker_.freeze();
356360
return true;
357361
}
358362

@@ -368,6 +372,31 @@ private static boolean isSeparatingTimePoints(final ScapeAcquisitionSettings set
368372
return settings.isUsingTimePoints() && settings.isUsingSeparateTimePoints();
369373
}
370374

375+
/**
376+
* The size of the run the status line counts against, as the events are built.
377+
*/
378+
private RunProgress.Shape progressShape(final ScapeAcquisitionSettings settings,
379+
final String[] cameraNames) {
380+
final int numPositions = settings.isUsingMultiplePositions()
381+
? positionList_.getNumberOfPositions() : 1;
382+
return new RunProgress.Shape(numTimePointsToAcquire(settings), numPositions,
383+
settings.volume().slicesPerView() * settings.channels().count()
384+
* cameraNames.length);
385+
}
386+
387+
/**
388+
* When each time point of a run in one dataset is due, on AcqEngJ's schedule. Under hardware
389+
* time points the controller paces them and nothing waits.
390+
*/
391+
private static RunProgress.Schedule runSchedule(final ScapeAcquisitionSettings settings,
392+
final Acquisition acquisition) {
393+
if (settings.isUsingHardwareTimePoints()) {
394+
return RunProgress.Schedule.NONE;
395+
}
396+
return RunProgress.fromAcquisitionStart(acquisition::getStartTimeMs,
397+
settings.timePointIntervalSec() * 1000.0, System::currentTimeMillis);
398+
}
399+
371400
/**
372401
* Resolves the camera device names the events are addressed to.
373402
*
@@ -972,6 +1001,10 @@ private boolean runSeparateTimePoints(final ScapeAcquisitionSettings settings,
9721001
}
9731002

9741003
final long seriesStartNs = System.nanoTime();
1004+
// the status line: the run's size, on the clock the wait below uses
1005+
final RunProgressTracker.Series status = tracker_.series(
1006+
progressShape(settings, cameraNames),
1007+
RunProgress.fromSeriesStart(seriesStartNs, intervalMs, System::nanoTime));
9751008
// directory names of the completed datasets
9761009
final List<String> datasetNames = new ArrayList<>();
9771010
// the time points that failed, reported together when the series ends
@@ -984,6 +1017,9 @@ private boolean runSeparateTimePoints(final ScapeAcquisitionSettings settings,
9841017
int inFlightIndex = -1;
9851018
try {
9861019
for (int timeIndex = 0; timeIndex < numTimePoints; timeIndex++) {
1020+
if (timeIndex > 0) {
1021+
status.awaiting(timeIndex);
1022+
}
9871023
if (!awaitTimePointSlot(seriesStartNs, timeIndex, intervalMs)) {
9881024
break; // stop requested during the wait
9891025
}
@@ -998,12 +1034,14 @@ private boolean runSeparateTimePoints(final ScapeAcquisitionSettings settings,
9981034
timeIndex)) {
9991035
break; // nothing started, so nothing to wait for
10001036
}
1037+
status.acquiring(timeIndex);
10011038
if (previousStore != null) {
10021039
closeDisplaysOnInterfaceThread(previousStore, timeIndex - 1);
10031040
previousStore = null;
10041041
}
10051042
// returns once the store is frozen, so the dataset is complete on disk
10061043
currentAcquisition_.waitForCompletion();
1044+
status.datasetComplete();
10071045

10081046
final String datasetDir = datastore_.getSavePath();
10091047
previousStore = datastore_;
@@ -1015,6 +1053,8 @@ private boolean runSeparateTimePoints(final ScapeAcquisitionSettings settings,
10151053
}
10161054
}
10171055
} finally {
1056+
// where the series stopped, not where the countdown is once the cleanup ends
1057+
tracker_.freeze();
10181058
// no save path means no directory was created, so there is nothing to mark
10191059
final String inFlightDir = datastore_ == null ? null : datastore_.getSavePath();
10201060
if (inFlightIndex >= 0 && inFlightDir != null) {

0 commit comments

Comments
 (0)