All Downloads are FREE. Search and download functionalities are using the official Maven repository.

us.ihmc.scs2.sessionVisualizer.jfx.session.mcap.MCAPLogSessionManagerController Maven / Gradle / Ivy

package us.ihmc.scs2.sessionVisualizer.jfx.session.mcap;

import com.jfoenix.controls.JFXTextField;
import com.jfoenix.controls.JFXTrimSlider;
import javafx.beans.property.LongProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.ListChangeListener;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TitledPane;
import javafx.scene.control.ToggleButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.Pane;
import javafx.stage.FileChooser;
import javafx.stage.FileChooser.ExtensionFilter;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import javafx.util.converter.IntegerStringConverter;
import javafx.util.converter.LongStringConverter;
import org.apache.commons.lang3.mutable.MutableBoolean;
import us.ihmc.log.LogTools;
import us.ihmc.messager.TopicListener;
import us.ihmc.messager.javafx.JavaFXMessager;
import us.ihmc.scs2.session.SessionRobotDefinitionListChange;
import us.ihmc.scs2.session.mcap.MCAPLogCropper;
import us.ihmc.scs2.session.mcap.MCAPLogCropper.OutputFormat;
import us.ihmc.scs2.session.mcap.MCAPLogFileReader;
import us.ihmc.scs2.session.mcap.MCAPLogSession;
import us.ihmc.scs2.sessionVisualizer.jfx.SessionVisualizerIOTools;
import us.ihmc.scs2.sessionVisualizer.jfx.SessionVisualizerTopics;
import us.ihmc.scs2.sessionVisualizer.jfx.managers.BackgroundExecutorManager;
import us.ihmc.scs2.sessionVisualizer.jfx.managers.SessionVisualizerToolkit;
import us.ihmc.scs2.sessionVisualizer.jfx.session.SessionControlsController;
import us.ihmc.scs2.sessionVisualizer.jfx.session.log.LogSessionManagerController;
import us.ihmc.scs2.sessionVisualizer.jfx.session.log.LogSessionManagerController.TimeStringBinding;
import us.ihmc.scs2.sessionVisualizer.jfx.tools.JavaFXMissingTools;
import us.ihmc.scs2.sessionVisualizer.jfx.tools.PositiveIntegerValueFilter;
import us.ihmc.scs2.sharedMemory.interfaces.YoBufferPropertiesReadOnly;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;

public class MCAPLogSessionManagerController implements SessionControlsController
{
   private static final double THUMBNAIL_WIDTH = 200.0;

   public static final String LOG_FILE_KEY = "MCAPLogFilePath";
   private static final String ROBOT_MODEL_FILE_KEY = "MCAPRobotModelFilePath";

   private static final String DEFAULT_MODEL_TEXT_FIELD_TEXT = "Path to model file";
   private final ObjectProperty multiVideoViewerObjectProperty = new SimpleObjectProperty<>(this, "multiVideoThumbnailViewer", null);
   private final ObjectProperty activeSessionProperty = new SimpleObjectProperty<>(this, "activeSession", null);
   private final LongProperty desiredLogDTProperty = new SimpleLongProperty(this, "desiredLogDT", TimeUnit.MILLISECONDS.toNanos(1));
   @FXML
   private AnchorPane mainPane;
   @FXML
   private ProgressIndicator loadingSpinner;
   @FXML
   private Button openSessionButton, endSessionButton;
   @FXML
   private Label sessionNameLabel, dateLabel, logPathLabel;
   @FXML
   private Pane cropControlsContainer;
   @FXML
   private ToggleButton showTrimsButton;
   @FXML
   private Button startTrimToCurrentButton, endTrimToCurrentButton, resetTrimsButton, cropAndExportButton;
   @FXML
   private ComboBox outputFormatComboBox; // FIXME
   @FXML
   private JFXTrimSlider logPositionSlider;
   @FXML
   private Pane cropProgressMonitorPane; // FIXME
   @FXML
   private JFXTextField currentModelFilePathTextField;
   @FXML
   private TextField desiredLogDTTextField;
   @FXML
   private TextField bufferRecordTickPeriodTextField;
   @FXML
   private TitledPane thumbnailsTitledPane;
   @FXML
   private FlowPane videoThumbnailPane;
   @FXML
   private TitledPane consoleOutputTitledPane;
   @FXML
   private MCAPConsoleLogOutputPaneController consoleOutputPaneController;
   private Stage stage;
   private SessionVisualizerTopics topics;
   private JavaFXMessager messager;
   private BackgroundExecutorManager backgroundExecutorManager;

   private File defaultRobotModelFile = null;

   @Override
   public void initialize(SessionVisualizerToolkit toolkit)
   {
      stage = new Stage();

      topics = toolkit.getTopics();
      messager = toolkit.getMessager();
      backgroundExecutorManager = toolkit.getBackgroundExecutorManager();

      logPositionSlider.setValueFactory(param -> new TimeStringBinding(param.valueProperty(), position ->
      {
         if (activeSessionProperty.get() == null)
            return 0;
         MCAPLogFileReader mcapLogFileReader = activeSessionProperty.get().getMCAPLogFileReader();
         return mcapLogFileReader.getRelativeTimestampAtIndex(position.intValue());
      }));

      TextFormatter recordPeriodFormatter = new TextFormatter<>(new IntegerStringConverter(), 0, new PositiveIntegerValueFilter());
      bufferRecordTickPeriodTextField.setTextFormatter(recordPeriodFormatter);
      messager.bindBidirectional(topics.getBufferRecordTickPeriod(), recordPeriodFormatter.valueProperty(), false);

      desiredLogDTTextField.setTextFormatter(new TextFormatter<>(new LongStringConverter(), 1000L, new PositiveIntegerValueFilter()));
      MutableBoolean desiredLogDTTextFieldUpdate = new MutableBoolean(false);
      desiredLogDTTextField.textProperty().addListener((o, oldValue, newValue) ->
                                                       {
                                                          if (desiredLogDTTextFieldUpdate.isTrue())
                                                             return;
                                                          desiredLogDTTextFieldUpdate.setTrue();
                                                          try
                                                          {
                                                             desiredLogDTProperty.set(TimeUnit.MICROSECONDS.toNanos(Long.parseLong(newValue)));
                                                          }
                                                          catch (NumberFormatException e)
                                                          {
                                                             desiredLogDTTextField.setText(Long.toString(TimeUnit.NANOSECONDS.toMicros(desiredLogDTProperty.get())));
                                                          }
                                                          finally
                                                          {
                                                             desiredLogDTTextFieldUpdate.setFalse();
                                                          }
                                                       });
      desiredLogDTProperty.addListener((o, oldValue, newValue) ->
                                       {
                                          if (desiredLogDTTextFieldUpdate.isTrue())
                                             return;
                                          desiredLogDTTextFieldUpdate.setTrue();
                                          desiredLogDTTextField.setText(Long.toString(TimeUnit.NANOSECONDS.toMicros(newValue.longValue())));
                                          desiredLogDTTextFieldUpdate.setFalse();
                                       });

      ChangeListener activeSessionListener = (o, oldValue, newValue) ->
      {
         if (newValue == null)
         {
            clearControls();
         }
         else
         {
            messager.submitMessage(topics.getStartNewSessionRequest(), newValue);
            initializeControls(newValue);
         }
      };

      AtomicBoolean logPositionUpdate = new AtomicBoolean(true);

      logPositionSlider.valueProperty().addListener((o, oldValue, newValue) ->
                                                    {
                                                       MCAPLogSession logSession = activeSessionProperty.get();
                                                       if (logSession == null || logPositionUpdate.get())
                                                          return;

                                                       logSession.submitLogPositionRequest(newValue.intValue());
                                                    });

      AtomicBoolean sliderFeedbackEnabled = new AtomicBoolean(true);

      logPositionSlider.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> sliderFeedbackEnabled.set(false));
      logPositionSlider.addEventFilter(MouseEvent.MOUSE_DRAGGED, e -> sliderFeedbackEnabled.set(false));
      logPositionSlider.addEventFilter(MouseEvent.MOUSE_RELEASED, e -> sliderFeedbackEnabled.set(true));

      Consumer logPositionUpdateListener = properties ->
      {
         MCAPLogSession logSession = activeSessionProperty.get();

         if (sliderFeedbackEnabled.get())
         {
            int currentLogPosition = logSession.getMCAPLogFileReader().getCurrentIndex();

            JavaFXMissingTools.runLater(getClass(), () ->
            {
               if (logSession == null || logSession.getMCAPLogFileReader() == null)
                  return;

               if (currentLogPosition != logPositionSlider.valueProperty().intValue())
               {
                  logPositionUpdate.set(true);
                  logPositionSlider.setValue(currentLogPosition);
                  logPositionUpdate.set(false);
               }
            });
         }
      };

      activeSessionProperty.addListener((o, oldValue, newValue) ->
                                        {
                                           if (oldValue != null)
                                              oldValue.removeCurrentBufferPropertiesListener(logPositionUpdateListener);
                                           if (newValue != null)
                                           {
                                              newValue.addCurrentBufferPropertiesListener(logPositionUpdateListener);
                                              if (newValue.getInitialRobotModelFile() != null && defaultRobotModelFile == null)
                                              { // Display the robot model file path if it is available.
                                                 currentModelFilePathTextField.setText(newValue.getInitialRobotModelFile().getAbsolutePath());
                                              }
                                              else if (newValue.getInitialRobotModelFile() == null)
                                              {
                                                 currentModelFilePathTextField.setText(DEFAULT_MODEL_TEXT_FIELD_TEXT);
                                              }
                                              // Otherwise the text field will be updated when the robot model file is loaded.
                                           }
                                        });

      multiVideoViewerObjectProperty.addListener((o, oldValue, newValue) ->
                                                 {
                                                    if (oldValue != null)
                                                       oldValue.stop();
                                                    if (newValue != null)
                                                       newValue.start();
                                                 });

      if (toolkit.getSession() instanceof MCAPLogSession mcapLogSession)
      {
         desiredLogDTProperty.set(mcapLogSession.getDesiredLogDT());
         activeSessionProperty.set(mcapLogSession);
         initializeControls(mcapLogSession);
      }
      else
      {
         clearControls();
      }

      activeSessionProperty.addListener(activeSessionListener);

      thumbnailsTitledPane.expandedProperty().addListener((o, oldValue, newValue) -> JavaFXMissingTools.runLater(getClass(), stage::sizeToScene));
      consoleOutputTitledPane.expandedProperty().addListener((o, oldValue, newValue) -> JavaFXMissingTools.runLater(getClass(), stage::sizeToScene));

      logPositionSlider.showTrimProperty().bind(showTrimsButton.selectedProperty());
      logPositionSlider.showTrimProperty().addListener((o, oldValue, newValue) ->
                                                       {
                                                          if (newValue)
                                                             resetTrims();
                                                       });
      startTrimToCurrentButton.disableProperty().bind(showTrimsButton.selectedProperty().not());
      endTrimToCurrentButton.disableProperty().bind(showTrimsButton.selectedProperty().not());
      resetTrimsButton.disableProperty().bind(showTrimsButton.selectedProperty().not());
      outputFormatComboBox.disableProperty().bind(showTrimsButton.selectedProperty().not());
      cropAndExportButton.disableProperty().bind(showTrimsButton.selectedProperty().not());
      cropProgressMonitorPane.getChildren().addListener((ListChangeListener) c ->
      {
         c.getList().forEach(node -> JavaFXMissingTools.setAnchorConstraints(node, 0.0));
         stage.sizeToScene();
      });

      loadingSpinner.visibleProperty().addListener((o, oldValue, newValue) -> openSessionButton.setDisable(newValue));
      openSessionButton.setOnAction(e -> openLogFile());

      endSessionButton.setOnAction(e ->
                                   {
                                      MCAPLogSession logSession = activeSessionProperty.get();
                                      if (logSession != null)
                                         logSession.shutdownSession();
                                      activeSessionProperty.set(null);
                                   });

      messager.addFXTopicListener(topics.getDisableUserControls(), m ->
      {
         openSessionButton.setDisable(m);
         endSessionButton.setDisable(m);
         cropControlsContainer.setDisable(m);
         logPositionSlider.setDisable(m);
      });

      consoleOutputPaneController.initialize(toolkit);

      stage.setScene(new Scene(mainPane));
      stage.setTitle("MCAP Log session controls");
      stage.getIcons().add(SessionVisualizerIOTools.LOG_SESSION_IMAGE);
      toolkit.getMainWindow().addEventHandler(WindowEvent.WINDOW_CLOSE_REQUEST, e ->
      {
         if (!e.isConsumed())
            shutdown();
      });
   }

   private void initializeControls(MCAPLogSession session)
   {
      File logFile = session.getMCAPFile();
      MCAPLogFileReader mcapLogFileReader = session.getMCAPLogFileReader();

      sessionNameLabel.setText(session.getSessionName());
      dateLabel.setText(LogSessionManagerController.parseTimestamp(logFile.getName()));
      logPathLabel.setText(logFile.getAbsolutePath());
      endSessionButton.setDisable(false);
      logPositionSlider.setDisable(false);
      logPositionSlider.setValue(0.0);
      logPositionSlider.setMin(0.0);
      logPositionSlider.setMax(mcapLogFileReader.getNumberOfEntries() - 1);
      cropControlsContainer.setDisable(false);
      FFMPEGMultiVideoDataReader multiReader = new FFMPEGMultiVideoDataReader(logFile.getParentFile(), backgroundExecutorManager);
      multiReader.readVideoFrameNow(mcapLogFileReader.getCurrentRelativeTimestamp());
      mcapLogFileReader.getCurrentTimestamp().addListener(v -> multiReader.readVideoFrameInBackground(mcapLogFileReader.getCurrentRelativeTimestamp()));
      multiVideoViewerObjectProperty.set(new FFMPEGMultiVideoViewer(stage, videoThumbnailPane, multiReader, THUMBNAIL_WIDTH));
      boolean logHasVideos = multiReader.getNumberOfVideos() > 0;
      thumbnailsTitledPane.setText(logHasVideos ? "Logged videos" : "No video");
      thumbnailsTitledPane.setExpanded(logHasVideos);
      thumbnailsTitledPane.setDisable(!logHasVideos);
      // TODO add a listener to check if there are console logs, and if not, disable the console output pane.

      consoleOutputPaneController.startSession(session);

      JavaFXMissingTools.runNFramesLater(5, () -> stage.sizeToScene());
      JavaFXMissingTools.runNFramesLater(6, () -> stage.toFront());
   }

   private void clearControls()
   {
      sessionNameLabel.setText("N/D");
      dateLabel.setText("N/D");
      logPathLabel.setText("N/D");
      endSessionButton.setDisable(true);
      logPositionSlider.setDisable(true);
      showTrimsButton.setSelected(false);
      cropControlsContainer.setDisable(true);
      multiVideoViewerObjectProperty.set(null);
      consoleOutputPaneController.stopSession();
   }

   public void openLogFile()
   {
      FileChooser fileChooser = new FileChooser();
      fileChooser.setInitialDirectory(SessionVisualizerIOTools.getDefaultFilePath(LOG_FILE_KEY));
      fileChooser.getExtensionFilters().add(new ExtensionFilter("MCAP Log file", "*.mcap"));
      fileChooser.setTitle("Choose MCAP log file");
      File result = fileChooser.showOpenDialog(stage);
      if (result == null)
         return;

      unloadSession();
      setIsLoading(true);

      backgroundExecutorManager.executeInBackground(() ->
                                                    {
                                                       try
                                                       {
                                                          LogTools.info("Creating log session.");
                                                          MCAPLogSession newSession = new MCAPLogSession(result,
                                                                                                         desiredLogDTProperty.get(),
                                                                                                         defaultRobotModelFile);
                                                          LogTools.info("Created log session.");
                                                          JavaFXMissingTools.runLater(getClass(), () -> activeSessionProperty.set(newSession));
                                                          SessionVisualizerIOTools.setDefaultFilePath(LOG_FILE_KEY, result);
                                                       }
                                                       catch (Exception ex)
                                                       {
                                                          ex.printStackTrace();
                                                          setIsLoading(false);
                                                       }
                                                    });
   }

   public void setIsLoading(boolean isLoading)
   {
      loadingSpinner.setVisible(isLoading);
   }

   @FXML
   public void resetTrims()
   {
      logPositionSlider.setTrimStartValue(0.0);
      logPositionSlider.setTrimEndValue(logPositionSlider.getMax());
   }

   @FXML
   public void snapStartTrimToCurrent()
   {
      logPositionSlider.setTrimStartValue(logPositionSlider.getValue());
   }

   @FXML
   public void snapEndTrimToCurrent()
   {
      logPositionSlider.setTrimEndValue(logPositionSlider.getValue());
   }

   @FXML
   public void cropAndExport() throws IOException
   {
      MCAPLogFileReader mcapLogFileReader = activeSessionProperty.get().getMCAPLogFileReader();
      MCAPLogCropper cropper = new MCAPLogCropper(mcapLogFileReader.getMCAP());
      long startTimestamp = mcapLogFileReader.getTimestampAtIndex((int) logPositionSlider.getTrimStartValue());
      long endTimestamp = mcapLogFileReader.getTimestampAtIndex((int) logPositionSlider.getTrimEndValue());
      cropper.setStartTimestamp(startTimestamp);
      cropper.setEndTimestamp(endTimestamp);
      cropper.setOutputFormat(outputFormatComboBox.getValue());
      FileChooser fileChooser = new FileChooser();
      fileChooser.setInitialDirectory(SessionVisualizerIOTools.getDefaultFilePath(LOG_FILE_KEY));
      fileChooser.getExtensionFilters().add(new ExtensionFilter("MCAP Log file", "*.mcap"));
      fileChooser.setTitle("Choose MCAP log file");
      File result = fileChooser.showSaveDialog(stage);
      if (result == null)
         return;

      backgroundExecutorManager.executeInBackground(() ->
                                                    {
                                                       setIsLoading(true);
                                                       messager.submitMessage(topics.getDisableUserControls(), true);

                                                       try (FileOutputStream os = new FileOutputStream(result))
                                                       {
                                                          cropper.crop(os);
                                                       }
                                                       catch (IOException e)
                                                       {
                                                          e.printStackTrace();
                                                       }
                                                       finally
                                                       {
                                                          messager.submitMessage(topics.getDisableUserControls(), false);
                                                          setIsLoading(false);
                                                       }
                                                    });
   }

   @Override
   public void notifySessionLoaded()
   {
      setIsLoading(false);
   }

   @Override
   public void unloadSession()
   {
   }

   @Override
   public void shutdown()
   {
      // TODO Auto-generated method stub

      stage.close();
   }

   @Override
   public Stage getStage()
   {
      return stage;
   }

   @FXML
   public void requestLoadRobotModelFile(ActionEvent actionEvent)
   {
      File result = SessionVisualizerIOTools.showOpenDialog(stage,
                                                            "Choose robot model file",
                                                            new ExtensionFilter("Robot model file", "*.urdf", "*.sdf"),
                                                            ROBOT_MODEL_FILE_KEY);
      if (result == null)
         return;

      backgroundExecutorManager.executeInBackground(() -> submitRobotDefinitionRequest(result));
   }

   private void submitRobotDefinitionRequest(File result)
   {
      MCAPLogSession logSession = activeSessionProperty.get();
      if (logSession == null)
      { // Save the file for the next session.
         defaultRobotModelFile = result;
         currentModelFilePathTextField.setText(result.getAbsolutePath());
         return;
      }

      boolean hasARobot = !logSession.getRobotDefinitions().isEmpty();
      SessionRobotDefinitionListChange request;
      if (hasARobot)
         request = SessionRobotDefinitionListChange.replace(result, logSession.getRobotDefinitions().get(0));
      else
         request = SessionRobotDefinitionListChange.add(result);

      messager.submitMessage(topics.getSessionRobotDefinitionListChangeRequest(), request);
      currentModelFilePathTextField.setText("Loading...");
      TopicListener listener = new TopicListener()
      {
         @Override
         public void receivedMessageForTopic(SessionRobotDefinitionListChange m)
         {
            if (m.getAddedRobotDefinition() != null)
            {
               currentModelFilePathTextField.setText(result.getAbsolutePath());
               defaultRobotModelFile = result;
            }
            else
            {
               currentModelFilePathTextField.setText("Failed to load.");
               defaultRobotModelFile = null;
            }
            messager.removeFXTopicListener(topics.getSessionRobotDefinitionListChangeState(), this);
         }
      };
      messager.addFXTopicListener(topics.getSessionRobotDefinitionListChangeState(), listener);
   }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy