package com.isti.traceview.transformations.psd; import java.awt.Color; import java.awt.Frame; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.geom.Rectangle2D; import java.awt.print.PageFormat; import java.awt.print.Printable; import java.awt.print.PrinterException; import java.awt.print.PrinterJob; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.BufferedOutputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.List; import java.util.StringTokenizer; import javax.swing.BoxLayout; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.WindowConstants; import org.apache.commons.configuration.Configuration; import org.apache.log4j.Logger; import org.jfree.chart.ChartFactory; import org.jfree.chart.JFreeChart; import org.jfree.chart.LegendItem; import org.jfree.chart.axis.LogarithmicAxis; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.event.ChartProgressEvent; import org.jfree.chart.event.ChartProgressListener; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.plot.XYPlot; import org.jfree.chart.renderer.xy.StandardXYItemRenderer; import org.jfree.chart.title.TextTitle; import org.jfree.data.Range; import org.jfree.data.xy.XYDataset; import org.jfree.data.xy.XYSeries; import org.jfree.data.xy.XYSeriesCollection; import com.isti.jevalresp.OutputGenerator; import com.isti.traceview.common.TimeInterval; import com.isti.traceview.common.TraceViewChartPanel; import com.isti.traceview.data.PlotDataProvider; import com.isti.traceview.data.SacTimeSeriesASCII; import com.isti.traceview.gui.ChannelView; import com.isti.traceview.gui.ColorModeFixed; import com.isti.traceview.gui.GraphPanel; import com.isti.traceview.gui.GraphUtil; import com.isti.traceview.gui.IChannelViewFactory; import com.isti.traceview.gui.ScaleModeAuto; import com.isti.traceview.processing.IstiUtilsMath; import com.isti.traceview.processing.Spectra; import com.isti.xmax.XMAX; import com.isti.xmax.XMAXconfiguration; import com.isti.xmax.gui.XMAXframe; /** * Dialog to view PSD results. Also performs deconvolution, convolution and * smoothing. * * @author Max Kokoulin */ class ViewPSD extends JDialog implements PropertyChangeListener, ChartProgressListener { private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(ViewPSD.class); private static final String huttFreqsFile = "hutt_freqs"; private static final String huttPeriodsKey = "HuttPeriods"; private static SimpleDateFormat df = new SimpleDateFormat("yyyy,DDD"); private static DecimalFormat huttFormat = new DecimalFormat("#####.##"); private static DecimalFormat psnFormat1 = new DecimalFormat("0.00000E00"); private static DecimalFormat psnFormat2 = new DecimalFormat("#####.####"); private static DecimalFormat screenDataFormat = new DecimalFormat("#####.####"); private JLabel crosshairPositionL = null; private JFreeChart chart = null; private JOptionPane optionPane; private MyOptionPane chartPanel = null; private XYSeriesCollection dataset = null; private TimeInterval ti = null; private List<Spectra> data = null; private Configuration configuration = null; private boolean showWaves = false; ViewPSD(Frame owner, List<Spectra> data, TimeInterval ti, Configuration configuration, List<PlotDataProvider> input) { super(owner, "Power Spectra Density", true); this.dataset = createDataset(data); this.ti = ti; this.data = data; this.configuration = configuration; Object[] options = { "Close", "Print", "Dump Freqs", "Export PSD", "Export SAC", "Export GRAPH", "Toggle waves" }; // Create the JOptionPane. optionPane = new JOptionPane(); optionPane.setVisible(false); optionPane.setMessageType(JOptionPane.PLAIN_MESSAGE); optionPane.setOptionType(JOptionPane.CLOSED_OPTION); optionPane.setIcon(null); optionPane.setOptions(options); optionPane.setInitialValue(options[0]); chartPanel = createChartPanel(createDataset(data), input); optionPane.setMessage(chartPanel); // Make this dialog display it. setContentPane(optionPane); optionPane.addPropertyChangeListener(this); setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent we) { /* * Instead of directly closing the window, we're going to change * the JOptionPane's value property. */ optionPane.setValue("Close"); } }); pack(); setLocationRelativeTo(owner); optionPane.setVisible(true); setVisible(true); } @Override public void propertyChange(PropertyChangeEvent e) { String prop = e.getPropertyName(); if (isVisible() && (e.getSource() == optionPane) && (prop.equals(JOptionPane.VALUE_PROPERTY))) { Object value = optionPane.getValue(); optionPane.setValue(JOptionPane.UNINITIALIZED_VALUE); // If you were going to check something // before closing the window, you'd do // it here. if (value.equals("Close")) { setVisible(false); dispose(); } else if (value.equals("Print")) { PrinterJob job = PrinterJob.getPrinterJob(); PageFormat pf = job.defaultPage(); pf.setOrientation(PageFormat.LANDSCAPE); PageFormat pf2 = job.pageDialog(pf); if (pf2 != pf) { job.setPrintable(chartPanel, pf2); if (job.printDialog()) { try { job.print(); } catch (PrinterException e1) { JOptionPane.showMessageDialog(this, e1); } } } } else if (value.equals("Export PSD")) { for (int i = 0; i < dataset.getSeriesCount() - 2; i++) { BufferedOutputStream stream = null; String fileName = null; try { String seriesName = (String) dataset.getSeriesKey(i); fileName = XMAXconfiguration.getInstance().getOutputPath() + File.separator + "PSD_" + seriesName.replace("/", "_"); stream = new BufferedOutputStream(new FileOutputStream(fileName, false)); for (int j = 0; j < dataset.getItemCount(i); j++) { stream.write((psnFormat1.format(dataset.getXValue(i, j)) + " " + psnFormat2.format(dataset.getYValue(i, j)) + "\n").getBytes()); } } catch (IOException e1) { JOptionPane.showMessageDialog(XMAXframe.getInstance(), "Can't write file " + fileName + "; " + e1, "Error", JOptionPane.ERROR_MESSAGE); } finally { try { stream.close(); } catch (IOException e1) { // do nothing logger.error("Can't close buffered output stream"); } } } JOptionPane.showMessageDialog(XMAXframe.getInstance(), "Data exported to PSD ascii", "Message", JOptionPane.INFORMATION_MESSAGE); } else if (value.equals("Export SAC")) { for (int i = 0; i < dataset.getSeriesCount() - 2; i++) { DataOutputStream ds = null; String fileName = null; try { String seriesName = (String) dataset.getSeriesKey(i); fileName = XMAXconfiguration.getInstance().getOutputPath() + File.separator + "PSD_" + seriesName.replace("/", "_") + ".SAC"; ds = new DataOutputStream(new FileOutputStream(new File(fileName))); float ydata[] = new float[dataset.getItemCount(i)]; float xdata[] = new float[dataset.getItemCount(i)]; for (int j = 0; j < dataset.getItemCount(i); j++) { xdata[j] = new Double(dataset.getXValue(i, j)).floatValue(); ydata[j] = new Double(dataset.getYValue(i, j)).floatValue(); } SacTimeSeriesASCII sacAscii = SacTimeSeriesASCII.getSAC(data.get(i).getChannel(), ti.getStartTime(), xdata, ydata); sacAscii.writeHeader(ds); sacAscii.writeData(ds); } catch (IOException e1) { JOptionPane.showMessageDialog(XMAXframe.getInstance(), "Can't write file " + fileName + "; " + e1, "Error", JOptionPane.ERROR_MESSAGE); } finally { try { ds.close(); } catch (Exception e1) { // do nothing logger.error("Can't close data output stream"); } } } JOptionPane.showMessageDialog(XMAXframe.getInstance(), "Data exported to SAC ascii", "Message", JOptionPane.INFORMATION_MESSAGE); } else if (value.equals("Dump Freqs")) { BufferedOutputStream stream = null; try { File file = new File( XMAXconfiguration.getInstance().getOutputPath() + File.separator + huttFreqsFile); stream = new BufferedOutputStream(new FileOutputStream(file, true)); for (int i = 0; i < dataset.getSeriesCount() - 2; i++) { String seriesName = (String) dataset.getSeriesKey(i); stream.write((seriesName + ":").getBytes()); for (double huttPeriod : getHuttPeriods()) { stream.write((" " + huttFormat.format(getValue(i, huttPeriod)) + " @ " + huttFormat.format(huttPeriod) + " s;").getBytes()); } stream.write((" " + df.format(ti.getStartTime()) + "\n").getBytes()); } JOptionPane.showMessageDialog(XMAXframe.getInstance(), "Dump frequencies file updated", "Message", JOptionPane.INFORMATION_MESSAGE); } catch (FileNotFoundException e1) { JOptionPane.showMessageDialog(XMAXframe.getInstance(), "Cannot find the dump frequencies file", "Error", JOptionPane.ERROR_MESSAGE); } catch (IOException e2) { JOptionPane.showMessageDialog(XMAXframe.getInstance(), "Cannot write the dump frequencies: " + e2, "Error", JOptionPane.ERROR_MESSAGE); } finally { try { stream.close(); } catch (IOException e1) { // do nothing logger.error("Cannot close the data stream"); } } } else if (value.equals("Export GRAPH")) { File exportFile = GraphUtil.saveGraphics((JPanel) optionPane.getMessage(), XMAX.getConfiguration().getUserDir("GRAPH")); if (exportFile != null) { XMAX.getConfiguration().setUserDir("GRAPH", exportFile.getParent()); } } else if (value.equals("Toggle waves")) { if (showWaves) { chartPanel.remove(chartPanel.getWavePanel()); showWaves = false; } else { chartPanel.add(chartPanel.getWavePanel(), 1); showWaves = true; } invalidate(); optionPane.invalidate(); chartPanel.invalidate(); synchronized(getTreeLock()) { validateTree(); } doLayout(); repaint(); } } } /** * get Hutt periods for PSD from plugin configuration, sec */ private double[] getHuttPeriods() { String periods = configuration.getString(huttPeriodsKey); if (periods != null) { StringTokenizer st = new StringTokenizer(periods, ","); double[] huttPeriods = new double[st.countTokens()]; int i = 0; while (st.hasMoreTokens()) { huttPeriods[i] = Double.parseDouble(st.nextToken()); i++; } return huttPeriods; } else { double[] huttPeriods = { 0.2, 1.0, 20.5, 109.2 }; return huttPeriods; } } /** * Setter of the PSD export Hutt periods * * @param periods * array of Hutt PSD export periods in seconds. */ public void setHuttPeriods(double[] periods) { configuration.clearProperty(huttPeriodsKey); if (periods.length > 0) { String toSave = ""; for (int i = 0; i < periods.length; i++) { toSave = toSave + (i == 0 ? "" : ",") + new Double(periods[i]).toString(); } configuration.addProperty(huttPeriodsKey, toSave); } XMAXconfiguration.getInstance().save(); } private XYSeriesCollection createDataset(List<Spectra> ds) { XYSeriesCollection ret = new XYSeriesCollection(); for (Spectra spectra : ds) { ret.addSeries(spectra.getPSDSeries(OutputGenerator.VELOCITY_UNIT_CONV)); } // MTH: later versions of XYCollection do NOT allow you to add more than // 1 series with // a given key=name! --> name must be unique // XYSeries lowNoiseModelSeries = new XYSeries("MODEL"); // XYSeries highNoiseModelSeries = new XYSeries("MODEL"); XYSeries lowNoiseModelSeries = new XYSeries("NLNM"); XYSeries highNoiseModelSeries = new XYSeries("NHNM"); double[] freqArr = ds.get(0).getFrequencies(); for (int i = 1; i < freqArr.length; i++) { double period = 1.0 / freqArr[i]; double lowModel = NoiseModel.fnlnm(period); if (lowModel != 0.0) { lowNoiseModelSeries.add(/* Math.log10( */period/* ) */, lowModel); } double highModel = NoiseModel.fnhnm(period); if (highModel != 0) { highNoiseModelSeries.add(/* Math.log10( */period/* ) */, highModel); } } ret = IstiUtilsMath.varismooth(ret); // Adding head of noise models, between 0.1 s and psd graph beginning int i = 2; double period = 0.0; while ((period = 1 / (i * freqArr[freqArr.length - 1])) > 0.1) { double lowModel = NoiseModel.fnlnm(period); if (lowModel != 0.0) { lowNoiseModelSeries.add(/* Math.log10( */period/* ) */, lowModel); } double highModel = NoiseModel.fnhnm(period); if (highModel != 0) { highNoiseModelSeries.add(/* Math.log10( */period/* ) */, highModel); } i++; } // Adding tail of noise models, besides psd graph ending, and 1000 s // value i = 2; period = 0.0; while ((period = i / (freqArr[1])) < 1000) { double lowModel = NoiseModel.fnlnm(period); if (lowModel != 0.0) { lowNoiseModelSeries.add(/* Math.log10( */period/* ) */, lowModel); } double highModel = NoiseModel.fnhnm(period); if (highModel != 0) { highNoiseModelSeries.add(/* Math.log10( */period/* ) */, highModel); } i++; } ret.addSeries(lowNoiseModelSeries); ret.addSeries(highNoiseModelSeries); return ret; } private MyOptionPane createChartPanel(XYDataset dataset, List<PlotDataProvider> input) { chart = ChartFactory.createXYLineChart(null, // title "Period, s", // x-axis label "Power spectra density (DB relative to 1m/s\u00B2)", // y-axis label dataset, // data PlotOrientation.VERTICAL, // orientation true, // create legend? true, // generate tooltips? false // generate URLs? ); chart.setBackgroundPaint(Color.white); chart.addProgressListener(this); TraceViewChartPanel cp = new TraceViewChartPanel(chart, true); TextTitle title = new TextTitle("Start time: " + TimeInterval.formatDate(ti.getStartTime(), TimeInterval.DateFormatType.DATE_FORMAT_NORMAL) + ", Duration: " + ti.convert(), getFont()); chart.setTitle(title); XYPlot plot = chart.getXYPlot(); NumberAxis domainAxis = new LogarithmicAxis("Period, s"); domainAxis.setRange(new Range(0.01, 1000.0)); plot.setDomainAxis(domainAxis); NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis(); rangeAxis.setAutoRange(true); rangeAxis.setAutoRangeIncludesZero(false); plot.setBackgroundPaint(Color.lightGray); plot.setDomainGridlinePaint(Color.white); plot.setRangeGridlinePaint(Color.white); plot.setDomainCrosshairVisible(true); plot.setRangeCrosshairVisible(true); plot.setDomainCrosshairLockedOnData(true); plot.setRenderer(new PSDItemRenderer()); return new MyOptionPane(cp, input); } /** * Handles a chart progress event. * * @param event * the event. */ @Override public void chartProgress(ChartProgressEvent event) { if (event.getType() != ChartProgressEvent.DRAWING_FINISHED) { return; } if (chart != null) { XYPlot plot = (XYPlot) chart.getPlot(); // XYDataset dataset = plot.getDataset(); // Comparable seriesKey = dataset.getSeriesKey(0); double xx = plot.getDomainCrosshairValue(); double yy = plot.getRangeCrosshairValue(); // update the screen... if (xx != 0.0 && yy != 0.0) { getCrosshairPositionL().setText( " Period: " + screenDataFormat.format(xx) + " s, PSD: " + screenDataFormat.format(yy)); } // lg.debug("X: " + xx + ", Y: " + yy); } } private JLabel getCrosshairPositionL() { if (crosshairPositionL == null) { crosshairPositionL = new JLabel(); crosshairPositionL.setText(" "); } return crosshairPositionL; } private double getValue(int series, double arg) { for (int i = 0; i < dataset.getItemCount(series); i++) { if (arg < dataset.getXValue(series, i)) { if(i > 0){ return dataset.getYValue(series, i - 1) + ((dataset.getYValue(series, i) - dataset.getYValue(series, i - 1)) * (arg - dataset.getXValue(series, i - 1)) / (dataset.getXValue(series, i) - dataset.getXValue(series, i - 1))); } } } return Double.NaN; } private class PSDItemRenderer extends StandardXYItemRenderer { private static final long serialVersionUID = 1L; public PSDItemRenderer() { super(); setSeriesPaint(dataset.getSeriesCount() - 1, Color.BLACK); setSeriesPaint(dataset.getSeriesCount() - 2, Color.BLACK); } @Override public LegendItem getLegendItem(int datasetIndex, int series) { if (series > dataset.getSeriesCount() - 3) { return null; } else return super.getLegendItem(datasetIndex, series); } } private class PSDChannelViewFactory implements IChannelViewFactory { @Override public int getInfoAreaWidth() { return 0; } @Override public ChannelView getChannelView(List<PlotDataProvider> channels) { return new ChannelView(channels, getInfoAreaWidth(), false, Color.WHITE, Color.WHITE); } @Override public ChannelView getChannelView(PlotDataProvider channel) { return new ChannelView(channel, getInfoAreaWidth(), false, Color.WHITE, Color.WHITE); } } private class MyOptionPane extends JPanel implements Printable { private static final long serialVersionUID = 1L; private TraceViewChartPanel cp = null; private JPanel waveP = null; private List<PlotDataProvider> input; private MyOptionPane(TraceViewChartPanel cp, List<PlotDataProvider> input) { this.cp = cp; this.input = input; BoxLayout mLayout = new BoxLayout(this, javax.swing.BoxLayout.Y_AXIS); setLayout(mLayout); add(cp); if (showWaves) { add(getWavePanel()); } add(getCrosshairPositionL()); } public JPanel getWavePanel() { if (waveP == null) { waveP = new JPanel(); BoxLayout oLayout = new BoxLayout(waveP, javax.swing.BoxLayout.Y_AXIS); waveP.setLayout(oLayout); GraphPanel gp = new GraphPanel(false); gp.setChannelViewFactory(new PSDChannelViewFactory()); gp.setShowBigCursor(false); gp.setColorMode(new ColorModeFixed()); gp.setScaleMode(new ScaleModeAuto()); gp.setBackground(Color.WHITE); gp.setChannelShowSet(input); gp.setTimeRange(ti); PSDItemRenderer ir = (PSDItemRenderer) cp.getChart().getXYPlot().getRenderer(); for (int i = 0; i < input.size(); i++) { PlotDataProvider channel = input.get(i); channel.setColor((Color) ir.lookupSeriesPaint(i)); } waveP.add(gp); waveP.setMaximumSize(new java.awt.Dimension(32767, 100)); waveP.setPreferredSize(new java.awt.Dimension(this.getWidth(), 100)); } return waveP; } @Override public int print(Graphics g, PageFormat pf, int pageIndex) throws PrinterException { if (pageIndex != 0) { return NO_SUCH_PAGE; } double factor; if (showWaves) { factor = new Double(cp.getHeight()) / (waveP.getHeight() + cp.getHeight()); } else { factor = 1.0; } Graphics2D g2 = (Graphics2D) g; double x = pf.getImageableX(); double y = pf.getImageableY(); double w = pf.getImageableWidth(); double h = pf.getImageableHeight() * factor; cp.getChart().draw(g2, new Rectangle2D.Double(x, y, w, h), null, null); if (showWaves) { g2.translate(x, y + h); g2.scale(w / waveP.getWidth(), (pf.getImageableHeight() * (1 - factor)) / waveP.getHeight()); waveP.paint(g2); } // double imageHeight = pf.getImageableHeight() * (1-factor); // BufferedImage image = new BufferedImage(new Double(w).intValue(), // new Double(imageHeight).intValue(), BufferedImage.TYPE_INT_RGB); // Graphics2D g2image = image.createGraphics(); // g2image.scale(w / new Double(optionP.getWidth()), imageHeight / // new Double(optionP.getHeight())); // optionP.paint(g2image); // g2.drawImage(image, new Double(x).intValue(), new // Double(y).intValue(), null, null); return PAGE_EXISTS; } } }