mirror of
https://github.com/PAMGuard/PAMGuard.git
synced 2024-11-25 16:42:27 +00:00
Merge branch 'main' of https://github.com/douggillespie/PAMGuard.git into main
This commit is contained in:
commit
484109703e
@ -6,7 +6,7 @@
|
|||||||
<attribute name="maven.pomderived" value="true"/>
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
</classpathentry>
|
</classpathentry>
|
||||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/Java 17">
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER">
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="module" value="true"/>
|
<attribute name="module" value="true"/>
|
||||||
<attribute name="maven.pomderived" value="true"/>
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -39,3 +39,4 @@ settings.xml
|
|||||||
.classpath
|
.classpath
|
||||||
.classpath
|
.classpath
|
||||||
.classpath
|
.classpath
|
||||||
|
.classpath
|
||||||
|
@ -400,7 +400,7 @@ public class PamObservable {//extends PanelOverlayDraw {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Timer t = new Timer(1000, new ActionListener() {
|
private Timer t = new Timer(4321, new ActionListener() {
|
||||||
public void actionPerformed(ActionEvent evt) {
|
public void actionPerformed(ActionEvent evt) {
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
if (cpuUsage == null) return;
|
if (cpuUsage == null) return;
|
||||||
@ -413,6 +413,16 @@ public class PamObservable {//extends PanelOverlayDraw {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Had some issues with the Timer holding a reference to the underlying PamDataBlock
|
||||||
|
* (RoccaContourDataBlock, in this case) and not releasing it for garbage collection.
|
||||||
|
* Added in this method to force the timer to stop and release it's hold.
|
||||||
|
*/
|
||||||
|
public void stopTimer() {
|
||||||
|
t.stop();
|
||||||
|
}
|
||||||
|
|
||||||
public double getCPUPercent(int objectIndex) {
|
public double getCPUPercent(int objectIndex) {
|
||||||
if (objectIndex < 0 || objectIndex >= cpuPercent.length) return -1;
|
if (objectIndex < 0 || objectIndex >= cpuPercent.length) return -1;
|
||||||
return cpuPercent[objectIndex];
|
return cpuPercent[objectIndex];
|
||||||
|
@ -23,17 +23,14 @@ package pamguard;
|
|||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import javax.swing.UIManager;
|
import javax.swing.UIManager;
|
||||||
|
|
||||||
import Acquisition.AcquisitionControl;
|
|
||||||
import Acquisition.FolderInputSystem;
|
import Acquisition.FolderInputSystem;
|
||||||
import PamController.PamController;
|
import PamController.PamController;
|
||||||
import PamController.PamGUIManager;
|
import PamController.PamGUIManager;
|
||||||
import PamController.PamSettingManager;
|
import PamController.PamSettingManager;
|
||||||
import PamController.PamguardVersionInfo;
|
import PamController.PamguardVersionInfo;
|
||||||
import PamController.pamBuoyGlobals;
|
import PamController.pamBuoyGlobals;
|
||||||
import PamController.command.TerminalController;
|
|
||||||
import PamModel.SMRUEnable;
|
import PamModel.SMRUEnable;
|
||||||
import PamUtils.FileFunctions;
|
import PamUtils.FileFunctions;
|
||||||
import PamUtils.PamCalendar;
|
|
||||||
import PamUtils.PamExceptionHandler;
|
import PamUtils.PamExceptionHandler;
|
||||||
import PamUtils.PlatformInfo;
|
import PamUtils.PlatformInfo;
|
||||||
import PamUtils.Splash;
|
import PamUtils.Splash;
|
||||||
@ -45,6 +42,7 @@ import PamguardMVC.debug.Debug;
|
|||||||
import binaryFileStorage.BinaryStore;
|
import binaryFileStorage.BinaryStore;
|
||||||
import dataPlotsFX.JamieDev;
|
import dataPlotsFX.JamieDev;
|
||||||
import generalDatabase.DBControl;
|
import generalDatabase.DBControl;
|
||||||
|
import rocca.RoccaDev;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -190,6 +188,10 @@ public class Pamguard {
|
|||||||
JamieDev.setEnabled(true);
|
JamieDev.setEnabled(true);
|
||||||
System.out.println("Enabling Jamie Macaulay modifications.");
|
System.out.println("Enabling Jamie Macaulay modifications.");
|
||||||
}
|
}
|
||||||
|
else if (anArg.equalsIgnoreCase("-rocca")) {
|
||||||
|
RoccaDev.setEnabled(true);
|
||||||
|
System.out.println("Enabling Rocca development mode");
|
||||||
|
}
|
||||||
else if (anArg.equalsIgnoreCase(Debug.flag)) {
|
else if (anArg.equalsIgnoreCase(Debug.flag)) {
|
||||||
Debug.setPrintDebug(true);
|
Debug.setPrintDebug(true);
|
||||||
Debug.out.println("Enabling debug terminal output.");
|
Debug.out.println("Enabling debug terminal output.");
|
||||||
|
@ -408,6 +408,10 @@ public class RoccaClassifier {
|
|||||||
|
|
||||||
// load the whistle classifier
|
// load the whistle classifier
|
||||||
if (roccaControl.roccaParameters.isClassifyWhistles()) {
|
if (roccaControl.roccaParameters.isClassifyWhistles()) {
|
||||||
|
if (roccaControl.roccaParameters.roccaClassifierModelFilename==null) {
|
||||||
|
roccaControl.roccaParameters.setClassifyWhistles(false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
String fname = roccaControl.roccaParameters.roccaClassifierModelFilename.getAbsolutePath();
|
String fname = roccaControl.roccaParameters.roccaClassifierModelFilename.getAbsolutePath();
|
||||||
File f = new File(fname);
|
File f = new File(fname);
|
||||||
if (f.exists() == false) {
|
if (f.exists() == false) {
|
||||||
@ -450,9 +454,14 @@ public class RoccaClassifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// serialVersionUID=24 2016/08/10 added to load click classifier
|
// serialVersionUID=24 2016/08/10 added to load click classifier
|
||||||
if (roccaControl.roccaParameters.isClassifyClicks()) {
|
if (roccaControl.roccaParameters.isClassifyClicks()) {
|
||||||
|
if (roccaControl.roccaParameters.roccaClickClassifierModelFilename==null) {
|
||||||
|
roccaControl.roccaParameters.setClassifyClicks(false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
String fname = roccaControl.roccaParameters.roccaClickClassifierModelFilename.getAbsolutePath();
|
String fname = roccaControl.roccaParameters.roccaClickClassifierModelFilename.getAbsolutePath();
|
||||||
File f = new File(fname);
|
File f = new File(fname);
|
||||||
if (f.exists() == false) {
|
if (f.exists() == false) {
|
||||||
@ -495,6 +504,7 @@ public class RoccaClassifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// if we didn't load either classifier, don't bother loading trying to load an encounter
|
// if we didn't load either classifier, don't bother loading trying to load an encounter
|
||||||
// classifier because it relies on the species. Just reset the side panel and return
|
// classifier because it relies on the species. Just reset the side panel and return
|
||||||
@ -505,6 +515,10 @@ public class RoccaClassifier {
|
|||||||
|
|
||||||
// serialVersionUID=24 2016/08/10 added to load event classifier
|
// serialVersionUID=24 2016/08/10 added to load event classifier
|
||||||
if (roccaControl.roccaParameters.isClassifyEvents()) {
|
if (roccaControl.roccaParameters.isClassifyEvents()) {
|
||||||
|
if (roccaControl.roccaParameters.roccaEventClassifierModelFilename==null) {
|
||||||
|
roccaControl.roccaParameters.setClassifyEvents(false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
String fname = roccaControl.roccaParameters.roccaEventClassifierModelFilename.getAbsolutePath();
|
String fname = roccaControl.roccaParameters.roccaEventClassifierModelFilename.getAbsolutePath();
|
||||||
File f = new File(fname);
|
File f = new File(fname);
|
||||||
if (f.exists() == false) {
|
if (f.exists() == false) {
|
||||||
@ -543,6 +557,7 @@ public class RoccaClassifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resetSidePanel();
|
resetSidePanel();
|
||||||
return true;
|
return true;
|
||||||
|
27
src/rocca/RoccaDev.java
Normal file
27
src/rocca/RoccaDev.java
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package rocca;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Global flag set at runtime for Rocca development flags.
|
||||||
|
* @author mo55
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class RoccaDev {
|
||||||
|
|
||||||
|
|
||||||
|
private static boolean enabled = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the enabled
|
||||||
|
*/
|
||||||
|
public static boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param enabled the enabled to set
|
||||||
|
*/
|
||||||
|
public static void setEnabled(boolean enabled) {
|
||||||
|
RoccaDev.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -26,7 +26,6 @@ import java.awt.Frame;
|
|||||||
import java.awt.GridBagConstraints;
|
import java.awt.GridBagConstraints;
|
||||||
import java.awt.GridBagLayout;
|
import java.awt.GridBagLayout;
|
||||||
import java.awt.GridLayout;
|
import java.awt.GridLayout;
|
||||||
import java.awt.Point;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
@ -48,8 +47,6 @@ import PamUtils.PamFileChooser;
|
|||||||
import PamView.dialog.PamDialog;
|
import PamView.dialog.PamDialog;
|
||||||
import PamView.dialog.SourcePanel;
|
import PamView.dialog.SourcePanel;
|
||||||
import PamguardMVC.PamDataBlock;
|
import PamguardMVC.PamDataBlock;
|
||||||
import PamguardMVC.PamDataUnit;
|
|
||||||
|
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
import java.awt.Insets;
|
import java.awt.Insets;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
@ -60,20 +57,14 @@ import java.io.BufferedInputStream;
|
|||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.FileReader;
|
import java.io.FileReader;
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import javax.swing.Box;
|
import javax.swing.Box;
|
||||||
import javax.swing.BoxLayout;
|
import javax.swing.BoxLayout;
|
||||||
import javax.swing.ButtonGroup;
|
|
||||||
import javax.swing.DefaultComboBoxModel;
|
import javax.swing.DefaultComboBoxModel;
|
||||||
import javax.swing.GroupLayout;
|
import javax.swing.GroupLayout;
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
import javax.swing.JCheckBox;
|
import javax.swing.JCheckBox;
|
||||||
import javax.swing.JComboBox;
|
import javax.swing.JComboBox;
|
||||||
import javax.swing.JComponent;
|
|
||||||
import javax.swing.JFrame;
|
|
||||||
import javax.swing.JRadioButton;
|
import javax.swing.JRadioButton;
|
||||||
import javax.swing.JFileChooser;
|
import javax.swing.JFileChooser;
|
||||||
import javax.swing.JScrollPane;
|
import javax.swing.JScrollPane;
|
||||||
@ -87,7 +78,6 @@ import javax.swing.border.EtchedBorder;
|
|||||||
import clickDetector.ClickControl;
|
import clickDetector.ClickControl;
|
||||||
import clickDetector.ClickDetection;
|
import clickDetector.ClickDetection;
|
||||||
import clickDetector.NoiseDataBlock;
|
import clickDetector.NoiseDataBlock;
|
||||||
import weka.classifiers.AbstractClassifier;
|
|
||||||
import weka.core.Attribute;
|
import weka.core.Attribute;
|
||||||
import weka.core.Instances;
|
import weka.core.Instances;
|
||||||
import weka.core.SerializationHelper;
|
import weka.core.SerializationHelper;
|
||||||
@ -523,7 +513,13 @@ public class RoccaParametersDialog extends PamDialog implements ActionListener,
|
|||||||
reclassifyButton.addActionListener(this);
|
reclassifyButton.addActionListener(this);
|
||||||
reclassifyButton.setToolTipText("Load the whistle data from the contour stats output file, and run it through the current Classifier");
|
reclassifyButton.setToolTipText("Load the whistle data from the contour stats output file, and run it through the current Classifier");
|
||||||
reclassifyButton.setVisible(true);
|
reclassifyButton.setVisible(true);
|
||||||
extraButtonsSubPanel.setVisible(true); // ******** THIS LINES CONTROLS THE VISIBILITY ********
|
|
||||||
|
// ******** THIS LINES CONTROLS THE VISIBILITY ********
|
||||||
|
if (RoccaDev.isEnabled()) {
|
||||||
|
extraButtonsSubPanel.setVisible(true);
|
||||||
|
} else {
|
||||||
|
extraButtonsSubPanel.setVisible(false);
|
||||||
|
}
|
||||||
GroupLayout extraPanelLayout = new GroupLayout(extraButtonsSubPanel);
|
GroupLayout extraPanelLayout = new GroupLayout(extraButtonsSubPanel);
|
||||||
extraButtonsSubPanel.setLayout(extraPanelLayout);
|
extraButtonsSubPanel.setLayout(extraPanelLayout);
|
||||||
extraPanelLayout.setAutoCreateGaps(true);
|
extraPanelLayout.setAutoCreateGaps(true);
|
||||||
|
@ -388,7 +388,9 @@ public class RoccaProcess extends PamProcess {
|
|||||||
rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) > 1.5 ||
|
rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) > 1.5 ||
|
||||||
rcdb.getContour().get(RoccaContourStats.ParamIndx.FREQABSSLOPEMEAN) < 2000. ||
|
rcdb.getContour().get(RoccaContourStats.ParamIndx.FREQABSSLOPEMEAN) < 2000. ||
|
||||||
rcdb.getContour().get(RoccaContourStats.ParamIndx.FREQABSSLOPEMEAN) > 28000. )) {
|
rcdb.getContour().get(RoccaContourStats.ParamIndx.FREQABSSLOPEMEAN) > 28000. )) {
|
||||||
rcdb.setNaturalLifetimeMillis(0);
|
// rcdb.setNaturalLifetimeMillis(0);
|
||||||
|
rcdb.stopTimer();
|
||||||
|
rcdb = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (roccaControl.roccaParameters.roccaClassifierModelFilename.getName().equals("HIWhist.model") &&
|
if (roccaControl.roccaParameters.roccaClassifierModelFilename.getName().equals("HIWhist.model") &&
|
||||||
@ -402,7 +404,9 @@ public class RoccaProcess extends PamProcess {
|
|||||||
rcdb.getContour().get(RoccaContourStats.ParamIndx.FREQABSSLOPEMEAN) > 60000. ||
|
rcdb.getContour().get(RoccaContourStats.ParamIndx.FREQABSSLOPEMEAN) > 60000. ||
|
||||||
rcdb.getContour().get(RoccaContourStats.ParamIndx.FREQRANGE) < 800. ||
|
rcdb.getContour().get(RoccaContourStats.ParamIndx.FREQRANGE) < 800. ||
|
||||||
rcdb.getContour().get(RoccaContourStats.ParamIndx.FREQRANGE) > 14000. )) {
|
rcdb.getContour().get(RoccaContourStats.ParamIndx.FREQRANGE) > 14000. )) {
|
||||||
rcdb.setNaturalLifetimeMillis(0);
|
// rcdb.setNaturalLifetimeMillis(0);
|
||||||
|
rcdb.stopTimer();
|
||||||
|
rcdb = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (roccaControl.roccaParameters.roccaClassifierModelFilename.getName().equals("NWAtlWhist.model") &&
|
if (roccaControl.roccaParameters.roccaClassifierModelFilename.getName().equals("NWAtlWhist.model") &&
|
||||||
@ -412,7 +416,9 @@ public class RoccaProcess extends PamProcess {
|
|||||||
rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) > 2.5 ||
|
rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) > 2.5 ||
|
||||||
rcdb.getContour().get(RoccaContourStats.ParamIndx.FREQABSSLOPEMEAN) < 9100. ||
|
rcdb.getContour().get(RoccaContourStats.ParamIndx.FREQABSSLOPEMEAN) < 9100. ||
|
||||||
rcdb.getContour().get(RoccaContourStats.ParamIndx.FREQABSSLOPEMEAN) > 82000. )) {
|
rcdb.getContour().get(RoccaContourStats.ParamIndx.FREQABSSLOPEMEAN) > 82000. )) {
|
||||||
rcdb.setNaturalLifetimeMillis(0);
|
// rcdb.setNaturalLifetimeMillis(0);
|
||||||
|
rcdb.stopTimer();
|
||||||
|
rcdb = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -431,7 +437,9 @@ public class RoccaProcess extends PamProcess {
|
|||||||
saveContourPoints(rcdb, rcdb.getChannelMap(), ++numDetections, sNum);
|
saveContourPoints(rcdb, rcdb.getChannelMap(), ++numDetections, sNum);
|
||||||
saveContourStats(rcdb, rcdb.getChannelMap(), numDetections, sNum);
|
saveContourStats(rcdb, rcdb.getChannelMap(), numDetections, sNum);
|
||||||
saveContour(rcdb, rcdb.getChannelMap(), numDetections, sNum);
|
saveContour(rcdb, rcdb.getChannelMap(), numDetections, sNum);
|
||||||
rcdb.setNaturalLifetimeMillis(0);
|
// rcdb.setNaturalLifetimeMillis(0);
|
||||||
|
rcdb.stopTimer();
|
||||||
|
rcdb = null;
|
||||||
|
|
||||||
/* if this is a click detection (signal, not noise) */
|
/* if this is a click detection (signal, not noise) */
|
||||||
} else if (o==manClickSourceData || o==autoClickSourceData) {
|
} else if (o==manClickSourceData || o==autoClickSourceData) {
|
||||||
@ -499,18 +507,24 @@ public class RoccaProcess extends PamProcess {
|
|||||||
(rcdb.getContour().get(RoccaContourStats.ParamIndx.SNR) > 35. ||
|
(rcdb.getContour().get(RoccaContourStats.ParamIndx.SNR) > 35. ||
|
||||||
rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) < 0.005 ||
|
rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) < 0.005 ||
|
||||||
rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) > 0.6 )) {
|
rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) > 0.6 )) {
|
||||||
|
rcdb.stopTimer();
|
||||||
|
rcdb = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (roccaControl.roccaParameters.roccaClassifierModelFilename.getName().equals("HIClick.model") &&
|
if (roccaControl.roccaParameters.roccaClassifierModelFilename.getName().equals("HIClick.model") &&
|
||||||
(rcdb.getContour().get(RoccaContourStats.ParamIndx.SNR) > 40. ||
|
(rcdb.getContour().get(RoccaContourStats.ParamIndx.SNR) > 40. ||
|
||||||
rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) < 0.01 ||
|
rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) < 0.01 ||
|
||||||
rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) > 0.6 )) {
|
rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) > 0.6 )) {
|
||||||
|
rcdb.stopTimer();
|
||||||
|
rcdb = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (roccaControl.roccaParameters.roccaClassifierModelFilename.getName().equals("NWAtlClick.model") &&
|
if (roccaControl.roccaParameters.roccaClassifierModelFilename.getName().equals("NWAtlClick.model") &&
|
||||||
(rcdb.getContour().get(RoccaContourStats.ParamIndx.SNR) > 35. ||
|
(rcdb.getContour().get(RoccaContourStats.ParamIndx.SNR) > 35. ||
|
||||||
rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) < 0.005 ||
|
rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) < 0.005 ||
|
||||||
rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) > 0.6 )) {
|
rcdb.getContour().get(RoccaContourStats.ParamIndx.DURATION) > 0.6 )) {
|
||||||
|
rcdb.stopTimer();
|
||||||
|
rcdb = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -529,7 +543,9 @@ public class RoccaProcess extends PamProcess {
|
|||||||
// add call to update side panel. Set the isClick flag to True
|
// add call to update side panel. Set the isClick flag to True
|
||||||
updateSidePanel(rcdb, true);
|
updateSidePanel(rcdb, true);
|
||||||
saveContourStats(rcdb, rcdb.getChannelMap(), numDetections, sNum);
|
saveContourStats(rcdb, rcdb.getChannelMap(), numDetections, sNum);
|
||||||
rcdb.setNaturalLifetimeMillis(0);
|
// rcdb.setNaturalLifetimeMillis(0);
|
||||||
|
rcdb.stopTimer();
|
||||||
|
rcdb = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user