diff --git a/README.md b/README.md index e8ec4ad5..f6e86d5d 100644 --- a/README.md +++ b/README.md @@ -42,18 +42,18 @@ We don't presently deploy versioned artifacts into a public repository like the #### **As a single runnable JAR** ```shell -java -jar coda-calibration/calibration-standalone/target/calibration-standalone-1.0.1-runnable.jar +java -jar coda-calibration/calibration-standalone/target/calibration-standalone-1.0.2-runnable.jar ``` #### **GUI alone** ```shell -java -jar coda-calibration/calibration-gui/target/calibration-gui-1.0.1-runnable.jar +java -jar coda-calibration/calibration-gui/target/calibration-gui-1.0.2-runnable.jar ``` #### **Calibration REST service alone** ```shell -java -jar coda-calibration/calibration-service/application/target/application-1.0.1-runnable.jar +java -jar coda-calibration/calibration-service/application/target/application-1.0.2-runnable.jar ``` #### A note about HTTPS @@ -89,7 +89,7 @@ As of 1.0, CCT is capable of loading four basic file types ```text STATION_CHANNEL_EVENTID_LOWFREQ_HIGHFREQ_UNITS_.*.env - (e.g. ANMO.STACK.999999_1.0_1.5_VEL_.env) + (e.g. ANMO_STACK_999999_1.0_1.5_VEL_.env) ``` 2. Reference events diff --git a/calibration-gui/pom.xml b/calibration-gui/pom.xml index e1059df7..88d13279 100644 --- a/calibration-gui/pom.xml +++ b/calibration-gui/pom.xml @@ -5,7 +5,7 @@ gov.llnl.gnem.apps.coda.calibration calibration-gui - 1.0.1 + 1.0.2 jar calibration-gui @@ -54,7 +54,7 @@ org.springframework.boot spring-boot-starter-parent - 2.0.0.RC2 + 2.0.2.RELEASE pom import @@ -65,12 +65,12 @@ gov.llnl.gnem.apps.coda.calibration externals - 1.0.1 + 1.0.2 gov.llnl.gnem.apps.coda.calibration model - 1.0.1 + 1.0.2 org.eclipse.persistence @@ -202,7 +202,7 @@ org.springframework.boot spring-boot-maven-plugin - 2.0.0.RC2 + 2.0.2.RELEASE true ${start-class} diff --git a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/CodaGuiController.java b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/CodaGuiController.java index f6767fde..fcc79e30 100644 --- a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/CodaGuiController.java +++ b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/CodaGuiController.java @@ -38,7 +38,7 @@ import gov.llnl.gnem.apps.coda.calibration.gui.controllers.ReferenceEventLoadingController; import gov.llnl.gnem.apps.coda.calibration.gui.controllers.WaveformLoadingController; import gov.llnl.gnem.apps.coda.calibration.gui.data.client.api.CalibrationClient; -import gov.llnl.gnem.apps.coda.calibration.gui.plotting.WaveformGui; +import gov.llnl.gnem.apps.coda.calibration.gui.events.ShowFailureReportEvent; import gov.llnl.gnem.apps.coda.calibration.gui.util.CalibrationProgressListener; import gov.llnl.gnem.apps.coda.calibration.gui.util.ProgressMonitor; import gov.llnl.gnem.apps.coda.calibration.model.domain.messaging.CalibrationStatusEvent; @@ -76,7 +76,6 @@ public class CodaGuiController { private EventBus bus; private ProgressGui loadingGui; - private WaveformGui waveformGui; private Map monitors = new HashMap<>(); @@ -88,13 +87,12 @@ public class CodaGuiController { @Autowired public CodaGuiController(WaveformLoadingController waveformLoadingController, CodaParamLoadingController codaParamLoadingController, ReferenceEventLoadingController refEventLoadingController, - CalibrationClient calibrationClient, WaveformGui waveformGui, EventBus bus) throws IOException { + CalibrationClient calibrationClient, EventBus bus) throws IOException { super(); this.waveformLoadingController = waveformLoadingController; this.codaParamLoadingController = codaParamLoadingController; this.refEventLoadingController = refEventLoadingController; this.calibrationClient = calibrationClient; - this.waveformGui = waveformGui; this.bus = bus; sacDirFileChooser.setTitle("Coda STACK File Directory"); sacFileChooser.getExtensionFilters().add(new ExtensionFilter("Coda STACK Files (.sac,.env)", "*.sac", "*.env")); @@ -118,8 +116,8 @@ private void openWaveformLoadingWindow() { } @FXML - private void openWaveformDisplay() { - waveformGui.show(); + private void openFailureReportDisplay() { + bus.post(new ShowFailureReportEvent()); } @FXML diff --git a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/GuiApplication.java b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/GuiApplication.java index d0a7f9a2..ae6ad2f6 100644 --- a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/GuiApplication.java +++ b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/GuiApplication.java @@ -18,11 +18,13 @@ import java.io.IOException; import java.lang.reflect.Method; import java.net.URL; +import java.util.TimeZone; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.jar.Attributes; import java.util.jar.Manifest; +import javax.annotation.PostConstruct; import javax.swing.SwingUtilities; import org.slf4j.Logger; @@ -52,6 +54,11 @@ public class GuiApplication extends Application { private Stage primaryStage; + @PostConstruct + void started() { + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + } + public GuiApplication() { } diff --git a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/FailureReportController.java b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/FailureReportController.java new file mode 100644 index 00000000..f5cc0a16 --- /dev/null +++ b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/FailureReportController.java @@ -0,0 +1,109 @@ +/* +* Copyright (c) 2018, Lawrence Livermore National Security, LLC. Produced at the Lawrence Livermore National Laboratory +* CODE-743439. +* All rights reserved. +* This file is part of CCT. For details, see https://github.com/LLNL/coda-calibration-tool. +* +* Licensed under the Apache License, Version 2.0 (the “Licensee”); 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. +* +* This work was performed under the auspices of the U.S. Department of Energy +* by Lawrence Livermore National Laboratory under Contract DE-AC52-07NA27344. +*/ +package gov.llnl.gnem.apps.coda.calibration.gui.controllers; + +import java.io.IOException; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; + +import gov.llnl.gnem.apps.coda.calibration.gui.events.LoadStartingEvent; +import gov.llnl.gnem.apps.coda.calibration.gui.events.ShowFailureReportEvent; +import gov.llnl.gnem.apps.coda.calibration.gui.util.CellBindingUtils; +import gov.llnl.gnem.apps.coda.calibration.model.domain.messaging.PassFailEvent; +import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.text.Font; +import javafx.stage.Stage; +import javafx.stage.StageStyle; + +@Component +public class FailureReportController { + + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + private Parent root; + private Scene scene; + private Stage stage; + + @FXML + TableView tableView; + + @FXML + TableColumn errorCol; + + private ObservableList errors = FXCollections.observableArrayList(); + + private EventBus bus; + + @Autowired + public FailureReportController(EventBus bus) { + this.bus = bus; + bus.register(this); + Platform.runLater(() -> { + FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/FailureReport.fxml")); + fxmlLoader.setController(this); + stage = new Stage(StageStyle.DECORATED); + Font.loadFont(getClass().getResource("/fxml/MaterialIcons-Regular.ttf").toExternalForm(), 18); + try { + root = fxmlLoader.load(); + scene = new Scene(root); + stage.setScene(scene); + } catch (IOException e) { + throw new IllegalStateException(e); + } + }); + } + + @Subscribe + private void listener(LoadStartingEvent event) { + errors.clear(); + } + + @Subscribe + private void listener(ShowFailureReportEvent event) { + Platform.runLater(() -> { + stage.show(); + stage.toFront(); + }); + } + + @Subscribe + private void listener(PassFailEvent event) { + if (event.getResult() != null && !event.getResult().isSuccess()) { + errors.addAll(event.getResult().getErrors().stream().map(e -> e.getMessage()).collect(Collectors.toList())); + } + } + + @FXML + public void initialize() { + CellBindingUtils.attachTextCellFactoriesString(errorCol, Function.identity()); + tableView.setItems(errors); + } +} diff --git a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/PathController.java b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/PathController.java index 2f520bed..aca45039 100644 --- a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/PathController.java +++ b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/PathController.java @@ -55,14 +55,18 @@ import javafx.scene.control.Tooltip; import javafx.scene.layout.StackPane; import llnl.gnem.core.gui.plotting.MouseOverPlotObject; +import llnl.gnem.core.gui.plotting.PaintMode; +import llnl.gnem.core.gui.plotting.PenStyle; import llnl.gnem.core.gui.plotting.jmultiaxisplot.JMultiAxisPlot; import llnl.gnem.core.gui.plotting.jmultiaxisplot.JSubplot; import llnl.gnem.core.gui.plotting.plotobject.Circle; +import llnl.gnem.core.gui.plotting.plotobject.Line; import llnl.gnem.core.gui.plotting.plotobject.PlotObject; import llnl.gnem.core.gui.plotting.plotobject.Square; import llnl.gnem.core.gui.plotting.plotobject.Symbol; import llnl.gnem.core.gui.plotting.plotobject.TriangleDn; import llnl.gnem.core.gui.plotting.plotobject.TriangleUp; +import llnl.gnem.core.util.SeriesMath; import llnl.gnem.core.util.Geometry.EModel; @Component @@ -382,7 +386,7 @@ private void plotSd() { if (plotObj.getYcenter() < ymin) { ymin = plotObj.getYcenter(); } - plot.AddPlotObject(plotObj); + plot.AddPlotObject(plotObj, 9); if (plotObj2.getXcenter() > xmax) { xmax = plotObj2.getXcenter(); @@ -483,7 +487,7 @@ private void plotBeforeAfter() { if (plotObj.getYcenter() < xmin) { xmin = plotObj.getYcenter(); } - plot.AddPlotObject(plotObj); + plot.AddPlotObject(plotObj, 9); if (plotObj2.getXcenter() > xmax) { xmax = plotObj2.getXcenter(); @@ -503,9 +507,22 @@ private void plotBeforeAfter() { } } + + double paddedXmin = xmin - (xmin * .1); + double paddedXmax = xmax + (xmax * .1); if (xmax != null) { - plot.SetAxisLimits(xmin - (xmin * .1), xmax + (xmax * .1), xmin - (xmin * .1), xmax + (xmax * .1)); + plot.SetAxisLimits(paddedXmin, paddedXmax, paddedXmin, paddedXmax); } + int points = 50; + double dx = (plot.getXaxis().getMax()) / (points - 1); + float[] xy = new float[points]; + for (int i = 0; i < points; i++) { + xy[i] = (float) (0 + (dx * i)); + } + Line line = new Line(xy, xy, Color.black, PaintMode.COPY, PenStyle.DASH, 2); + + plot.AddPlotObject(line, 1); + if (stationDistance == null) { stationDistance = 0.0; } diff --git a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/ProgressGui.java b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/ProgressGui.java index 6644ea5e..832407ec 100644 --- a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/ProgressGui.java +++ b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/ProgressGui.java @@ -16,6 +16,7 @@ import java.io.IOException; +import gov.llnl.gnem.apps.coda.calibration.gui.util.ClickUtils; import gov.llnl.gnem.apps.coda.calibration.gui.util.ProgressMonitor; import javafx.application.Platform; import javafx.beans.binding.Bindings; @@ -74,8 +75,9 @@ public TableCell call(TableColumn return new TableCell() { @Override protected void updateItem(Node item, boolean empty) { - if (item == getItem()) + if (item == getItem()) { return; + } super.updateItem(item, empty); if (item == null) { @@ -90,6 +92,11 @@ protected void updateItem(Node item, boolean empty) { } }); progressTable.setItems(monitors); + progressTable.getSelectionModel().selectedItemProperty().addListener((obs, prevSelection, newSelection) -> { + if (newSelection != null) { + ClickUtils.clickNode(newSelection); + } + }); } catch (IOException e) { throw new IllegalStateException(e); } diff --git a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/WaveformLoadingController.java b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/WaveformLoadingController.java index 9f03981b..c83ac6b1 100644 --- a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/WaveformLoadingController.java +++ b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/WaveformLoadingController.java @@ -38,19 +38,26 @@ import gov.llnl.gnem.apps.coda.calibration.gui.converters.api.FileToWaveformConverter; import gov.llnl.gnem.apps.coda.calibration.gui.converters.sac.SacExporter; import gov.llnl.gnem.apps.coda.calibration.gui.data.client.api.WaveformClient; +import gov.llnl.gnem.apps.coda.calibration.gui.events.LoadStartingEvent; +import gov.llnl.gnem.apps.coda.calibration.gui.events.ShowFailureReportEvent; import gov.llnl.gnem.apps.coda.calibration.gui.util.PassFailEventProgressListener; import gov.llnl.gnem.apps.coda.calibration.gui.util.ProgressEventProgressListener; import gov.llnl.gnem.apps.coda.calibration.gui.util.ProgressMonitor; import gov.llnl.gnem.apps.coda.calibration.model.domain.Waveform; +import gov.llnl.gnem.apps.coda.calibration.model.domain.messaging.PassFailEvent; import gov.llnl.gnem.apps.coda.calibration.model.domain.messaging.Progress; import gov.llnl.gnem.apps.coda.calibration.model.domain.messaging.ProgressEvent; import gov.llnl.gnem.apps.coda.calibration.model.domain.messaging.Result; +import javafx.application.Platform; +import javafx.scene.input.MouseEvent; //TODO: This class needs a GUI to display a list of files it's attempting to load and process + pass/fail indicators @Component @ConfigurationProperties("waveform.client") public class WaveformLoadingController { + private static final Long LOCAL_FAIL_EVENT = -1l; + private final Logger log = LoggerFactory.getLogger(this.getClass()); private WaveformClient client; @@ -120,11 +127,18 @@ public void loadFiles(List inputFiles) { }); if (!files.isEmpty()) { - Progress fileProcessingProgress = new Progress(0l, 0l); + bus.post(new LoadStartingEvent()); + // TODO: Condense these bars into a composite pass/fail progress bar + Progress fileProcessingProgress = new Progress(-1l, 0l); + Progress fileFailedProgress = new Progress(-1l, 0l); ProgressEvent processingProgressEvent = new ProgressEvent(idCounter.getAndIncrement(), fileProcessingProgress); + ProgressEvent processingFailedProgressEvent = new ProgressEvent(idCounter.getAndIncrement(), fileFailedProgress); ProgressMonitor processingMonitor = new ProgressMonitor("File Processing", new ProgressEventProgressListener(bus, processingProgressEvent)); + ProgressMonitor processingFailedMonitor = new ProgressMonitor("Processing Failures", new ProgressEventProgressListener(bus, processingFailedProgressEvent)); + processingFailedMonitor.addEventFilter(MouseEvent.MOUSE_CLICKED, e -> bus.post(new ShowFailureReportEvent())); + Platform.runLater(() -> processingFailedMonitor.getProgressBar().getStyleClass().add("red-bar")); - Progress fileUploadProgress = new Progress(0l, 0l); + Progress fileUploadProgress = new Progress(-1l, 0l); ProgressEvent fileUploadProgressEvent = new ProgressEvent(idCounter.getAndIncrement(), fileUploadProgress); ProgressMonitor uploadMonitor = new ProgressMonitor("Saving Data", new PassFailEventProgressListener(bus, fileUploadProgressEvent)); @@ -132,6 +146,7 @@ public void loadFiles(List inputFiles) { ProgressGui progressGui = new ProgressGui(); progressGui.show(); progressGui.addProgressMonitor(processingMonitor); + progressGui.addProgressMonitor(processingFailedMonitor); progressGui.addProgressMonitor(uploadMonitor); fileUploadProgress.setTotal((long) (files.size() / maxBatching) + 1); @@ -140,15 +155,28 @@ public void loadFiles(List inputFiles) { fileProcessingProgress.setTotal(Integer.toUnsignedLong(files.size())); bus.post(processingProgressEvent); + fileFailedProgress.setTotal(0l); + bus.post(processingFailedProgressEvent); + fileConverters.stream().forEach(fileConverter -> fileConverter.convertFiles(files).buffer(maxBatching, ArrayList::new).subscribe(results -> { - // TODO: Feedback to the user about failure causes! try { List> successfulResults = results.stream().filter(Result::isSuccess).collect(Collectors.toList()); + List> failedResults = results.stream().filter(r -> !r.isSuccess()).collect(Collectors.toList()); client.postWaveforms(fileUploadProgressEvent.getId(), successfulResults.stream().map(result -> result.getResultPayload().get()).collect(Collectors.toList())) .retry(3) .subscribe(); fileProcessingProgress.setCurrent(fileProcessingProgress.getCurrent() + successfulResults.size()); + fileUploadProgress.setTotal((long) ((files.size() - failedResults.size()) / maxBatching) + 1); + bus.post(fileUploadProgressEvent); + bus.post(processingProgressEvent); + if (failedResults.size() > 0) { + fileProcessingProgress.setTotal(fileProcessingProgress.getTotal() - failedResults.size()); + fileFailedProgress.setTotal(fileFailedProgress.getTotal() + failedResults.size()); + fileFailedProgress.setCurrent(fileFailedProgress.getCurrent() + failedResults.size()); + bus.post(processingFailedProgressEvent); + failedResults.forEach(r -> bus.post(new PassFailEvent(LOCAL_FAIL_EVENT, "", r))); + } } catch (JsonProcessingException ex) { log.trace(ex.getMessage(), ex); } @@ -160,7 +188,6 @@ public void loadFiles(List inputFiles) { } catch (IllegalStateException e) { log.error("Unable to instantiate loading display {}", e.getMessage(), e); } - }); } diff --git a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/parameters/ModelController.java b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/parameters/ModelController.java index 4cceeaf9..6ab2b001 100644 --- a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/parameters/ModelController.java +++ b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/parameters/ModelController.java @@ -14,9 +14,7 @@ */ package gov.llnl.gnem.apps.coda.calibration.gui.controllers.parameters; -import java.text.NumberFormat; import java.util.Objects; -import java.util.Optional; import javax.annotation.PostConstruct; @@ -25,17 +23,18 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import com.fasterxml.jackson.core.JsonProcessingException; + import gov.llnl.gnem.apps.coda.calibration.gui.data.client.api.ParameterClient; -import gov.llnl.gnem.apps.coda.calibration.gui.util.NumberFormatFactory; +import gov.llnl.gnem.apps.coda.calibration.gui.util.CellBindingUtils; import gov.llnl.gnem.apps.coda.calibration.model.domain.MdacParametersFI; import gov.llnl.gnem.apps.coda.calibration.model.domain.MdacParametersPS; -import javafx.beans.binding.Bindings; import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import javafx.event.ActionEvent; +import javafx.event.Event; import javafx.fxml.FXML; import javafx.scene.control.TableColumn; -import javafx.scene.control.TableColumn.CellDataFeatures; +import javafx.scene.control.TableColumn.CellEditEvent; import javafx.scene.control.TableView; @Component @@ -43,8 +42,6 @@ public class ModelController { private final Logger log = LoggerFactory.getLogger(this.getClass()); - private final NumberFormat dfmt2 = NumberFormatFactory.twoDecimalOneLeadingZero(); - @FXML private TableView fiTableView; @@ -134,7 +131,7 @@ public ModelController(ParameterClient client) { } @FXML - private void reloadTable(ActionEvent e) { + private void reloadTable(Event e) { requestData(); } @@ -143,6 +140,28 @@ private void onSpringStartupFinished() { requestData(); } + @FXML + private void postUpdate(CellEditEvent e) { + fiData.forEach(p -> { + try { + client.postFiParameters(p); + } catch (JsonProcessingException e1) { + log.error(e1.getMessage(), e1); + } + }); + + psData.forEach(p -> { + try { + client.postPsParameters(p); + } catch (JsonProcessingException e1) { + log.error(e1.getMessage(), e1); + } + }); + fiData.clear(); + psData.clear(); + requestData(); + } + protected void requestData() { fiData.clear(); psData.clear(); @@ -154,172 +173,30 @@ protected void requestData() { @FXML public void initialize() { - phaseCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(MdacParametersPS::getPhase) - .filter(Objects::nonNull) - .orElseGet(String::new))); - - q0Col.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(MdacParametersPS::getQ0) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - delQ0Col.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(MdacParametersPS::getDelQ0) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - gammaCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(MdacParametersPS::getGamma0) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - delGammaCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(MdacParametersPS::getDelGamma0) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - u0Col.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(MdacParametersPS::getU0) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - etaCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(MdacParametersPS::getEta) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - delEtaCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(MdacParametersPS::getDelEta) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - distCritCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(MdacParametersPS::getDistCrit) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - snrCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(MdacParametersPS::getSnr) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - sigmaCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(MdacParametersFI::getSigma) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - delSigmaCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(MdacParametersFI::getDelSigma) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - psiCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(MdacParametersFI::getPsi) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - delPsiCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(MdacParametersFI::getDelPsi) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - zetaCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(MdacParametersFI::getZeta) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - m0RefCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(MdacParametersFI::getM0ref) - .filter(Objects::nonNull) - .map(d -> d.toString()) - .orElseGet(String::new))); - - alphasCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(MdacParametersFI::getAlphas) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - betasCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(MdacParametersFI::getBetas) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - rhosCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(MdacParametersFI::getRhos) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - radpatPCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(MdacParametersFI::getRadPatP) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - radpatSCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(MdacParametersFI::getRadPatS) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - alpharCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(MdacParametersFI::getAlphaR) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - betarCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(MdacParametersFI::getBetaR) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - rhorCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(MdacParametersFI::getRhor) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); + CellBindingUtils.attachTextCellFactoriesString(phaseCol, MdacParametersPS::getPhase); + CellBindingUtils.attachEditableTextCellFactories(q0Col, MdacParametersPS::getQ0, MdacParametersPS::setQ0); + CellBindingUtils.attachEditableTextCellFactories(delQ0Col, MdacParametersPS::getDelQ0, MdacParametersPS::setDelQ0); + CellBindingUtils.attachEditableTextCellFactories(gammaCol, MdacParametersPS::getGamma0, MdacParametersPS::setGamma0); + CellBindingUtils.attachEditableTextCellFactories(delGammaCol, MdacParametersPS::getDelGamma0, MdacParametersPS::setDelGamma0); + CellBindingUtils.attachEditableTextCellFactories(u0Col, MdacParametersPS::getU0, MdacParametersPS::setU0); + CellBindingUtils.attachEditableTextCellFactories(etaCol, MdacParametersPS::getEta, MdacParametersPS::setEta); + CellBindingUtils.attachEditableTextCellFactories(delEtaCol, MdacParametersPS::getDelEta, MdacParametersPS::setDelEta); + CellBindingUtils.attachEditableTextCellFactories(distCritCol, MdacParametersPS::getDistCrit, MdacParametersPS::setDistCrit); + CellBindingUtils.attachEditableTextCellFactories(snrCol, MdacParametersPS::getSnr, MdacParametersPS::setSnr); + CellBindingUtils.attachEditableTextCellFactories(sigmaCol, MdacParametersFI::getSigma, MdacParametersFI::setSigma); + CellBindingUtils.attachEditableTextCellFactories(delSigmaCol, MdacParametersFI::getDelSigma, MdacParametersFI::setDelSigma); + CellBindingUtils.attachEditableTextCellFactories(psiCol, MdacParametersFI::getPsi, MdacParametersFI::setPsi); + CellBindingUtils.attachEditableTextCellFactories(delPsiCol, MdacParametersFI::getDelPsi, MdacParametersFI::setDelPsi); + CellBindingUtils.attachEditableTextCellFactories(zetaCol, MdacParametersFI::getZeta, MdacParametersFI::setZeta); + CellBindingUtils.attachEditableTextCellFactories(m0RefCol, MdacParametersFI::getM0ref, MdacParametersFI::setM0ref); + CellBindingUtils.attachEditableTextCellFactories(alphasCol, MdacParametersFI::getAlphas, MdacParametersFI::setAlphas); + CellBindingUtils.attachEditableTextCellFactories(betasCol, MdacParametersFI::getBetas, MdacParametersFI::setBetas); + CellBindingUtils.attachEditableTextCellFactories(rhosCol, MdacParametersFI::getRhos, MdacParametersFI::setRhos); + CellBindingUtils.attachEditableTextCellFactories(radpatPCol, MdacParametersFI::getRadPatP, MdacParametersFI::setRadPatP); + CellBindingUtils.attachEditableTextCellFactories(radpatSCol, MdacParametersFI::getRadPatS, MdacParametersFI::setRadPatS); + CellBindingUtils.attachEditableTextCellFactories(alpharCol, MdacParametersFI::getAlphaR, MdacParametersFI::setAlphaR); + CellBindingUtils.attachEditableTextCellFactories(betarCol, MdacParametersFI::getBetaR, MdacParametersFI::setBetaR); + CellBindingUtils.attachEditableTextCellFactories(rhorCol, MdacParametersFI::getRhor, MdacParametersFI::setRhor); fiTableView.setItems(fiData); psTableView.setItems(psData); diff --git a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/parameters/SharedBandController.java b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/parameters/SharedBandController.java index 2f920b5b..a0e3de2d 100644 --- a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/parameters/SharedBandController.java +++ b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/controllers/parameters/SharedBandController.java @@ -16,7 +16,6 @@ import java.text.NumberFormat; import java.util.Objects; -import java.util.Optional; import javax.annotation.PostConstruct; @@ -25,17 +24,19 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import com.fasterxml.jackson.core.JsonProcessingException; + import gov.llnl.gnem.apps.coda.calibration.gui.data.client.api.ParameterClient; +import gov.llnl.gnem.apps.coda.calibration.gui.util.CellBindingUtils; import gov.llnl.gnem.apps.coda.calibration.gui.util.MaybeNumericStringComparator; import gov.llnl.gnem.apps.coda.calibration.gui.util.NumberFormatFactory; import gov.llnl.gnem.apps.coda.calibration.model.domain.SharedFrequencyBandParameters; -import javafx.beans.binding.Bindings; import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import javafx.event.ActionEvent; +import javafx.event.Event; import javafx.fxml.FXML; import javafx.scene.control.TableColumn; -import javafx.scene.control.TableColumn.CellDataFeatures; +import javafx.scene.control.TableColumn.CellEditEvent; import javafx.scene.control.TableView; @Component @@ -43,8 +44,6 @@ public class SharedBandController { private final Logger log = LoggerFactory.getLogger(this.getClass()); - private final NumberFormat dfmt2 = NumberFormatFactory.twoDecimalOneLeadingZero(); - private final NumberFormat dfmt4 = NumberFormatFactory.fourDecimalOneLeadingZero(); @FXML @@ -114,154 +113,53 @@ public SharedBandController(ParameterClient client) { } @FXML - private void reloadTable(ActionEvent e) { + private void reloadTable(Event e) { + requestData(); + } + + @FXML + private void postUpdate(CellEditEvent e) { + sharedFbData.forEach(fb -> { + try { + client.postSharedFrequencyBandParameters(fb); + } catch (JsonProcessingException e1) { + log.error(e1.getMessage(), e1); + } + }); + sharedFbData.clear(); requestData(); } @FXML public void initialize() { - lowFreqCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(SharedFrequencyBandParameters::getLowFrequency) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); + CellBindingUtils.attachEditableTextCellFactories(lowFreqCol, SharedFrequencyBandParameters::getLowFrequency, SharedFrequencyBandParameters::setLowFrequency); lowFreqCol.comparatorProperty().set(new MaybeNumericStringComparator()); - highFreqCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(SharedFrequencyBandParameters::getHighFrequency) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); + CellBindingUtils.attachEditableTextCellFactories(highFreqCol, SharedFrequencyBandParameters::getHighFrequency, SharedFrequencyBandParameters::setHighFrequency); highFreqCol.comparatorProperty().set(new MaybeNumericStringComparator()); - v0Col.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(SharedFrequencyBandParameters::getVelocity0) - .filter(Objects::nonNull) - .map(dfmt4::format) - .orElseGet(String::new))); - - v1Col.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(SharedFrequencyBandParameters::getVelocity1) - .filter(Objects::nonNull) - .map(dfmt4::format) - .orElseGet(String::new))); - - v2Col.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(SharedFrequencyBandParameters::getVelocity2) - .filter(Objects::nonNull) - .map(dfmt4::format) - .orElseGet(String::new))); - - b0Col.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(SharedFrequencyBandParameters::getBeta0) - .filter(Objects::nonNull) - .map(dfmt4::format) - .orElseGet(String::new))); - - b1Col.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(SharedFrequencyBandParameters::getBeta1) - .filter(Objects::nonNull) - .map(dfmt4::format) - .orElseGet(String::new))); - - b2Col.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(SharedFrequencyBandParameters::getBeta2) - .filter(Objects::nonNull) - .map(dfmt4::format) - .orElseGet(String::new))); - - g0Col.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(SharedFrequencyBandParameters::getGamma0) - .filter(Objects::nonNull) - .map(dfmt4::format) - .orElseGet(String::new))); - - g1Col.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(SharedFrequencyBandParameters::getGamma1) - .filter(Objects::nonNull) - .map(dfmt4::format) - .orElseGet(String::new))); - - g2Col.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(SharedFrequencyBandParameters::getGamma2) - .filter(Objects::nonNull) - .map(dfmt4::format) - .orElseGet(String::new))); - - minSnrCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(SharedFrequencyBandParameters::getMinSnr) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - s1Col.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(SharedFrequencyBandParameters::getS1) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - s2Col.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(SharedFrequencyBandParameters::getS2) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - xcCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(SharedFrequencyBandParameters::getXc) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - xtCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(SharedFrequencyBandParameters::getXt) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - qCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(SharedFrequencyBandParameters::getQ) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - minLengthCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(SharedFrequencyBandParameters::getMinLength) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - maxLengthCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(SharedFrequencyBandParameters::getMaxLength) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); - - measureTimeCol.setCellValueFactory(x -> Bindings.createStringBinding(() -> Optional.ofNullable(x) - .map(CellDataFeatures::getValue) - .map(SharedFrequencyBandParameters::getMeasurementTime) - .filter(Objects::nonNull) - .map(dfmt2::format) - .orElseGet(String::new))); + CellBindingUtils.attachTextCellFactories(v0Col, SharedFrequencyBandParameters::getVelocity0, dfmt4); + CellBindingUtils.attachTextCellFactories(v1Col, SharedFrequencyBandParameters::getVelocity1, dfmt4); + CellBindingUtils.attachTextCellFactories(v2Col, SharedFrequencyBandParameters::getVelocity2, dfmt4); + CellBindingUtils.attachTextCellFactories(b0Col, SharedFrequencyBandParameters::getBeta0, dfmt4); + CellBindingUtils.attachTextCellFactories(b1Col, SharedFrequencyBandParameters::getBeta1, dfmt4); + CellBindingUtils.attachTextCellFactories(b2Col, SharedFrequencyBandParameters::getBeta2, dfmt4); + CellBindingUtils.attachTextCellFactories(g0Col, SharedFrequencyBandParameters::getGamma0, dfmt4); + CellBindingUtils.attachTextCellFactories(g1Col, SharedFrequencyBandParameters::getGamma1, dfmt4); + CellBindingUtils.attachTextCellFactories(g2Col, SharedFrequencyBandParameters::getGamma2, dfmt4); + + CellBindingUtils.attachEditableTextCellFactories(minSnrCol, SharedFrequencyBandParameters::getMinSnr, SharedFrequencyBandParameters::setMinSnr); + + CellBindingUtils.attachTextCellFactories(s1Col, SharedFrequencyBandParameters::getS1); + CellBindingUtils.attachTextCellFactories(s2Col, SharedFrequencyBandParameters::getS2); + CellBindingUtils.attachTextCellFactories(xcCol, SharedFrequencyBandParameters::getXc); + CellBindingUtils.attachTextCellFactories(xtCol, SharedFrequencyBandParameters::getXt); + CellBindingUtils.attachTextCellFactories(qCol, SharedFrequencyBandParameters::getQ); + + CellBindingUtils.attachEditableTextCellFactories(minLengthCol, SharedFrequencyBandParameters::getMinLength, SharedFrequencyBandParameters::setMinLength); + CellBindingUtils.attachEditableTextCellFactories(maxLengthCol, SharedFrequencyBandParameters::getMaxLength, SharedFrequencyBandParameters::setMaxLength); + CellBindingUtils.attachEditableTextCellFactories(measureTimeCol, SharedFrequencyBandParameters::getMeasurementTime, SharedFrequencyBandParameters::setMeasurementTime); codaSharedTableView.setItems(sharedFbData); } diff --git a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/converters/sac/SacLoader.java b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/converters/sac/SacLoader.java index 37bf5f1f..87419f6c 100644 --- a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/converters/sac/SacLoader.java +++ b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/converters/sac/SacLoader.java @@ -79,7 +79,7 @@ public Result convertSacFileToWaveform(File file) { return exceptionalResult(new LightweightIllegalStateException(String.format("Error parsing (%s): file does not exist or is unreadable. %s", "NULL", "File reference is null"))); } - String fileName = file.getName(); + String fileName = file.getPath().toString(); log.trace("Reading {} ", fileName); SACFileReader reader = null; try { @@ -96,16 +96,22 @@ public Result convertSacFileToWaveform(File file) { } String stationName = headerResult.getResultPayload().get(); + TimeT originTime = header.getOriginTime() != null ? header.getOriginTime() : header.getReferenceTime(); + if (originTime == null) { + return exceptionalResult(new LightweightIllegalStateException("Both reference time and origin time may not be null!")); + } + String evid = null; headerResult = validateHeaderDefined(fileName, "NEVID", header.nevid); if (headerResult.isSuccess() && !headerResult.getResultPayload().get().equals("0")) { evid = headerResult.getResultPayload().get(); - } - else { + } else { headerResult = validateHeaderDefined(fileName, "KEVNM", header.kevnm); if (headerResult.isSuccess() && headerResult.getResultPayload().isPresent() && !headerResult.getResultPayload().get().isEmpty()) { evid = headerResult.getResultPayload().get(); - } + } else { + evid = getOrCreateEvid(header); + } } headerResult = validateHeaderDefined(fileName, "KCMPNM", header.kcmpnm); @@ -184,11 +190,15 @@ public Result convertSacFileToWaveform(File file) { double sampleRate = header.delta > 0 ? 1.0 / header.delta : 1.0; float[] rawVals = sequence.getArray(); Double[] segment = new Double[rawVals.length]; - IntStream.range(0, rawVals.length).forEach(index -> segment[index] = Double.valueOf(rawVals[index])); - - TimeT originTime = header.getOriginTime() != null ? header.getOriginTime() : header.getReferenceTime(); - if (originTime == null) { - return exceptionalResult(new IllegalArgumentException("Both reference time and origin time may not be null!")); + try { + IntStream.range(0, rawVals.length).forEach(index -> { + segment[index] = Double.valueOf(rawVals[index]); + if (!Double.isFinite(segment[index])) { + throw new LightweightIllegalStateException("Invalid data in segment for file: " + fileName); + } + }); + } catch (LightweightIllegalStateException e) { + return exceptionalResult(e); } return new Result<>(true, @@ -241,14 +251,12 @@ private List getPicksFromHeader(SACHeader header) { private Result validateDoubleDefinedAndInRange(String fileName, String headerName, float value, double minVal, double maxVal) { Result validation = new Result<>(false, ""); - List exceptions = new ArrayList<>(); - if (SACHeader.isDefault(value)) { - exceptions.add(new LightweightIllegalStateException(String.format(SAC_HEADER_NOT_SET, fileName, headerName))); + validation.getErrors().add(new LightweightIllegalStateException(String.format(SAC_HEADER_NOT_SET, fileName, headerName))); } if (value < minVal || value > maxVal) { - exceptions.add(new LightweightIllegalStateException(String.format("Error parsing (%s): SAC header variable %s must be between %f and %f! Actual value is %f", + validation.getErrors().add(new LightweightIllegalStateException(String.format("Error parsing (%s): SAC header variable %s must be between %f and %f! Actual value is %f", fileName, headerName, minVal, @@ -266,10 +274,9 @@ private Result validateDoubleDefinedAndInRange(String fileName, String h private Result validateHeaderDefined(String fileName, String headerName, int headerValue) { String validHeader = null; Result validation = new Result<>(false, validHeader); - List exceptions = new ArrayList<>(); if (SACHeader.isDefault(headerValue)) { - exceptions.add(new LightweightIllegalStateException(String.format(SAC_HEADER_NOT_SET, fileName, headerName))); + validation.getErrors().add(new LightweightIllegalStateException(String.format(SAC_HEADER_NOT_SET, fileName, headerName))); } else { validHeader = Integer.toString(headerValue); validation.setResultPayload(Optional.of(validHeader)); @@ -286,10 +293,9 @@ private Result validateHeaderDefined(String fileName, String headerName, String validHeader = StringUtils.trimToEmpty(headerValue); Result validation = new Result<>(false, validHeader); - List exceptions = new ArrayList<>(); if (SACHeader.isDefault(validHeader)) { - exceptions.add(new LightweightIllegalStateException(String.format(SAC_HEADER_NOT_SET, fileName, headerName))); + validation.getErrors().add(new LightweightIllegalStateException(String.format(SAC_HEADER_NOT_SET, fileName, headerName))); } if (validation.getErrors().isEmpty()) { @@ -313,4 +319,25 @@ private Result exceptionalResult(List errors) { public PathMatcher getMatchingPattern() { return filter; } + + private String getOrCreateEvid(SACHeader header) { + int evid = 0; + Double time = 0d; + if (header.getOriginTime() != null) { + time = header.getOriginTime().getEpochTime(); + } else if (header.getReferenceTime() != null) { + time = header.getReferenceTime().getEpochTime(); + } + evid = createJDateMinuteResolutionFromEpoch(time); + return Integer.toString(evid); + } + + private int createJDateMinuteResolutionFromEpoch(Double instant) { + TimeT time = new TimeT(instant); + int jDate = time.getYear() % 100; + jDate = jDate * 1000 + time.getJDay(); + jDate = jDate * 100 + time.getHour(); + jDate = jDate * 100 + time.getMinute(); + return jDate; + } } diff --git a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/events/LoadStartingEvent.java b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/events/LoadStartingEvent.java new file mode 100644 index 00000000..4fa39cbf --- /dev/null +++ b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/events/LoadStartingEvent.java @@ -0,0 +1,18 @@ +/* +* Copyright (c) 2018, Lawrence Livermore National Security, LLC. Produced at the Lawrence Livermore National Laboratory +* CODE-743439. +* All rights reserved. +* This file is part of CCT. For details, see https://github.com/LLNL/coda-calibration-tool. +* +* Licensed under the Apache License, Version 2.0 (the “Licensee”); 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. +* +* This work was performed under the auspices of the U.S. Department of Energy +* by Lawrence Livermore National Laboratory under Contract DE-AC52-07NA27344. +*/ +package gov.llnl.gnem.apps.coda.calibration.gui.events; + +public class LoadStartingEvent { +} diff --git a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/events/ShowFailureReportEvent.java b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/events/ShowFailureReportEvent.java new file mode 100644 index 00000000..885badc8 --- /dev/null +++ b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/events/ShowFailureReportEvent.java @@ -0,0 +1,18 @@ +/* +* Copyright (c) 2018, Lawrence Livermore National Security, LLC. Produced at the Lawrence Livermore National Laboratory +* CODE-743439. +* All rights reserved. +* This file is part of CCT. For details, see https://github.com/LLNL/coda-calibration-tool. +* +* Licensed under the Apache License, Version 2.0 (the “Licensee”); 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. +* +* This work was performed under the auspices of the U.S. Department of Energy +* by Lawrence Livermore National Laboratory under Contract DE-AC52-07NA27344. +*/ +package gov.llnl.gnem.apps.coda.calibration.gui.events; + +public class ShowFailureReportEvent { +} diff --git a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/plotting/CodaWaveformPlot.java b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/plotting/CodaWaveformPlot.java index 8eac6cff..b6ff8b23 100644 --- a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/plotting/CodaWaveformPlot.java +++ b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/plotting/CodaWaveformPlot.java @@ -53,183 +53,229 @@ public class CodaWaveformPlot extends SeriesPlot { - private final Logger log = LoggerFactory.getLogger(this.getClass()); - - private NumberFormat dfmt4 = NumberFormatFactory.fourDecimalOneLeadingZero(); - - private Map pickLineMap = new HashMap<>(); - - private WaveformClient waveformClient; - - private ParameterClient paramClient; - - private ShapeMeasurementClient shapeClient; - - public CodaWaveformPlot(String xLabel, WaveformClient waveformClient, ShapeMeasurementClient shapeClient, ParameterClient paramClient, TimeSeries... seismograms) { - super(xLabel, seismograms); - this.waveformClient = waveformClient; - this.shapeClient = shapeClient; - this.paramClient = paramClient; - } - - private static final long serialVersionUID = 1L; - - public void setWaveform(Waveform waveform) { - setWaveform(waveform, null); - } - - public void setWaveform(SyntheticCoda synth) { - setWaveform(synth.getSourceWaveform(), synth); - } - - public void setWaveform(Waveform waveform, SyntheticCoda synth) { - this.clear(); - pickLineMap.clear(); - - if (waveform.getSegment() != null) { - getXaxis().setLabelText(""); - paramClient.getSharedFrequencyBandParametersForFrequency(new FrequencyBand(waveform.getLowFrequency(), waveform.getHighFrequency())).subscribe(params -> { - shapeClient.getMeasuredShape(waveform.getId()).subscribe(shape -> { - TimeT beginTime = new TimeT(waveform.getBeginTime()); - float[] waveformSegment = doublesToFloats(waveform.getSegment()); - TimeSeries series = new TimeSeries(waveformSegment, waveform.getSampleRate(), beginTime); - this.addSeismogram(series); - JSubplot subplot = this.getSubplot(series); - subplot.setYlimits(subplot.getYaxis().getMin() - 1.0, subplot.getYaxis().getMax() + 1.0); - Legend legend = new Legend(getTitle().getFontName(), getTitle().getFontSize(), HorizPinEdge.RIGHT, VertPinEdge.TOP, 5, 5); - legend.addLabeledLine(waveform.getStream().getStation().getStationName() + "_" + waveform.getEvent().getEventId() + "_" + waveform.getLowFrequency() + "_" - + waveform.getHighFrequency(), new Line(0, series.getDelta(), series.getData(), 1)); - subplot.AddPlotObject(legend); - - List picks = waveform.getAssociatedPicks(); - if (picks != null) { - //1221: Plotting throws a runtime error if a pick is before/after the bounds of the waveform so we need to check that - for (WaveformPick pick : picks) { - double pickTime = new TimeT(waveform.getEvent().getOriginTime()).getEpochTime() + pick.getPickTimeSecFromOrigin(); - if (pickTime >= new TimeT(waveform.getBeginTime()).getEpochTime() && pickTime <= new TimeT(waveform.getEndTime()).getEpochTime()) { - Collection pickLines = this.addPick(pick.getPickName(), pickTime); - for (VPickLine pickLine : pickLines) { - pickLine.setDraggable(true); - pickLineMap.put(pickLine, pick); - } - } - } - } - - if (shape != null && shape.getId() != null) { - try { - series = new TimeSeries(waveformSegment, waveform.getSampleRate(), beginTime); - series.interpolate(1.0); - float[] fitSegment = new float[series.getData().length]; - - double gamma = shape.getMeasuredGamma(); - double beta = shape.getMeasuredBeta(); - double intercept = shape.getMeasuredIntercept(); - - int timeShift = (int) (new TimeT(shape.getMeasuredTime()).subtractD(beginTime) - 0.5); - for (int i = 0; i < series.getData().length; i++) { - fitSegment[i] = (float) (intercept - gamma * Math.log10(i + 1) + beta * (i + 1)); - } - - TimeSeries fitSeries = new TimeSeries(fitSegment, series.getSamprate(), series.getTime()); - subplot.AddPlotObject(createLine(timeShift, 0.0, fitSeries, Color.GRAY)); - repaint(); - } catch (IllegalArgumentException e) { - log.warn(e.getMessage(), e); - } - } - - if (synth != null && params != null) { - try { - series = new TimeSeries(waveformSegment, waveform.getSampleRate(), beginTime); - float[] synthSegment = doublesToFloats(synth.getSegment()); - TimeSeries synthSeries = new TimeSeries(synthSegment, synth.getSampleRate(), new TimeT(synth.getBeginTime())); - WaveformPick endPick = null; - for (WaveformPick p : waveform.getAssociatedPicks()) { - if (PICK_TYPES.F.name().equals(p.getPickType())) { - endPick = p; - break; - } - } - TimeT endTime; - if (endPick != null && endPick.getPickTimeSecFromOrigin() > 0 && waveform.getEvent() != null) { - endTime = new TimeT(waveform.getEvent().getOriginTime()).add(endPick.getPickTimeSecFromOrigin()); - } else { - endTime = new TimeT(synth.getEndTime()); - } - - series.interpolate(synthSeries.getSamprate()); - - Station station = synth.getSourceWaveform().getStream().getStation(); - Event event = synth.getSourceWaveform().getEvent(); - - double distance = EModel.getDistanceWGS84(event.getLatitude(), event.getLongitude(), station.getLatitude(), station.getLongitude()); - double vr = params.getVelocity0() - params.getVelocity1() / (params.getVelocity2() + distance); - if (vr == 0.0) { - vr = 1.0; - } - TimeT originTime = new TimeT(event.getOriginTime()); - TimeT startTime = originTime.add(distance / vr); - - if (startTime.lt(endTime)) { - series.cut(startTime, endTime); - synthSeries.cut(startTime, endTime); - - TimeSeries diffSeis = series.subtract(synthSeries); - - int timeShift = (int) (startTime.subtractD(beginTime) - 0.5); - - double median = diffSeis.getMedian(); - double baz = EModel.getBAZ(station.getLatitude(), station.getLongitude(), event.getLatitude(), event.getLongitude()); - - getXaxis().setLabelText(getXaxis().getLabelText() + "Shift: " + dfmt4.format(median) + ", Distance: " + dfmt4.format(distance) + ", BAz: " + dfmt4.format(baz)); - subplot.AddPlotObject(createLine(timeShift, median, synthSeries, Color.GREEN)); - repaint(); - } - } catch (IllegalArgumentException e) { - log.warn(e.getMessage(), e); - } - } - }); - }); - } - } - - private PlotObject createLine(int timeShift, double valueShift, TimeSeries timeSeries, Color lineColor) { - - Line line = new Line(timeShift, timeSeries.getDelta(), SeriesMath.Add(timeSeries.getData(), valueShift), 1); - line.setColor(lineColor); - line.setWidth(3); - return line; - } - - /* (non-Javadoc) - * @see llnl.gnem.core.gui.waveform.WaveformPlot#handlePickMovedState(java.lang.Object)T - */ - @Override - protected void handlePickMovedState(Object obj) { - super.handlePickMovedState(obj); - PickMovedState pms = (PickMovedState) obj; - VPickLine vpl = pms.getPickLine(); - - if (vpl != null) { - try { - WaveformPick pick = pickLineMap.get(vpl); - if (pick != null) { - pick.setPickTimeSecFromOrigin((float) (vpl.getXval() - new TimeT(pick.getWaveform().getEvent().getOriginTime()).subtractD(new TimeT(pick.getWaveform().getBeginTime())))); - waveformClient.postWaveform(pick.getWaveform()).subscribe(this::setWaveform); - } - } catch (ClassCastException | JsonProcessingException e) { - log.info("Error updating Waveform, {}", e); - } - } - } - - public static float[] doublesToFloats(Double[] x) { - float[] xfloats = new float[x.length]; - for (int i = 0; i < x.length; i++) { - xfloats[i] = x[i].floatValue(); - } - return xfloats; - } + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + private NumberFormat dfmt4 = NumberFormatFactory.fourDecimalOneLeadingZero(); + + private Map pickLineMap = new HashMap<>(); + + private WaveformClient waveformClient; + + private ParameterClient paramClient; + + private ShapeMeasurementClient shapeClient; + + public CodaWaveformPlot(String xLabel, WaveformClient waveformClient, ShapeMeasurementClient shapeClient, + ParameterClient paramClient, TimeSeries... seismograms) { + super(xLabel, seismograms); + this.waveformClient = waveformClient; + this.shapeClient = shapeClient; + this.paramClient = paramClient; + } + + private static final long serialVersionUID = 1L; + + public void setWaveform(Waveform waveform) { + setWaveform(waveform, null); + } + + public void setWaveform(SyntheticCoda synth) { + setWaveform(synth.getSourceWaveform(), synth); + } + + public void setWaveform(Waveform waveform, SyntheticCoda synth) { + this.clear(); + pickLineMap.clear(); + + if (waveform.getSegment() != null) { + getXaxis().setLabelText(""); + paramClient .getSharedFrequencyBandParametersForFrequency(new FrequencyBand(waveform.getLowFrequency(), + waveform.getHighFrequency())) + .subscribe(params -> { + shapeClient.getMeasuredShape(waveform.getId()).subscribe(shape -> { + TimeT beginTime = new TimeT(waveform.getBeginTime()); + float[] waveformSegment = doublesToFloats(waveform.getSegment()); + + TimeSeries series = new TimeSeries( waveformSegment, waveform.getSampleRate(), + beginTime); + + this.addSeismogram(series); + JSubplot subplot = this.getSubplot(series); + subplot.setYlimits( subplot.getYaxis().getMin() - 1.0, + subplot.getYaxis().getMax() + 1.0); + Legend legend = new Legend( getTitle().getFontName(), getTitle().getFontSize(), + HorizPinEdge.RIGHT, VertPinEdge.TOP, 5, 5); + legend.addLabeledLine( waveform.getStream().getStation().getStationName() + "_" + + waveform.getEvent().getEventId() + "_" + waveform.getLowFrequency() + "_" + + waveform.getHighFrequency(), + new Line(0, series.getDelta(), series.getData(), 1)); + subplot.AddPlotObject(legend); + + List picks = waveform.getAssociatedPicks(); + if (picks != null) { + // 1221: Plotting throws a runtime error if + // a pick is before/after the bounds of the + // waveform so we need to check that + for (WaveformPick pick : picks) { + double pickTime = new TimeT(waveform.getEvent().getOriginTime()).getEpochTime() + + pick.getPickTimeSecFromOrigin(); + if (pickTime >= new TimeT(waveform.getBeginTime()).getEpochTime() + && pickTime <= new TimeT(waveform.getEndTime()).getEpochTime()) { + Collection pickLines = this.addPick( pick.getPickName(), + pickTime); + for (VPickLine pickLine : pickLines) { + pickLine.setDraggable(true); + pickLineMap.put(pickLine, pick); + } + } + } + } + + if (shape != null && shape.getId() != null) { + try { + series = new TimeSeries(waveformSegment, waveform.getSampleRate(), beginTime); + series.interpolate(1.0); + float[] fitSegment = new float[series.getData().length]; + + double gamma = shape.getMeasuredGamma(); + double beta = shape.getMeasuredBeta(); + double intercept = shape.getMeasuredIntercept(); + + int timeShift = (int) (new TimeT(shape.getMeasuredTime()).subtractD(beginTime) + - 0.5); + for (int i = 0; i < series.getData().length; i++) { + fitSegment[i] = (float) (intercept - gamma * Math.log10(i + 1) + + beta * (i + 1)); + } + + TimeSeries fitSeries = new TimeSeries( fitSegment, series.getSamprate(), + series.getTime()); + subplot.AddPlotObject(createLine(timeShift, 0.0, fitSeries, Color.GRAY)); + repaint(); + } catch (IllegalArgumentException e) { + log.warn(e.getMessage(), e); + } + } + + if (synth != null && params != null) { + try { + series = new TimeSeries(waveformSegment, waveform.getSampleRate(), beginTime); + float[] synthSegment = doublesToFloats(synth.getSegment()); + TimeSeries synthSeries = new TimeSeries(synthSegment, synth.getSampleRate(), + new TimeT(synth.getBeginTime())); + WaveformPick endPick = null; + for (WaveformPick p : waveform.getAssociatedPicks()) { + if (PICK_TYPES.F.name().equals(p.getPickType())) { + endPick = p; + break; + } + } + TimeT endTime; + if (endPick != null && endPick.getPickTimeSecFromOrigin() > 0 + && waveform.getEvent() != null) { + endTime = new TimeT(waveform.getEvent() + .getOriginTime()).add(endPick.getPickTimeSecFromOrigin()); + } else { + endTime = new TimeT(synth.getEndTime()); + } + + series.interpolate(synthSeries.getSamprate()); + + Station station = synth.getSourceWaveform().getStream().getStation(); + Event event = synth.getSourceWaveform().getEvent(); + + double distance = EModel.getDistanceWGS84( event.getLatitude(), + event.getLongitude(), + station.getLatitude(), + station.getLongitude()); + double vr = params.getVelocity0() + - params.getVelocity1() / (params.getVelocity2() + distance); + if (vr == 0.0) { + vr = 1.0; + } + TimeT originTime = new TimeT(event.getOriginTime()); + TimeT startTime; + TimeT trimTime = originTime.add(distance / vr); + TimeSeries trimmedWaveform = new TimeSeries(waveformSegment, waveform.getSampleRate(), beginTime); + try { + trimmedWaveform.cutBefore(trimTime); + trimmedWaveform.cutAfter(trimTime.add(30.0)); + + startTime = new TimeT(trimTime.getEpochTime() + + trimmedWaveform.getMaxTime()[0]); + } catch (IllegalArgumentException e) { + startTime = trimTime; + } + + if (startTime.lt(endTime)) { + series.cut(startTime, endTime); + synthSeries.cut(startTime, endTime); + + TimeSeries diffSeis = series.subtract(synthSeries); + + int timeShift = (int) (startTime.subtractD(beginTime) - 0.5); + + double median = diffSeis.getMedian(); + double baz = EModel.getBAZ( station.getLatitude(), station.getLongitude(), + event.getLatitude(), event.getLongitude()); + + getXaxis().setLabelText(getXaxis().getLabelText() + "Shift: " + + dfmt4.format(median) + ", Distance: " + dfmt4.format(distance) + + ", BAz: " + dfmt4.format(baz)); + subplot.AddPlotObject(createLine( timeShift, median, synthSeries, + Color.GREEN)); + repaint(); + } + } catch (IllegalArgumentException e) { + log.warn(e.getMessage(), e); + } + } + }); + }); + } + } + + private PlotObject createLine(int timeShift, double valueShift, TimeSeries timeSeries, Color lineColor) { + + Line line = new Line(timeShift, timeSeries.getDelta(), SeriesMath.Add(timeSeries.getData(), valueShift), 1); + line.setColor(lineColor); + line.setWidth(3); + return line; + } + + /* + * (non-Javadoc) + * + * @see + * llnl.gnem.core.gui.waveform.WaveformPlot#handlePickMovedState(java.lang. + * Object)T + */ + @Override + protected void handlePickMovedState(Object obj) { + super.handlePickMovedState(obj); + PickMovedState pms = (PickMovedState) obj; + VPickLine vpl = pms.getPickLine(); + + if (vpl != null) { + try { + WaveformPick pick = pickLineMap.get(vpl); + if (pick != null) { + pick.setPickTimeSecFromOrigin((float) (vpl.getXval() + - new TimeT(pick.getWaveform().getEvent() + .getOriginTime()).subtractD(new TimeT(pick.getWaveform().getBeginTime())))); + waveformClient.postWaveform(pick.getWaveform()).subscribe(this::setWaveform); + } + } catch (ClassCastException | JsonProcessingException e) { + log.info("Error updating Waveform, {}", e); + } + } + } + + public static float[] doublesToFloats(Double[] x) { + float[] xfloats = new float[x.length]; + for (int i = 0; i < x.length; i++) { + xfloats[i] = x[i].floatValue(); + } + return xfloats; + } } diff --git a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/util/CalibrationProgressListener.java b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/util/CalibrationProgressListener.java index ee6c4842..a7b99076 100644 --- a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/util/CalibrationProgressListener.java +++ b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/util/CalibrationProgressListener.java @@ -24,7 +24,7 @@ public class CalibrationProgressListener extends ProgressListener { private CalibrationStatusEvent cachedEvent; - private Progress progress = new Progress(1l, 0l); + private Progress progress = new Progress(1l, -1l); public CalibrationProgressListener(EventBus bus, CalibrationStatusEvent event) { bus.register(this); @@ -38,7 +38,7 @@ private void listener(CalibrationStatusEvent event) { if (cachedEvent.getStatus() == Status.COMPLETE || cachedEvent.getStatus() == Status.ERROR) { progress.setCurrent(1l); } else { - progress.setCurrent(0l); + progress.setCurrent(-1l); } this.setChanged(); this.notifyObservers(progress); diff --git a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/util/CellBindingUtils.java b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/util/CellBindingUtils.java new file mode 100644 index 00000000..f951a501 --- /dev/null +++ b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/util/CellBindingUtils.java @@ -0,0 +1,67 @@ +/* +* Copyright (c) 2018, Lawrence Livermore National Security, LLC. Produced at the Lawrence Livermore National Laboratory +* CODE-743439. +* All rights reserved. +* This file is part of CCT. For details, see https://github.com/LLNL/coda-calibration-tool. +* +* Licensed under the Apache License, Version 2.0 (the “Licensee”); 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. +* +* This work was performed under the auspices of the U.S. Department of Energy +* by Lawrence Livermore National Laboratory under Contract DE-AC52-07NA27344. +*/ +package gov.llnl.gnem.apps.coda.calibration.gui.util; + +import java.text.NumberFormat; +import java.util.Objects; +import java.util.Optional; +import java.util.function.BiConsumer; +import java.util.function.Function; + +import javafx.beans.binding.Bindings; +import javafx.beans.value.ObservableValue; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableColumn.CellDataFeatures; +import javafx.scene.control.cell.TextFieldTableCell; +import javafx.util.Callback; + +public class CellBindingUtils { + + private static NumberFormat DEFAULT_NUMBER_FORMAT = NumberFormatFactory.twoDecimalOneLeadingZero(); + + public static void attachTextCellFactories(TableColumn col, Function getValue) { + attachTextCellFactories(col, getValue, DEFAULT_NUMBER_FORMAT); + } + + public static void attachTextCellFactoriesString(TableColumn col, Function getValue) { + col.setCellValueFactory(stringFormatCellBinding(getValue)); + } + + public static void attachTextCellFactories(TableColumn col, Function getValue, NumberFormat format) { + col.setCellValueFactory(numberFormatCellBinding(getValue, format)); + } + + public static void attachEditableTextCellFactories(TableColumn col, Function getValue, BiConsumer setValue) { + attachEditableTextCellFactories(col, getValue, setValue, DEFAULT_NUMBER_FORMAT); + } + + public static void attachEditableTextCellFactories(TableColumn col, Function getValue, BiConsumer setValue, NumberFormat format) { + attachTextCellFactories(col, getValue, format); + col.setCellFactory(TextFieldTableCell.forTableColumn()); + col.setOnEditCommit(new WrappingEventHandler(col.getOnEditCommit(), setValue)); + } + + private static Callback, ObservableValue> numberFormatCellBinding(Function getValue, NumberFormat formatter) { + return x -> Bindings.createStringBinding(() -> getOptionalValue(x, getValue).map(formatter::format).orElseGet(String::new)); + } + + private static Callback, ObservableValue> stringFormatCellBinding(Function getValue) { + return x -> Bindings.createStringBinding(() -> getOptionalValue(x, getValue).orElseGet(String::new)); + } + + private static Optional getOptionalValue(CellDataFeatures x, Function getValue) { + return Optional.ofNullable(x).map(CellDataFeatures::getValue).map(getValue::apply).filter(Objects::nonNull); + } +} diff --git a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/util/ClickUtils.java b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/util/ClickUtils.java new file mode 100644 index 00000000..50a5cbf9 --- /dev/null +++ b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/util/ClickUtils.java @@ -0,0 +1,32 @@ +/* +* Copyright (c) 2018, Lawrence Livermore National Security, LLC. Produced at the Lawrence Livermore National Laboratory +* CODE-743439. +* All rights reserved. +* This file is part of CCT. For details, see https://github.com/LLNL/coda-calibration-tool. +* +* Licensed under the Apache License, Version 2.0 (the “Licensee”); 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. +* +* This work was performed under the auspices of the U.S. Department of Energy +* by Lawrence Livermore National Laboratory under Contract DE-AC52-07NA27344. +*/ +package gov.llnl.gnem.apps.coda.calibration.gui.util; + +import javafx.event.Event; +import javafx.scene.Node; +import javafx.scene.input.MouseButton; +import javafx.scene.input.MouseEvent; + +public class ClickUtils { + + public static void clickNode(Node target) { + try { + Event.fireEvent(target, new MouseEvent(MouseEvent.MOUSE_CLICKED, + target.getScene().getX(), target.getScene().getY(), target.getScene().getX(), target.getScene().getY(), MouseButton.PRIMARY, 1, + false, false, false, false, true, false, false, false, false, true, null)); + } + finally {} + } +} diff --git a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/util/ProgressMonitor.java b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/util/ProgressMonitor.java index cc5035fa..7df53e31 100644 --- a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/util/ProgressMonitor.java +++ b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/util/ProgressMonitor.java @@ -50,7 +50,7 @@ public ProgressMonitor(String displayableName, ProgressListener progressListener Parent root = fxmlLoader.load(); this.getChildren().add(root); - if (progressListener.getProgress() > 0.0) { + if (progressListener.getProgress() >= 0.0) { progressBar.setProgress(progressListener.getProgress()); } else { progressBar.setProgress(ProgressBar.INDETERMINATE_PROGRESS); @@ -67,7 +67,7 @@ public void update(Observable o, Object event) { if (event instanceof Progress) { Progress progress = (Progress) event; Platform.runLater(() -> { - if (progress.getTotal() > 0.0) { + if (progress.getTotal() >= 0.0) { label.setText(progress.getCurrent() + "/" + progress.getTotal()); progressBar.setProgress(progress.getProgress()); } else { @@ -81,4 +81,8 @@ public void update(Observable o, Object event) { public String getDisplayableName() { return displayableName; } + + public ProgressBar getProgressBar() { + return progressBar; + } } diff --git a/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/util/WrappingEventHandler.java b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/util/WrappingEventHandler.java new file mode 100644 index 00000000..2a8576df --- /dev/null +++ b/calibration-gui/src/main/java/gov/llnl/gnem/apps/coda/calibration/gui/util/WrappingEventHandler.java @@ -0,0 +1,43 @@ +/* +* Copyright (c) 2018, Lawrence Livermore National Security, LLC. Produced at the Lawrence Livermore National Laboratory +* CODE-743439. +* All rights reserved. +* This file is part of CCT. For details, see https://github.com/LLNL/coda-calibration-tool. +* +* Licensed under the Apache License, Version 2.0 (the “Licensee”); 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. +* +* This work was performed under the auspices of the U.S. Department of Energy +* by Lawrence Livermore National Laboratory under Contract DE-AC52-07NA27344. +*/ +package gov.llnl.gnem.apps.coda.calibration.gui.util; + +import java.util.function.BiConsumer; + +import javafx.event.EventHandler; +import javafx.scene.control.TableColumn.CellEditEvent; + +public class WrappingEventHandler implements EventHandler> { + + private EventHandler> existingHandler; + private BiConsumer setValue; + + public WrappingEventHandler(EventHandler> existingHandler, BiConsumer setValue) { + this.existingHandler = existingHandler; + this.setValue = setValue; + } + + @Override + public void handle(CellEditEvent e) { + try { + setValue.accept(e.getRowValue(), Double.valueOf(e.getNewValue())); + } catch (Exception ex) { + } + if (existingHandler != null) { + existingHandler.handle(e); + } + } + +} \ No newline at end of file diff --git a/calibration-gui/src/main/resources/fxml/CodaGui.css b/calibration-gui/src/main/resources/fxml/CodaGui.css index 8bb202a1..3fc3427d 100644 --- a/calibration-gui/src/main/resources/fxml/CodaGui.css +++ b/calibration-gui/src/main/resources/fxml/CodaGui.css @@ -10,4 +10,8 @@ -fx-word-wrap: normal; -fx-white-space: nowrap; -fx-direction: ltr; +} + +.red-bar { + -fx-accent: red; } \ No newline at end of file diff --git a/calibration-gui/src/main/resources/fxml/CodaGui.fxml b/calibration-gui/src/main/resources/fxml/CodaGui.fxml index 8aa4eb8d..b78b28c0 100644 --- a/calibration-gui/src/main/resources/fxml/CodaGui.fxml +++ b/calibration-gui/src/main/resources/fxml/CodaGui.fxml @@ -32,7 +32,7 @@ - + diff --git a/calibration-gui/src/main/resources/fxml/FailureReport.fxml b/calibration-gui/src/main/resources/fxml/FailureReport.fxml new file mode 100644 index 00000000..b05e5fe4 --- /dev/null +++ b/calibration-gui/src/main/resources/fxml/FailureReport.fxml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/calibration-gui/src/main/resources/fxml/Model.fxml b/calibration-gui/src/main/resources/fxml/Model.fxml index bbf2eeca..37c29c91 100644 --- a/calibration-gui/src/main/resources/fxml/Model.fxml +++ b/calibration-gui/src/main/resources/fxml/Model.fxml @@ -12,18 +12,18 @@ - + - - - - - - - - - - + + + + + + + + + + @@ -33,22 +33,22 @@ - + - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/calibration-gui/src/main/resources/fxml/SharedBand.fxml b/calibration-gui/src/main/resources/fxml/SharedBand.fxml index 9d873a42..f6cdf5cb 100644 --- a/calibration-gui/src/main/resources/fxml/SharedBand.fxml +++ b/calibration-gui/src/main/resources/fxml/SharedBand.fxml @@ -9,12 +9,12 @@ - + - + - - + + @@ -38,19 +38,19 @@ - + - - + + - - - - - - - + + + + + + + diff --git a/calibration-gui/src/main/resources/leaflet/leaflet-debug.html b/calibration-gui/src/main/resources/leaflet/leaflet-debug.html index 95f00c24..523ffbde 100644 --- a/calibration-gui/src/main/resources/leaflet/leaflet-debug.html +++ b/calibration-gui/src/main/resources/leaflet/leaflet-debug.html @@ -1,20 +1,22 @@ + - - - - - + + + + + -
- +
+ + \ No newline at end of file diff --git a/calibration-service/application/pom.xml b/calibration-service/application/pom.xml index 6b196117..465e7882 100644 --- a/calibration-service/application/pom.xml +++ b/calibration-service/application/pom.xml @@ -4,7 +4,7 @@ gov.llnl.gnem.apps.coda.calibration calibration-service - 1.0.1 + 1.0.2 4.0.0 application diff --git a/calibration-service/application/src/main/java/gov/llnl/gnem/apps/coda/calibration/CalibrationApplication.java b/calibration-service/application/src/main/java/gov/llnl/gnem/apps/coda/calibration/CalibrationApplication.java index 5e2a7fca..be1a7bc3 100644 --- a/calibration-service/application/src/main/java/gov/llnl/gnem/apps/coda/calibration/CalibrationApplication.java +++ b/calibration-service/application/src/main/java/gov/llnl/gnem/apps/coda/calibration/CalibrationApplication.java @@ -14,6 +14,10 @@ */ package gov.llnl.gnem.apps.coda.calibration; +import java.util.TimeZone; + +import javax.annotation.PostConstruct; + import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -21,6 +25,11 @@ @SpringBootApplication @EnableAutoConfiguration public class CalibrationApplication { + @PostConstruct + void started() { + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + } + public static void main(String[] args) { SpringApplication.run(CalibrationApplication.class, args); } diff --git a/calibration-service/application/src/main/java/gov/llnl/gnem/apps/coda/calibration/application/config/WebSocketConfig.java b/calibration-service/application/src/main/java/gov/llnl/gnem/apps/coda/calibration/application/config/WebSocketConfig.java index 0d751199..791e6b24 100644 --- a/calibration-service/application/src/main/java/gov/llnl/gnem/apps/coda/calibration/application/config/WebSocketConfig.java +++ b/calibration-service/application/src/main/java/gov/llnl/gnem/apps/coda/calibration/application/config/WebSocketConfig.java @@ -17,13 +17,13 @@ import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.scheduling.concurrent.DefaultManagedTaskScheduler; -import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; @Configuration @EnableWebSocketMessageBroker -public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { +public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { diff --git a/calibration-service/application/src/main/resources/application-dev.properties b/calibration-service/application/src/main/resources/application-dev.properties index fff5dd05..b65fd4d5 100644 --- a/calibration-service/application/src/main/resources/application-dev.properties +++ b/calibration-service/application/src/main/resources/application-dev.properties @@ -8,7 +8,7 @@ spring.datasource.url=jdbc\:h2\:file\:./codaH2.db;DB_CLOSE_DELAY\=-1;COMPRESS=TR #spring.jpa.properties.hibernate.format_sql=true #spring.jpa.properties.hibernate.generate_statistics=true #spring.jpa.show-sql=true -spring.messages.cache-seconds=0 +spring.messages.cache-duration=0 server.port=53921 spring.jpa.hibernate.ddl-auto=update server.ssl.key-alias=selfsigned @@ -17,4 +17,5 @@ server.ssl.key-store=classpath:coda-keystore.jks server.ssl.key-store-provider=SUN server.ssl.key-store-type=JKS spring.datasource.continueOnError=true -spring.jdbc.template.fetch-size=1000 \ No newline at end of file +spring.jdbc.template.fetch-size=1000 +spring.jpa.properties.hibernate.jdbc.time_zone = UTC \ No newline at end of file diff --git a/calibration-service/application/src/main/resources/application-file.properties b/calibration-service/application/src/main/resources/application-file.properties index 891e7925..58776c3c 100644 --- a/calibration-service/application/src/main/resources/application-file.properties +++ b/calibration-service/application/src/main/resources/application-file.properties @@ -12,4 +12,5 @@ server.ssl.key-store-provider=SUN server.ssl.key-store-type=JKS spring.datasource.continueOnError=true spring.datasource.hikari.connection-timeout=0 -spring.jdbc.template.fetch-size=1000 \ No newline at end of file +spring.jdbc.template.fetch-size=1000 +spring.jpa.properties.hibernate.jdbc.time_zone = UTC \ No newline at end of file diff --git a/calibration-service/application/src/main/resources/application.properties b/calibration-service/application/src/main/resources/application.properties index 80c9f22b..354402d8 100644 --- a/calibration-service/application/src/main/resources/application.properties +++ b/calibration-service/application/src/main/resources/application.properties @@ -10,4 +10,5 @@ server.ssl.key-store=classpath:coda-keystore.jks server.ssl.key-store-provider=SUN server.ssl.key-store-type=JKS spring.datasource.continueOnError=true -spring.jdbc.template.fetch-size=1000 \ No newline at end of file +spring.jdbc.template.fetch-size=1000 +spring.jpa.properties.hibernate.jdbc.time_zone = UTC \ No newline at end of file diff --git a/calibration-service/application/src/main/resources/data.sql b/calibration-service/application/src/main/resources/data.sql index dd230b29..dffdd6ce 100644 --- a/calibration-service/application/src/main/resources/data.sql +++ b/calibration-service/application/src/main/resources/data.sql @@ -5,17 +5,17 @@ INSERT INTO "MDACPS" (ID, DEL_ETA, DEL_GAMMA0, DELQ0, DIST_CRIT, ETA, GAMMA0, PH INSERT INTO "MDACFI" (ID, ALPHAR, ALPHAS, BETAS, BETAR, DEL_PSI, DEL_SIGMA, M0REF, PSI, RAD_PATP, RAD_PATS, RHOR, RHOS, SIGMA, VERSION, ZETA) VALUES ('1', '5000', '6000', '3500', '2900', '0', '0', '10000000000000000', '0.25', '0.44', '0.6', '2500', '2700', '0.3', '0', '1') -INSERT INTO "SHARED_FB_PARAMS" (ID, BETA0, BETA1, BETA2, GAMMA0, GAMMA1, GAMMA2, LOW_FREQUENCY, HIGH_FREQUENCY,MIN_SNR, Q, S1, S2, VELOCITY0, VELOCITY1, VELOCITY2, VERSION, XC, XT, MIN_LENGTH, MAX_LENGTH, MEASURE_TIME) VALUES ('1', '0', '0', '0', '0', '0', '0', '0.0200', '0.0300', '1.5', '0', '0', '0', '0', '0', '0', '0', '0', '0', '300', '1000','100') -INSERT INTO "SHARED_FB_PARAMS" (ID, BETA0, BETA1, BETA2, GAMMA0, GAMMA1, GAMMA2, LOW_FREQUENCY, HIGH_FREQUENCY,MIN_SNR, Q, S1, S2, VELOCITY0, VELOCITY1, VELOCITY2, VERSION, XC, XT, MIN_LENGTH, MAX_LENGTH, MEASURE_TIME) VALUES ('2', '0', '0', '0', '0', '0', '0', '0.0300', '0.0500', '1.5', '0', '0', '0', '0', '0', '0', '0', '0', '0', '300', '800', '100') -INSERT INTO "SHARED_FB_PARAMS" (ID, BETA0, BETA1, BETA2, GAMMA0, GAMMA1, GAMMA2, LOW_FREQUENCY, HIGH_FREQUENCY,MIN_SNR, Q, S1, S2, VELOCITY0, VELOCITY1, VELOCITY2, VERSION, XC, XT, MIN_LENGTH, MAX_LENGTH, MEASURE_TIME) VALUES ('3', '0', '0', '0', '0', '0', '0', '0.0500', '0.1000', '1.5', '0', '0', '0', '0', '0', '0', '0', '0', '0', '250', '600', '100') -INSERT INTO "SHARED_FB_PARAMS" (ID, BETA0, BETA1, BETA2, GAMMA0, GAMMA1, GAMMA2, LOW_FREQUENCY, HIGH_FREQUENCY,MIN_SNR, Q, S1, S2, VELOCITY0, VELOCITY1, VELOCITY2, VERSION, XC, XT, MIN_LENGTH, MAX_LENGTH, MEASURE_TIME) VALUES ('4', '0', '0', '0', '0', '0', '0', '0.1000', '0.2000', '1.5', '0', '0', '0', '0', '0', '0', '0', '0', '0', '250', '550', '100') -INSERT INTO "SHARED_FB_PARAMS" (ID, BETA0, BETA1, BETA2, GAMMA0, GAMMA1, GAMMA2, LOW_FREQUENCY, HIGH_FREQUENCY,MIN_SNR, Q, S1, S2, VELOCITY0, VELOCITY1, VELOCITY2, VERSION, XC, XT, MIN_LENGTH, MAX_LENGTH, MEASURE_TIME) VALUES ('5', '0', '0', '0', '0', '0', '0', '0.2000', '0.3000', '1.5', '0', '0', '0', '0', '0', '0', '0', '0', '0', '150', '550', '100') -INSERT INTO "SHARED_FB_PARAMS" (ID, BETA0, BETA1, BETA2, GAMMA0, GAMMA1, GAMMA2, LOW_FREQUENCY, HIGH_FREQUENCY,MIN_SNR, Q, S1, S2, VELOCITY0, VELOCITY1, VELOCITY2, VERSION, XC, XT, MIN_LENGTH, MAX_LENGTH, MEASURE_TIME) VALUES ('6', '0', '0', '0', '0', '0', '0', '0.3000', '0.5000', '1.5', '0', '0', '0', '0', '0', '0', '0', '0', '0', '150', '500', '100') -INSERT INTO "SHARED_FB_PARAMS" (ID, BETA0, BETA1, BETA2, GAMMA0, GAMMA1, GAMMA2, LOW_FREQUENCY, HIGH_FREQUENCY,MIN_SNR, Q, S1, S2, VELOCITY0, VELOCITY1, VELOCITY2, VERSION, XC, XT, MIN_LENGTH, MAX_LENGTH, MEASURE_TIME) VALUES ('7', '0', '0', '0', '0', '0', '0', '0.5000', '0.7000', '1.5', '0', '0', '0', '0', '0', '0', '0', '0', '0', '150', '500', '100') -INSERT INTO "SHARED_FB_PARAMS" (ID, BETA0, BETA1, BETA2, GAMMA0, GAMMA1, GAMMA2, LOW_FREQUENCY, HIGH_FREQUENCY,MIN_SNR, Q, S1, S2, VELOCITY0, VELOCITY1, VELOCITY2, VERSION, XC, XT, MIN_LENGTH, MAX_LENGTH, MEASURE_TIME) VALUES ('8', '0', '0', '0', '0', '0', '0', '0.7000', '1.0000', '1.5', '0', '0', '0', '0', '0', '0', '0', '0', '0', '120', '450', '100') -INSERT INTO "SHARED_FB_PARAMS" (ID, BETA0, BETA1, BETA2, GAMMA0, GAMMA1, GAMMA2, LOW_FREQUENCY, HIGH_FREQUENCY,MIN_SNR, Q, S1, S2, VELOCITY0, VELOCITY1, VELOCITY2, VERSION, XC, XT, MIN_LENGTH, MAX_LENGTH, MEASURE_TIME) VALUES ('9', '0', '0', '0', '0', '0', '0', '1.0000', '1.5000', '1.5', '0', '0', '0', '0', '0', '0', '0', '0', '0', '90', '450', '100') -INSERT INTO "SHARED_FB_PARAMS" (ID, BETA0, BETA1, BETA2, GAMMA0, GAMMA1, GAMMA2, LOW_FREQUENCY, HIGH_FREQUENCY,MIN_SNR, Q, S1, S2, VELOCITY0, VELOCITY1, VELOCITY2, VERSION, XC, XT, MIN_LENGTH, MAX_LENGTH, MEASURE_TIME) VALUES ('10', '0', '0', '0', '0', '0', '0', '1.5000', '2.0000', '1.5', '0', '0', '0', '0', '0', '0', '0', '0', '0', '80', '400', '100') -INSERT INTO "SHARED_FB_PARAMS" (ID, BETA0, BETA1, BETA2, GAMMA0, GAMMA1, GAMMA2, LOW_FREQUENCY, HIGH_FREQUENCY,MIN_SNR, Q, S1, S2, VELOCITY0, VELOCITY1, VELOCITY2, VERSION, XC, XT, MIN_LENGTH, MAX_LENGTH, MEASURE_TIME) VALUES ('11', '0', '0', '0', '0', '0', '0', '2.0000', '3.0000', '1.5', '0', '0', '0', '0', '0', '0', '0', '0', '0', '60', '400', '100') -INSERT INTO "SHARED_FB_PARAMS" (ID, BETA0, BETA1, BETA2, GAMMA0, GAMMA1, GAMMA2, LOW_FREQUENCY, HIGH_FREQUENCY,MIN_SNR, Q, S1, S2, VELOCITY0, VELOCITY1, VELOCITY2, VERSION, XC, XT, MIN_LENGTH, MAX_LENGTH, MEASURE_TIME) VALUES ('12', '0', '0', '0', '0', '0', '0', '3.0000', '4.0000', '1.5', '0', '0', '0', '0', '0', '0', '0', '0', '0', '60', '400', '100') -INSERT INTO "SHARED_FB_PARAMS" (ID, BETA0, BETA1, BETA2, GAMMA0, GAMMA1, GAMMA2, LOW_FREQUENCY, HIGH_FREQUENCY,MIN_SNR, Q, S1, S2, VELOCITY0, VELOCITY1, VELOCITY2, VERSION, XC, XT, MIN_LENGTH, MAX_LENGTH, MEASURE_TIME) VALUES ('13', '0', '0', '0', '0', '0', '0', '4.0000', '6.0000', '1.5', '0', '0', '0', '0', '0', '0', '0', '0', '0', '60', '400', '100') -INSERT INTO "SHARED_FB_PARAMS" (ID, BETA0, BETA1, BETA2, GAMMA0, GAMMA1, GAMMA2, LOW_FREQUENCY, HIGH_FREQUENCY,MIN_SNR, Q, S1, S2, VELOCITY0, VELOCITY1, VELOCITY2, VERSION, XC, XT, MIN_LENGTH, MAX_LENGTH, MEASURE_TIME) VALUES ('14', '0', '0', '0', '0', '0', '0', '6.0000', '8.0000', '1.5', '0', '0', '0', '0', '0', '0', '0', '0', '0', '40', '350', '100') \ No newline at end of file +INSERT INTO "SHARED_FB_PARAMS" (ID, BETA0, BETA1, BETA2, GAMMA0, GAMMA1, GAMMA2, LOW_FREQUENCY, HIGH_FREQUENCY,MIN_SNR, Q, S1, S2, VELOCITY0, VELOCITY1, VELOCITY2, VERSION, XC, XT, MIN_LENGTH, MAX_LENGTH, MEASURE_TIME) VALUES ('1', '0', '0', '0', '0', '0', '0', '0.0200', '0.0300', '0.5', '0', '0', '0', '0', '0', '0', '0', '0', '0', '300', '1000','100') +INSERT INTO "SHARED_FB_PARAMS" (ID, BETA0, BETA1, BETA2, GAMMA0, GAMMA1, GAMMA2, LOW_FREQUENCY, HIGH_FREQUENCY,MIN_SNR, Q, S1, S2, VELOCITY0, VELOCITY1, VELOCITY2, VERSION, XC, XT, MIN_LENGTH, MAX_LENGTH, MEASURE_TIME) VALUES ('2', '0', '0', '0', '0', '0', '0', '0.0300', '0.0500', '0.5', '0', '0', '0', '0', '0', '0', '0', '0', '0', '300', '800', '100') +INSERT INTO "SHARED_FB_PARAMS" (ID, BETA0, BETA1, BETA2, GAMMA0, GAMMA1, GAMMA2, LOW_FREQUENCY, HIGH_FREQUENCY,MIN_SNR, Q, S1, S2, VELOCITY0, VELOCITY1, VELOCITY2, VERSION, XC, XT, MIN_LENGTH, MAX_LENGTH, MEASURE_TIME) VALUES ('3', '0', '0', '0', '0', '0', '0', '0.0500', '0.1000', '0.5', '0', '0', '0', '0', '0', '0', '0', '0', '0', '250', '600', '100') +INSERT INTO "SHARED_FB_PARAMS" (ID, BETA0, BETA1, BETA2, GAMMA0, GAMMA1, GAMMA2, LOW_FREQUENCY, HIGH_FREQUENCY,MIN_SNR, Q, S1, S2, VELOCITY0, VELOCITY1, VELOCITY2, VERSION, XC, XT, MIN_LENGTH, MAX_LENGTH, MEASURE_TIME) VALUES ('4', '0', '0', '0', '0', '0', '0', '0.1000', '0.2000', '0.5', '0', '0', '0', '0', '0', '0', '0', '0', '0', '250', '550', '100') +INSERT INTO "SHARED_FB_PARAMS" (ID, BETA0, BETA1, BETA2, GAMMA0, GAMMA1, GAMMA2, LOW_FREQUENCY, HIGH_FREQUENCY,MIN_SNR, Q, S1, S2, VELOCITY0, VELOCITY1, VELOCITY2, VERSION, XC, XT, MIN_LENGTH, MAX_LENGTH, MEASURE_TIME) VALUES ('5', '0', '0', '0', '0', '0', '0', '0.2000', '0.3000', '0.5', '0', '0', '0', '0', '0', '0', '0', '0', '0', '150', '550', '100') +INSERT INTO "SHARED_FB_PARAMS" (ID, BETA0, BETA1, BETA2, GAMMA0, GAMMA1, GAMMA2, LOW_FREQUENCY, HIGH_FREQUENCY,MIN_SNR, Q, S1, S2, VELOCITY0, VELOCITY1, VELOCITY2, VERSION, XC, XT, MIN_LENGTH, MAX_LENGTH, MEASURE_TIME) VALUES ('6', '0', '0', '0', '0', '0', '0', '0.3000', '0.5000', '0.5', '0', '0', '0', '0', '0', '0', '0', '0', '0', '150', '500', '100') +INSERT INTO "SHARED_FB_PARAMS" (ID, BETA0, BETA1, BETA2, GAMMA0, GAMMA1, GAMMA2, LOW_FREQUENCY, HIGH_FREQUENCY,MIN_SNR, Q, S1, S2, VELOCITY0, VELOCITY1, VELOCITY2, VERSION, XC, XT, MIN_LENGTH, MAX_LENGTH, MEASURE_TIME) VALUES ('7', '0', '0', '0', '0', '0', '0', '0.5000', '0.7000', '0.5', '0', '0', '0', '0', '0', '0', '0', '0', '0', '150', '500', '100') +INSERT INTO "SHARED_FB_PARAMS" (ID, BETA0, BETA1, BETA2, GAMMA0, GAMMA1, GAMMA2, LOW_FREQUENCY, HIGH_FREQUENCY,MIN_SNR, Q, S1, S2, VELOCITY0, VELOCITY1, VELOCITY2, VERSION, XC, XT, MIN_LENGTH, MAX_LENGTH, MEASURE_TIME) VALUES ('8', '0', '0', '0', '0', '0', '0', '0.7000', '1.0000', '0.5', '0', '0', '0', '0', '0', '0', '0', '0', '0', '120', '450', '100') +INSERT INTO "SHARED_FB_PARAMS" (ID, BETA0, BETA1, BETA2, GAMMA0, GAMMA1, GAMMA2, LOW_FREQUENCY, HIGH_FREQUENCY,MIN_SNR, Q, S1, S2, VELOCITY0, VELOCITY1, VELOCITY2, VERSION, XC, XT, MIN_LENGTH, MAX_LENGTH, MEASURE_TIME) VALUES ('9', '0', '0', '0', '0', '0', '0', '1.0000', '1.5000', '0.6', '0', '0', '0', '0', '0', '0', '0', '0', '0', '90', '450', '100') +INSERT INTO "SHARED_FB_PARAMS" (ID, BETA0, BETA1, BETA2, GAMMA0, GAMMA1, GAMMA2, LOW_FREQUENCY, HIGH_FREQUENCY,MIN_SNR, Q, S1, S2, VELOCITY0, VELOCITY1, VELOCITY2, VERSION, XC, XT, MIN_LENGTH, MAX_LENGTH, MEASURE_TIME) VALUES ('10', '0', '0', '0', '0', '0', '0', '1.5000', '2.0000', '0.75', '0', '0', '0', '0', '0', '0', '0', '0', '0', '80', '400', '100') +INSERT INTO "SHARED_FB_PARAMS" (ID, BETA0, BETA1, BETA2, GAMMA0, GAMMA1, GAMMA2, LOW_FREQUENCY, HIGH_FREQUENCY,MIN_SNR, Q, S1, S2, VELOCITY0, VELOCITY1, VELOCITY2, VERSION, XC, XT, MIN_LENGTH, MAX_LENGTH, MEASURE_TIME) VALUES ('11', '0', '0', '0', '0', '0', '0', '2.0000', '3.0000', '0.75', '0', '0', '0', '0', '0', '0', '0', '0', '0', '60', '400', '100') +INSERT INTO "SHARED_FB_PARAMS" (ID, BETA0, BETA1, BETA2, GAMMA0, GAMMA1, GAMMA2, LOW_FREQUENCY, HIGH_FREQUENCY,MIN_SNR, Q, S1, S2, VELOCITY0, VELOCITY1, VELOCITY2, VERSION, XC, XT, MIN_LENGTH, MAX_LENGTH, MEASURE_TIME) VALUES ('12', '0', '0', '0', '0', '0', '0', '3.0000', '4.0000', '0.75', '0', '0', '0', '0', '0', '0', '0', '0', '0', '60', '400', '100') +INSERT INTO "SHARED_FB_PARAMS" (ID, BETA0, BETA1, BETA2, GAMMA0, GAMMA1, GAMMA2, LOW_FREQUENCY, HIGH_FREQUENCY,MIN_SNR, Q, S1, S2, VELOCITY0, VELOCITY1, VELOCITY2, VERSION, XC, XT, MIN_LENGTH, MAX_LENGTH, MEASURE_TIME) VALUES ('13', '0', '0', '0', '0', '0', '0', '4.0000', '6.0000', '1.0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '60', '400', '100') +INSERT INTO "SHARED_FB_PARAMS" (ID, BETA0, BETA1, BETA2, GAMMA0, GAMMA1, GAMMA2, LOW_FREQUENCY, HIGH_FREQUENCY,MIN_SNR, Q, S1, S2, VELOCITY0, VELOCITY1, VELOCITY2, VERSION, XC, XT, MIN_LENGTH, MAX_LENGTH, MEASURE_TIME) VALUES ('14', '0', '0', '0', '0', '0', '0', '6.0000', '8.0000', '1.0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '40', '350', '100') \ No newline at end of file diff --git a/calibration-service/integration/pom.xml b/calibration-service/integration/pom.xml index 87531c45..b575b146 100644 --- a/calibration-service/integration/pom.xml +++ b/calibration-service/integration/pom.xml @@ -4,7 +4,7 @@ gov.llnl.gnem.apps.coda.calibration calibration-service - 1.0.1 + 1.0.2 4.0.0 integration diff --git a/calibration-service/model/pom.xml b/calibration-service/model/pom.xml index 6c67cb8a..664faa56 100644 --- a/calibration-service/model/pom.xml +++ b/calibration-service/model/pom.xml @@ -4,7 +4,7 @@ gov.llnl.gnem.apps.coda.calibration calibration-service - 1.0.1 + 1.0.2 4.0.0 model diff --git a/calibration-service/model/src/main/java/gov/llnl/gnem/apps/coda/calibration/model/domain/messaging/PassFailEvent.java b/calibration-service/model/src/main/java/gov/llnl/gnem/apps/coda/calibration/model/domain/messaging/PassFailEvent.java index 72db5871..eb91403b 100644 --- a/calibration-service/model/src/main/java/gov/llnl/gnem/apps/coda/calibration/model/domain/messaging/PassFailEvent.java +++ b/calibration-service/model/src/main/java/gov/llnl/gnem/apps/coda/calibration/model/domain/messaging/PassFailEvent.java @@ -23,10 +23,10 @@ public class PassFailEvent { private String messageId; - private Result result; + private Result result; @JsonCreator - public PassFailEvent(@JsonProperty("id") Long id, @JsonProperty("messageId") String messageId, @JsonProperty("result") Result result) { + public PassFailEvent(@JsonProperty("id") Long id, @JsonProperty("messageId") String messageId, @JsonProperty("result") Result result) { this.id = id; this.messageId = messageId; this.result = result; @@ -41,7 +41,7 @@ public PassFailEvent setId(Long id) { return this; } - public Result getResult() { + public Result getResult() { return result; } diff --git a/calibration-service/model/src/main/java/gov/llnl/gnem/apps/coda/calibration/model/domain/util/PICK_TYPES.java b/calibration-service/model/src/main/java/gov/llnl/gnem/apps/coda/calibration/model/domain/util/PICK_TYPES.java index 6108dc7e..1c2e936d 100644 --- a/calibration-service/model/src/main/java/gov/llnl/gnem/apps/coda/calibration/model/domain/util/PICK_TYPES.java +++ b/calibration-service/model/src/main/java/gov/llnl/gnem/apps/coda/calibration/model/domain/util/PICK_TYPES.java @@ -16,20 +16,21 @@ public enum PICK_TYPES { - F("f"), A("a"), B("b"), PN("Pn"), PG("Pg"), SN("Sn"), LG("Lg"), O("o"), UNKNOWN("UNK"); + F("f"), A("a"), B("b"), PN("Pn"), PG("Pg"), SN("Sn"), LG("Lg"), O("o"), AP("ap"), UNKNOWN("UNK"); - private String phase; + private String phase; - private PICK_TYPES(String phase) { - this.phase = phase; - } + private PICK_TYPES(String phase) { + this.phase = phase; + } - public String getPhase() { - return phase; - } + public String getPhase() { + return phase; + } - public static boolean isKnownPhase(String type) { - return PN.getPhase().equalsIgnoreCase(type) || PG.getPhase().equalsIgnoreCase(type) || SN.getPhase().equalsIgnoreCase(type) || LG.getPhase().equalsIgnoreCase(type); - } + public static boolean isKnownPhase(String type) { + return PN.getPhase().equalsIgnoreCase(type) || PG.getPhase().equalsIgnoreCase(type) + || SN.getPhase().equalsIgnoreCase(type) || LG.getPhase().equalsIgnoreCase(type); + } } diff --git a/calibration-service/pom.xml b/calibration-service/pom.xml index 6ceaf7ff..a1722732 100644 --- a/calibration-service/pom.xml +++ b/calibration-service/pom.xml @@ -9,7 +9,7 @@ gov.llnl.gnem.apps.coda.calibration coda-calibration - 1.0.1 + 1.0.2 diff --git a/calibration-service/repository/pom.xml b/calibration-service/repository/pom.xml index 23f5b585..b0dc05f6 100644 --- a/calibration-service/repository/pom.xml +++ b/calibration-service/repository/pom.xml @@ -4,7 +4,7 @@ gov.llnl.gnem.apps.coda.calibration calibration-service - 1.0.1 + 1.0.2 4.0.0 repository diff --git a/calibration-service/service-api/pom.xml b/calibration-service/service-api/pom.xml index 95a6103a..a8c046f5 100644 --- a/calibration-service/service-api/pom.xml +++ b/calibration-service/service-api/pom.xml @@ -4,7 +4,7 @@ gov.llnl.gnem.apps.coda.calibration calibration-service - 1.0.1 + 1.0.2 4.0.0 service.api diff --git a/calibration-service/service-api/src/main/java/gov/llnl/gnem/apps/coda/calibration/service/api/EndTimePicker.java b/calibration-service/service-api/src/main/java/gov/llnl/gnem/apps/coda/calibration/service/api/EndTimePicker.java index f3ff04a9..cc958a0c 100644 --- a/calibration-service/service-api/src/main/java/gov/llnl/gnem/apps/coda/calibration/service/api/EndTimePicker.java +++ b/calibration-service/service-api/src/main/java/gov/llnl/gnem/apps/coda/calibration/service/api/EndTimePicker.java @@ -15,6 +15,7 @@ package gov.llnl.gnem.apps.coda.calibration.service.api; public interface EndTimePicker { - public double getEndTime(float[] subsection, float[] synthSubsection, double sampleRate, double startTimeEpochSeconds, double minLengthSec, double maxLengthSec, double minimumSnr, double centerFreq, - double distance); + public double getEndTime(float[] subsection, float[] synthSubsection, double sampleRate, + double startTimeEpochSeconds, int startOffset, double minLengthSec, double maxLengthSec, double minimumSnr, + double noiseAmp, double centerFreq, double distance); } diff --git a/calibration-service/service-impl/pom.xml b/calibration-service/service-impl/pom.xml index ccb8a19b..89c0e5d3 100644 --- a/calibration-service/service-impl/pom.xml +++ b/calibration-service/service-impl/pom.xml @@ -4,7 +4,7 @@ gov.llnl.gnem.apps.coda.calibration calibration-service - 1.0.1 + 1.0.2 4.0.0 service.impl diff --git a/calibration-service/service-impl/src/main/java/gov/llnl/gnem/apps/coda/calibration/service/impl/CalibrationServiceImpl.java b/calibration-service/service-impl/src/main/java/gov/llnl/gnem/apps/coda/calibration/service/impl/CalibrationServiceImpl.java index eadf694d..e9d1f835 100644 --- a/calibration-service/service-impl/src/main/java/gov/llnl/gnem/apps/coda/calibration/service/impl/CalibrationServiceImpl.java +++ b/calibration-service/service-impl/src/main/java/gov/llnl/gnem/apps/coda/calibration/service/impl/CalibrationServiceImpl.java @@ -15,11 +15,13 @@ package gov.llnl.gnem.apps.coda.calibration.service.impl; import java.time.LocalDateTime; +import java.util.AbstractMap; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; @@ -47,6 +49,7 @@ import gov.llnl.gnem.apps.coda.calibration.model.domain.SpectraMeasurement; import gov.llnl.gnem.apps.coda.calibration.model.domain.Station; import gov.llnl.gnem.apps.coda.calibration.model.domain.Waveform; +import gov.llnl.gnem.apps.coda.calibration.model.domain.WaveformPick; import gov.llnl.gnem.apps.coda.calibration.model.domain.messaging.CalibrationStatusEvent; import gov.llnl.gnem.apps.coda.calibration.model.domain.messaging.Result; import gov.llnl.gnem.apps.coda.calibration.model.domain.util.PICK_TYPES; @@ -70,185 +73,235 @@ @Transactional public class CalibrationServiceImpl implements CalibrationService { - private final Logger log = LoggerFactory.getLogger(this.getClass()); - - private WaveformService waveformService; - private PeakVelocityMeasurementService peakVelocityMeasurementsService; - private SharedFrequencyBandParametersService sharedParametersService; - private ShapeCalibrationService shapeCalibrationService; - private SpectraMeasurementService spectraMeasurementService; - private SyntheticCodaGenerationService syntheticGenerationService; - private PathCalibrationService pathCalibrationService; - private MdacParametersFiService mdacFiService; - private MdacParametersPsService mdacPsService; - private ReferenceMwParametersService referenceMwService; - private SiteCalibrationService siteCalibrationService; - private SyntheticService syntheticService; - private NotificationService notificationService; - private DatabaseCleaningService cleaningService; - - private static final AtomicLong atomicLong = new AtomicLong(0l); - - private static final ExecutorService service = new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1), r -> { - Thread thread = new Thread(r); - thread.setDaemon(true); - return thread; - }); - - @Autowired - public CalibrationServiceImpl(WaveformService waveformService, PeakVelocityMeasurementService peakVelocityMeasurementsService, SharedFrequencyBandParametersService sharedParametersService, - ShapeCalibrationService shapeCalibrationService, SpectraMeasurementService spectraMeasurementService, SyntheticCodaGenerationService syntheticGenerationService, - PathCalibrationService pathCalibrationService, MdacParametersFiService mdacFiService, MdacParametersPsService mdacPsService, ReferenceMwParametersService referenceMwService, - SiteCalibrationService siteCalibrationService, SyntheticService syntheticService, NotificationService notificationService, DatabaseCleaningService cleaningService) { - this.waveformService = waveformService; - this.peakVelocityMeasurementsService = peakVelocityMeasurementsService; - this.sharedParametersService = sharedParametersService; - this.shapeCalibrationService = shapeCalibrationService; - this.spectraMeasurementService = spectraMeasurementService; - this.syntheticGenerationService = syntheticGenerationService; - this.pathCalibrationService = pathCalibrationService; - this.mdacFiService = mdacFiService; - this.mdacPsService = mdacPsService; - this.referenceMwService = referenceMwService; - this.siteCalibrationService = siteCalibrationService; - this.syntheticService = syntheticService; - this.notificationService = notificationService; - this.cleaningService = cleaningService; - } - - @Override - public boolean start(Boolean autoPickingEnabled) { - // FIXME: These *All methods should be *AllByProjectID instead! - final Long id = atomicLong.getAndIncrement(); - try { - service.submit(() -> { - try { - notificationService.post(new CalibrationStatusEvent(id, CalibrationStatusEvent.Status.STARTING)); - log.info("Starting calibration at {}", LocalDateTime.now()); - - //TODO: Look at removing auto picking code from the methods below and centralizing it to here instead. - Map frequencyBandParameterMap = mapParamsToFrequencyBands(sharedParametersService.findAll()); - final Map snrFilterMap = new HashMap<>(frequencyBandParameterMap); - - List stacks = waveformService.getAllStacks(); - // In general each step produces output that the next step consumes - - // 1) Compute the peak velocity, amplitude, and SNR values for - // the given coda stacks using theoretical group velocities to cut - // the windows for noise and SN/LG arrival - Collection velocityMeasurements = peakVelocityMeasurementsService.measureVelocities(stacks); - - // First step is to clean up all the intermediary results if - // they exist. This is wildly not-thread-safe as you might imagine. - peakVelocityMeasurementsService.deleteAll(); - syntheticService.deleteAll(); - - //We want to filter out the ones that don't pass the user's SNR threshold - List snrFilteredVelocity = velocityMeasurements.stream().filter(vel -> { - boolean valid = false; - if (vel.getWaveform() != null) { - FrequencyBand fb = new FrequencyBand(vel.getWaveform().getLowFrequency(), vel.getWaveform().getHighFrequency()); - SharedFrequencyBandParameters params = snrFilterMap.get(fb); - valid = params != null && vel.getSnr() >= params.getMinSnr(); - } - return valid; - }).collect(Collectors.toList()); - - // Now save the new ones we just calculated - peakVelocityMeasurementsService.save(snrFilteredVelocity); - - // 2) Compute the shape parameters describing each stack - // (Velocity V0-2, Beta B0-2, Gamma G0-2) and then fit models to each of - // those parameters for each frequency band that can be used to generate - // synthetic coda at any given distance and frequency band combination - frequencyBandParameterMap = shapeCalibrationService.measureShapes(snrFilteredVelocity, frequencyBandParameterMap, autoPickingEnabled); - - frequencyBandParameterMap = mapParamsToFrequencyBands(sharedParametersService.save(frequencyBandParameterMap.values())); - - // 3) Now we need to generate some basic synthetics for the - // measurement code to use to determine where to measure the raw - // amplitudes. Then feed the synthetics to the measurement - // service and get raw at start and raw at measurement time values back - List spectra = spectraMeasurementService.measureSpectra(syntheticGenerationService.generateSynthetics(stacks, frequencyBandParameterMap), - frequencyBandParameterMap, - autoPickingEnabled); - - // 4) For each event in the data set find all stations that - // recorded the event, then compute what the estimated path effect - // correction needs to be for each frequency band - frequencyBandParameterMap = pathCalibrationService.measurePathCorrections(spectraByFrequencyBand(spectra), frequencyBandParameterMap); - - frequencyBandParameterMap = mapParamsToFrequencyBands(sharedParametersService.save(frequencyBandParameterMap.values())); - - // 5) Measure the amplitudes again but this time we can compute - // ESH path corrected values - spectra = spectraMeasurementService.measureSpectra(syntheticGenerationService.generateSynthetics(stacks, frequencyBandParameterMap), frequencyBandParameterMap, autoPickingEnabled); - - // 6) Now using those path correction values plus a list of - // trusted Mw/spectra measurements for some subset of events in the data - // set we can compute what the offset is at each station from the - // expected source spectra for that MW value. This value is recorded as - // the site specific offset for measured values at each frequency band - Map> frequencyBandSiteParameterMap = siteCalibrationService.measureSiteCorrections(spectraByFrequencyBand(spectra), - mdacFiService.findFirst(), - collectByFrequencyBand(mdacPsService.findAll()), - collectByEvid(referenceMwService.findAll()), - frequencyBandParameterMap, - PICK_TYPES.LG); - // 7) Measure the amplitudes one last time to fill out the - // Path+Site corrected amplitude values - spectra = spectraMeasurementService.measureSpectra(syntheticService.save(syntheticGenerationService.generateSynthetics(stacks, frequencyBandParameterMap)), - frequencyBandParameterMap, - autoPickingEnabled, - frequencyBandSiteParameterMap); - - log.info("Calibration complete at {}", LocalDateTime.now()); - notificationService.post(new CalibrationStatusEvent(id, CalibrationStatusEvent.Status.COMPLETE)); - } catch (Exception ex) { - log.error(ex.getMessage(), ex); - notificationService.post(new CalibrationStatusEvent(id, CalibrationStatusEvent.Status.ERROR, new Result(false, ex))); - throw ex; - } - return new CompletableFuture<>(); - }); - } catch (RejectedExecutionException e) { - notificationService.post(new CalibrationStatusEvent(id, CalibrationStatusEvent.Status.ERROR, new Result(false, e))); - return false; - } - return true; - } - - private Map> collectByEvid(List refMws) { - return refMws.stream().collect(Collectors.groupingBy(ReferenceMwParameters::getEventId)); - } - - private Map collectByFrequencyBand(List mdacPs) { - return mdacPs.stream().filter(ps -> PICK_TYPES.isKnownPhase(ps.getPhase())).collect(Collectors.toMap(ps -> (PICK_TYPES) PICK_TYPES.valueOf(ps.getPhase().toUpperCase()), Function.identity())); - } - - private Map> spectraByFrequencyBand(List spectra) { - return spectra.stream() - .filter(Objects::nonNull) - .filter(s -> s.getWaveform() != null) - .collect(Collectors.groupingBy(s -> new FrequencyBand(s.getWaveform().getLowFrequency(), s.getWaveform().getHighFrequency()))); - } - - private Map mapParamsToFrequencyBands(Collection params) { - return params.stream().collect(Collectors.toMap(fbp -> new FrequencyBand(fbp.getLowFrequency(), fbp.getHighFrequency()), fbp -> fbp)); - } - - @PreDestroy - private void stop() { - service.shutdownNow(); - } - - @Override - public boolean clearData() { - if (cleaningService != null) { - return cleaningService.clearAll(); - } - - return false; - } + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + private WaveformService waveformService; + private PeakVelocityMeasurementService peakVelocityMeasurementsService; + private SharedFrequencyBandParametersService sharedParametersService; + private ShapeCalibrationService shapeCalibrationService; + private SpectraMeasurementService spectraMeasurementService; + private SyntheticCodaGenerationService syntheticGenerationService; + private PathCalibrationService pathCalibrationService; + private MdacParametersFiService mdacFiService; + private MdacParametersPsService mdacPsService; + private ReferenceMwParametersService referenceMwService; + private SiteCalibrationService siteCalibrationService; + private SyntheticService syntheticService; + private NotificationService notificationService; + private DatabaseCleaningService cleaningService; + + private static final AtomicLong atomicLong = new AtomicLong(0l); + + private static final ExecutorService service = new ThreadPoolExecutor( 1, 1, 0, TimeUnit.SECONDS, + new ArrayBlockingQueue<>(1), r -> { + Thread thread = new Thread(r); + thread.setDaemon(true); + return thread; + }); + + @Autowired + public CalibrationServiceImpl(WaveformService waveformService, + PeakVelocityMeasurementService peakVelocityMeasurementsService, + SharedFrequencyBandParametersService sharedParametersService, + ShapeCalibrationService shapeCalibrationService, SpectraMeasurementService spectraMeasurementService, + SyntheticCodaGenerationService syntheticGenerationService, PathCalibrationService pathCalibrationService, + MdacParametersFiService mdacFiService, MdacParametersPsService mdacPsService, + ReferenceMwParametersService referenceMwService, SiteCalibrationService siteCalibrationService, + SyntheticService syntheticService, NotificationService notificationService, + DatabaseCleaningService cleaningService) { + this.waveformService = waveformService; + this.peakVelocityMeasurementsService = peakVelocityMeasurementsService; + this.sharedParametersService = sharedParametersService; + this.shapeCalibrationService = shapeCalibrationService; + this.spectraMeasurementService = spectraMeasurementService; + this.syntheticGenerationService = syntheticGenerationService; + this.pathCalibrationService = pathCalibrationService; + this.mdacFiService = mdacFiService; + this.mdacPsService = mdacPsService; + this.referenceMwService = referenceMwService; + this.siteCalibrationService = siteCalibrationService; + this.syntheticService = syntheticService; + this.notificationService = notificationService; + this.cleaningService = cleaningService; + } + + @Override + public boolean start(Boolean autoPickingEnabled) { + // FIXME: These *All methods should be *AllByProjectID instead! + final Long id = atomicLong.getAndIncrement(); + try { + service.submit(() -> { + try { + notificationService.post(new CalibrationStatusEvent(id, CalibrationStatusEvent.Status.STARTING)); + log.info("Starting calibration at {}", LocalDateTime.now()); + + // TODO: Look at removing auto picking code from the methods + // below and centralizing it to here instead. + Map frequencyBandParameterMap = mapParamsToFrequencyBands(sharedParametersService.findAll()); + final Map snrFilterMap = new HashMap<>(frequencyBandParameterMap); + + List stacks = waveformService.getAllStacks(); + // In general each step produces output that the next step + // consumes + + // 1) Compute the peak velocity, amplitude, and SNR values + // for + // the given coda stacks using theoretical group velocities + // to cut + // the windows for noise and SN/LG arrival + Collection velocityMeasurements = peakVelocityMeasurementsService.measureVelocities(stacks); + + // First step is to clean up all the intermediary results if + // they exist. This is wildly not-thread-safe as you might + // imagine. + peakVelocityMeasurementsService.deleteAll(); + syntheticService.deleteAll(); + + // We want to filter out the ones that don't pass the user's + // SNR threshold + List snrFilteredVelocity = velocityMeasurements.stream().filter(vel -> { + boolean valid = false; + if (vel.getWaveform() != null) { + FrequencyBand fb = new FrequencyBand( vel.getWaveform().getLowFrequency(), + vel.getWaveform().getHighFrequency()); + SharedFrequencyBandParameters params = snrFilterMap.get(fb); + valid = params != null && vel.getSnr() >= params.getMinSnr(); + } + return valid; + }).collect(Collectors.toList()); + + // Now save the new ones we just calculated + peakVelocityMeasurementsService.save(snrFilteredVelocity); + + // 2) Compute the shape parameters describing each stack + // (Velocity V0-2, Beta B0-2, Gamma G0-2) and then fit + // models to each of + // those parameters for each frequency band that can be used + // to generate + // synthetic coda at any given distance and frequency band + // combination + frequencyBandParameterMap = shapeCalibrationService.measureShapes( snrFilteredVelocity, + frequencyBandParameterMap, + autoPickingEnabled); + + frequencyBandParameterMap = mapParamsToFrequencyBands(sharedParametersService.save(frequencyBandParameterMap.values())); + + // 3) Now we need to generate some basic synthetics for the + // measurement code to use to determine where to measure the + // raw + // amplitudes. Then feed the synthetics to the measurement + // service and get raw at start and raw at measurement time + // values back + stacks = stacks.parallelStream().filter(wave -> wave.getAssociatedPicks() != null).map(wave -> { + + Optional pick = wave .getAssociatedPicks().stream() + .filter(p -> PICK_TYPES.F .name() + .equalsIgnoreCase(p.getPickType())) + .findFirst(); + if (pick.isPresent() && pick.get().getPickTimeSecFromOrigin() > 0) { + return wave; + } else { + return null; + } + }).filter(Objects::nonNull).collect(Collectors.toList()); + + List spectra = spectraMeasurementService.measureSpectra(syntheticGenerationService.generateSynthetics( stacks, + frequencyBandParameterMap), + frequencyBandParameterMap, + autoPickingEnabled); + + // 4) For each event in the data set find all stations that + // recorded the event, then compute what the estimated path + // effect + // correction needs to be for each frequency band + frequencyBandParameterMap = pathCalibrationService.measurePathCorrections( spectraByFrequencyBand(spectra), + frequencyBandParameterMap); + + frequencyBandParameterMap = mapParamsToFrequencyBands(sharedParametersService.save(frequencyBandParameterMap.values())); + + // 5) Measure the amplitudes again but this time we can + // compute + // ESH path corrected values + spectra = spectraMeasurementService.measureSpectra( syntheticGenerationService.generateSynthetics( stacks, + frequencyBandParameterMap), + frequencyBandParameterMap, autoPickingEnabled); + + // 6) Now using those path correction values plus a list of + // trusted Mw/spectra measurements for some subset of events + // in the data + // set we can compute what the offset is at each station + // from the + // expected source spectra for that MW value. This value is + // recorded as + // the site specific offset for measured values at each + // frequency band + Map> frequencyBandSiteParameterMap = siteCalibrationService.measureSiteCorrections(spectraByFrequencyBand(spectra), + mdacFiService.findFirst(), + collectByFrequencyBand(mdacPsService.findAll()), + collectByEvid(referenceMwService.findAll()), + frequencyBandParameterMap, + PICK_TYPES.LG); + // 7) Measure the amplitudes one last time to fill out the + // Path+Site corrected amplitude values + spectra = spectraMeasurementService.measureSpectra( syntheticService.save(syntheticGenerationService.generateSynthetics(stacks, + frequencyBandParameterMap)), + frequencyBandParameterMap, autoPickingEnabled, + frequencyBandSiteParameterMap); + + log.info("Calibration complete at {}", LocalDateTime.now()); + notificationService.post(new CalibrationStatusEvent(id, CalibrationStatusEvent.Status.COMPLETE)); + } catch (Exception ex) { + log.error(ex.getMessage(), ex); + notificationService.post(new CalibrationStatusEvent(id, CalibrationStatusEvent.Status.ERROR, + new Result(false, ex))); + throw ex; + } + return new CompletableFuture<>(); + }); + } catch (RejectedExecutionException e) { + notificationService.post(new CalibrationStatusEvent(id, CalibrationStatusEvent.Status.ERROR, + new Result(false, e))); + return false; + } + return true; + } + + private Map> collectByEvid(List refMws) { + return refMws.stream().collect(Collectors.groupingBy(ReferenceMwParameters::getEventId)); + } + + private Map collectByFrequencyBand(List mdacPs) { + return mdacPs .stream().filter(ps -> PICK_TYPES.isKnownPhase(ps.getPhase())) + .collect(Collectors.toMap( ps -> (PICK_TYPES) PICK_TYPES.valueOf(ps.getPhase().toUpperCase()), + Function.identity())); + } + + private Map> spectraByFrequencyBand(List spectra) { + return spectra .stream().filter(Objects::nonNull).filter(s -> s.getWaveform() != null) + .collect(Collectors.groupingBy(s -> new FrequencyBand( s.getWaveform().getLowFrequency(), + s.getWaveform().getHighFrequency()))); + } + + private Map mapParamsToFrequencyBands( + Collection params) { + return params.stream().collect(Collectors.toMap( + fbp -> new FrequencyBand( fbp.getLowFrequency(), + fbp.getHighFrequency()), + fbp -> fbp)); + } + + @PreDestroy + private void stop() { + service.shutdownNow(); + } + + @Override + public boolean clearData() { + if (cleaningService != null) { + return cleaningService.clearAll(); + } + + return false; + } } diff --git a/calibration-service/service-impl/src/main/java/gov/llnl/gnem/apps/coda/calibration/service/impl/ShapeCalibrationServiceImpl.java b/calibration-service/service-impl/src/main/java/gov/llnl/gnem/apps/coda/calibration/service/impl/ShapeCalibrationServiceImpl.java index 49e6324a..a8f1afd8 100644 --- a/calibration-service/service-impl/src/main/java/gov/llnl/gnem/apps/coda/calibration/service/impl/ShapeCalibrationServiceImpl.java +++ b/calibration-service/service-impl/src/main/java/gov/llnl/gnem/apps/coda/calibration/service/impl/ShapeCalibrationServiceImpl.java @@ -29,94 +29,260 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import gov.llnl.gnem.apps.coda.calibration.model.domain.EnvelopeFit; import gov.llnl.gnem.apps.coda.calibration.model.domain.FrequencyBand; import gov.llnl.gnem.apps.coda.calibration.model.domain.PeakVelocityMeasurement; import gov.llnl.gnem.apps.coda.calibration.model.domain.ShapeMeasurement; import gov.llnl.gnem.apps.coda.calibration.model.domain.SharedFrequencyBandParameters; +import gov.llnl.gnem.apps.coda.calibration.model.domain.SyntheticCoda; +import gov.llnl.gnem.apps.coda.calibration.model.domain.Waveform; import gov.llnl.gnem.apps.coda.calibration.model.domain.WaveformPick; import gov.llnl.gnem.apps.coda.calibration.model.domain.util.PICK_TYPES; +import gov.llnl.gnem.apps.coda.calibration.service.api.EndTimePicker; import gov.llnl.gnem.apps.coda.calibration.service.api.ShapeCalibrationService; import gov.llnl.gnem.apps.coda.calibration.service.api.ShapeMeasurementService; +import gov.llnl.gnem.apps.coda.calibration.service.api.WaveformPickService; +import gov.llnl.gnem.apps.coda.calibration.service.api.WaveformService; import gov.llnl.gnem.apps.coda.calibration.service.impl.processing.CalibrationCurveFitter; import gov.llnl.gnem.apps.coda.calibration.service.impl.processing.ShapeCalculator; +import gov.llnl.gnem.apps.coda.calibration.service.impl.processing.SyntheticCodaModel; +import gov.llnl.gnem.apps.coda.calibration.service.impl.processing.WaveformToTimeSeriesConverter; +import gov.llnl.gnem.apps.coda.calibration.service.impl.processing.WaveformUtils; +import llnl.gnem.core.util.TimeT; +import llnl.gnem.core.waveform.seismogram.TimeSeries; @Service public class ShapeCalibrationServiceImpl implements ShapeCalibrationService { - private final Logger log = LoggerFactory.getLogger(this.getClass()); - - private ShapeMeasurementService shapeMeasurementService; - private ShapeCalculator shapeCalc; - - @Autowired - public ShapeCalibrationServiceImpl(ShapeMeasurementService shapeMeasurementService, ShapeCalculator shapeCalc) { - this.shapeMeasurementService = shapeMeasurementService; - this.shapeCalc = shapeCalc; - } - - @Override - public Map measureShapes(Collection velocityMeasurements, - Map frequencyBandParameters, boolean autoPickingEnabled) { - if (frequencyBandParameters.isEmpty()) { - // TODO: Propagate warning to the status API - log.warn("No frequency band parameters available, unable to compute shape parameters without them!"); - return new HashMap<>(); - } - final CalibrationCurveFitter fitter = new CalibrationCurveFitter(); - - Map frequencyBandCurveFits = fitter.fitAllVelocity(velocityMeasurements.stream() - .filter(vel -> vel.getWaveform() != null) - .collect(Collectors.groupingBy(vel -> new FrequencyBand(vel.getWaveform() - .getLowFrequency(), - vel.getWaveform() - .getHighFrequency()))), - frequencyBandParameters); - - // 1) If auto-picking is enabled attempt to pick any envelopes that - // don't already have F-picks in this set -// if (autoPickingEnabled) { -// velocityMeasurements = autoPickWaveforms(velocityMeasurements, frequencyBandParameters); -// } - - // 2) Filter to only measurements with an end pick - Collection> filteredVelocityMeasurements = filterMeasurementsToEndPickedOnly(velocityMeasurements); - - // 3) For every Waveform remaining, measure picks based on start - // (computed from velocity) and end (from 'End'/'F' picks) - List betaAndGammaMeasurements = shapeCalc.fitShapelineToMeasuredEnvelopes(filteredVelocityMeasurements, frequencyBandCurveFits); - - // Shape measurements are intermediary results so rather than trying to - // merge them we want to just drop them wholesale if they exist and - // replace them with the new data set. - // TODO: Need to only delete these for the current project - shapeMeasurementService.deleteAll(); - - Map> frequencyBandShapeMeasurementMap = shapeMeasurementService.save(betaAndGammaMeasurements) - .stream() - .filter(meas -> meas.getWaveform() != null) - .collect(Collectors.groupingBy(meas -> new FrequencyBand(meas.getWaveform() - .getLowFrequency(), - meas.getWaveform() - .getHighFrequency()))); - - frequencyBandCurveFits = fitter.fitAllBeta(frequencyBandShapeMeasurementMap, frequencyBandCurveFits); - frequencyBandCurveFits = fitter.fitAllGamma(frequencyBandShapeMeasurementMap, frequencyBandCurveFits); - return frequencyBandCurveFits; - } - - /** - * Down-select the given {@link PeakVelocityMeasurement} collection to only - * ones with 'End' picks defined and associate them into an Entry - */ - private Collection> filterMeasurementsToEndPickedOnly(Collection velocityMeasurements) { - return velocityMeasurements.parallelStream().filter(vel -> vel.getWaveform() != null).filter(vel -> vel.getWaveform().getAssociatedPicks() != null).map(vel -> { - - Optional pick = vel.getWaveform().getAssociatedPicks().stream().filter(p -> PICK_TYPES.F.name().equalsIgnoreCase(p.getPickType())).findFirst(); - if (pick.isPresent()) { - return new AbstractMap.SimpleEntry<>(vel, pick.get()); - } else { - return null; - } - }).filter(Objects::nonNull).collect(Collectors.toList()); - } + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + private ShapeMeasurementService shapeMeasurementService; + private ShapeCalculator shapeCalc; + private EndTimePicker endTimePicker; + private WaveformToTimeSeriesConverter converter; + private SyntheticCodaModel syntheticCodaModel; + private WaveformService waveService; + private WaveformPickService pickService; + + @Autowired + public ShapeCalibrationServiceImpl(ShapeMeasurementService shapeMeasurementService, ShapeCalculator shapeCalc, + EndTimePicker endTimePicker, SyntheticCodaModel syntheticCodaModel, WaveformService waveService, + WaveformPickService pickService) { + this.shapeMeasurementService = shapeMeasurementService; + this.shapeCalc = shapeCalc; + this.endTimePicker = endTimePicker; + this.converter = new WaveformToTimeSeriesConverter(); + this.syntheticCodaModel = syntheticCodaModel; + this.waveService = waveService; + this.pickService = pickService; + } + + @Override + public Map measureShapes( + Collection velocityMeasurements, + Map frequencyBandParameters, boolean autoPickingEnabled) { + if (frequencyBandParameters.isEmpty()) { + // TODO: Propagate warning to the status API + log.warn("No frequency band parameters available, unable to compute shape parameters without them!"); + return new HashMap<>(); + } + final CalibrationCurveFitter fitter = new CalibrationCurveFitter(); + + Map frequencyBandCurveFits = fitter.fitAllVelocity( velocityMeasurements.stream() + .filter(vel -> vel.getWaveform() != null) + .collect(Collectors.groupingBy(vel -> new FrequencyBand(vel .getWaveform() + .getLowFrequency(), + vel .getWaveform() + .getHighFrequency()))), + frequencyBandParameters); + + // 1) If auto-picking is enabled attempt to pick any envelopes that + // don't already have F-picks in this set + if (autoPickingEnabled) { + velocityMeasurements = autoPickWaveforms(velocityMeasurements, frequencyBandParameters); + velocityMeasurements.forEach(v -> pickService.save(waveService.save(v.getWaveform()).getAssociatedPicks())); + } + + // 2) Filter to only measurements with an end pick + Collection> filteredVelocityMeasurements = filterMeasurementsToEndPickedOnly(velocityMeasurements); + + // 3) For every Waveform remaining, measure picks based on start + // (computed from velocity) and end (from 'End'/'F' picks) + List betaAndGammaMeasurements = shapeCalc.fitShapelineToMeasuredEnvelopes(filteredVelocityMeasurements, + frequencyBandCurveFits); + + // Shape measurements are intermediary results so rather than trying to + // merge them we want to just drop them wholesale if they exist and + // replace them with the new data set. + // TODO: Need to only delete these for the current project + shapeMeasurementService.deleteAll(); + + Map> frequencyBandShapeMeasurementMap = shapeMeasurementService .save(betaAndGammaMeasurements) + .stream() + .filter(meas -> meas.getWaveform() != null) + .collect(Collectors.groupingBy(meas -> new FrequencyBand( meas.getWaveform() + .getLowFrequency(), + meas.getWaveform() + .getHighFrequency()))); + + frequencyBandCurveFits = fitter.fitAllBeta(frequencyBandShapeMeasurementMap, frequencyBandCurveFits); + frequencyBandCurveFits = fitter.fitAllGamma(frequencyBandShapeMeasurementMap, frequencyBandCurveFits); + return frequencyBandCurveFits; + } + + private Collection autoPickWaveforms( + final Collection velocityMeasurements, + final Map frequencyBandParameters) { + return velocityMeasurements .parallelStream().filter(vel -> vel.getWaveform() != null) + .filter(vel -> vel.getWaveform().getAssociatedPicks() != null).map(vel -> { + SharedFrequencyBandParameters params = frequencyBandParameters.get(new FrequencyBand( vel.getWaveform() + .getLowFrequency(), + vel .getWaveform() + .getHighFrequency())); + Optional pick = vel .getWaveform().getAssociatedPicks().stream() + .filter(p -> PICK_TYPES.AP .name() + .equalsIgnoreCase(p.getPickType())) + .findFirst(); + + Optional endPick = vel.getWaveform().getAssociatedPicks().stream() + .filter(p -> PICK_TYPES.F .name() + .equalsIgnoreCase(p.getPickType())) + .findFirst(); + if ((!endPick.isPresent() || pick.isPresent()) && params != null) { + vel.getWaveform().getAssociatedPicks().forEach(p -> p.setWaveform(null)); + vel.getWaveform().getAssociatedPicks().clear(); + double noiseamp; + try { + noiseamp = WaveformUtils.getNoiseWindow(vel.getDistance(), + new TimeT(vel .getWaveform() + .getEvent().getOriginTime()), + converter.convert(vel.getWaveform())) + .getMean(); + } catch (IllegalArgumentException e) { + log.warn( "Error attempting to measure seismogram amplitude for seismogram {}; {}", + vel.getWaveform(), e.getMessage()); + return null; + } + + double minlength = params.getMinLength(); + double maxlength = params.getMaxLength(); + + double vr = params.getVelocity0() - params.getVelocity1() + / (params.getVelocity2() + vel.getDistance()); + if (vr == 0.0) { + vr = 1.0; + } + TimeT originTime = new TimeT(vel.getWaveform().getEvent().getOriginTime()); + TimeT startTime; + TimeT trimTime = originTime.add(vel.getDistance() / vr); + TimeT beginTime = new TimeT(vel.getWaveform().getBeginTime()); + TimeSeries trimmedWaveform = converter.convert(vel.getWaveform()); + try { + trimmedWaveform.cutBefore(trimTime); + trimmedWaveform.cutAfter(trimTime.add(30.0)); + + startTime = new TimeT(trimTime.getEpochTime() + + trimmedWaveform.getMaxTime()[0]); + } catch (IllegalArgumentException e) { + startTime = trimTime; + } + + TimeSeries segment = converter.convert(vel.getWaveform()); + segment.interpolate(1.0); + + double stopTime = endTimePicker.getEndTime( segment.getData(), + WaveformUtils.doublesToFloats(quickSynth( vel.getWaveform(), + startTime).getSegment()), + segment.getSamprate(), + startTime.getEpochTime(), + segment.getIndexForTime(startTime.getEpochTime()), + minlength, maxlength, + params.getMinSnr(), noiseamp, + params.getLowFrequency() + + ((params.getHighFrequency() + - params.getLowFrequency()) + / 2.0), + vel.getDistance()); + + if (new TimeT(stopTime).gt(startTime)) { + stopTime = stopTime + beginTime.subtractD(originTime); + } + stopTime = new TimeT(stopTime).subtractD(startTime); + + WaveformPick autoPick = new WaveformPick() .setPickType(PICK_TYPES.F.name()) + .setPickName(PICK_TYPES.F.name()) + .setWaveform(vel.getWaveform()) + .setPickTimeSecFromOrigin((float) stopTime); + + WaveformPick startPick = new WaveformPick() .setPickType(PICK_TYPES.AP.name()) + .setPickName(PICK_TYPES.AP.name()) + .setWaveform(vel.getWaveform()) + .setPickTimeSecFromOrigin((float) startTime.subtractD(originTime)); + + if (autoPick.getPickTimeSecFromOrigin() > startPick.getPickTimeSecFromOrigin()) { + vel.getWaveform().getAssociatedPicks().add(autoPick); + vel.getWaveform().getAssociatedPicks().add(startPick); + } + } + return vel; + }).filter(Objects::nonNull).collect(Collectors.toList()); + } + + private SyntheticCoda quickSynth(Waveform waveform, TimeT startTime) { + CalibrationCurveFitter curveFitter = new CalibrationCurveFitter(); + SyntheticCoda synth = new SyntheticCoda(); + double dt = 1d; + TimeSeries seis = converter.convert(waveform); + TimeSeries fitSeis = converter.convert(waveform); + seis.cut(startTime, new TimeT(waveform.getEndTime())); + fitSeis.cut(startTime, startTime.add(100)); + seis.interpolate(1 / dt); + EnvelopeFit curve = curveFitter.fitCodaCMAESStraightLine(fitSeis.getData()); + + int npts = seis.getNsamp(); + + // TODO: Set synthetic end time to max length of measurement (+1?) for + // FB if it's set and > 0.0 + Double[] Ac = new Double[npts]; + + double gr = curve.getGamma(); + double br = curve.getBeta(); + for (int ii = 0; ii < Ac.length; ii++) { + double t = (ii + 1) * dt; + Ac[ii] = syntheticCodaModel.getSyntheticPointAtTime(gr, br, t); + } + + synth.setSegment(Ac); + synth.setBeginTime(seis.getTime().getDate()); + synth.setEndTime(seis.getEndtime().getDate()); + synth.setSampleRate(seis.getSamprate()); + synth.setSourceWaveform(waveform); + synth.setSourceModel(null); + synth.setMeasuredV(0.0); + synth.setMeasuredB(br); + synth.setMeasuredG(gr); + + return synth; + } + + /** + * Down-select the given {@link PeakVelocityMeasurement} collection to only + * ones with 'End' picks defined and associate them into an Entry + */ + private Collection> filterMeasurementsToEndPickedOnly( + Collection velocityMeasurements) { + return velocityMeasurements .parallelStream().filter(vel -> vel.getWaveform() != null) + .filter(vel -> vel.getWaveform().getAssociatedPicks() != null).map(vel -> { + + Optional pick = vel .getWaveform().getAssociatedPicks().stream() + .filter(p -> PICK_TYPES.F .name() + .equalsIgnoreCase(p.getPickType())) + .findFirst(); + if (pick.isPresent()) { + return new AbstractMap.SimpleEntry<>(vel, pick.get()); + } else { + return null; + } + }).filter(Objects::nonNull).collect(Collectors.toList()); + } } diff --git a/calibration-service/service-impl/src/main/java/gov/llnl/gnem/apps/coda/calibration/service/impl/processing/CalibrationCurveFitter.java b/calibration-service/service-impl/src/main/java/gov/llnl/gnem/apps/coda/calibration/service/impl/processing/CalibrationCurveFitter.java index 6f91fa34..70f02c98 100755 --- a/calibration-service/service-impl/src/main/java/gov/llnl/gnem/apps/coda/calibration/service/impl/processing/CalibrationCurveFitter.java +++ b/calibration-service/service-impl/src/main/java/gov/llnl/gnem/apps/coda/calibration/service/impl/processing/CalibrationCurveFitter.java @@ -170,7 +170,72 @@ public EnvelopeFit fitCodaCMAES(final float[] segment) { return fit; } - public double[] gridSearchCodaVApacheCMAES(List> velocityDistancePairs) { + public EnvelopeFit fitCodaCMAESStraightLine(float[] segment) { + + double minInt = 0.01; + double maxInt = 10.0; + double minBeta = -1.0; + double maxBeta = 0.0; + + EnvelopeFit fit = new EnvelopeFit(); + + SimpleRegression regression = new SimpleRegression(); + for (int j = 0; j < segment.length; j++) { + regression.addData(j + 1, segment[j]); + } + double startIntercept = regression.getIntercept(); + double startBeta = regression.getSlope(); + + MultivariateFunction prediction = point -> { + double intercept = point[0]; + double gamma = 0.0; + double beta = point[1]; + double sum = 0.0; + for (int j = 0; j < segment.length; j++) { + double t = j + 1.0; + double actual = segment[j]; + double predicted = intercept - (gamma * Math.log10(t)) + (beta * t); + sum = sum + FastMath.sqrt((predicted - actual) * (predicted - actual)); + } + return sum; + }; + + ConvergenceChecker convergenceChecker = new SimplePointChecker<>(0.0005, -1.0, 100000); + + if (Double.isNaN(startIntercept)) { + startIntercept = ThreadLocalRandom.current().nextDouble(minInt, maxInt); + startBeta = minBeta; + } + + if (startIntercept > maxInt) { + maxInt += startIntercept; + } else if (startIntercept < minInt) { + startIntercept = minInt; + } + + if (startBeta > maxBeta) { + maxBeta += startBeta; + } else if (startBeta < minBeta) { + startBeta = minBeta; + } + + PointValuePair bestResult = optimizeCMAES(prediction, + new InitialGuess(new double[] { startIntercept, startBeta }), + new CMAESOptimizer.Sigma(new double[] { 0.5, 0.05 }), + convergenceChecker, + 50, + new SimpleBounds(new double[] { minInt, minBeta }, new double[] { maxInt, maxBeta })); + + double[] curve = bestResult.getKey(); + fit.setIntercept(curve[0]); + fit.setGamma(0.0); + fit.setBeta(curve[1]); + fit.setError(bestResult.getValue()); + + return fit; + } + + public double[] gridSearchCodaVApacheCMAES(List> velocityDistancePairs) { return gridSearchCodaVApacheCMAES(velocityDistancePairs, YVV_MIN, YVV_MAX, 0, V_DIST_MAX); } diff --git a/calibration-service/service-impl/src/main/java/gov/llnl/gnem/apps/coda/calibration/service/impl/processing/CodaConsensusEndTimePicker.java b/calibration-service/service-impl/src/main/java/gov/llnl/gnem/apps/coda/calibration/service/impl/processing/CodaConsensusEndTimePicker.java index b04342ec..a1cc8a45 100644 --- a/calibration-service/service-impl/src/main/java/gov/llnl/gnem/apps/coda/calibration/service/impl/processing/CodaConsensusEndTimePicker.java +++ b/calibration-service/service-impl/src/main/java/gov/llnl/gnem/apps/coda/calibration/service/impl/processing/CodaConsensusEndTimePicker.java @@ -29,18 +29,17 @@ public class CodaConsensusEndTimePicker implements EndTimePicker { private static final double BAD_PICK = -100.0; @Override - public double getEndTime(float[] subsection, float[] synthSubsection, double sampleRate, double startTimeEpochSeconds, double minLengthSec, double maxLengthSec, double minimumSnr, - double centerFreq, double distance) { + public double getEndTime(float[] subsection, float[] synthSubsection, double sampleRate, double startTimeEpochSeconds, int startOffset, double minLengthSec, double maxLengthSec, double minimumSnr, + double noiseAmp, double centerFreq, double distance) { List snrPicks = new ArrayList<>(); - snrPicks.add(getSnrEndPick(subsection, sampleRate, minLengthSec, maxLengthSec, minimumSnr, 10)); - snrPicks.add(getSnrEndPick(subsection, sampleRate, minLengthSec, maxLengthSec, minimumSnr, 15)); - snrPicks.add(getSnrEndPick(subsection, sampleRate, minLengthSec, maxLengthSec, minimumSnr, 20)); - snrPicks.add(getSnrEndPick(subsection, sampleRate, minLengthSec, maxLengthSec, minimumSnr, 40)); - snrPicks.add(getSnrEndPick(subsection, sampleRate, minLengthSec, maxLengthSec, minimumSnr, 60)); - snrPicks.add(getSnrEndPick(subsection, sampleRate, minLengthSec, maxLengthSec, minimumSnr, 80)); + if (centerFreq >= 1.0) { + snrPicks.add(getSnrEndPick(subsection, sampleRate, startOffset, minLengthSec, maxLengthSec, minimumSnr, noiseAmp, 20, centerFreq)); + } List syntheticPicks = new ArrayList<>(); - syntheticPicks.add(getSyntheticEndPick(subsection, synthSubsection, sampleRate, centerFreq)); + if (centerFreq <= 3.0) { + syntheticPicks.add(getSyntheticEndPick(subsection, synthSubsection, sampleRate, startOffset, centerFreq)); + } Double snrAggregatePick = getFilteredGeometricMean(snrPicks, minLengthSec, maxLengthSec); @@ -65,7 +64,7 @@ public double getEndTime(float[] subsection, float[] synthSubsection, double sam } } - return startTimeEpochSeconds + overallPick; + return startTimeEpochSeconds + (overallPick * sampleRate); } /** @@ -83,6 +82,9 @@ public double getEndTime(float[] subsection, float[] synthSubsection, double sam * not possible. */ private static Double getFilteredGeometricMean(final List data, final double min, final double max) { + if (data != null && data.size() == 1) { + return data.get(0); + } DescriptiveStatistics stats = new DescriptiveStatistics(); for (Double value : data) { if (value > min) { @@ -92,32 +94,33 @@ private static Double getFilteredGeometricMean(final List data, final do return stats.getGeometricMean(); } - private Double getSyntheticEndPick(final float[] subsection, final float[] synthSubsection, final double samprate, final double centerFreq) { + private Double getSyntheticEndPick(final float[] subsection, final float[] synthSubsection, final double samprate, int startOffset, final double centerFreq) { double windowSize; double overlap; double slopemax; + double minLength; if (centerFreq <= 0.25) { - windowSize = 50.0; - overlap = 30.0; - slopemax = 1.3; + windowSize = 90.0; + overlap = 10.0; + slopemax = 2.3; + minLength = 200.0; } else if (centerFreq <= 0.85) { - windowSize = 40.0; - overlap = 20.0; - slopemax = 0.7; + windowSize = 60.0; + overlap = 10.0; + slopemax = 2.0; + minLength = 120.0; } else if (centerFreq <= 3.5) { - windowSize = 30.0; - overlap = 20.0; - slopemax = 0.4; - } else if (centerFreq <= 6.5) { - windowSize = 15.0; + windowSize = 50.0; overlap = 10.0; - slopemax = 0.5; + slopemax = 2.1; + minLength = 90.0; } else { windowSize = 10.0; - overlap = 7.0; - slopemax = 0.5; + overlap = 2.0; + slopemax = 2.8; + minLength = 10.0; } boolean done = false; @@ -129,36 +132,52 @@ private Double getSyntheticEndPick(final float[] subsection, final float[] synth SimpleRegression obs = new SimpleRegression(); SimpleRegression synth = new SimpleRegression(); + DescriptiveStatistics obsStats = new DescriptiveStatistics(); int ii = 0; + int stepCount = 0; while (!done) { if (ii + winLength < dataLength) { obs.clear(); synth.clear(); + obsStats.clear(); for (int i = ii; i < ii + winLength; i++) { obs.addData(i, subsection[i]); synth.addData(i, synthSubsection[i]); + obsStats.addValue(subsection[i]); } ii += (winLength - (overlap * samprate)); - double diffSlope = Math.abs(synth.getSlope() - obs.getSlope()); - double ratioSlope = diffSlope / Math.abs(synth.getSlope()); - - if (ratioSlope > slopemax && obs.getSlope() > -0.005) { - slopeTimePick = ii; - done = true; + if (ii >= startOffset) { + double minToMax = obsStats.getMin() / obsStats.getMax(); + double diffSlope = Math.abs(synth.getSlope() - obs.getSlope()); + double ratioSlope = diffSlope / Math.abs(obs.getSlope()); + double synInt = synth.getIntercept() + obsStats.getMean(); + double offset = obs.getIntercept() / synInt; + + if (stepCount > 0 && (ratioSlope > slopemax || obs.getSlope() < -0.05)) { + slopeTimePick = ii; + done = true; + } else if (stepCount > 0 && (offset > 1.8 || offset < -0.2 || minToMax > 1.0 || minToMax < -0.2)) { + slopeTimePick = ii; + done = true; + } } + stepCount++; + } else { slopeTimePick = subsection.length; done = true; } } return slopeTimePick; + } - private Double getSnrEndPick(final float[] subsection, final double samprate, final double minlength, final double maxlength, final double minimumSnr, final int windowSize) { + private Double getSnrEndPick(final float[] subsection, final double samprate, int startOffset, final double minlength, final double maxlength, final double minimumSnr, final double noiseAmp, + final int windowSize, final double centerFreq) { // end defined by the point at which the signal drops below a // minimum level (e.g. 2x noise amplitude) int ii = 0; @@ -166,8 +185,18 @@ private Double getSnrEndPick(final float[] subsection, final double samprate, fi boolean done = false; int winLength = (int) ((windowSize * samprate) + 2); + + if (centerFreq <= 0.25) { + winLength = (int) (winLength * 2.5); + } else if (centerFreq <= 0.85) { + winLength = (int) (winLength * 2.0); + } else if (centerFreq <= 3.5) { + winLength = (int) (winLength * 1.5); + } + DescriptiveStatistics stats = new DescriptiveStatistics(winLength); - double snrTimePick = 0.0; + DescriptiveStatistics shortObs = new DescriptiveStatistics(5); + double snrTimePick = BAD_PICK; while (!done) { ++ii; @@ -178,21 +207,27 @@ private Double getSnrEndPick(final float[] subsection, final double samprate, fi break; } - if (subsection[ii] > (.3 + subsection[ii - 1])) { - snrTimePick = (ii - 1) / samprate; - break; - } + if (ii >= startOffset) { + shortObs.addValue(subsection[ii]); - stats.addValue(subsection[ii]); + if (subsection[ii] > (1.10 * shortObs.getMean())) { + snrTimePick = (ii - 1) / samprate; + break; + } - if (ii / samprate >= maxlength) { - snrTimePick = maxlength; - done = true; - } + stats.addValue(subsection[ii]); - if (ii > winLength && stats.getMean() < minimumSnr) { - snrTimePick = (ii - 1) / samprate; - done = true; + if (ii / samprate >= maxlength) { + snrTimePick = maxlength; + done = true; + } + + if (ii > winLength && (stats.getMean() - noiseAmp) < minimumSnr) { + if (((ii - startOffset) / samprate) > minlength) { + snrTimePick = (ii - 1) / samprate; + } + done = true; + } } } return snrTimePick; diff --git a/calibration-service/service-impl/src/main/java/gov/llnl/gnem/apps/coda/calibration/service/impl/processing/SpectraCalculator.java b/calibration-service/service-impl/src/main/java/gov/llnl/gnem/apps/coda/calibration/service/impl/processing/SpectraCalculator.java index 970f2252..e5fa00a6 100644 --- a/calibration-service/service-impl/src/main/java/gov/llnl/gnem/apps/coda/calibration/service/impl/processing/SpectraCalculator.java +++ b/calibration-service/service-impl/src/main/java/gov/llnl/gnem/apps/coda/calibration/service/impl/processing/SpectraCalculator.java @@ -69,497 +69,540 @@ @Component public class SpectraCalculator { - private static final double LG_PHASE_SPEED_KM_S = 3.5; - - private final Logger log = LoggerFactory.getLogger(this.getClass()); - - private WaveformToTimeSeriesConverter converter; - private SyntheticCodaModel syntheticCodaModel; - private EndTimePicker endTimePicker; - private MdacCalculatorService mdacService; - private MdacParametersFiService mdacFiService; - private MdacParametersPsService mdacPsService; - - @Autowired - public SpectraCalculator(WaveformToTimeSeriesConverter converter, SyntheticCodaModel syntheticCodaModel, EndTimePicker endTimePicker, MdacCalculatorService mdacService, - MdacParametersFiService mdacFiService, MdacParametersPsService mdacPsService) { - this.converter = converter; - this.syntheticCodaModel = syntheticCodaModel; - this.endTimePicker = endTimePicker; - this.mdacService = mdacService; - this.mdacFiService = mdacFiService; - this.mdacPsService = mdacPsService; - } - - public List measureAmplitudes(List generatedSynthetics, Map frequencyBandParameterMap, - Boolean autoPickingEnabled) { - return measureAmplitudes(generatedSynthetics, frequencyBandParameterMap, autoPickingEnabled, null); - } - - public List measureAmplitudes(List generatedSynthetics, Map frequencyBandParameterMap, Boolean autoPickingEnabled, - Map> frequencyBandSiteParameterMap) { - return generatedSynthetics.parallelStream() - .map(synth -> measureAmplitudeForSynthetic(synth, frequencyBandParameterMap, frequencyBandSiteParameterMap, autoPickingEnabled)) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - } - - private SpectraMeasurement measureAmplitudeForSynthetic(SyntheticCoda synth, Map frequencyBandParameterMap, - Map> frequencyBandSiteParameterMap, Boolean autoPickingEnabled) { - - FrequencyBand frequencyBand = new FrequencyBand(synth.getSourceWaveform().getLowFrequency(), synth.getSourceWaveform().getHighFrequency()); - SharedFrequencyBandParameters params = frequencyBandParameterMap.get(frequencyBand); - if (params != null) { - TimeSeries envSeis = converter.convert(synth.getSourceWaveform()); - TimeSeries synthSeis = new TimeSeries(WaveformUtils.doublesToFloats(synth.getSegment()), synth.getSampleRate(), new TimeT(synth.getBeginTime())); - - envSeis.interpolate(synthSeis.getSamprate()); - - Station station = synth.getSourceWaveform().getStream().getStation(); - Event event = synth.getSourceWaveform().getEvent(); - - double distance = EModel.getDistanceWGS84(event.getLatitude(), event.getLongitude(), station.getLatitude(), station.getLongitude()); - double vr = params.getVelocity0() - params.getVelocity1() / (params.getVelocity2() + distance); - if (vr == 0.0) { - vr = 1.0; - } - TimeT originTime = new TimeT(event.getOriginTime()); - TimeT startTime = originTime.add(distance / vr); - - double siteCorrection = 0.0; - - if (frequencyBandSiteParameterMap != null && frequencyBandSiteParameterMap.containsKey(frequencyBand) && frequencyBandSiteParameterMap.get(frequencyBand).get(station) != null) { - siteCorrection = frequencyBandSiteParameterMap.get(frequencyBand).get(station).getSiteTerm(); - } - - double eshCorrection = log10ESHcorrection(synth.getSourceWaveform().getLowFrequency(), - synth.getSourceWaveform().getHighFrequency(), - params.getS1(), - params.getS2(), - params.getXc(), - params.getXt(), - params.getQ(), - distance); - double noiseamp; - try { - // measure the amplitude in the noise window the end cut is - // defined where the data envelope falls below minSNR (usually - // 2*noise-amp (0.3 in log10 space)) - noiseamp = WaveformUtils.getNoiseWindow(distance, originTime, envSeis).getMean(); - } catch (IllegalArgumentException e) { - log.warn("Error attempting to measure seismogram amplitude for seismogram {}; {}", synth, e.getMessage()); - return null; - } - - double minlength = params.getMinLength(); - double maxlength = params.getMaxLength(); - - TimeT endTime = null; - - if (synth.getSourceWaveform().getAssociatedPicks() != null) { - endTime = synth.getSourceWaveform().getAssociatedPicks().stream().filter(p -> PICK_TYPES.F.name().equalsIgnoreCase(p.getPickType())).findFirst().map(pick -> { - double startpick = startTime.subtractD(originTime); - TimeT result = originTime; - result = pick.getPickTimeSecFromOrigin() > (startpick + maxlength) ? result.add(startpick + maxlength) : result.add(pick.getPickTimeSecFromOrigin()); - return result; - }).orElse(null); - } - - if (endTime == null && autoPickingEnabled) { - endTime = new TimeT(endTimePicker.getEndTime(envSeis.getData(), - synthSeis.getData(), - envSeis.getSamprate(), - startTime.getEpochTime(), - minlength, - maxlength, - noiseamp + params.getMinSnr(), - params.getLowFrequency() + (params.getHighFrequency() - params.getLowFrequency()), - distance)); - } - - if (endTime == null) { - log.trace("Unable to determine and time of Coda for waveform: {}", synth.getSourceWaveform()); - return null; - } - - // cut the coda window portion of the seismograms - if (startTime.ge(endTime)) { - log.trace("Coda envelope start time is after the seismograms end time, start: {} end: {} for envelope: {}", startTime.getDate(), endTime.getDate(), synth.getSourceWaveform()); - return null; - } else if (endTime.le(startTime)) { - log.trace("Coda envelope end time is before the start of the seismogram, start: {} end: {} for envelope: {}", startTime.getDate(), endTime.getDate(), synth.getSourceWaveform()); - return null; - } - - // Note this mutates envSeis and synthSeis! - boolean cutSucceeded = cutSeismograms(envSeis, synthSeis, startTime, endTime); - - if (cutSucceeded) { - float[] envdata = envSeis.getData(); - float[] synthdata = synthSeis.getData(); - - // envelope minus synthetic - double rawAmp = new TimeSeries(SeriesMath.Subtract(envdata, synthdata), synthSeis.getSamprate(), synthSeis.getTime()).getMedian(); - - // NOTE - this is what Rengin refers to as the RAW Amplitude; - // calculated below - double rawAtMeasurementTime = 0.; - - float[] synthscaled = SeriesMath.Add(synthdata, rawAmp); - Double fit = FitnessCriteria.CVRMSD(envdata, synthscaled); - - // path corrected using Scott's Extended Street and Herrman - // method and site correction - double pathCorrectedAmp = rawAmp + eshCorrection; - double correctedAmp = pathCorrectedAmp + siteCorrection; - - // user defined coda measurement time - double measurementtime = params.getMeasurementTime(); - if (measurementtime > 0.) { - double shiftedvalue = syntheticCodaModel.getPointAtTimeAndDistance(params, measurementtime, distance); - rawAtMeasurementTime = rawAmp + shiftedvalue; - } else { - rawAtMeasurementTime = rawAmp; - } - pathCorrectedAmp = rawAtMeasurementTime + eshCorrection; - correctedAmp = pathCorrectedAmp + siteCorrection; - - if (endTime.subtract(startTime).getEpochTime() < minlength) { - log.info("Coda window length too short for envelope: {}", synth.getSourceWaveform()); - return null; - } - - if (siteCorrection <= 0.) { - correctedAmp = 0.; - } - - return new SpectraMeasurement().setWaveform(synth.getSourceWaveform()) - .setRawAtStart(rawAmp) - .setRawAtMeasurementTime(rawAtMeasurementTime) - .setPathCorrected(pathCorrectedAmp) - .setPathAndSiteCorrected(correctedAmp) - .setStartCutSec(startTime.subtract(originTime).getEpochTime()) - .setEndCutSec(startTime.subtract(originTime).getEpochTime() + endTime.subtract(startTime).getEpochTime()) - .setRmsFit(fit); - } - } - - return null; - - } - - private boolean cutSeismograms(TimeSeries a, TimeSeries b, TimeT startTime, TimeT endTime) { - boolean completedCut = false; - try { - a.cut(startTime, endTime); - b.cut(startTime, endTime); - - // These might be off by some small number of samples due to precision - // errors during cut and SeriesMath will throw an ArrayBounds if they - // don't match exactly so we need to double check! - if (a.getNsamp() != b.getNsamp()) { - - TimeT start = a.getTime(); - TimeT end = a.getEndtime(); - TimeT startA = a.getTime(); - TimeT startB = b.getTime(); - TimeT endA = a.getEndtime(); - TimeT endB = b.getEndtime(); - - // choose the latest start time and the earliest end time for the - // cut window - if (startA.lt(startB)) { - start = startB; - } - if (endA.gt(endB)) { - end = endB; - } - if (start.ge(end)) { - // don't continue if the seismograms don't overlap - return completedCut; - } - - int begin_index_a = a.getIndexForTime(start.getEpochTime()); - int end_index_a = a.getIndexForTime(end.getEpochTime()); - - int begin_index_b = b.getIndexForTime(start.getEpochTime()); - int end_index_b = b.getIndexForTime(end.getEpochTime()); - - int nptsa = (end_index_a - begin_index_a) + 1; - int nptsb = (end_index_b - begin_index_b) + 1; - - int npts = Math.min(nptsa, nptsb); - // now cut the traces again - a.cut(begin_index_a, (begin_index_a + npts - 1)); - b.cut(begin_index_b, (begin_index_b + npts - 1)); - } - completedCut = true; - } catch (IllegalArgumentException e) { - log.warn("Error attempting to cut seismograms during amplitude measurement; {}", e.getMessage()); - } - - return completedCut; - } - - /** - * Scott Phillips Extended Street and Hermann path correction including - * distance and Q - * - * @param lowfreq - * the low frequency cut - * @param highfreq - * the high frequency cut - * @param alpha1 - * @param alpha2 - * @param xc - * xcross - * @param xt - * xtrans - * @param distance - * source-receiver distance - * @param q - * the attenuation term - * @return -log10(esh) + distance*pi*f0*log10(e) / (vphase* q) - */ - public double log10ESHcorrection(double lowfreq, double highfreq, double alpha1, double alpha2, double xc, double xt, double q, double distance) { - double log10esh = log10ESHcorrection(alpha1, alpha2, xc, xt, distance); - - if ((log10esh == 0.) || (q == 0.)) { - return 0.; // no ESH correction - } - - double f0 = Math.sqrt(lowfreq * highfreq); - double efact = Math.log10(Math.E); - // TODO: make this generic w.r.t. phase - double vphase = LG_PHASE_SPEED_KM_S; - double distQ = distance * Math.PI * f0 * efact / (q * vphase); - // We want to return a positive number for path correction - return -1 * log10esh + distQ; - } - - /** - * Extended Street and Herrmann path correction - * - * calculate log10 extended Street-Herrmann model for one distance no site, - * no Q terms - * - * translated from Scott Phillips original fortran function eshmod - */ - public double log10ESHcorrection(double s1, double s2, double xcross, double xtrans, double distance) { - double eshmod; - double xstart = xcross / xtrans; - double xend = xcross * xtrans; - - if (distance <= xstart) { - eshmod = -1.0 * s1 * Math.log10(distance); - } else if (distance >= xend) { - double ds = s2 - s1; - eshmod = -1.0 * s1 * Math.log10(xstart) - (s1 + ds / 2.) * Math.log10(xend / xstart) - s2 * Math.log10(distance / xend); - } else { - // singular if xtrans=1, but should not get here - double s = (s2 - s1) / Math.log10(xend / xstart); - double ds = s * Math.log10(distance / xstart); - eshmod = -1.0 * s1 * Math.log10(xstart) - (s1 + ds / 2.) * Math.log10(distance / xstart); - } - - return eshmod; - } - - public Spectra computeReferenceSpectra(ReferenceMwParameters refEvent, List bands, PICK_TYPES selectedPhase) { - - MdacParametersFI mdacFiEntry = mdacFiService.findFirst(); - MdacParametersPS psRows = mdacPsService.findMatchingPhase(selectedPhase.getPhase()); - - List xyPoints = new ArrayList<>(); - for (FrequencyBand band : bands) { - - double centerFreq = band.getLowFrequency() + (band.getHighFrequency() - band.getLowFrequency()) / 2.; - - double amplitude; - if (refEvent.getStressDropInMpa() != null && refEvent.getStressDropInMpa() > 0.0) { - //If we know a MPA stress drop for this reference event we want to use stress instead of apparent stress - //so we set Psi == 0.0 to use Sigma as stress drop - mdacFiEntry.setSigma(refEvent.getStressDropInMpa()); - mdacFiEntry.setPsi(0.0); - amplitude = mdacService.calculateMdacAmplitudeForMw(psRows, mdacFiEntry, refEvent.getRefMw(), centerFreq, selectedPhase, refEvent.getStressDropInMpa()); - } else { - amplitude = mdacService.calculateMdacAmplitudeForMw(psRows, mdacFiEntry, refEvent.getRefMw(), centerFreq, selectedPhase); - } - - if (amplitude > 0) { - Point2D.Double point = new Point2D.Double(Math.log10(centerFreq), amplitude); - xyPoints.add(point); - } - } - - Collections.sort(xyPoints, (p1, p2) -> Double.compare(p1.getX(), p2.getX())); - - return new Spectra(SPECTRA_TYPES.REF, xyPoints, refEvent.getRefMw(), refEvent.getStressDropInMpa()); - } - - public Spectra computeFitSpectra(MeasuredMwParameters event, List bands, PICK_TYPES selectedPhase) { - - MdacParametersFI mdacFiEntry = mdacFiService.findFirst(); - MdacParametersPS psRows = mdacPsService.findMatchingPhase(selectedPhase.getPhase()); - - List xyPoints = new ArrayList<>(); - for (FrequencyBand band : bands) { - double centerFreq = band.getLowFrequency() + (band.getHighFrequency() - band.getLowFrequency()) / 2.; - double amplitude; - if (event.getStressDropInMpa() != null && event.getStressDropInMpa() > 0.0) { - //If we know a MPA stress drop for this reference event we want to use stress instead of apparent stress - //so we set Psi == 0.0 to use Sigma as stress drop - mdacFiEntry.setSigma(event.getStressDropInMpa()); - mdacFiEntry.setPsi(0.0); - amplitude = mdacService.calculateMdacAmplitudeForMw(psRows, mdacFiEntry, event.getMw(), centerFreq, selectedPhase, event.getStressDropInMpa()); - } else { - amplitude = mdacService.calculateMdacAmplitudeForMw(psRows, mdacFiEntry, event.getMw(), centerFreq, selectedPhase); - } - - if (amplitude > 0) { - Point2D.Double point = new Point2D.Double(Math.log10(centerFreq), amplitude); - xyPoints.add(point); - } - } - - Collections.sort(xyPoints, (p1, p2) -> Double.compare(p1.getX(), p2.getX())); - - return new Spectra(SPECTRA_TYPES.FIT, xyPoints, event.getMw(), event.getStressDropInMpa()); - } - - /** - * An estimate of the Mw and corner frequency based on Mdac spectra Based on - * the MDAC2 spectra calculations published by Walters and Taylor, 2001 - * UCRL-ID-146882 - * - */ - public List measureMws(final Map> evidMap, final PICK_TYPES selectedPhase) { - final MdacParametersFI mdacFi = mdacFiService.findFirst(); - final MdacParametersPS mdacPs = mdacPsService.findMatchingPhase(selectedPhase.getPhase()); - List measuredMws = new ArrayList<>(); - - for (Entry> entry : evidMap.entrySet()) { - Map measurements = entry.getValue(); - double[] MoMw = fitMw(measurements, selectedPhase, mdacFi, mdacPs); - if (MoMw == null) { - log.warn("MoMw calculation returned null value"); - continue; - } - measuredMws.add(new MeasuredMwParameters().setEventId(entry.getKey().getEventId()).setMw(MoMw[1]).setStressDropInMpa(MoMw[4])); - } - return measuredMws; - } - - /** - * Grid search across a range of corner frequency and stress drop parameters - * looking for the best fit theoretical source spectra and return the - * resulting MW measurement. - * - * @param measurements - * - the measured spectra values by frequency band - * @param phase - * - the phase (e.g. "Lg") this should be present in the Mdac - * parameter set - * @return double[] containing the final fit measurements - */ - public double[] fitMw(final Map measurements, final PICK_TYPES phase, final MdacParametersFI mdacFi, final MdacParametersPS mdacPs) { - double[] result = new double[5]; - - final Map weightMap = new TreeMap<>(); - final double minMW = 0.5; - final double maxMW = 8.0; - final double minMPA = 0.00001; - final double maxMPA = 10.00; - int dataCount = 0; - - for (Entry meas : measurements.entrySet()) { - double logAmplitude = meas.getValue().getMean(); - double lowFreq = meas.getKey().getLowFrequency(); - double highFreq = meas.getKey().getHighFrequency(); - double centerFreq = lowFreq + 0.25 * (highFreq - lowFreq); - weightMap.put(centerFreq, 0.0); - if (logAmplitude > 0.0) { - dataCount++; - } - } - - int count = 0; - for (Entry entry : weightMap.entrySet()) { - entry.setValue(count == 0 || count == 2 ? 0.5 : count == 1 ? 1.0 : count == 3 ? 0.25 : 0.1); - count++; - } - - MultivariateFunction mdacFunction = new MultivariateFunction() { - - @Override - public double value(double[] point) { - double testMw = point[0]; - double testSigma = point[1]; - - HashMap dataMap = new HashMap<>(); - - for (Entry meas : measurements.entrySet()) { - double logAmplitude = meas.getValue().getMean(); - double lowFreq = meas.getKey().getLowFrequency(); - double highFreq = meas.getKey().getHighFrequency(); - double centerFreq = lowFreq + 0.25 * (highFreq - lowFreq); - - if (mdacPs != null) { - // Note this is in dyne-cm to match Kevin - double log10mdacM0 = mdacService.calculateMdacAmplitudeForMw(mdacPs, mdacFi, testMw, centerFreq, phase, testSigma); - - if (logAmplitude > 0.) { - double[] coda_vs_mdac = new double[] { logAmplitude, log10mdacM0 }; - dataMap.put(centerFreq, coda_vs_mdac); - } - } - } - - return WCVRMSD(weightMap, dataMap); - } - }; - - ConvergenceChecker convergenceChecker = new SimplePointChecker<>(0.001, 0.001, 100000); - CMAESOptimizer optimizer = new CMAESOptimizer(1000000, 0, true, 0, 10, new MersenneTwister(), true, convergenceChecker); - PointValuePair optimizerResult = optimizer.optimize(new MaxEval(1000000), - new ObjectiveFunction(mdacFunction), - GoalType.MINIMIZE, - new SimpleBounds(new double[] { minMW, minMPA }, new double[] { maxMW, maxMPA }), - new InitialGuess(new double[] { ThreadLocalRandom.current().nextDouble(minMW, maxMW), - ThreadLocalRandom.current().nextDouble(minMPA, maxMPA) }), - new CMAESOptimizer.Sigma(new double[] { 0.05, 1.0 }), - new CMAESOptimizer.PopulationSize(50)); - - // converted back into dyne-cm to match Kevin's format - double testMw = optimizerResult.getPoint()[0]; - // log10M0 - result[0] = Math.log10(mdacService.getMwInDyne(testMw)); - result[1] = testMw; // best Mw fit - // this is the number of elements that have a signal measurement - result[2] = dataCount; - // this is the rmsfit measurement - result[3] = optimizerResult.getValue(); - // this is the stress drop - result[4] = optimizerResult.getPoint()[1]; - - return result; - } - - /** - * @param weightMap - * @param dataMap - * @return - */ - protected static double WCVRMSD(Map weightMap, HashMap dataMap) { - for (Entry entry : dataMap.entrySet()) { - if (weightMap.containsKey(entry.getKey())) { - double[] val = entry.getValue(); - for (int i = 0; i < val.length; i++) { - val[i] *= weightMap.get(entry.getKey()); - } - dataMap.put(entry.getKey(), val); - } - } - return FitnessCriteria.CVRMSD(dataMap); - } + private static final double LG_PHASE_SPEED_KM_S = 3.5; + + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + private WaveformToTimeSeriesConverter converter; + private SyntheticCodaModel syntheticCodaModel; + private EndTimePicker endTimePicker; + private MdacCalculatorService mdacService; + private MdacParametersFiService mdacFiService; + private MdacParametersPsService mdacPsService; + + @Autowired + public SpectraCalculator(WaveformToTimeSeriesConverter converter, SyntheticCodaModel syntheticCodaModel, + EndTimePicker endTimePicker, MdacCalculatorService mdacService, MdacParametersFiService mdacFiService, + MdacParametersPsService mdacPsService) { + this.converter = converter; + this.syntheticCodaModel = syntheticCodaModel; + this.endTimePicker = endTimePicker; + this.mdacService = mdacService; + this.mdacFiService = mdacFiService; + this.mdacPsService = mdacPsService; + } + + public List measureAmplitudes(List generatedSynthetics, + Map frequencyBandParameterMap, Boolean autoPickingEnabled) { + return measureAmplitudes(generatedSynthetics, frequencyBandParameterMap, autoPickingEnabled, null); + } + + public List measureAmplitudes(List generatedSynthetics, + Map frequencyBandParameterMap, Boolean autoPickingEnabled, + Map> frequencyBandSiteParameterMap) { + return generatedSynthetics .parallelStream() + .map(synth -> measureAmplitudeForSynthetic( synth, frequencyBandParameterMap, + frequencyBandSiteParameterMap, + autoPickingEnabled)) + .filter(Objects::nonNull).collect(Collectors.toList()); + } + + private SpectraMeasurement measureAmplitudeForSynthetic(SyntheticCoda synth, + Map frequencyBandParameterMap, + Map> frequencyBandSiteParameterMap, + Boolean autoPickingEnabled) { + + FrequencyBand frequencyBand = new FrequencyBand(synth.getSourceWaveform().getLowFrequency(), + synth.getSourceWaveform().getHighFrequency()); + SharedFrequencyBandParameters params = frequencyBandParameterMap.get(frequencyBand); + if (params != null) { + TimeSeries envSeis = converter.convert(synth.getSourceWaveform()); + TimeSeries synthSeis = new TimeSeries( WaveformUtils.doublesToFloats(synth.getSegment()), + synth.getSampleRate(), new TimeT(synth.getBeginTime())); + + envSeis.interpolate(synthSeis.getSamprate()); + + Station station = synth.getSourceWaveform().getStream().getStation(); + Event event = synth.getSourceWaveform().getEvent(); + + double distance = EModel.getDistanceWGS84( event.getLatitude(), event.getLongitude(), station.getLatitude(), + station.getLongitude()); + double vr = params.getVelocity0() - params.getVelocity1() / (params.getVelocity2() + distance); + if (vr == 0.0) { + vr = 1.0; + } + TimeT originTime = new TimeT(event.getOriginTime()); + final TimeT startTime; + TimeT tempTime; + TimeT trimTime = originTime.add(distance / vr); + TimeSeries trimmedWaveform = new TimeSeries(envSeis); + try { + trimmedWaveform.cutBefore(trimTime); + trimmedWaveform.cutAfter(trimTime.add(30.0)); + + tempTime = new TimeT(trimTime.getEpochTime() + trimmedWaveform.getMaxTime()[0]); + } catch (IllegalArgumentException e) { + tempTime = trimTime; + } + startTime = tempTime; + + double siteCorrection = 0.0; + + if (frequencyBandSiteParameterMap != null && frequencyBandSiteParameterMap.containsKey(frequencyBand) + && frequencyBandSiteParameterMap.get(frequencyBand).get(station) != null) { + siteCorrection = frequencyBandSiteParameterMap.get(frequencyBand).get(station).getSiteTerm(); + } + + double eshCorrection = log10ESHcorrection( synth.getSourceWaveform().getLowFrequency(), + synth.getSourceWaveform().getHighFrequency(), params.getS1(), + params.getS2(), params.getXc(), params.getXt(), params.getQ(), + distance); + double noiseamp; + try { + // measure the amplitude in the noise window the end cut is + // defined where the data envelope falls below minSNR (usually + // 2*noise-amp (0.3 in log10 space)) + noiseamp = WaveformUtils.getNoiseWindow(distance, originTime, envSeis).getMean(); + } catch (IllegalArgumentException e) { + log.warn( "Error attempting to measure seismogram amplitude for seismogram {}; {}", synth, + e.getMessage()); + return null; + } + + double minlength = params.getMinLength(); + double maxlength = params.getMaxLength(); + + TimeT endTime = null; + + if (synth.getSourceWaveform().getAssociatedPicks() != null) { + endTime = synth .getSourceWaveform().getAssociatedPicks().stream() + .filter(p -> PICK_TYPES.F.name().equalsIgnoreCase(p.getPickType())).findFirst() + .map(pick -> { + double startpick = startTime.subtractD(originTime); + TimeT result = originTime; + result = pick.getPickTimeSecFromOrigin() > (startpick + maxlength) + ? result.add(startpick + maxlength) + : result.add(pick.getPickTimeSecFromOrigin()); + return result; + }).orElse(null); + } + + if (endTime == null && autoPickingEnabled) { + endTime = new TimeT(endTimePicker.getEndTime( envSeis.getData(), synthSeis.getData(), + envSeis.getSamprate(), startTime.getEpochTime(), + envSeis.getIndexForTime(startTime.getEpochTime()), + minlength, maxlength, params.getMinSnr(), noiseamp, + params.getLowFrequency() + ((params.getHighFrequency() + - params.getLowFrequency()) / 2.0), + distance)); + } + + if (endTime == null) { + log.trace("Unable to determine and time of Coda for waveform: {}", synth.getSourceWaveform()); + return null; + } + + // cut the coda window portion of the seismograms + if (startTime.ge(endTime)) { + log.trace( "Coda envelope start time is after the seismograms end time, start: {} end: {} for envelope: {}", + startTime.getDate(), endTime.getDate(), synth.getSourceWaveform()); + return null; + } else if (endTime.le(startTime)) { + log.trace( "Coda envelope end time is before the start of the seismogram, start: {} end: {} for envelope: {}", + startTime.getDate(), endTime.getDate(), synth.getSourceWaveform()); + return null; + } + + // Note this mutates envSeis and synthSeis! + boolean cutSucceeded = cutSeismograms(envSeis, synthSeis, startTime, endTime); + + if (cutSucceeded) { + float[] envdata = envSeis.getData(); + float[] synthdata = synthSeis.getData(); + + // envelope minus synthetic + double rawAmp = new TimeSeries( SeriesMath.Subtract(envdata, synthdata), synthSeis.getSamprate(), + synthSeis.getTime()).getMedian(); + + // NOTE - this is what Rengin refers to as the RAW Amplitude; + // calculated below + double rawAtMeasurementTime = 0.; + + float[] synthscaled = SeriesMath.Add(synthdata, rawAmp); + Double fit = FitnessCriteria.CVRMSD(envdata, synthscaled); + + // path corrected using Scott's Extended Street and Herrman + // method and site correction + double pathCorrectedAmp = rawAmp + eshCorrection; + double correctedAmp = pathCorrectedAmp + siteCorrection; + + // user defined coda measurement time + double measurementtime = params.getMeasurementTime(); + if (measurementtime > 0.) { + double shiftedvalue = syntheticCodaModel.getPointAtTimeAndDistance( params, measurementtime, + distance); + rawAtMeasurementTime = rawAmp + shiftedvalue; + } else { + rawAtMeasurementTime = rawAmp; + } + pathCorrectedAmp = rawAtMeasurementTime + eshCorrection; + correctedAmp = pathCorrectedAmp + siteCorrection; + + if (endTime.subtract(startTime).getEpochTime() < minlength) { + log.info("Coda window length too short for envelope: {}", synth.getSourceWaveform()); + return null; + } + + if (siteCorrection <= 0.) { + correctedAmp = 0.; + } + + return new SpectraMeasurement() .setWaveform(synth.getSourceWaveform()).setRawAtStart(rawAmp) + .setRawAtMeasurementTime(rawAtMeasurementTime) + .setPathCorrected(pathCorrectedAmp) + .setPathAndSiteCorrected(correctedAmp) + .setStartCutSec(startTime.subtract(originTime).getEpochTime()) + .setEndCutSec(endTime.getEpochTime()).setRmsFit(fit); + } + } + + return null; + + } + + private boolean cutSeismograms(TimeSeries a, TimeSeries b, TimeT startTime, TimeT endTime) { + boolean completedCut = false; + try { + a.cut(startTime, endTime); + b.cut(startTime, endTime); + + // These might be off by some small number of samples due to + // precision + // errors during cut and SeriesMath will throw an ArrayBounds if + // they + // don't match exactly so we need to double check! + if (a.getNsamp() != b.getNsamp()) { + + TimeT start = a.getTime(); + TimeT end = a.getEndtime(); + TimeT startA = a.getTime(); + TimeT startB = b.getTime(); + TimeT endA = a.getEndtime(); + TimeT endB = b.getEndtime(); + + // choose the latest start time and the earliest end time for + // the + // cut window + if (startA.lt(startB)) { + start = startB; + } + if (endA.gt(endB)) { + end = endB; + } + if (start.ge(end)) { + // don't continue if the seismograms don't overlap + return completedCut; + } + + int begin_index_a = a.getIndexForTime(start.getEpochTime()); + int end_index_a = a.getIndexForTime(end.getEpochTime()); + + int begin_index_b = b.getIndexForTime(start.getEpochTime()); + int end_index_b = b.getIndexForTime(end.getEpochTime()); + + int nptsa = (end_index_a - begin_index_a) + 1; + int nptsb = (end_index_b - begin_index_b) + 1; + + int npts = Math.min(nptsa, nptsb); + // now cut the traces again + a.cut(begin_index_a, (begin_index_a + npts - 1)); + b.cut(begin_index_b, (begin_index_b + npts - 1)); + } + completedCut = true; + } catch (IllegalArgumentException e) { + log.warn("Error attempting to cut seismograms during amplitude measurement; {}", e.getMessage()); + } + + return completedCut; + } + + /** + * Scott Phillips Extended Street and Hermann path correction including + * distance and Q + * + * @param lowfreq + * the low frequency cut + * @param highfreq + * the high frequency cut + * @param alpha1 + * @param alpha2 + * @param xc + * xcross + * @param xt + * xtrans + * @param distance + * source-receiver distance + * @param q + * the attenuation term + * @return -log10(esh) + distance*pi*f0*log10(e) / (vphase* q) + */ + public double log10ESHcorrection(double lowfreq, double highfreq, double alpha1, double alpha2, double xc, + double xt, double q, double distance) { + double log10esh = log10ESHcorrection(alpha1, alpha2, xc, xt, distance); + + if ((log10esh == 0.) || (q == 0.)) { + return 0.; // no ESH correction + } + + double f0 = Math.sqrt(lowfreq * highfreq); + double efact = Math.log10(Math.E); + // TODO: make this generic w.r.t. phase + double vphase = LG_PHASE_SPEED_KM_S; + double distQ = distance * Math.PI * f0 * efact / (q * vphase); + // We want to return a positive number for path correction + return -1 * log10esh + distQ; + } + + /** + * Extended Street and Herrmann path correction + * + * calculate log10 extended Street-Herrmann model for one distance no site, + * no Q terms + * + * translated from Scott Phillips original fortran function eshmod + */ + public double log10ESHcorrection(double s1, double s2, double xcross, double xtrans, double distance) { + double eshmod; + double xstart = xcross / xtrans; + double xend = xcross * xtrans; + + if (distance <= xstart) { + eshmod = -1.0 * s1 * Math.log10(distance); + } else if (distance >= xend) { + double ds = s2 - s1; + eshmod = -1.0 * s1 * Math.log10(xstart) - (s1 + ds / 2.) * Math.log10(xend / xstart) + - s2 * Math.log10(distance / xend); + } else { + // singular if xtrans=1, but should not get here + double s = (s2 - s1) / Math.log10(xend / xstart); + double ds = s * Math.log10(distance / xstart); + eshmod = -1.0 * s1 * Math.log10(xstart) - (s1 + ds / 2.) * Math.log10(distance / xstart); + } + + return eshmod; + } + + public Spectra computeReferenceSpectra(ReferenceMwParameters refEvent, List bands, + PICK_TYPES selectedPhase) { + + MdacParametersFI mdacFiEntry = mdacFiService.findFirst(); + MdacParametersPS psRows = mdacPsService.findMatchingPhase(selectedPhase.getPhase()); + + List xyPoints = new ArrayList<>(); + for (FrequencyBand band : bands) { + + double centerFreq = band.getLowFrequency() + (band.getHighFrequency() - band.getLowFrequency()) / 2.; + + double amplitude; + if (refEvent.getStressDropInMpa() != null && refEvent.getStressDropInMpa() > 0.0) { + // If we know a MPA stress drop for this reference event we want + // to use stress instead of apparent stress + // so we set Psi == 0.0 to use Sigma as stress drop + mdacFiEntry.setSigma(refEvent.getStressDropInMpa()); + mdacFiEntry.setPsi(0.0); + amplitude = mdacService.calculateMdacAmplitudeForMw(psRows, mdacFiEntry, refEvent.getRefMw(), + centerFreq, selectedPhase, + refEvent.getStressDropInMpa()); + } else { + amplitude = mdacService.calculateMdacAmplitudeForMw(psRows, mdacFiEntry, refEvent.getRefMw(), + centerFreq, selectedPhase); + } + + if (amplitude > 0) { + Point2D.Double point = new Point2D.Double(Math.log10(centerFreq), amplitude); + xyPoints.add(point); + } + } + + Collections.sort(xyPoints, (p1, p2) -> Double.compare(p1.getX(), p2.getX())); + + return new Spectra(SPECTRA_TYPES.REF, xyPoints, refEvent.getRefMw(), refEvent.getStressDropInMpa()); + } + + public Spectra computeFitSpectra(MeasuredMwParameters event, List bands, PICK_TYPES selectedPhase) { + + MdacParametersFI mdacFiEntry = mdacFiService.findFirst(); + MdacParametersPS psRows = mdacPsService.findMatchingPhase(selectedPhase.getPhase()); + + List xyPoints = new ArrayList<>(); + for (FrequencyBand band : bands) { + double centerFreq = band.getLowFrequency() + (band.getHighFrequency() - band.getLowFrequency()) / 2.; + double amplitude; + if (event.getStressDropInMpa() != null && event.getStressDropInMpa() > 0.0) { + // If we know a MPA stress drop for this reference event we want + // to use stress instead of apparent stress + // so we set Psi == 0.0 to use Sigma as stress drop + mdacFiEntry.setSigma(event.getStressDropInMpa()); + mdacFiEntry.setPsi(0.0); + amplitude = mdacService.calculateMdacAmplitudeForMw(psRows, mdacFiEntry, event.getMw(), centerFreq, + selectedPhase, event.getStressDropInMpa()); + } else { + amplitude = mdacService.calculateMdacAmplitudeForMw(psRows, mdacFiEntry, event.getMw(), centerFreq, + selectedPhase); + } + + if (amplitude > 0) { + Point2D.Double point = new Point2D.Double(Math.log10(centerFreq), amplitude); + xyPoints.add(point); + } + } + + Collections.sort(xyPoints, (p1, p2) -> Double.compare(p1.getX(), p2.getX())); + + return new Spectra(SPECTRA_TYPES.FIT, xyPoints, event.getMw(), event.getStressDropInMpa()); + } + + /** + * An estimate of the Mw and corner frequency based on Mdac spectra Based on + * the MDAC2 spectra calculations published by Walters and Taylor, 2001 + * UCRL-ID-146882 + * + */ + public List measureMws(final Map> evidMap, + final PICK_TYPES selectedPhase) { + final MdacParametersFI mdacFi = mdacFiService.findFirst(); + final MdacParametersPS mdacPs = mdacPsService.findMatchingPhase(selectedPhase.getPhase()); + List measuredMws = new ArrayList<>(); + + for (Entry> entry : evidMap.entrySet()) { + Map measurements = entry.getValue(); + double[] MoMw = fitMw(measurements, selectedPhase, mdacFi, mdacPs); + if (MoMw == null) { + log.warn("MoMw calculation returned null value"); + continue; + } + measuredMws.add(new MeasuredMwParameters() .setEventId(entry.getKey().getEventId()).setMw(MoMw[1]) + .setStressDropInMpa(MoMw[4])); + } + return measuredMws; + } + + /** + * Grid search across a range of corner frequency and stress drop parameters + * looking for the best fit theoretical source spectra and return the + * resulting MW measurement. + * + * @param measurements + * - the measured spectra values by frequency band + * @param phase + * - the phase (e.g. "Lg") this should be present in the Mdac + * parameter set + * @return double[] containing the final fit measurements + */ + public double[] fitMw(final Map measurements, final PICK_TYPES phase, + final MdacParametersFI mdacFi, final MdacParametersPS mdacPs) { + double[] result = new double[5]; + + final Map weightMap = new TreeMap<>(); + final double minMW = 0.5; + final double maxMW = 8.0; + final double minMPA = 0.00001; + final double maxMPA = 10.00; + int dataCount = 0; + + for (Entry meas : measurements.entrySet()) { + double logAmplitude = meas.getValue().getMean(); + double lowFreq = meas.getKey().getLowFrequency(); + double highFreq = meas.getKey().getHighFrequency(); + double centerFreq = lowFreq + 0.25 * (highFreq - lowFreq); + weightMap.put(centerFreq, 0.0); + if (logAmplitude > 0.0) { + dataCount++; + } + } + + int count = 0; + for (Entry entry : weightMap.entrySet()) { + entry.setValue(count == 0 || count == 2 ? 0.5 : count == 1 ? 1.0 : count == 3 ? 0.25 : 0.1); + count++; + } + + MultivariateFunction mdacFunction = new MultivariateFunction() { + + @Override + public double value(double[] point) { + double testMw = point[0]; + double testSigma = point[1]; + + HashMap dataMap = new HashMap<>(); + + for (Entry meas : measurements.entrySet()) { + double logAmplitude = meas.getValue().getMean(); + double lowFreq = meas.getKey().getLowFrequency(); + double highFreq = meas.getKey().getHighFrequency(); + double centerFreq = lowFreq + 0.25 * (highFreq - lowFreq); + + if (mdacPs != null) { + // Note this is in dyne-cm to match Kevin + double log10mdacM0 = mdacService.calculateMdacAmplitudeForMw( mdacPs, mdacFi, testMw, centerFreq, + phase, testSigma); + + if (logAmplitude > 0.) { + double[] coda_vs_mdac = new double[] { logAmplitude, log10mdacM0 }; + dataMap.put(centerFreq, coda_vs_mdac); + } + } + } + + return WCVRMSD(weightMap, dataMap); + } + }; + + ConvergenceChecker convergenceChecker = new SimplePointChecker<>(0.001, 0.001, 100000); + CMAESOptimizer optimizer = new CMAESOptimizer( 1000000, 0, true, 0, 10, new MersenneTwister(), true, + convergenceChecker); + PointValuePair optimizerResult = optimizer.optimize(new MaxEval(1000000), new ObjectiveFunction(mdacFunction), + GoalType.MINIMIZE, + new SimpleBounds( new double[] { minMW, minMPA }, + new double[] { maxMW, maxMPA }), + new InitialGuess(new double[] { + ThreadLocalRandom.current().nextDouble( minMW, + maxMW), + ThreadLocalRandom.current().nextDouble( minMPA, + maxMPA) }), + new CMAESOptimizer.Sigma(new double[] { 0.05, 1.0 }), + new CMAESOptimizer.PopulationSize(50)); + + // converted back into dyne-cm to match Kevin's format + double testMw = optimizerResult.getPoint()[0]; + // log10M0 + result[0] = Math.log10(mdacService.getMwInDyne(testMw)); + result[1] = testMw; // best Mw fit + // this is the number of elements that have a signal measurement + result[2] = dataCount; + // this is the rmsfit measurement + result[3] = optimizerResult.getValue(); + // this is the stress drop + result[4] = optimizerResult.getPoint()[1]; + + return result; + } + + /** + * @param weightMap + * @param dataMap + * @return + */ + protected static double WCVRMSD(Map weightMap, HashMap dataMap) { + for (Entry entry : dataMap.entrySet()) { + if (weightMap.containsKey(entry.getKey())) { + double[] val = entry.getValue(); + for (int i = 0; i < val.length; i++) { + val[i] *= weightMap.get(entry.getKey()); + } + dataMap.put(entry.getKey(), val); + } + } + return FitnessCriteria.CVRMSD(dataMap); + } } \ No newline at end of file diff --git a/calibration-service/service-impl/src/main/java/gov/llnl/gnem/apps/coda/calibration/service/impl/processing/WaveformUtils.java b/calibration-service/service-impl/src/main/java/gov/llnl/gnem/apps/coda/calibration/service/impl/processing/WaveformUtils.java index 560aeb90..90863cb1 100644 --- a/calibration-service/service-impl/src/main/java/gov/llnl/gnem/apps/coda/calibration/service/impl/processing/WaveformUtils.java +++ b/calibration-service/service-impl/src/main/java/gov/llnl/gnem/apps/coda/calibration/service/impl/processing/WaveformUtils.java @@ -45,14 +45,19 @@ public class WaveformUtils { */ public static TimeSeries getNoiseWindow(double distance, TimeT origintime, TimeSeries segment) { - TimeSeries result = new TimeSeries(segment); - + TimeSeries result1 = new TimeSeries(segment); + result1.cutBefore(result1.getTime().add(noiseWindowOffset)); + double mean1 = result1.getMean(); + + TimeSeries result2 = new TimeSeries(segment); double groupVelocity = distance / groupVelocityDenominator; TimeT noiseStart = origintime.subtract(noiseWindowOffset); TimeT noiseEnd = origintime.add(groupVelocity); - result.cut(noiseStart, noiseEnd); - return result; + result2.cut(noiseStart, noiseEnd); + double mean2 = result2.getMean(); + + return mean1 > mean2 ? result2 : result1; } /** diff --git a/calibration-standalone/pom.xml b/calibration-standalone/pom.xml index 582a1b99..8d6be272 100644 --- a/calibration-standalone/pom.xml +++ b/calibration-standalone/pom.xml @@ -5,7 +5,7 @@ gov.llnl.gnem.apps.coda.calibration coda-calibration - 1.0.1 + 1.0.2 4.0.0 @@ -72,12 +72,12 @@ gov.llnl.gnem.apps.coda.calibration calibration-gui - 1.0.1 + 1.0.2 gov.llnl.gnem.apps.coda.calibration application - 1.0.1 + 1.0.2 diff --git a/calibration-standalone/src/main/java/gov/llnl/gnem/apps/coda/calibration/standalone/CodaCalibrationStandalone.java b/calibration-standalone/src/main/java/gov/llnl/gnem/apps/coda/calibration/standalone/CodaCalibrationStandalone.java index fa2f1094..850a900c 100644 --- a/calibration-standalone/src/main/java/gov/llnl/gnem/apps/coda/calibration/standalone/CodaCalibrationStandalone.java +++ b/calibration-standalone/src/main/java/gov/llnl/gnem/apps/coda/calibration/standalone/CodaCalibrationStandalone.java @@ -17,10 +17,12 @@ import java.awt.Toolkit; import java.lang.reflect.Method; import java.net.URL; +import java.util.TimeZone; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import javax.annotation.PostConstruct; import javax.swing.SwingUtilities; import org.slf4j.Logger; @@ -46,6 +48,11 @@ public class CodaCalibrationStandalone extends Application { private static volatile ConfigurableApplicationContext springContext; private static String[] initialArgs; + @PostConstruct + void started() { + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + } + public static synchronized void main(String[] args) { try { initialArgs = args; diff --git a/calibration-standalone/src/main/resources/application-file.properties b/calibration-standalone/src/main/resources/application-file.properties index 6bc3100a..de9e8e64 100644 --- a/calibration-standalone/src/main/resources/application-file.properties +++ b/calibration-standalone/src/main/resources/application-file.properties @@ -12,4 +12,5 @@ server.ssl.key-store-provider=SUN server.ssl.key-store-type=JKS spring.datasource.continueOnError=true spring.datasource.hikari.connection-timeout=0 -spring.jdbc.template.fetch-size=1000 \ No newline at end of file +spring.jdbc.template.fetch-size=1000 +spring.jpa.properties.hibernate.jdbc.time_zone = UTC \ No newline at end of file diff --git a/calibration-standalone/src/main/resources/application.properties b/calibration-standalone/src/main/resources/application.properties index 85baf522..3b700bea 100644 --- a/calibration-standalone/src/main/resources/application.properties +++ b/calibration-standalone/src/main/resources/application.properties @@ -19,4 +19,5 @@ server.ssl.key-store=classpath:coda-keystore.jks server.ssl.key-store-provider=SUN server.ssl.key-store-type=JKS spring.datasource.continueOnError=true -spring.jdbc.template.fetch-size=1000 \ No newline at end of file +spring.jdbc.template.fetch-size=1000 +spring.jpa.properties.hibernate.jdbc.time_zone = UTC \ No newline at end of file diff --git a/externals/pom.xml b/externals/pom.xml index cc082d21..2e68ed3e 100644 --- a/externals/pom.xml +++ b/externals/pom.xml @@ -5,7 +5,7 @@ gov.llnl.gnem.apps.coda.calibration externals - 1.0.1 + 1.0.2 jar externals diff --git a/externals/src/main/java/llnl/gnem/core/io/SAC/SACHeader.java b/externals/src/main/java/llnl/gnem/core/io/SAC/SACHeader.java index d7d80f2c..7a6c4e48 100755 --- a/externals/src/main/java/llnl/gnem/core/io/SAC/SACHeader.java +++ b/externals/src/main/java/llnl/gnem/core/io/SAC/SACHeader.java @@ -1909,6 +1909,9 @@ private void syncStationAndEvent() { * @return the String minus the ignorable characters */ final String trimIgnorableCharacters(String string) { + if (string == null || string.isEmpty() || string.trim().isEmpty()) { + return SACHeader.STRINGDEFAULT; + } char[] charstring = string.toCharArray(); StringBuilder sb = new StringBuilder(); diff --git a/pom.xml b/pom.xml index e51638d9..d3de36d7 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 gov.llnl.gnem.apps.coda.calibration coda-calibration - 1.0.1 + 1.0.2 coda-calibration pom @@ -41,7 +41,7 @@ 1.2.0.RC2 3.6.1 2.10.4 - 2.0.0.RC2 + 2.0.2.RELEASE @@ -49,7 +49,7 @@ gov.llnl.gnem.apps.coda.calibration externals - 1.0.1 + 1.0.2 org.springframework.boot