プロジェクト

全般

プロフィール

« | » 

リビジョン e268e7e2

高徹 高橋 徹 さんが5年以上前に追加

Add implementation of all behaviors without help

差分を表示:

.gitignore
.idea/workspace.xml
.idea/tasks.xml
.idea/usage.statistics.xml
.idea/disctionaries
.idea/dictionaries
.idea/uiDesigner.xml
.idea/gradle.xml
.idea/codeStyles/codeStyleConfig.xml
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>
src/main/java/com/torutk/spectrum/data/SpectrumData.java
package com.torutk.spectrum.data;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.logging.Logger;
import java.util.stream.IntStream;
/**
* One spectrum data.
*
* <pre>
* Reference Level --> +------------------------------+ <-- power = 0
* | |
* ^ -- +------------------------------+
* scale | | |
* V -- +------------------------------+
* | |
* : :
* +------------------------------+ <-- power = 255
* ^ ^
* Start Frequency Stop Frequency
* </pre>
*/
public class SpectrumData {
private static final Logger logger = Logger.getLogger(SpectrumData.class.getName());
private static final float RC_WEIGHT = 0.65f;
private int id;
private final String name;
private final double stopFrequency;
private final double startFrequency;
private final float referenceLevel;
private final float scale;
private final byte[] powers;
private float averagePower = Float.NaN; // lazy
/**
* Unit conversion from dBm to mW.
*
* @param dbm to be converted [dBm]
* @return converted value [mW]
*/
public static double toMilliwatt(double dbm) {
return Math.pow(10, dbm / 10);
}
/**
* Unit conversion from mW to dBm.
*
* @param milliwatt to be converted [mW]
* @return converted value [dBm]
*/
public static double toDbm(double milliwatt) {
return 10 * Math.log10(milliwatt);
}
/**
* Constructor with full parameters.
*
* @param name of this spectrum
* @param startFrequency of this spectrum [MHz]
* @param stopFrequency of this spectrum [MHz]
* @param referenceLevel of this spectrum [dBm]
* @param scale of this spectrum [dBm/DIV]
* @param powers power with range 0-255, 0 is the highest power in scales, 255 is the lowers power in scales.
*/
public SpectrumData(
String name, double startFrequency, double stopFrequency, float referenceLevel, float scale, byte[] powers
) {
this(name.hashCode(), name, startFrequency, stopFrequency, referenceLevel, scale, powers);
}
public SpectrumData(
int id, String name, double startFrequency, double stopFrequency, float referenceLevel, float scale,
byte[] powers
) {
this.id = id;
this.name = name;
this.startFrequency = startFrequency;
this.stopFrequency = stopFrequency;
this.referenceLevel = referenceLevel;
this.scale = scale;
this.powers = powers;
}
/**
* Apply RC filter (Low-pass filter) to previous value and current value with specified weight.
*
* <pre>
* OUTn = weight * OUTn-1 + (1 - weight) * CURRENT
* </pre>
*
* @param previous previous output
* @param current input
* @return RC filtered output
*/
public static double applyRcFilter(double previous, double current) {
double previousMilliwatt = toMilliwatt(previous);
double currentMilliwatt = toMilliwatt(current);
double filteredMilliwatt = RC_WEIGHT * previousMilliwatt + (1 - RC_WEIGHT) * currentMilliwatt;
return toDbm(filteredMilliwatt);
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public double getStartFrequency() {
return startFrequency;
}
public double getStopFrequency() {
return stopFrequency;
}
public float getReferenceLevel() {
return referenceLevel;
}
public float getScale() {
return scale;
}
public int size() {
return powers.length;
}
/**
* Gets all powers from start frequency to stop frequency with interval of sampling.
* @return powers array, the unit of each element is [dBm]
*/
public float[] getPowers() {
float[] decodedPowers = new float[powers.length];
for (int i = 0; i < powers.length; i++) {
decodedPowers[i] = decode(powers[i]);
}
return decodedPowers;
}
/**
* Gets all frequencies of all sampling point.
* @return frequencies array, the unit of each element is [MHz]
*/
public float[] getFrequencies() {
float[] frequencies = new float[powers.length];
for (int i = 0; i < powers.length; i++) {
frequencies[i] = getFrequencyAt(i);
}
return frequencies;
}
/**
* Get powers from start frequency to stop frequency with detrend specified spectrum.
*
* detrend calculation is on dBm scale, not mW.
*
* @param detrend detrend spectrum
* @return detrended powers [dBm]
*/
public float[] getPowersDetrend(SpectrumData detrend) {
if (detrend == null) {
return getPowers();
}
float[] decodedPowers = new float[powers.length];
for (int i = 0; i < powers.length; i++) {
float frequency = getFrequencyAt(i);
if (detrend.containsFrequency(frequency)) {
double bias = detrend.getPowerAt(frequency) - detrend.getAveragePower();
double detrended = decode(powers[i]) - bias;
decodedPowers[i] = (float) detrended;
} else {
decodedPowers[i] = decode(powers[i]);
}
}
return decodedPowers;
}
/**
* decode power to dBm.
*
* @param encodedPower encoded power value
* @return decoded power [dBm]
*/
public float decode(byte encodedPower) {
return referenceLevel - scale * 10 * Byte.toUnsignedInt(encodedPower) / 255;
}
/**
* get the frequency at the specified index.
* @param index of frequency [MHz]
* @return frequency of the specified index
*/
public float getFrequencyAt(int index) {
float start = (float) startFrequency;
float stop = (float) stopFrequency;
return start + (stop - start) * index / powers.length;
}
/**
* Returns true if the specified frequency is between start frequency and stop frequency.
* @param frequency the frequency to search for
* @return true if the specified frequency is in, false otherwise.
*/
public boolean containsFrequency(float frequency) {
return (startFrequency <= frequency) && (frequency <= stopFrequency);
}
/**
* Returns a power at the specified frequency.
*
* @param frequency to specified [MHz]
* @return power at the frequency [dBm]
*/
public float getPowerAt(float frequency) {
assert containsFrequency(frequency);
int index = (int) Math.floor((frequency - startFrequency) / getSamplingRate());
return decode(powers[index]);
}
/**
* Calculate sampling rate.
* @return sampling rate [MHz]
*/
public double getSamplingRate() {
return (stopFrequency - startFrequency) / powers.length;
}
public float getAveragePower() {
if (Float.isNaN(averagePower)) {
double averagePowerMilliwatt = (float) IntStream.range(0, powers.length)
.mapToDouble(i -> (double) decode(powers[i]))
.map(SpectrumData::toMilliwatt)
.average().orElseGet(() -> powers[0]);
averagePower = (float) toDbm(averagePowerMilliwatt);
}
return averagePower;
}
/**
*
* @return byte array expression of powers
*/
public byte[] getPowersAsBytes() {
return powers;
}
@Override
public String toString() {
return "SpectrumData{" +
"name='" + name + '\'' +
", startFrequency=" + startFrequency +
", stopFrequency=" + stopFrequency +
", referenceLevel=" + referenceLevel +
", scale=" + scale +
", powers.length=" + powers.length +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SpectrumData that = (SpectrumData) o;
return id == that.id;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
src/main/java/com/torutk/spectrum/data/SpectrumDataExporter.java
package com.torutk.spectrum.data;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
public class SpectrumDataExporter {
/**
* Exports SpectrumData to CSV file.
*
* <pre>
* "Frequency[MHz]", "Power[dBm]"
* 3456.7, -89.1
* : :
* </pre>
* @param toDirectory save file in this directory
* @param data save this data as CSV
* @throws IOException if the file failed to write
*/
public static void exportAsCsv(Path toDirectory, SpectrumData data) throws IOException {
Path csvPath = toDirectory.resolve(data.getName() + ".csv");
float[] frequencies = data.getFrequencies();
float[] powers = data.getPowers();
try (BufferedWriter writer = Files.newBufferedWriter(csvPath, Charset.forName("Windows-31J"))) {
writer.write("Frequency[MHz], Power[dBm]");
writer.newLine();
for (int i = 0; i < data.size(); i++) {
writer.write(String.format("%f, %f", frequencies[i], powers[i]));
writer.newLine();
}
}
}
}
src/main/java/com/torutk/spectrum/data/SpectrumDataParser.java
package com.torutk.spectrum.data;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.Path;
/**
* Parse data file saved from Glowlink Model 1030/8000.
*
* Data File Format:
* <pre>
* |Number of samples | Start Frequency |
* | Stop Frequency | Ref.Lev. |Scale |
* | data#1 | data#2 | data#3 | data#4 |
* | data#5 | data#6 | data#7 | data#8 |
* | : | : | : | : |
* </pre>
* <ul>
* <li>Number of samples : 64bit integer</li>
* <li>Start Frequency : 64bit double</li>
* <li>Stop Frequency : 64bit double</li>
* <li>Reference Level : 32bit float</li>
* <li>Scale : 32bit float</li>
* <li>data#x : 32bit integer</li>
* </ul>
*/
public class SpectrumDataParser {
public static SpectrumData parse(Path path) throws IOException {
var buffer = ByteBuffer.wrap(Files.readAllBytes(path));
buffer.order(ByteOrder.LITTLE_ENDIAN);
var numData = (int) buffer.getLong();
var startFrequency = buffer.getDouble();
var stopFrequency = buffer.getDouble();
var referenceLevel = buffer.getFloat();
var scale = buffer.getFloat();
var powers = new byte[numData];
for (int i = 0; i < numData; i++) {
powers[i] = (byte) buffer.getInt();
}
var name = getBaseName(path.getFileName().toString());
return new SpectrumData(name, startFrequency, stopFrequency, referenceLevel, scale, powers);
}
/**
* Returns the name omitted the extension.
* @param name full name included extension
* @return the name omitted the extension
*/
private static String getBaseName(String name) {
int index = name.lastIndexOf('.');
if (index <= 0) {
return name;
} else {
return name.substring(0, index);
}
}
}
src/main/java/com/torutk/spectrum/view/SpectrumFileViewController.java
package com.torutk.spectrum.view;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.css.PseudoClass;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Cursor;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.ToggleButton;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Pane;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.util.converter.DoubleStringConverter;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.logging.Logger;
import java.util.stream.Stream;
public class SpectrumFileViewController implements Initializable {
private static final Logger logger = Logger.getLogger(SpectrumFileViewController.class.getName());
private static final PseudoClass HAZE_PSEUDO_CLASS = PseudoClass.getPseudoClass("haze");
private FileChooser fileChooser = new FileChooser();
private DirectoryChooser directoryChooer = new DirectoryChooser();
private DirectoryChooser directoryChooser = new DirectoryChooser();
private ResourceBundle resources;
private double chartDragPointX;
private final SpectrumFileViewModel model = SpectrumFileViewModel.INSTANCE;
......
@FXML private LineChart<Float, Float> chart;
@FXML private NumberAxis xAxis;
@FXML private NumberAxis yAxis;
@FXML private ToggleButton rightPaneToggleButton;
@FXML private Pane rightPane;
@FXML private TextField startFrequencyField;
@FXML private TextField stopFrequencyField;
@FXML private TextField referenceLevelField;
@FXML private TextField scaleField;
@FXML private Button updateButton;
@FXML private CheckBox detrendCheckBox;
@FXML private Label detrendFileLabel;
@FXML private CheckBox rcFilterCheckBox;
@FXML
private void open(ActionEvent ev) {
logger.fine("User operation 'open' triggered.");
List<File> files = fileChooser.showOpenMultipleDialog(getStage());
if (files == null) {
logger.fine("User operation 'open' cancelled.");
return;
}
files.forEach(file -> {
model.lastOpenDirectoryProperty().set(file.getParentFile());
try {
model.loadFromFile(file.toPath());
} catch (IOException e) {
logger.warning("could not parse file: " + file.getName());
}
});
recreateAllSeries();
refreshSettingFields();
}
@FXML
private void export(ActionEvent ev) {
logger.fine("User operation 'export' triggered.");
File directory = directoryChooser.showDialog(getStage());
if (directory == null) {
logger.fine("User operation 'export' cancelled.");
return;
} else {
model.lastOpenDirectoryProperty().set(directory.getParentFile());
}
try {
model.exportTo(directory.toPath());
} catch (IOException e) {
logger.warning("could not export to the directory:" + directory);
}
}
@FXML
......
@FXML
private void updateSettings(ActionEvent event) {
logger.fine("User operation 'update' triggered.");
double previousSpan = model.getSpan();
model.setStartFrequency(Double.parseDouble(startFrequencyField.getText()));
model.setStopFrequency(Double.parseDouble(stopFrequencyField.getText()));
model.setReferenceLevel(Double.parseDouble(referenceLevelField.getText()));
model.setScale(Double.parseDouble(scaleField.getText()));
updateButton.setDisable(true);
if (model.needsRecreate(previousSpan)) {
recreateAllSeries();
}
}
private void openDetrend() {
logger.fine("User operation 'open detrend file' triggered.");
File file = fileChooser.showOpenDialog(getStage());
if (file == null) {
logger.fine("User operation 'open detrended file' cancelled.");
return;
}
try {
model.loadDetrendFromFile(file.toPath());
recreateAllSeries();
} catch (IOException e) {
logger.warning("could not parse detrend file:" + file.toString());
}
}
@Override
......
fileChooser.getExtensionFilters().add(
new FileChooser.ExtensionFilter("Spectrum Files", "*.dat")
);
directoryChooer.setTitle(resources.getString("spectrum.view.directorychooser.title"));
directoryChooer.initialDirectoryProperty().bindBidirectional(model.lastOpenDirectoryProperty());
directoryChooser.setTitle(resources.getString("spectrum.view.directorychooser.title"));
directoryChooser.initialDirectoryProperty().bindBidirectional(model.lastOpenDirectoryProperty());
initializeChart();
initializeRightPane();
}
......
chart.setOnMouseReleased(event -> chart.setCursor(Cursor.DEFAULT));
}
private void updateChartLegendItemsHandler() {
chart.lookupAll(".chart-legend-item").stream()
.filter(node -> node instanceof Label)
.map(node -> (Label) node)
.forEach(label -> {
label.setOnMouseClicked(ev -> {
if (ev.getButton() == MouseButton.PRIMARY) {
getSeriesByName(label.getText()).ifPresent(series -> {
boolean toBeInvisible = series.getNode().isVisible();
series.getNode().setVisible(!toBeInvisible);
label.pseudoClassStateChanged(HAZE_PSEUDO_CLASS, toBeInvisible);
});
}
});
label.setContextMenu(createLegendLabelContextMenu(label));
});
}
private Optional<XYChart.Series<Float, Float>> getSeriesByName(String name) {
return chart.getData().stream()
.filter(series -> series.getName().equals(name))
.findAny();
}
private ContextMenu createLegendLabelContextMenu(Label label) {
var removeItem = new MenuItem(resources.getString("spectrum.view.chart.legend.menu.remove"));
removeItem.setOnAction(event -> getSeriesByName(label.getText()).ifPresent(this::removeSeries));
return new ContextMenu(removeItem);
}
private void initializeRightPane() {
rightPane.managedProperty().bind(rightPaneToggleButton.selectedProperty());
rightPane.visibleProperty().bind(rightPaneToggleButton.selectedProperty());
rightPaneToggleButton.selectedProperty().addListener((obs, ov, nv) -> refreshSettingFields());
Stream.of(startFrequencyField, stopFrequencyField, referenceLevelField, scaleField)
.forEach(this::initializeSettingsTextFields);
detrendFileLabel.disableProperty().bind(detrendCheckBox.selectedProperty().not());
detrendFileLabel.setOnMouseClicked(event -> openDetrend());
model.useDetrendProperty().bind(detrendCheckBox.selectedProperty());
detrendCheckBox.selectedProperty().addListener((obs, ov, nv) -> recreateAllSeries());
detrendFileLabel.textProperty().bind(Bindings.select(model.detrendProperty(), "name"));
model.useRcFilterProperty().bind(rcFilterCheckBox.selectedProperty());
rcFilterCheckBox.selectedProperty().addListener((obs, ov, nv) -> recreateAllSeries());
}
private void initializeSettingsTextFields(TextField field) {
field.textProperty().addListener((obs, ov, nv) -> updateButton.setDisable(false));
var tf = new TextFormatter<>(new DoubleStringConverter());
field.setTextFormatter(tf);
field.setOnScroll(ev ->
tf.setValue(tf.getValue() + Math.signum(ev.getDeltaY()) * (tf.getValue() > 100 ? 1 : 0.5))
);
}
/**
* refresh values in settings panel.
*/
private void refreshSettingFields() {
startFrequencyField.setText(String.format("%10.4f", model.getStartFrequency()));
stopFrequencyField.setText(String.format("%10.4f", model.getStopFrequency()));
......
scaleField.setText(String.format("%4.1f", model.getScale()));
updateButton.setDisable(true);
}
private void recreateAllSeries() {
model.recreateAllSeries();
updateChartLegendItemsHandler();
}
// This method is called from context menu of legend label to be removed.
// UpdateChartLegendItemHandler removes the legend label of context menu,
// So it should not be called in this thread but in another thread later.
private void removeSeries(XYChart.Series<Float, Float> series) {
model.removeSpectrumData(series.getName());
Platform.runLater(this::updateChartLegendItemsHandler);
}
private Stage getStage() {
return (Stage) chart.getScene().getWindow();
}
}
src/main/java/com/torutk/spectrum/view/SpectrumFileViewModel.java
package com.torutk.spectrum.view;
import com.torutk.spectrum.data.SpectrumData;
import com.torutk.spectrum.data.SpectrumDataExporter;
import com.torutk.spectrum.data.SpectrumDataParser;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
......
import javafx.scene.chart.XYChart;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import java.util.stream.Collector;
import java.util.stream.IntStream;
/**
* View Model of Spectrum Viewer Application.
......
* <li>Holds all data to be displayed.</li>
* <li>Mediate between view and domain data(Model).</li>
* </ul>
*
*/
enum SpectrumFileViewModel {
INSTANCE;
private static final int SPECTRUM_DISPLAY_PIXELS = 1024; // temporary assume 1k display pixel
private static final Logger logger = Logger.getLogger(SpectrumFileViewModel.class.getName());
private final DoubleProperty startFrequencyProperty = new SimpleDoubleProperty(950d);
......
private final DoubleProperty scaleProperty = new SimpleDoubleProperty(5d);
private final ObjectProperty<ObservableList<XYChart.Series<Float, Float>>> spectrumSeriesProperty =
new SimpleObjectProperty<>(FXCollections.observableArrayList());
private final BooleanProperty useDetrendProperty = new SimpleBooleanProperty();
private final ObjectProperty<SpectrumData> detrendProperty = new SimpleObjectProperty<>();
private final BooleanProperty useRcFilterProperty = new SimpleBooleanProperty();
private final ObjectProperty<File> lastOpenDirectoryProperty =
new SimpleObjectProperty<>(new File(System.getProperty("user.dir")));
private final List<SpectrumData> spectrumDataList = new ArrayList<>();
/**
* Loads spectrum data from the specified file, then holds in the spectrum list to be displayed.
*
* @param path the file path to be loaded.
* @throws IOException if the specified file cannot be read.
*/
public void loadFromFile(Path path) throws IOException {
SpectrumData spectrum = SpectrumDataParser.parse(path);
if (spectrumDataList.contains(spectrum)) {
logger.info(() -> String.format("Already loaded %s from file %s ", spectrum, path));
return;
}
logger.info(() -> String.format("Loaded %s from file %s", spectrum, path));
load(spectrum);
}
private void load(SpectrumData spectrum) {
spectrumDataList.add(spectrum);
setStartFrequency(spectrum.getStartFrequency());
setStopFrequency(spectrum.getStopFrequency());
setReferenceLevel(spectrum.getReferenceLevel());
setScale(spectrum.getScale());
}
/**
* Loads spectrum data from the specified file, then holds as the detrend.
*
* @param path the file path to be loaded.
* @throws IOException if the specified file cannot be read.
*/
void loadDetrendFromFile(Path path) throws IOException {
detrendProperty.set(SpectrumDataParser.parse(path));
logger.info(String.format("Loaded Detrend %s from file %s", detrendProperty.get(), path));
}
/**
* Make a decision the rate to be decimate for display.
*
* @param samplingRate data's sampling rate.(means the number of points in the display frequency)
* @return decimation ratio.(3 means 1 of 3 point is displayed)
*/
private int decimation(double samplingRate) {
int numPointsInData = (int) ((getStopFrequency() - getStartFrequency()) / samplingRate);
int decimation = numPointsInData / SPECTRUM_DISPLAY_PIXELS;
logger.fine(String.format("arg %f, full points %d, decimation %d", samplingRate, numPointsInData, decimation));
return decimation == 0 ? 1 : decimation;
}
/**
* @return start frequency of display in MHz.
*/
......
return spectrumSeriesProperty;
}
BooleanProperty useDetrendProperty() {
return useDetrendProperty;
}
ObjectProperty<SpectrumData> detrendProperty() {
return detrendProperty;
}
BooleanProperty useRcFilterProperty() {
return useRcFilterProperty;
}
ObjectProperty<File> lastOpenDirectoryProperty() {
return lastOpenDirectoryProperty;
}
void removeSpectrumData(String name) {
spectrumDataList.removeIf(data -> data.getName().equals(name));
spectrumSeriesProperty.get().removeIf(series -> series.getName().equals(name));
}
private XYChart.Series<Float, Float> createDecimatedSeries(String name, float[] xArray, float[] yArray, int decimation) {
assert xArray.length == yArray.length;
Collector<XYChart.Data<Float, Float>, ObservableList<XYChart.Data<Float, Float>>,
XYChart.Series<Float, Float>> collector;
if (useRcFilterProperty().get()) {
collector = toSeriesWithRcFilter(name);
} else {
collector = toSeries(name);
}
return IntStream.range(0, xArray.length)
.filter(i -> i % decimation == 0)
.mapToObj(i -> new XYChart.Data<Float, Float>(xArray[i], maxWithin(yArray, i, i + decimation)))
.collect(collector);
}
/**
* Search max value from specified data array between from index to to index.
*
* @param data whole data to be searched
* @param fromIndex start index to be searched from the data
* @param toIndexExclude stop index (not include this value) to be searched from the data
* @return the max value
*/
private float maxWithin(float[] data, int fromIndex, int toIndexExclude) {
return (float) IntStream.range(fromIndex, Math.min(toIndexExclude, data.length))
.mapToDouble(i -> data[i])
.max()
.orElse(data[fromIndex]);
}
/**
* Collector collect the each element of {@code XYChart.Data<Float, Float>} to the list of
* {@code ObservableList<XYChart.Data<Float, Float>>} with applying RC filter, then output
* {@code XYChart.Series<Float, Float>}.
*
* @param name set to the return object of XYChart.Series.
* @return XYChart.Series to be displayed.
*/
private static Collector<XYChart.Data<Float, Float>, ObservableList<XYChart.Data<Float, Float>>,
XYChart.Series<Float, Float>> toSeriesWithRcFilter(String name)
{
return Collector.of(
FXCollections::observableArrayList,
(l, e) -> {
e.setYValue((float) SpectrumData.applyRcFilter(
l.size() > 0 ? l.get(l.size() - 1).getYValue() : e.getYValue(), e.getYValue()
));
l.add(e);
},
(left, right) -> {
left.addAll(right);
return left;
},
l -> new XYChart.Series<>(name, l)
);
}
/**
* Collector collect the each element of {@code XYChart.Data<Float, Float>} to the list of
* {@code ObservableList<XYChart.Data<Float, Float>>}, then output {@code XYChart.Series<Float, Float>}.
*
* @param name set to the return object of XYChart.Series.
* @return XYChart.Series to be displayed.
*/
private static Collector<XYChart.Data<Float, Float>, ObservableList<XYChart.Data<Float, Float>>,
XYChart.Series<Float, Float>> toSeries(String name)
{
return Collector.of(
FXCollections::observableArrayList,
ObservableList::add,
(l, r) -> {l.addAll(r); return l;},
l -> new XYChart.Series<>(name, l)
);
}
/**
* Export SpectrumData in list to the specified directory with CSV format.
*
* @param toPath the directory to be exported
* @throws IOException error in file I/O
*/
public void exportTo(Path toPath) throws IOException {
for (SpectrumData data: spectrumDataList) {
SpectrumDataExporter.exportAsCsv(toPath, data);
logger.info(String.format("exported data %s to file %s", data, toPath));
}
}
/**
* Recreate all XYChart.Series.
* This is needed when start/stop frequency, reference level, or scale is changed
* and re decimated with new value.
*/
public void recreateAllSeries() {
ObservableList<XYChart.Series<Float, Float>> list = spectrumDataList.stream()
.map(data -> createDecimatedSeries(
data.getName(), data.getFrequencies(), getPowersDetrend(data),
decimation(data.getSamplingRate())
))
.collect(FXCollections::observableArrayList, ObservableList::add, ObservableList::addAll);
spectrumSeriesProperty.set(list);
}
private float[] getPowersDetrend(SpectrumData data) {
if (useDetrendProperty.get() && detrendProperty.get() != null) {
return data.getPowersDetrend(detrendProperty.get());
} else {
return data.getPowers();
}
}
double getSpan() {
return getStopFrequency() - getStartFrequency();
}
boolean needsRecreate(double previousSpan) {
double span = getSpan();
return Math.max(span, previousSpan) / Math.min(span, previousSpan) > 2;
}
}
src/main/resources/com/torutk/spectrum/view/SpectrumFileView.fxml
<Insets left="4.0" right="4.0" />
</VBox.margin>
</HBox>
<CheckBox fx:id="rcFilterCheckbox" mnemonicParsing="false" selected="true" text="%spectrum.view.rcfilter">
<CheckBox fx:id="rcFilterCheckBox" mnemonicParsing="false" selected="true" text="%spectrum.view.rcfilter">
<VBox.margin>
<Insets left="4.0" right="4.0" top="8.0" />
</VBox.margin>
src/main/resources/com/torutk/spectrum/view/SpectrumFileViewApp.properties
spectrum.view.version = #VERSION_TO_BE_REPLACED#
spectrum.view.title = Spectrum Viewer
spectrum.view.toolbar.db = Show Database
spectrum.view.toolbar.db.tooltip = Show/Hide Database catalog pane in left side
spectrum.view.toolbar.open = Open
spectrum.view.toolbar.open.tooltip = Read DAT file saved on Glowlink
spectrum.view.toolbar.export = Export
......
spectrum.view.chart.xaxis = Frequency [MHz]
spectrum.view.chart.yaxis = Power [dBm]
spectrum.view.chart.legend.menu.remove = Remove from chart
spectrum.view.chart.legend.menu.register = Register database
spectrum.view.registerdb.succeeded = Succeeded to register {0} with database
spectrum.view.registerdb.failed = Failed to register {0} with database
spectrum.view.start_frequency = Start Frequency
spectrum.view.stop_frequency = Stop Frequency
spectrum.view.reference_level = Reference Level
......
spectrum.view.update = Update
spectrum.view.detrend = Detrend
spectrum.view.rcfilter = Apply RC Filter
spectrum.registerview.title = Register spectrum and observation to the database
spectrum.registerview.spectrum.label = Spectrum name
spectrum.registerview.site.label = Site
spectrum.registerview.equipment.label = Equipment
spectrum.registerview.satellite.label = Satellite
spectrum.registerview.polarizationtype.label = Polarization type
spectrum.registerview.azimuth.label = Azimuth
spectrum.registerview.elevation.label = Elevation
spectrum.registerview.polarization.label = Polarization
spectrum.registerview.timestamp.label = Timestamp
spectrum.regsiterview.register.button = Register database
src/main/resources/com/torutk/spectrum/view/SpectrumFileViewApp_ja.properties
spectrum.view.title = 周波数解析表示
spectrum.view.toolbar.db = DB表示
spectrum.view.toolbar.db.tooltip = データベース領域を左脇に表示する
spectrum.view.toolbar.open = 開く
spectrum.view.toolbar.open.tooltip = Glowlink計測器で保存したデータファイルを読み込む
spectrum.view.toolbar.export = 出力
......
spectrum.view.chart.xaxis = 周波数 [MHz]
spectrum.view.chart.yaxis = 電力 [dBm]
spectrum.view.chart.legend.menu.remove = グラフから削除
spectrum.view.chart.legend.menu.register = データベースへ登録
spectrum.view.registerdb.succeeded = {0}をデータベースへ登録しました
spectrum.view.registerdb.failed = {0}をデータベースに登録できませんでした
spectrum.view.start_frequency = 開始周波数
spectrum.view.stop_frequency = 終了周波数
spectrum.view.reference_level = 基準レベル
......
spectrum.view.update = 更新
spectrum.view.detrend = 変動除去
spectrum.view.rcfilter = 平滑(RC)フィルタ適用
spectrum.registerview.title = スペクトラムと観測データをデータベースへ登録
spectrum.registerview.spectrum.label = スペクトラム名
spectrum.registerview.site.label = 観測地
spectrum.registerview.equipment.label = 観測機器
spectrum.registerview.satellite.label = 衛星
spectrum.registerview.polarizationtype.label = 偏波方式
spectrum.registerview.azimuth.label = 方位角
spectrum.registerview.elevation.label = 仰角
spectrum.registerview.polarization.label = 偏波回転角
spectrum.registerview.timestamp.label = 観測日時
spectrum.regsiterview.register.button = データベース登録

他の形式にエクスポート: Unified diff