Commit 3c25b987 authored by Aliaksandr Halavatyi's avatar Aliaksandr Halavatyi
Browse files

new automated protocol with rotation

parent 10c7ac20
package feedback.fly.embryo.jobdistributors;
//import ij.Prefs;
import ij.gui.GenericDialog;
import ij.gui.WaitForUserDialog;
import ij.plugin.PlugIn;
import automic.online.jobdistributors.JobDistributor_Abstract;
import automic.online.jobs.common.Job_RecordFinish;
import automic.parameters.ParameterCollection;
import automic.parameters.gui.ParameterGuiManager;
import automic.table.TableModel;
import automic.table.TableProcessor;
import feedback.fly.embryo.jobs.Job_AutofocusInitOffset;
import feedback.fly.embryo.jobs.Job_FocusEmbryoAuto;
import feedback.fly.embryo.jobs.Job_SelectMultipleEmbryosAutoRotation;
public class JobDistributor_AutoSelectionMultiple_AFocus_Rotation extends JobDistributor_Abstract implements PlugIn {
//private static final String PREFS_PREFIX="feebback.fly.embryo";
//private ParameterCollection manualTrackParameterCollectionSelect;
//private ParameterCollection manualTrackParameterCollectionFocus;
@Override
protected void fillJobList(){
//Job_SelectRecordManual manualTrackJob=new Job_SelectRecordManual();
//manualTrackJob.parseInputParameterValues(manualTrackParameterCollection);
//manualTrackJob.setZInvert(invertZ);
Job_AutofocusInitOffset job1=new Job_AutofocusInitOffset();
ParameterCollection job1Parameters=job1.createJobParameters();
job1Parameters.setUndefinedValuesFromDefaults();
ParameterGuiManager pgManager1=new ParameterGuiManager(job1Parameters);
try{
pgManager1.refineParametersViaDialog("Coverslip focusing");
}catch(Exception _ex){
new WaitForUserDialog("error in parameter values");
}
job1.parseInputParameterValues(pgManager1.getParameterCollection());
super.addImageJob(job1, "DE_1_", "AFocus", true);
Job_SelectMultipleEmbryosAutoRotation job2=new Job_SelectMultipleEmbryosAutoRotation();
ParameterCollection job2Parameters=job2.createJobParameters();
job2Parameters.setUndefinedValuesFromDefaults();
ParameterGuiManager pgManager2=new ParameterGuiManager(job2Parameters);
try{
pgManager2.refineParametersViaDialog("Parameters for embryo selection");
}catch(Exception _ex){
new WaitForUserDialog("error in parameter values");
}
job2.parseInputParameterValues(pgManager2.getParameterCollection());
super.addImageJob(job2, "DE_2_", "LZ.Image", true);
Job_FocusEmbryoAuto job3=new Job_FocusEmbryoAuto();
ParameterCollection job3Parameters=job3.createJobParameters();
job3Parameters.setUndefinedValuesFromDefaults();
ParameterGuiManager pgManager3=new ParameterGuiManager(job3Parameters);
try{
pgManager3.refineParametersViaDialog("Parameters for embryo focusing");
}catch(Exception _ex){
new WaitForUserDialog("error in parameter values");
}
job3.parseInputParameterValues(pgManager3.getParameterCollection());
super.addImageJob(job3, "TR1_1_", "Focus.Image", true);
super.addImageJob(Job_RecordFinish.class, "TR2_1_", "Result.Image", true);
}
@Override
protected TableModel constructTabModel(String _rpth){
TableModel outTbl=new TableModel(_rpth);
outTbl.addColumn("Date.Time");
outTbl.addFileColumns("AFocus", "IMG");
outTbl.addFileColumns("LZ.Image", "IMG");
outTbl.addFileColumns("Focus.Image", "IMG");
outTbl.addFileColumns("Result.Image", "IMG");
outTbl.addValueColumn("Zoom.X", "NUM");
outTbl.addValueColumn("Zoom.Y", "NUM");
outTbl.addValueColumn("Focus.Z", "NUM");
outTbl.addValueColumn("Success", "BOOL");
outTbl.addRow(new Object[outTbl.getColumnCount()]);
return outTbl;
}
@Override
protected TableProcessor configureTableProcessor(TableModel _tModel)throws Exception{
TableProcessor tProcessor=new TableProcessor(_tModel);
tProcessor.addFileColumns("Selected.Embryo.LZ", "ROI");
return tProcessor;
}
@Override
protected void putProtocolPreferencesToDialog(GenericDialog _dialog){
//manualTrackParameterCollectionSelect=new Job_SelectMultipleEmbryosAutoInit().createJobParameters();
//manualTrackParameterCollectionFocus=new Job_FocusEmbryoAuto().createJobParameters();
//_dialog.addCheckbox(Job_SelectEmbryoManual.KEY_INVERT_Z, Prefs.getBoolean(generatePrefsKey(Job_SelectEmbryoManual.KEY_INVERT_Z),false));
//_dialog.addNumericField(Job_SelectEmbryoManual.KEY_LOCATION_X, Prefs.getInt(generatePrefsKey(Job_SelectEmbryoManual.KEY_LOCATION_X),1000), 0);
//_dialog.addNumericField(Job_SelectEmbryoManual.KEY_LOCATION_Y, Prefs.getInt(generatePrefsKey(Job_SelectEmbryoManual.KEY_LOCATION_Y),120), 0);
//_dialog.addNumericField(Job_SelectEmbryoManual.KEY_ZOOM_ITERATIONS, Prefs.getInt(generatePrefsKey(Job_SelectEmbryoManual.KEY_ZOOM_ITERATIONS),2), 0);
}
@Override
protected void getProtocolPreferencesFromDialog(GenericDialog _dialog){
//manualTrackParameterCollectionSelect.setUndefinedValuesFromDefaults();
//manualTrackParameterCollectionFocus.setUndefinedValuesFromDefaults();
// boolean invertZ=_dialog.getNextBoolean();
// manualTrackParameterCollection.setParameterValue(Job_SelectEmbryoManual.KEY_INVERT_Z, invertZ);
// Prefs.set(generatePrefsKey(Job_SelectEmbryoManual.KEY_INVERT_Z), invertZ);
//
// int locationX=(int)_dialog.getNextNumber();
// manualTrackParameterCollection.setParameterValue(Job_SelectEmbryoManual.KEY_LOCATION_X, locationX);
// Prefs.set(generatePrefsKey(Job_SelectEmbryoManual.KEY_LOCATION_X), invertZ);
//
// int locationY=(int)_dialog.getNextNumber();
// manualTrackParameterCollection.setParameterValue(Job_SelectEmbryoManual.KEY_LOCATION_Y, locationY);
// Prefs.set(generatePrefsKey(Job_SelectEmbryoManual.KEY_LOCATION_Y), invertZ);
//
// int zoomIterations=(int)_dialog.getNextNumber();
// manualTrackParameterCollection.setParameterValue(Job_SelectEmbryoManual.KEY_ZOOM_ITERATIONS, zoomIterations);
// Prefs.set(generatePrefsKey(Job_SelectEmbryoManual.KEY_ZOOM_ITERATIONS), invertZ);
}
@Override
protected boolean showDialogInDebugRun(){
return false;
}
@Override
protected void setDebugConfiguration(){
final String searchPath="C:/tempDat/AutoFRAP_test";
this.setGeneralOptions(searchPath, true, false);
this.fileExtension="czi";
//manualTrackParameterCollectionSelect=new Job_SelectMultipleEmbryosAutoInit().createJobParameters();
//manualTrackParameterCollectionSelect.setUndefinedValuesFromDefaults();
//manualTrackParameterCollectionFocus=new Job_SelectMultipleEmbryosAutoInit().createJobParameters();
//manualTrackParameterCollectionFocus.setUndefinedValuesFromDefaults();
}
// private String generatePrefsKey(String _key){
// return String.format("%s.%s",PREFS_PREFIX, _key);
// }
}
package feedback.fly.embryo.jobs;
import java.awt.Color;
import java.awt.geom.Point2D;
import java.io.File;
import java.util.concurrent.TimeUnit;
import automic.online.jobdistributors.ZeissKeys;
import automic.online.jobs.Job_Default;
import automic.parameters.ParameterCollection;
import automic.parameters.ParameterType;
import automic.utils.ArrIndUtils;
import automic.utils.DebugVisualiserSettings;
import automic.utils.roi.ParticleFilterer;
import automic.utils.roi.ROIManipulator2D;
import ij.IJ;
import ij.ImageJ;
import ij.ImagePlus;
import ij.gui.Overlay;
import ij.gui.Roi;
import ij.plugin.Duplicator;
import ij.plugin.filter.MaximumFinder;
import ij.plugin.filter.ParticleAnalyzer;
import ij.plugin.filter.RankFilters;
import ij.plugin.frame.RoiManager;
import ij.process.ImageProcessor;
import ij.process.ImageStatistics;
import loci.plugins.BF;
import loci.plugins.in.ImporterOptions;
public class Job_SelectMultipleEmbryosAutoRotation extends Job_Default{
public static final String KEY_WELL_CHANNEL_INDEX="LZ well (brightfield) channel index";
public static final String KEY_WELL_CHANNEL_THRESHOLD="LZ well (brightfield) channel threshold";
public static final String KEY_EMBRYO_CHANNEL_INDEX="LZ embryo (fluorescence) channel index";
public static final String KEY_EMBRYO_CHANNEL_FILTER_RADIUS="LZ embryo (fluorescence) channel filter radius";
public static final String KEY_EMBRYO_CHANNEL_THRESHOLD="LZ embryo (fluorescence) channel threshold";
public static final String KEY_WATERSHED_TOLERANCE="Watershed tolerance";
public static final String KEY_EMBRYO_MIN_SIZE="Embryo minimal size (pixels)";
public static final String KEY_EMBRYO_MAX_SIZE="Embryo maximal size (pixels)";
public static final String KEY_EMBRYO_MIN_INTENSITY="Embryo minimal intensity";
public static final String KEY_EMBRYO_MAX_INTENSITY="Embryo maximal intensity";
public static final String KEY_EMBRYO_MIN_AR="Embryo minimal aspect ratio";
public static final String KEY_EMBRYO_MAX_AR="Embryo maximal aspect ratio";
public static final String KEY_EMBRYO_MAX_COUNT="Maximal number of embryos from well";
//private static final Roi nullRoi=null;
protected ImagePlus img=null;
//private Roi selectedPointsRoi;
//private Integer selectedXPosition;
//private Integer selectedYPosition;
private Point2D.Double[] selectedPoints;
private Roi wellRoi;
private Roi[] embryoRois;
private Roi[] selectedEmbryoRois;
private Double[] rotations;
//private boolean selectionMade=false;
//private boolean invert_Z;
private int wellBrightFieldChannel;
private int wellBrightFieldThreshold;
private int embryoFluorChannel;
private double embryoFluorFilterRadius;
private int embryoFluorThreshold;
private double watershedTolerance;
private int embryoMinSize;
private int embryoMaxSize;
private double embryoMinIntensity;
private double embryoMaxIntensity;
private double embryoMinAR;
private double embryoMaxAR;
private int embryoMaxCount;
//private int initialLocationY;
@Override
protected void cleanIterOutput(){
img=null;
selectedPoints=null;
rotations=null;
wellRoi=null;
embryoRois=null;
selectedEmbryoRois=null;
}
@Override
protected void preProcessOnline()throws Exception{
currentTable.setFileAbsolutePath(newImgFile, curDInd, imgColumnNm, "IMG");
String Exper_nm=newImgFile.getName();
Exper_nm=Exper_nm.substring(0, Exper_nm.indexOf(fileTag));
this.setSharedValue("Experiment Name", Exper_nm);
TimeUnit.MILLISECONDS.sleep(2000);
//img=ImageOpenerWithBioformats.openImage(newImgFile);
img=openSelectedSlices(newImgFile);
}
@Override
protected void preProcessOffline()throws Exception{
//img=ImageOpenerWithBioformats.openImage(currentTable.getFile(curDInd, imgColumnNm, "IMG"));
img=openSelectedSlices(newImgFile);
}
protected ImagePlus openSelectedSlices(File _imageFile)throws Exception{
ImporterOptions options = new ImporterOptions();
options.setAutoscale(true);
options.setId(_imageFile.getAbsolutePath());
return BF.openImagePlus(options)[0];
}
@Override
protected boolean runProcessing()throws Exception{
this.showDebug(img, "original image", true);
wellRoi=getWellRoi(new Duplicator().run(img, wellBrightFieldChannel, wellBrightFieldChannel, 1, 1, 1, 1));
if (wellRoi==null)
return false;
identifyEmbryos(new Duplicator().run(img, embryoFluorChannel, embryoFluorChannel, 1, 1, 1, 1));
if (embryoRois==null) return false;
if (embryoRois.length<1) return false;
if (embryoRois.length<=embryoMaxCount){
selectedEmbryoRois=embryoRois;
}
else{
int[] selectedIndexes=ArrIndUtils.getRandomIndexes(embryoRois.length, embryoMaxCount);
selectedEmbryoRois=new Roi[embryoMaxCount];
for (int i=0;i<embryoMaxCount;i++)
selectedEmbryoRois[i]=embryoRois[selectedIndexes[i]];
}
for (Roi r:selectedEmbryoRois)
r.setStrokeColor(Color.cyan);
double x,y;
selectedPoints=new Point2D.Double[selectedEmbryoRois.length];
rotations=new Double[selectedEmbryoRois.length];
for (int i=0;i<selectedEmbryoRois.length;i++){
x=selectedEmbryoRois[i].getContourCentroid()[0];
y=selectedEmbryoRois[i].getContourCentroid()[1];
selectedPoints[i]=new Point2D.Double(x, y);
img.setRoi(selectedEmbryoRois[i]);
ImageStatistics stat=img.getStatistics(ImageStatistics.ELLIPSE);
rotations[i]=stat.angle;
}
return true;
}
private Roi getWellRoi(ImagePlus _2dimage)throws Exception{
IJ.setThreshold(_2dimage, wellBrightFieldThreshold, Double.MAX_VALUE);
RoiManager rm=ROIManipulator2D.getEmptyRm();
ParticleAnalyzer pAnalyzer=new ParticleAnalyzer(ParticleAnalyzer.ADD_TO_MANAGER|ParticleAnalyzer.SHOW_NONE|ParticleAnalyzer.FOUR_CONNECTED,
0,
null,
_2dimage.getWidth()*_2dimage.getHeight()/5, Double.MAX_VALUE, 0.5, 1.0);
pAnalyzer.analyze(_2dimage);
if (rm.getCount()<1)
return null;
Roi[] segmentedRegions=rm.getRoisAsArray();
Roi identifiedWellRoi=ROIManipulator2D.selBiggestRoi(_2dimage, segmentedRegions);
identifiedWellRoi.setStrokeColor(Color.MAGENTA);
this.showDebug(_2dimage, "Well segmentation", false, new Overlay(identifiedWellRoi));
return identifiedWellRoi;
}
private void identifyEmbryos(ImagePlus _embryoImage)throws Exception {
new RankFilters().rank(_embryoImage.getProcessor(), embryoFluorFilterRadius, RankFilters.MEDIAN);
this.showDebug(_embryoImage, "Image for embryo segmentation", true);
ImageProcessor maskProcessor=new MaximumFinder().findMaxima(_embryoImage.getProcessor(),watershedTolerance,embryoFluorThreshold,MaximumFinder.SEGMENTED,false,false);
ImagePlus watershedMaskImage=new ImagePlus("Embryo Mask Image", maskProcessor);
this.showDebug(watershedMaskImage, "Watershed mask", true);
IJ.setThreshold(watershedMaskImage, 125, 255);
RoiManager rm=ROIManipulator2D.getEmptyRm();
ParticleAnalyzer pAnalyzer=new ParticleAnalyzer(ParticleAnalyzer.ADD_TO_MANAGER|ParticleAnalyzer.SHOW_NONE|ParticleAnalyzer.FOUR_CONNECTED|ParticleAnalyzer.EXCLUDE_EDGE_PARTICLES,
0,
null,
embryoMinSize, embryoMaxSize, 0.0, 1.0);
pAnalyzer.analyze(watershedMaskImage);
if (rm.getCount()<1)
return;
Roi[] identifiedRois=rm.getRoisAsArray();
ParticleFilterer embryoFilter=new ParticleFilterer(_embryoImage.getProcessor(), identifiedRois);
embryoFilter.filterInRoi(wellRoi);
embryoFilter.filterThr(ParticleFilterer.MEAN, embryoMinIntensity, embryoMaxIntensity);
embryoFilter.filterThr(ParticleFilterer.ASPECT_RATIO, embryoMinAR, embryoMaxAR);
embryoRois=embryoFilter.getPassedRois();
if (embryoRois==null)
return;
for (Roi r:embryoRois)
r.setStrokeColor(Color.yellow);
this.showDebug(_embryoImage, "Segmented embryos", true,this.createOverlay());
return;
}
@Override
protected Overlay createOverlay(){
Overlay o=new Overlay();
if (wellRoi!=null)
o.add(wellRoi);
if (embryoRois!=null)
for (Roi r:embryoRois){
o.add(r);
}
return o;
}
@Override
public void visualise(int _xvis, int _yvis){
img.setDisplayMode(IJ.COMPOSITE);
this.visualiseImg(img, getOverlay(), _xvis, _yvis);
img.setC(embryoFluorChannel);
}
@Override
public void postProcessSuccess()throws Exception{
//PointRoi saveRoi=null;
//form comma-separated strings with coordinates
String xstr="", ystr="",zstr="",rotstr="";
// submit 3d points in future
for (int i=0;i<selectedEmbryoRois.length;i++){
if (i>0){
xstr+=";";
ystr+=";";
zstr+=";";
rotstr+=";";
}
xstr+=selectedPoints[i].x;
ystr+=selectedPoints[i].y;
zstr+="0";
rotstr+=rotations[i];
}
ZeissKeys.submitCommandsToMicroscope("trigger1", xstr,ystr,zstr,rotstr,"","","","","");
saveRoisForImage(newImgFile, getOverlay().toArray());
//saveRoiForImage(newImgFile,saveRoi);
this.setSharedValue("Zoom Points", selectedPoints);
this.setSharedValue("Zoom Counter", -1);
this.setSharedValue("Selected Embryo Rois", selectedEmbryoRois);
}
@Override
public ParameterCollection createJobParameters(){
ParameterCollection jobCollection=new ParameterCollection();
jobCollection.addParameter(KEY_WELL_CHANNEL_INDEX, null, 2, ParameterType.INT_PARAMETER);
jobCollection.addParameter(KEY_WELL_CHANNEL_THRESHOLD, null, 10, ParameterType.INT_PARAMETER);
jobCollection.addParameter(KEY_EMBRYO_CHANNEL_INDEX, null, 1, ParameterType.INT_PARAMETER);
jobCollection.addParameter(KEY_EMBRYO_CHANNEL_FILTER_RADIUS, null, 5.0, ParameterType.DOUBLE_PARAMETER);
jobCollection.addParameter(KEY_EMBRYO_CHANNEL_THRESHOLD, null, 70, ParameterType.INT_PARAMETER);
jobCollection.addParameter(KEY_WATERSHED_TOLERANCE, null, 100.0, ParameterType.DOUBLE_PARAMETER);
jobCollection.addParameter(KEY_EMBRYO_MIN_SIZE, null, 500, ParameterType.INT_PARAMETER);
jobCollection.addParameter(KEY_EMBRYO_MAX_SIZE, null, 10000, ParameterType.INT_PARAMETER);
jobCollection.addParameter(KEY_EMBRYO_MIN_INTENSITY, null, 50.0, ParameterType.DOUBLE_PARAMETER);
jobCollection.addParameter(KEY_EMBRYO_MAX_INTENSITY, null, 256.0, ParameterType.DOUBLE_PARAMETER);
jobCollection.addParameter(KEY_EMBRYO_MIN_AR, null, 1.8, ParameterType.DOUBLE_PARAMETER);
jobCollection.addParameter(KEY_EMBRYO_MAX_AR, null, 10.0, ParameterType.DOUBLE_PARAMETER);
jobCollection.addParameter(KEY_EMBRYO_MAX_COUNT,null, 5, ParameterType.INT_PARAMETER);
return jobCollection;
}
@Override
public void parseInputParameterValues(ParameterCollection _jobParameterCollection){
this.wellBrightFieldChannel=(Integer)_jobParameterCollection.getParameterValue(KEY_WELL_CHANNEL_INDEX);
this.wellBrightFieldThreshold=(Integer)_jobParameterCollection.getParameterValue(KEY_WELL_CHANNEL_THRESHOLD);
this.embryoFluorChannel=(Integer)_jobParameterCollection.getParameterValue(KEY_EMBRYO_CHANNEL_INDEX);
this.embryoFluorFilterRadius=(Double)_jobParameterCollection.getParameterValue(KEY_EMBRYO_CHANNEL_FILTER_RADIUS);
this.embryoFluorThreshold=(Integer)_jobParameterCollection.getParameterValue(KEY_EMBRYO_CHANNEL_THRESHOLD);
this.watershedTolerance=(Double)_jobParameterCollection.getParameterValue(KEY_WATERSHED_TOLERANCE);
this.embryoMinSize=(Integer)_jobParameterCollection.getParameterValue(KEY_EMBRYO_MIN_SIZE);
this.embryoMaxSize=(Integer)_jobParameterCollection.getParameterValue(KEY_EMBRYO_MAX_SIZE);
this.embryoMinIntensity=(Double)_jobParameterCollection.getParameterValue(KEY_EMBRYO_MIN_INTENSITY);
this.embryoMaxIntensity=(Double)_jobParameterCollection.getParameterValue(KEY_EMBRYO_MAX_INTENSITY);
this.embryoMinAR=(Double)_jobParameterCollection.getParameterValue(KEY_EMBRYO_MIN_AR);
this.embryoMaxAR=(Double)_jobParameterCollection.getParameterValue(KEY_EMBRYO_MAX_AR);
this.embryoMaxCount=(Integer)_jobParameterCollection.getParameterValue(KEY_EMBRYO_MAX_COUNT);
}
@Override
protected DebugVisualiserSettings getDebugVisualiserSettings(){
return new DebugVisualiserSettings(-2, 10,10,2);
}
/**
* offline debugging
* @param args unsused
*/
public static void main(String[] args)throws Exception{
// start ImageJ
new ImageJ();
String tblPth="Z:/halavaty/temp/fly feedback files 20180404/2018-4-4 Auto Test";
String tblFnm="summary_test02_.txt";
Job_SelectMultipleEmbryosAutoRotation testJob=new Job_SelectMultipleEmbryosAutoRotation();
testJob.initialise(null, "LZ.Image", false);
testJob.testJobMicTable(0, tblPth, tblFnm);
}
}
...@@ -9,5 +9,6 @@ Plugins>Auto Mic Tools>Fly Embryo Screen, "Manual selection", feedback.fly.embry ...@@ -9,5 +9,6 @@ Plugins>Auto Mic Tools>Fly Embryo Screen, "Manual selection", feedback.fly.embry
Plugins>Auto Mic Tools>Fly Embryo Screen, "Manual selection of multiple embryos", feedback.fly.embryo.jobdistributors.JobDistributor_ManualSelectionMultiple Plugins>Auto Mic Tools>Fly Embryo Screen, "Manual selection of multiple embryos", feedback.fly.embryo.jobdistributors.JobDistributor_ManualSelectionMultiple
Plugins>Auto Mic Tools>Fly Embryo Screen, "Automated selection of multiple embryos", feedback.fly.embryo.jobdistributors.JobDistributor_AutoSelectionMultiple Plugins>Auto Mic Tools>Fly Embryo Screen, "Automated selection of multiple embryos", feedback.fly.embryo.jobdistributors.JobDistributor_AutoSelectionMultiple
Plugins>Auto Mic Tools>Fly Embryo Screen, "Automated selection of multiple embryos with reflection autofocus", feedback.fly.embryo.jobdistributors.JobDistributor_AutoSelectionMultiple_AFocus Plugins>Auto Mic Tools>Fly Embryo Screen, "Automated selection of multiple embryos with reflection autofocus", feedback.fly.embryo.jobdistributors.JobDistributor_AutoSelectionMultiple_AFocus
Plugins>Auto Mic Tools>Fly Embryo Screen, "Automated selection of multiple embryos with reflection autofocus and embryo rotation", feedback.fly.embryo.jobdistributors.JobDistributor_AutoSelectionMultiple_AFocus_Rotation
Plugins>Auto Mic Tools>Fly Embryo Screen,"-" Plugins>Auto Mic Tools>Fly Embryo Screen,"-"
Plugins>Auto Mic Tools>Fly Embryo Screen, "Test segmentation parameters", feedback.fly.embryo.jobtests.Embryo_Selection_Tester Plugins>Auto Mic Tools>Fly Embryo Screen, "Test segmentation parameters", feedback.fly.embryo.jobtests.Embryo_Selection_Tester
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment