/* * Copyright 2015 Alexander Nozik. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package inr.numass.viewer; /* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ import hep.dataforge.context.Context; import hep.dataforge.context.ProcessManager; import hep.dataforge.io.ColumnedDataWriter; import hep.dataforge.meta.Meta; import hep.dataforge.meta.MetaBuilder; import hep.dataforge.plots.XYPlotFrame; import hep.dataforge.plots.XYPlottable; import hep.dataforge.plots.data.ChangeablePlottableData; import hep.dataforge.plots.data.PlotDataUtils; import hep.dataforge.plots.data.PlottableData; import hep.dataforge.plots.fx.PlotContainer; import hep.dataforge.plots.jfreechart.JFreeChartFrame; import hep.dataforge.storage.commons.JSONMetaWriter; import hep.dataforge.tables.DataPoint; import hep.dataforge.tables.ListTable; import hep.dataforge.tables.MapPoint; import hep.dataforge.tables.Table; import hep.dataforge.tables.XYAdapter; import inr.numass.storage.NMPoint; import inr.numass.storage.NumassData; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.ResourceBundle; import java.util.function.Predicate; import java.util.stream.Collectors; import javafx.application.Platform; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.concurrent.Task; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.fxml.Initializable; import javafx.geometry.Insets; import javafx.geometry.Orientation; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.ChoiceBox; import javafx.scene.control.Label; import javafx.scene.control.Separator; import javafx.scene.control.Tab; import javafx.scene.control.TextArea; import javafx.scene.control.TextField; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.VBox; import javafx.stage.FileChooser; import javafx.util.converter.NumberStringConverter; import org.controlsfx.control.CheckListView; import org.controlsfx.control.RangeSlider; import org.controlsfx.validation.ValidationSupport; import org.controlsfx.validation.Validator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * FXML Controller class * * @author darksnake */ public class NumassLoaderViewComponent extends AnchorPane implements Initializable { private final Context context; Logger logger = LoggerFactory.getLogger(NumassLoaderViewComponent.class); private NumassData data; private PlotContainer detectorPlot; private XYPlotFrame spectrumPlotFrame; private ChangeablePlottableData spectrumData; private List points; private ChoiceBox detectorBinningSelector; private CheckBox detectorNormalizeSwitch; private Button detectorDataExportButton; @FXML private AnchorPane detectorPlotPane; @FXML private CheckListView detectorPointListView; @FXML private Tab detectorTab; @FXML private Tab hvTab; @FXML private Tab spectrumTab; @FXML private TextArea infoTextBox; @FXML private AnchorPane spectrumPlotPane; @FXML private VBox spectrumOptionsPane; @FXML private TextField lowChannelField; @FXML private TextField upChannelField; @FXML private RangeSlider channelSlider; @FXML private Button spectrumExportButton; @FXML private TextField dTimeField; public NumassLoaderViewComponent(Context context) { this.context = context; FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/NumassLoaderView.fxml")); loader.setRoot(this); loader.setController(this); try { loader.load(); } catch (IOException ex) { throw new RuntimeException(ex); } } /** * Initializes the controller class. * * @param url * @param rb */ @Override public void initialize(URL url, ResourceBundle rb) { //setup detector pane plot and sidebar Label l = new Label("Bin size:"); l.setPadding(new Insets(5)); detectorBinningSelector = new ChoiceBox<>(FXCollections.observableArrayList(1, 2, 5, 10, 20, 50)); detectorBinningSelector.setMaxWidth(Double.MAX_VALUE); detectorBinningSelector.getSelectionModel().select(4); detectorNormalizeSwitch = new CheckBox("Normailize"); detectorNormalizeSwitch.setSelected(true); detectorNormalizeSwitch.setPadding(new Insets(5)); detectorPlot = PlotContainer.anchorTo(detectorPlotPane); detectorPlot.addToSideBar(0, l, detectorBinningSelector, detectorNormalizeSwitch, new Separator(Orientation.HORIZONTAL)); detectorDataExportButton = new Button("Export"); detectorDataExportButton.setMaxWidth(Double.MAX_VALUE); detectorDataExportButton.setOnAction(this::onExportButtonClick); detectorPlot.addToSideBar(detectorDataExportButton); detectorPlot.setSideBarPosition(0.7); //setup spectrum pane lowChannelField.textProperty().bindBidirectional(channelSlider.lowValueProperty(), new NumberStringConverter()); upChannelField.textProperty().bindBidirectional(channelSlider.highValueProperty(), new NumberStringConverter()); channelSlider.setHighValue(1900d); channelSlider.setLowValue(300d); ChangeListener rangeChangeListener = (ObservableValue observable, Number oldValue, Number newValue) -> { updateSpectrumPane(points); }; dTimeField.textProperty().addListener((ObservableValue observable, String oldValue, String newValue) -> { updateSpectrumPane(points); }); channelSlider.lowValueProperty().addListener(rangeChangeListener); channelSlider.highValueProperty().addListener(rangeChangeListener); ValidationSupport validationSupport = new ValidationSupport(); Predicate isNumber = (String t) -> { try { Double.parseDouble(t); return true; } catch (NumberFormatException | NullPointerException ex) { return false; } }; validationSupport.registerValidator(dTimeField, Validator.createPredicateValidator(isNumber, "Must be number")); } public NumassData getData() { return data; } public void loadData(NumassData data) { this.data = data; if (data != null) { context.processManager().>post("viewer.numass.load", (ProcessManager.Callback callback) -> { callback.updateTitle("Load numass data (" + data.getName() + ")"); points = data.getNMPoints(); Platform.runLater(() -> { //setup detector data setupDetectorPane(points); //setup spectrum plot updateSpectrumPane(points); setupInfo(data); }); }); } else { logger.error("The data model is null"); } detectorTab.getTabPane().getSelectionModel().select(detectorTab); } private class LoadPointsTask extends Task> { private final NumassData loader; public LoadPointsTask(NumassData loader) { this.loader = loader; } @Override protected List call() throws Exception { updateTitle("Load numass data (" + loader.getName() + ")"); List points = loader.getNMPoints(); Platform.runLater(() -> { //setup detector data setupDetectorPane(points); //setup spectrum plot updateSpectrumPane(points); setupInfo(data); }); return points; } } /** * setup detector pane * * @param points */ private void setupDetectorPane(List points) { boolean normalize = detectorNormalizeSwitch.isSelected(); int binning = detectorBinningSelector.getValue(); updateDetectorPane(fillDetectorData(points, binning, normalize)); detectorBinningSelector.getSelectionModel().selectedItemProperty() .addListener((ObservableValue observable, Integer oldValue, Integer newValue) -> { boolean norm = detectorNormalizeSwitch.isSelected(); updateDetectorPane(fillDetectorData(NumassLoaderViewComponent.this.points, newValue, norm)); }); detectorNormalizeSwitch.selectedProperty().addListener((ObservableValue observable, Boolean oldValue, Boolean newValue) -> { int bin = detectorBinningSelector.getValue(); updateDetectorPane(fillDetectorData(NumassLoaderViewComponent.this.points, bin, newValue)); }); detectorDataExportButton.setDisable(false); } private void setupInfo(NumassData loader) { Meta info = loader.getInfo(); infoTextBox.setText(new JSONMetaWriter().writeString(info, null). replace("\\r", "\r\t").replace("\\n", "\n\t")); } private void updateSpectrumPane(List points) { if (spectrumPlotFrame == null) { Meta plotMeta = new MetaBuilder("plot") .setValue("xAxis.axisTitle", "U") .setValue("xAxis.axisUnits", "V") .setValue("yAxis.axisTitle", "count rate") .setValue("yAxis.axisUnits", "Hz") .setValue("legend.show", false); spectrumPlotFrame = new JFreeChartFrame(plotMeta).display(spectrumPlotPane); } if (spectrumData == null) { spectrumData = new ChangeablePlottableData("spectrum"); spectrumPlotFrame.add(spectrumData); } int lowChannel = (int) channelSlider.getLowValue(); int highChannel = (int) channelSlider.getHighValue(); if (points == null || points.isEmpty()) { spectrumData.clear(); } else { spectrumData.fillData(points.stream() .map((NMPoint point) -> getSpectrumPoint(point, lowChannel, highChannel, getDTime())) .collect(Collectors.toList())); } } private double getDTime() { try { return Double.parseDouble(dTimeField.getText()) * 1e-6; } catch (NumberFormatException ex) { return 0; } } private DataPoint getSpectrumPoint(NMPoint point, int lowChannel, int upChannel, double dTime) { double u = point.getUread(); return new MapPoint(new String[]{"x", "y", "yErr"}, u, point.getCountRate(lowChannel, upChannel, dTime), point.getCountRateErr(lowChannel, upChannel, dTime)); } /** * update detector pane with new data */ private void updateDetectorPane(List detectorData) { Platform.runLater(() -> { if (detectorData == null) { throw new IllegalArgumentException("Detector data not defined"); } // detectorPointListView.getItems().clear();//removing all checkboxes // detectorPlotPane.getChildren().clear();//removing plot Meta frameMeta = new MetaBuilder("frame") .setValue("frameTitle", "Detector response plot") .setNode(new MetaBuilder("xAxis") .setValue("axisTitle", "ADC") .setValue("axisUnits", "channels") .build()) .setNode(new MetaBuilder("yAxis") .setValue("axisTitle", "count rate") .setValue("axisUnits", "Hz") .build()) .setNode(new MetaBuilder("legend") .setValue("show", false)) .build(); // detectorPlot = PlotContainer.anchorTo(detectorPlotPane); XYPlotFrame detectorPlotFrame = new JFreeChartFrame(frameMeta); for (XYPlottable pl : detectorData) { detectorPlotFrame.add(pl); //TODO add update instead of replace action } detectorPlot.setPlot(detectorPlotFrame); }); } private List fillDetectorData(List points, int binning, boolean normalize) { List plottables = new ArrayList<>(); Meta plottableConfig = new MetaBuilder("plot") .setValue("connectionType", "step") .setValue("thickness", 2) .setValue("showLine", true) .setValue("showSymbol", false) .setValue("showErrors", false) .build(); for (NMPoint point : points) { String seriesName = String.format("%d: %.2f (%.2f)", points.indexOf(point), point.getUset(), point.getUread()); PlottableData datum = PlottableData.plot(seriesName, new XYAdapter("chanel", "count"), point.getData(binning, normalize)); datum.configure(plottableConfig); plottables.add(datum); } return plottables; } @FXML private void checkAllAction(ActionEvent event) { detectorPointListView.getCheckModel().checkAll(); } @FXML private void uncheckAllAction(ActionEvent event) { detectorPointListView.getCheckModel().clearChecks(); } @FXML private void checkSelectedAction(ActionEvent event) { for (Integer i : detectorPointListView.getSelectionModel().getSelectedIndices()) { detectorPointListView.getCheckModel().check(i); } } @FXML private void uncheckSelectedAction(ActionEvent event) { for (Integer i : detectorPointListView.getSelectionModel().getSelectedIndices()) { detectorPointListView.getCheckModel().clearCheck(i); } } @FXML private void onSpectrumExportClick(ActionEvent event) { if (points != null && !points.isEmpty()) { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("Choose text export destination"); fileChooser.setInitialFileName(data.getName() + "_spectrum.out"); File destination = fileChooser.showSaveDialog(spectrumPlotPane.getScene().getWindow()); if (destination != null) { String[] names = new String[]{"Uset", "Uread", "Length", "Total", "Window", "CR", "CRerr", "Timestamp"}; int loChannel = (int) channelSlider.getLowValue(); int upChannel = (int) channelSlider.getHighValue(); double dTime = getDTime(); ListTable.Builder spectrumDataSet = new ListTable.Builder(names); for (NMPoint point : points) { spectrumDataSet.addRow(new MapPoint(names, new Object[]{ point.getUset(), point.getUread(), point.getLength(), point.getEventsCount(), point.getCountInWindow(loChannel, upChannel), point.getCountRate(loChannel, upChannel, dTime), point.getCountRateErr(loChannel, upChannel, dTime), point.getStartTime() } )); } try { String comment = String.format("Numass data viewer spectrum data export for %s%n" + "Window: (%d, %d)%n" + "Dead time per event: %g%n", data.getName(), loChannel, upChannel, dTime); ColumnedDataWriter .writeDataSet(destination, spectrumDataSet.build(), comment, false); } catch (IOException ex) { LoggerFactory.getLogger(getClass()).error("Destination file not found", ex); } } } } private void onExportButtonClick(ActionEvent event) { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("Choose text export destination"); fileChooser.setInitialFileName(data.getName() + "_detector.out"); File destination = fileChooser.showSaveDialog(detectorPlotPane.getScene().getWindow()); if (destination != null) { Table detectorData = PlotDataUtils.collectXYDataFromPlot((XYPlotFrame) detectorPlot.getPlot(), true); try { ColumnedDataWriter .writeDataSet(destination, detectorData, "Numass data viewer detector data export for " + data.getName(), false); } catch (IOException ex) { LoggerFactory.getLogger(getClass()).error("Destination file not found", ex); } } } }