Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.micromanager.lightsheetmanager.gui.tabs.acquisition.PositionPanel;
import org.micromanager.lightsheetmanager.gui.tabs.acquisition.SavePanel;
import org.micromanager.lightsheetmanager.gui.tabs.acquisition.SlicePanel;
import org.micromanager.lightsheetmanager.gui.tabs.acquisition.StatusPanel;
import org.micromanager.lightsheetmanager.gui.tabs.acquisition.TimePointsPanel;
import org.micromanager.lightsheetmanager.gui.tabs.acquisition.DurationPanel;
import org.micromanager.lightsheetmanager.gui.tabs.acquisition.VolumePanel;
Expand Down Expand Up @@ -49,6 +50,9 @@ public class AcquisitionTab extends Panel implements ListeningPanel, SettingsLis
// durations
private DurationPanel pnlDurations_;

// where the run is
private StatusPanel pnlStatus_;

// time points
private TimePointsPanel pnlTimePoints_;
private CheckBox cbxUseTimePoints_;
Expand Down Expand Up @@ -113,6 +117,7 @@ private void createUserInterface() {
);

pnlDurations_ = new DurationPanel(model_);
pnlStatus_ = new StatusPanel(model_);
pnlVolumeSettings_ = new VolumePanel(model_);

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

pnlRight_.add(pnlVolumeSettings_, "growx, wrap");
pnlRight_.add(pnlSliceSettings_, "growx, wrap");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package org.micromanager.lightsheetmanager.gui.tabs.acquisition;

import org.micromanager.lightsheetmanager.LightSheetManager;
import org.micromanager.lightsheetmanager.gui.components.Panel;
import org.micromanager.lightsheetmanager.model.acquisitions.AcquisitionEngine;
import org.micromanager.lightsheetmanager.model.acquisitions.RunProgress;

import javax.swing.JLabel;
import javax.swing.JProgressBar;
import javax.swing.SwingConstants;
import javax.swing.Timer;
import java.util.Objects;

/**
* The status line of the acquisition tab: where the run is, how long until the next time point
* and how the last run ended, with a bar of the images so far.
*
* <p>Polls the engine on the interface thread while the panel is displayable, so the
* acquisition's threads never touch Swing, and a run started from a script shows too.
*/
public class StatusPanel extends Panel {

// often enough that the countdown steps once a second
private static final int POLL_MS = 250;

// the bar's resolution
private static final int BAR_STEPS = 1000;

private final AcquisitionEngine engine;
private final JLabel lblStatus;
private final JProgressBar barImages;
private final JLabel lblImages;
private final Timer timer;

public StatusPanel(final LightSheetManager model) {
super("Status");
engine = Objects.requireNonNull(model).acquisitions();

// the bar takes the width the image count leaves
setMigLayout("", "[grow, fill][]", "");

lblStatus = new JLabel();
// two lines high, so a long message wraps instead of resizing the panel
lblStatus.setVerticalAlignment(SwingConstants.TOP);
final int lineHeight = lblStatus.getFontMetrics(lblStatus.getFont()).getHeight();

barImages = new JProgressBar(0, BAR_STEPS);
// the count goes beside the bar: some themes paint text on a bar in nearly the color of
// its filled part
lblImages = new JLabel();

add(lblStatus, "span 2, wmin 0, h " + 2 * lineHeight + "!, wrap");
add(barImages, "wmin 0");
add(lblImages, "");

timer = new Timer(POLL_MS, e -> refresh());
refresh();
}

@Override
public void addNotify() {
super.addNotify();
timer.start();
}

@Override
public void removeNotify() {
// the window is being disposed: a running timer would keep the panel alive
timer.stop();
super.removeNotify();
}

/**
* Shows the engine's progress as it is now. The timer calls this while the panel is showing.
* A label set to the text it already shows is neither laid out again nor repainted.
*/
public void refresh() {
final RunProgress now = engine.progress();
// html, so a message longer than the panel wraps onto the second line
lblStatus.setText("<html>" + now.describe() + "</html>");
final long expected = now.imagesExpected();
barImages.setValue(expected == 0 ? 0
: (int) (BAR_STEPS * Math.min(now.images(), expected) / expected));
lblImages.setText(now.describeImages());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ public abstract class AcquisitionEngine implements AcquisitionManager, MMAcquist
// true while finish() restores the system after a run, for the stop and abort log lines
private volatile boolean cleaningUp_ = false;

final RunProgressTracker tracker_ = new RunProgressTracker(() -> this.datastore_);

private final AutofocusAdapter autofocus_;

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

/**
* A snapshot of where the run in flight is, or of how the last run ended.
*/
public RunProgress progress() {
return tracker_.now();
}

/**
* Records whether a series is waiting between time points, for the stop and abort log lines.
*
Expand Down Expand Up @@ -425,6 +434,8 @@ private Future<?> requestRun(boolean testAcquisition) {
model_.logging().reportError("Acquisition is already running.");
return;
}
// in the task: the executor runs one at a time, so this cannot reach a run in flight
tracker_.start();

// marker data
long runId = -1; // set at START, so STOP is logged only after a START
Expand Down Expand Up @@ -477,6 +488,8 @@ private Future<?> requestRun(boolean testAcquisition) {
model_.logging().reportError(e, "Error during acquisition cleanup");
} finally {
cleaningUp_ = false;
// before the acquisition is let go: it says how the run ended
tracker_.end(currentAcquisition_, stopRequested_);
// must ALWAYS run: if currentAcquisition_ is left set, every future
// acquisition is rejected until the plugin restarts
currentAcquisition_ = null;
Expand All @@ -488,6 +501,8 @@ private Future<?> requestRun(boolean testAcquisition) {
// free the datastore so a large store isn't kept in memory (matches MM's
// AcqEngJAdapter.onAcquisitionEnded); also what the save guard checks to skip aborted/empty runs
datastore_ = null;
// after the run is let go, so the log cannot hold it
tracker_.logEnd(studio_.logs());
// LSM-ACQ-STOP in the innermost finally: fires on completion, error, abort, throwing finish()
if (runId != -1) {
final long elapsedMs = (System.nanoTime() - startNs) / 1_000_000L;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,11 @@ boolean run() {
// never wait on an Acquisition that was not finished: the wait is unbounded
return false;
}
tracker_.show(new RunProgress.Source(progressShape(settings, cameraNames), 0, 0L,
tracker_::imagesInDataset, runSchedule(settings, currentAcquisition_)));
currentAcquisition_.waitForCompletion();
// the dataset is complete, so its count is final
tracker_.freeze();
return true;
}

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

/**
* The size of the run the status line counts against, as the events are built.
*/
private RunProgress.Shape progressShape(final ScapeAcquisitionSettings settings,
final String[] cameraNames) {
final int numPositions = settings.isUsingMultiplePositions()
? positionList_.getNumberOfPositions() : 1;
return new RunProgress.Shape(numTimePointsToAcquire(settings), numPositions,
settings.volume().slicesPerView() * settings.channels().count()
* cameraNames.length);
}

/**
* When each time point of a run in one dataset is due, on AcqEngJ's schedule. Under hardware
* time points the controller paces them and nothing waits.
*/
private static RunProgress.Schedule runSchedule(final ScapeAcquisitionSettings settings,
final Acquisition acquisition) {
if (settings.isUsingHardwareTimePoints()) {
return RunProgress.Schedule.NONE;
}
return RunProgress.fromAcquisitionStart(acquisition::getStartTimeMs,
settings.timePointIntervalSec() * 1000.0, System::currentTimeMillis);
}

/**
* Resolves the camera device names the events are addressed to.
*
Expand Down Expand Up @@ -972,6 +1001,10 @@ private boolean runSeparateTimePoints(final ScapeAcquisitionSettings settings,
}

final long seriesStartNs = System.nanoTime();
// the status line: the run's size, on the clock the wait below uses
final RunProgressTracker.Series status = tracker_.series(
progressShape(settings, cameraNames),
RunProgress.fromSeriesStart(seriesStartNs, intervalMs, System::nanoTime));
// directory names of the completed datasets
final List<String> datasetNames = new ArrayList<>();
// the time points that failed, reported together when the series ends
Expand All @@ -984,6 +1017,9 @@ private boolean runSeparateTimePoints(final ScapeAcquisitionSettings settings,
int inFlightIndex = -1;
try {
for (int timeIndex = 0; timeIndex < numTimePoints; timeIndex++) {
if (timeIndex > 0) {
status.awaiting(timeIndex);
}
if (!awaitTimePointSlot(seriesStartNs, timeIndex, intervalMs)) {
break; // stop requested during the wait
}
Expand All @@ -998,12 +1034,14 @@ private boolean runSeparateTimePoints(final ScapeAcquisitionSettings settings,
timeIndex)) {
break; // nothing started, so nothing to wait for
}
status.acquiring(timeIndex);
if (previousStore != null) {
closeDisplaysOnInterfaceThread(previousStore, timeIndex - 1);
previousStore = null;
}
// returns once the store is frozen, so the dataset is complete on disk
currentAcquisition_.waitForCompletion();
status.datasetComplete();

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