Skip to content

Commit

Permalink
Merge branch 'release/alpha-20191122'
Browse files Browse the repository at this point in the history
  • Loading branch information
Sylvain Bertrand committed Nov 22, 2019
2 parents 1fe034d + 7ff337d commit 42e94a5
Show file tree
Hide file tree
Showing 18 changed files with 217 additions and 80 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ plugins {

ihmc {
group = "us.ihmc"
version = "alpha-20191117"
version = "alpha-20191122"
vcsUrl = "https://github.com/ihmcrobotics/simulation-construction-set-2"
openSource = true

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package us.ihmc.scs2.sessionVisualizer.session.log;

import java.nio.ByteBuffer;

import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import us.ihmc.codecs.generated.RGBPicture;
import us.ihmc.codecs.generated.YUVPicture;
import us.ihmc.codecs.loader.NativeLibraryLoader;
import us.ihmc.codecs.util.ByteBufferProvider;

public class JavaFXPictureConverter
{
static
{
NativeLibraryLoader.loadIHMCVideoCodecsLibrary();
}

private ByteBufferProvider byteBufferProvider = new ByteBufferProvider();

/**
* Convert YUVPicture to BufferedImage, minimizing object allocation
*
* @param picture YUVPicture to convert
* @return new BufferedImage.
*/
public WritableImage toFXImage(YUVPicture picture)
{
return toFXImage(picture, null);
}

/**
* Convert YUVPicture to BufferedImage, minimizing object allocation
*
* @param picture YUVPicture to convert
* @param imageToPack Image to output to. If picture.size() != imageToPack.size() then a new
* BufferedImage is allocated
* @return imageToPack if sizes match, new BufferedImage otherwise.
*/
public WritableImage toFXImage(YUVPicture picture, WritableImage imageToPack)
{
RGBPicture rgb = picture.toRGB();
WritableImage img = toFXImage(rgb, imageToPack);
rgb.delete();
return img;
}

/**
* Convert RGBPicture to BufferedImage, minimizing object allocation
*
* @param picture RGBPicture to convert
* @param imageToPack Image to output to. If picture.size() != imageToPack.size() then a new
* BufferedImage is allocated
* @return imageToPack if sizes match, new BufferedImage otherwise.
*/
public WritableImage toFXImage(RGBPicture picture, WritableImage imageToPack)
{
WritableImage target = imageToPack;
int w = picture.getWidth();
int h = picture.getHeight();
if (target == null || target.getWidth() != w || target.getHeight() != h)
{
target = new WritableImage(w, h);
}
PixelWriter pixelWriter = target.getPixelWriter();

ByteBuffer dstBuffer = byteBufferProvider.getOrCreateBuffer(w * h * 3);
picture.get(dstBuffer);

int x = 0;
int y = 0;

while (dstBuffer.position() < dstBuffer.limit())
{
int b = dstBuffer.get() & 0xff;
int g = dstBuffer.get() & 0xff;
int r = dstBuffer.get() & 0xff;
int argb = (0xff << 24) | (r << 16) | (g << 8) | b;
pixelWriter.setArgb(x, y, argb);
x++;
if (x >= w)
{
x = 0;
y++;
}
}
return target;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import java.util.function.ToLongFunction;

import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXSlider;
import com.jfoenix.controls.JFXSpinner;
import com.jfoenix.controls.JFXToggleButton;

Expand All @@ -35,10 +34,12 @@
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
import javafx.stage.FileChooser.ExtensionFilter;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import javafx.util.Callback;
import us.ihmc.log.LogTools;
import us.ihmc.robotDataLogger.LogProperties;
import us.ihmc.robotDataLogger.logger.LogPropertiesReader;
import us.ihmc.scs2.session.Session;
import us.ihmc.scs2.sessionVisualizer.SessionVisualizerIOTools;
Expand Down Expand Up @@ -93,22 +94,13 @@ public void initialize(SessionVisualizerToolkit toolkit)

backgroundExecutorManager = toolkit.getBackgroundExecutorManager();

mainPane.getStylesheets().add(SessionVisualizerIOTools.GENERAL_STYLESHEET.toExternalForm());

logPositionSlider.setValueFactory(new Callback<JFXSlider, StringBinding>()
logPositionSlider.setValueFactory(param -> new TimeStringBinding(param.valueProperty(), position ->
{
@Override
public StringBinding call(JFXSlider param)
{
return new TimeStringBinding(param.valueProperty(), position ->
{
if (activeSessionProperty.get() == null)
return 0;
LogDataReader logDataReader = activeSessionProperty.get().getLogDataReader();
return logDataReader.getRelativeTimestamp(position.intValue());
});
}
});
if (activeSessionProperty.get() == null)
return 0;
LogDataReader logDataReader = activeSessionProperty.get().getLogDataReader();
return logDataReader.getRelativeTimestamp(position.intValue());
}));

ChangeListener<? super LogSession> activeSessionListener = (o, oldValue, newValue) ->
{
Expand Down Expand Up @@ -137,7 +129,7 @@ public StringBinding call(JFXSlider param)
LogPropertiesReader logProperties = newValue.getLogProperties();

sessionNameLabel.setText(newValue.getSessionName());
dateLabel.setText(logProperties.getTimestampAsString());
dateLabel.setText(getDate(logProperties));
logPathLabel.setText(logDirectory.getAbsolutePath());
endSessionButton.setDisable(false);
logPositionSlider.setDisable(false);
Expand Down Expand Up @@ -231,10 +223,11 @@ public StringBinding call(JFXSlider param)
openSessionButton.disableProperty().bind(loadingSpinner.visibleProperty());
openSessionButton.setOnAction(e ->
{
DirectoryChooser directoryChooser = new DirectoryChooser();
directoryChooser.setInitialDirectory(SessionVisualizerIOTools.getDefaultFilePath(LOG_FILE_KEY));
directoryChooser.setTitle("Choose log directory");
File result = directoryChooser.showDialog(stage);
FileChooser fileChooser = new FileChooser();
fileChooser.setInitialDirectory(SessionVisualizerIOTools.getDefaultFilePath(LOG_FILE_KEY));
fileChooser.getExtensionFilters().add(new ExtensionFilter("Log property file", "*.log"));
fileChooser.setTitle("Choose log directory");
File result = fileChooser.showOpenDialog(stage);
if (result == null)
return;

Expand All @@ -247,7 +240,7 @@ public StringBinding call(JFXSlider param)
try
{
LogTools.info("Creating log session.");
newSession = new LogSession(result, null); // TODO Need a progress window
newSession = new LogSession(result.getParentFile(), null); // TODO Need a progress window
LogTools.info("Created log session.");
Platform.runLater(() -> activeSessionProperty.set(newSession));
SessionVisualizerIOTools.setDefaultFilePath(LOG_FILE_KEY, result);
Expand Down Expand Up @@ -378,4 +371,18 @@ protected String computeValue()
return time;
}
}

private static String getDate(LogProperties logProperties)
{
String timestampAsString = logProperties.getTimestampAsString();

String year = timestampAsString.substring(0, 4);
String month = timestampAsString.substring(4, 6);
String day = timestampAsString.substring(6, 8);
String hour = timestampAsString.substring(9, 11);
String minute = timestampAsString.substring(11, 13);
String second = timestampAsString.substring(13, 15);

return year + "-" + month + "-" + day + " " + hour + ":" + minute + ":" + second;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ public static int crop(File source, File target, long startPTS, long endPTS, Pro
}
}

builder.close();
if (builder != null)
builder.close();
demuxer.delete();

return frameRate;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
package us.ihmc.scs2.sessionVisualizer.session.log;

import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.commons.lang3.mutable.MutableObject;

import gnu.trove.list.array.TLongArrayList;
import javafx.scene.image.WritableImage;
import us.ihmc.codecs.demuxer.MP4VideoDemuxer;
import us.ihmc.codecs.generated.YUVPicture;
import us.ihmc.codecs.yuv.YUVPictureConverter;
import us.ihmc.concurrent.ConcurrentCopier;
import us.ihmc.robotDataLogger.Camera;

public class VideoDataReader
Expand All @@ -29,16 +30,15 @@ public class VideoDataReader
private long bmdTimeBaseDen;

private final MP4VideoDemuxer demuxer;

private final YUVPictureConverter converter = new YUVPictureConverter();
private final JavaFXPictureConverter converter = new JavaFXPictureConverter();

private int currentlyShowingIndex = 0;
private long currentlyShowingRobotTimestamp = 0;
private long upcomingRobotTimestamp = 0;

private final File videoFile;
private final Camera camera;
private final AtomicReference<BufferedImage> currentFrame = new AtomicReference<>(null);
private final ConcurrentCopier<MutableObject<WritableImage>> imageBuffer = new ConcurrentCopier<>(MutableObject::new);

public VideoDataReader(Camera camera, File dataDirectory, boolean hasTimeBase) throws IOException
{
Expand Down Expand Up @@ -100,7 +100,10 @@ public void readVideoFrame(long timestamp)
{
demuxer.seekToPTS(videoTimestamp);
YUVPicture nextFrame = demuxer.getNextFrame();
currentFrame.set(converter.toBufferedImage(nextFrame, null));
MutableObject<WritableImage> copyForWriting = imageBuffer.getCopyForWriting();
copyForWriting.setValue(converter.toFXImage(nextFrame, copyForWriting.getValue()));

imageBuffer.commit();
}
catch (IOException e)
{
Expand Down Expand Up @@ -276,8 +279,8 @@ public Camera getCamera()
return camera;
}

public BufferedImage pollCurrentFrame()
public WritableImage pollCurrentFrame()
{
return currentFrame.getAndSet(null);
return imageBuffer.getCopyForReading().getValue();
}
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
package us.ihmc.scs2.sessionVisualizer.session.log;

import java.awt.image.BufferedImage;

import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.embed.swing.SwingFXUtils;
import javafx.geometry.Rectangle2D;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.Window;
Expand All @@ -26,27 +24,33 @@

public class VideoViewer
{
private static final double THUMBNAIL_HIGHLIGHT_SCALE = 1.05;

private final ImageView thumbnail = new ImageView();
private final StackPane thumbnailContainer = new StackPane(thumbnail);
private final ImageView videoView = new ImageView();

private final BooleanProperty updateVideoView = new SimpleBooleanProperty(this, "updateVideoView", false);
private final ObjectProperty<Stage> videoWindowProperty = new SimpleObjectProperty<>(this, "videoWindow", null);
private final VideoDataReader reader;
private final double defaultThumbnailSize;

public VideoViewer(Window owner, VideoDataReader reader, double defaultThumbnailSize)
{
this.reader = reader;
this.defaultThumbnailSize = defaultThumbnailSize;
thumbnail.setPreserveRatio(true);
videoView.setPreserveRatio(true);
thumbnail.setFitWidth(defaultThumbnailSize);
thumbnail.setOnMouseEntered(e ->
{
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(0.1), new KeyValue(thumbnail.fitWidthProperty(), 1.05 * defaultThumbnailSize)));
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(0.1),
new KeyValue(thumbnail.fitWidthProperty(), THUMBNAIL_HIGHLIGHT_SCALE * defaultThumbnailSize, Interpolator.EASE_BOTH)));
timeline.playFromStart();
});
thumbnail.setOnMouseExited(e ->
{
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(0.1), new KeyValue(thumbnail.fitWidthProperty(), defaultThumbnailSize)));
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(0.1), new KeyValue(thumbnail.fitWidthProperty(), defaultThumbnailSize, Interpolator.EASE_BOTH)));
timeline.playFromStart();
});

Expand Down Expand Up @@ -117,15 +121,18 @@ protected void layoutChildren()

public void update()
{
BufferedImage currentFrame = reader.pollCurrentFrame();
Image currentFrame = reader.pollCurrentFrame();

if (currentFrame == null)
return;

WritableImage newFrame = SwingFXUtils.toFXImage(currentFrame, null);
thumbnail.setImage(newFrame);
thumbnailContainer.setPrefWidth(THUMBNAIL_HIGHLIGHT_SCALE * defaultThumbnailSize);
thumbnailContainer.setPrefHeight(THUMBNAIL_HIGHLIGHT_SCALE * defaultThumbnailSize * currentFrame.getHeight() / currentFrame.getWidth());

thumbnail.setImage(currentFrame);

if (updateVideoView.get())
videoView.setImage(newFrame);
videoView.setImage(currentFrame);
}

public void stop()
Expand All @@ -139,6 +146,6 @@ public void stop()

public Node getThumbnail()
{
return thumbnail;
return thumbnailContainer;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,20 @@ public void crop(File destination, int from, int to, ProgressConsumer progressCo
if (!destination.isDirectory())
{
progressConsumer.error("Destination " + destination.getAbsolutePath() + " already exists.");
progressConsumer.progress(0.0);
progressConsumer.done();
return;
}
else if (destination.list().length > 0)
{
progressConsumer.error("Destination " + destination.getAbsolutePath() + " is not empty.");
progressConsumer.progress(0.0);
progressConsumer.done();
return;
}
}
else if (!destination.mkdir())
{
progressConsumer.error("Cannot make directory " + destination.getAbsolutePath());
progressConsumer.progress(0.0);
progressConsumer.done();
return;
}

Expand Down
Loading

0 comments on commit 42e94a5

Please sign in to comment.