package org.janelia.saalfeldlab.paintera.ui.dialogs.create; import bdv.util.volatiles.SharedQueue; import bdv.viewer.Source; import com.sun.javafx.application.PlatformImpl; import javafx.application.Platform; import javafx.beans.property.DoubleProperty; import javafx.beans.property.IntegerProperty; import javafx.beans.property.LongProperty; import javafx.beans.property.StringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.Alert; import javafx.scene.control.Button; import javafx.scene.control.ButtonType; import javafx.scene.control.Menu; import javafx.scene.control.MenuButton; import javafx.scene.control.MenuItem; import javafx.scene.control.TextField; import javafx.scene.control.TitledPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.Region; import javafx.scene.layout.VBox; import javafx.stage.Stage; import javafx.util.Pair; import net.imglib2.RandomAccessibleInterval; import net.imglib2.img.cell.AbstractCellImg; import net.imglib2.img.cell.CellGrid; import net.imglib2.realtransform.AffineTransform3D; import net.imglib2.type.numeric.integer.UnsignedByteType; import net.imglib2.type.volatiles.VolatileUnsignedByteType; import org.janelia.saalfeldlab.fx.ui.DirectoryField; import org.janelia.saalfeldlab.fx.ui.Exceptions; import org.janelia.saalfeldlab.fx.ui.NamedNode; import org.janelia.saalfeldlab.fx.ui.ObjectField; import org.janelia.saalfeldlab.fx.ui.SpatialField; import org.janelia.saalfeldlab.n5.N5FSReader; import org.janelia.saalfeldlab.n5.N5Reader; import org.janelia.saalfeldlab.paintera.Paintera; import org.janelia.saalfeldlab.paintera.data.DataSource; import org.janelia.saalfeldlab.paintera.data.n5.N5DataSource; import org.janelia.saalfeldlab.paintera.data.n5.N5FSMeta; import org.janelia.saalfeldlab.paintera.data.n5.ReflectionException; import org.janelia.saalfeldlab.paintera.state.SourceState; import org.janelia.saalfeldlab.paintera.ui.PainteraAlerts; import org.janelia.saalfeldlab.util.n5.N5Data; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Optional; public class CreateDataset { private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private static final double NAME_WIDTH = 150; private final Source<?> currentSource; private final List<SourceState<?, ?>> allSources; private final ObservableList<MipMapLevel> mipmapLevels = FXCollections.observableArrayList(); private final Node mipmapLevelsNode = MipMapLevel.makeNode( mipmapLevels, 100, NAME_WIDTH, 30, ObjectField.SubmitOn.values()); private final MenuItem populateFromCurrentSource = new MenuItem("_From Current Source"); private final Menu populateFromSource = new Menu("_Select Source"); private final MenuButton setFromButton = new MenuButton("_Populate", null, populateFromSource, populateFromCurrentSource); private final HBox setFromCurrentBox = new HBox(new Region(), setFromButton); { HBox.setHgrow(setFromCurrentBox.getChildren().get(0), Priority.ALWAYS); populateFromCurrentSource.setOnAction(e -> { e.consume(); this.populateFrom(currentSource()); }); } private final TextField name = new TextField(); { name.setMaxWidth(Double.MAX_VALUE); } private final DirectoryField n5Container = new DirectoryField(System.getProperty("user.home"), 100); private final ObjectField<String, StringProperty> dataset = ObjectField.stringField( "", ObjectField.SubmitOn.values()); { dataset.valueProperty().addListener((obs, oldv, newv) -> { if (newv != null && Optional.ofNullable(name.textProperty().get()).orElse("").equals("")) { final String[] split = newv.split("/"); name.setText(split[split.length - 1]); } }); } private final SpatialField<LongProperty> dimensions = SpatialField.longField( 1, d -> d > 0, 100, ObjectField.SubmitOn.values()); private final SpatialField<IntegerProperty> blockSize = SpatialField.intField( 1, d -> d > 0, 100, ObjectField.SubmitOn.values()); private final SpatialField<DoubleProperty> resolution = SpatialField.doubleField( 1.0, r -> r > 0, 100, ObjectField.SubmitOn.values()); private final SpatialField<DoubleProperty> offset = SpatialField.doubleField( 0.0, o -> true, 100, ObjectField.SubmitOn.values()); private final TitledPane scaleLevels = new TitledPane("Scale Levels", mipmapLevelsNode); private final VBox pane = new VBox( NamedNode.nameIt("Name", NAME_WIDTH, true, name), NamedNode.nameIt("N5", NAME_WIDTH, true, n5Container.asNode()), NamedNode.nameIt("Dataset", NAME_WIDTH, true, dataset.textField()), NamedNode.nameIt("Dimensions", NAME_WIDTH, false, NamedNode.bufferNode(new Region()), dimensions.getNode() ), NamedNode.nameIt("Block Size", NAME_WIDTH, false, NamedNode.bufferNode(new Region()), blockSize.getNode()), NamedNode.nameIt("Resolution", NAME_WIDTH, false, NamedNode.bufferNode(new Region()), resolution.getNode() ), NamedNode.nameIt("Offset", NAME_WIDTH, false, NamedNode.bufferNode(new Region()), offset.getNode()), setFromCurrentBox, scaleLevels ); public CreateDataset( Source<?> currentSource, SourceState<?, ?>... allSources) { this(currentSource, Arrays.asList(allSources)); } public CreateDataset( Source<?> currentSource, Collection<SourceState<?, ?>> allSources) { this.currentSource = currentSource; this.allSources = new ArrayList<>(allSources); this.allSources.forEach(s -> { final MenuItem mi = new MenuItem(s.nameProperty().get()); mi.setOnAction(e -> this.populateFrom(s.getDataSource())); mi.setMnemonicParsing(false); this.populateFromSource.getItems().add(mi); }); this.populateFromSource.setVisible(this.allSources.size() > 0); Optional.ofNullable(currentSource).ifPresent(this::populateFrom); } public Optional<Pair<N5FSMeta, String>> showDialog() { final Alert alert = PainteraAlerts.confirmation("C_reate", "_Cancel", true); alert.setHeaderText("Create new Label dataset"); alert.getDialogPane().setContent(this.pane); alert.getDialogPane().lookupButton(ButtonType.OK).addEventFilter( ActionEvent.ACTION, e -> { final String container = this.n5Container.directoryProperty().getValue().getAbsolutePath(); final String dataset = this.dataset.valueProperty().get(); final String name = this.name.getText(); try { LOG.debug("Trying to create empty label dataset `{}' in container `{}'", dataset, container); if (dataset == null || dataset.equals("")) throw new IOException("Dataset not specified!"); if (name == null || name.equals("")) throw new IOException("Name not specified!"); N5Data.createEmptyLabelDataset( container, dataset, dimensions.getAs(new long[3]), blockSize.getAs(new int[3]), resolution.getAs(new double[3]), offset.getAs(new double[3]), mipmapLevels.stream().map(MipMapLevel::downsamplingFactors).toArray(double[][]::new), mipmapLevels.stream().mapToInt(MipMapLevel::maxNumEntries).toArray() ); } catch (IOException ex) { LOG.error("Unable to create empty dataset", ex); e.consume(); Alert exceptionAlert = Exceptions.exceptionAlert( Paintera.Constants.NAME, "Unable to create new dataset: " + ex.getMessage(), ex ); exceptionAlert.show(); } }); final Optional<ButtonType> button = alert.showAndWait(); final String container = this.n5Container.directoryProperty().getValue().getAbsolutePath(); final String dataset = this.dataset.valueProperty().get(); final String name = this.name.getText(); return button.filter( ButtonType.OK::equals ).map( bt -> new Pair<>(new N5FSMeta(container, dataset), name)); } private Source<?> currentSource() { return this.currentSource; } private void populateFrom(Source<?> source) { if (source == null) return; if (source instanceof N5DataSource<?, ?>) { N5DataSource<?, ?> n5s = (N5DataSource<?, ?>) source; if (n5s.meta() instanceof N5FSMeta) { n5Container.directoryProperty().setValue(new File(((N5FSMeta) n5s.meta()).basePath())); } } final RandomAccessibleInterval<?> data = source.getSource(0, 0); this.dimensions.getX().valueProperty().set(data.dimension(0)); this.dimensions.getY().valueProperty().set(data.dimension(1)); this.dimensions.getZ().valueProperty().set(data.dimension(2)); if (data instanceof AbstractCellImg<?, ?, ?, ?>) { final CellGrid grid = ((AbstractCellImg<?, ?, ?, ?>) data).getCellGrid(); this.blockSize.getX().valueProperty().set(grid.cellDimension(0)); this.blockSize.getY().valueProperty().set(grid.cellDimension(1)); this.blockSize.getZ().valueProperty().set(grid.cellDimension(2)); } AffineTransform3D transform = new AffineTransform3D(); source.getSourceTransform(0, 0, transform); this.resolution.getX().valueProperty().set(transform.get(0, 0)); this.resolution.getY().valueProperty().set(transform.get(1, 1)); this.resolution.getZ().valueProperty().set(transform.get(2, 2)); this.offset.getX().valueProperty().set(transform.get(0, 3)); this.offset.getY().valueProperty().set(transform.get(1, 3)); this.offset.getZ().valueProperty().set(transform.get(2, 3)); } public static void main(String[] args) throws IOException, ReflectionException { PlatformImpl.startup(() -> { }); final AffineTransform3D tf = new AffineTransform3D(); tf.set( 4.0, 0.0, 0.0, 1.0, 0.0, 5.0, 0.0, 5.0, 0.0, 0.0, 40., -1. ); final N5Reader reader = new N5FSReader( "/home/phil/local/tmp/sample_a_padded_20160501.n5"); final DataSource<UnsignedByteType, VolatileUnsignedByteType> raw = N5Data.openRawAsSource( reader, "volumes/raw/data/s0", tf, new SharedQueue(1, 20), 1, "NAME" ); final CreateDataset cd = new CreateDataset(raw); Platform.runLater(() -> { final Button b = new Button("BUTTON"); b.setOnAction(e -> LOG.info( "Got new dataset meta: {}", cd.showDialog())); final Scene scene = new Scene(b); final Stage stage = new Stage(); stage.setScene(scene); stage.show(); }); } }