Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
I
imagej-elphel
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
3
Issues
3
List
Board
Labels
Milestones
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
Elphel
imagej-elphel
Commits
123e14a3
Commit
123e14a3
authored
Jul 15, 2014
by
Andrey Filippov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added initial z0 estimate, switched squared parameters to exp()
parent
b2797800
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
96 additions
and
66 deletions
+96
-66
FocusingField.java
src/main/java/FocusingField.java
+96
-66
No files found.
src/main/java/FocusingField.java
View file @
123e14a3
...
@@ -96,7 +96,7 @@ public class FocusingField {
...
@@ -96,7 +96,7 @@ public class FocusingField {
private
boolean
[]
rslt_show_chn
;
private
boolean
[]
rslt_show_chn
;
// not saved/restored
// not saved/restored
private
double
[]
z0_estimates
=
null
;
// initial estimates for z0 from the lowest value on data
private
double
lambdaStepUp
;
// multiply lambda by this if result is worse
private
double
lambdaStepUp
;
// multiply lambda by this if result is worse
private
double
lambdaStepDown
;
// multiply lambda by this if result is better
private
double
lambdaStepDown
;
// multiply lambda by this if result is better
private
double
thresholdFinish
;
// (copied from series) stop iterations if 2 last steps had less improvement (but not worsening )
private
double
thresholdFinish
;
// (copied from series) stop iterations if 2 last steps had less improvement (but not worsening )
...
@@ -170,6 +170,7 @@ public class FocusingField {
...
@@ -170,6 +170,7 @@ public class FocusingField {
public
void
setDefaults
(){
public
void
setDefaults
(){
z0_estimates
=
null
;
sagittalMaster
=
false
;
// center data is the same, when true sagittal fitting only may change r=0 coefficients,
sagittalMaster
=
false
;
// center data is the same, when true sagittal fitting only may change r=0 coefficients,
parallelOnly
=
true
;
// only process measurements for parallel moves
parallelOnly
=
true
;
// only process measurements for parallel moves
filterInput
=
true
;
filterInput
=
true
;
...
@@ -666,7 +667,12 @@ private boolean [] filterConcave(
...
@@ -666,7 +667,12 @@ private boolean [] filterConcave(
int
numFilteredInsufficient
=
0
;
int
numFilteredInsufficient
=
0
;
int
numFiltered
=
0
;
int
numFiltered
=
0
;
int
[][]
numPoints
=
new
int
[
getNumChannels
()][
getNumSamples
()];
int
[][]
numPoints
=
new
int
[
getNumChannels
()][
getNumSamples
()];
for
(
int
chn
=
0
;
chn
<
numPoints
.
length
;
chn
++)
for
(
int
sample
=
0
;
sample
<
numPoints
[
chn
].
length
;
sample
++)
numPoints
[
chn
][
sample
]=
0
;
double
[][][]
z0EstData
=
new
double
[
getNumChannels
()][
getNumSamples
()][
2
];
for
(
int
chn
=
0
;
chn
<
numPoints
.
length
;
chn
++)
for
(
int
sample
=
0
;
sample
<
numPoints
[
chn
].
length
;
sample
++)
{
numPoints
[
chn
][
sample
]=
0
;
z0EstData
[
chn
][
sample
][
0
]=
0.0
;
z0EstData
[
chn
][
sample
][
1
]=
0.0
;
}
for
(
int
index
=
0
;
index
<
dataVector
.
length
;
index
++)
if
((
index
>=
enable_in
.
length
)
||
enable_in
[
index
]){
for
(
int
index
=
0
;
index
<
dataVector
.
length
;
index
++)
if
((
index
>=
enable_in
.
length
)
||
enable_in
[
index
]){
numPoints
[
dataVector
[
index
].
channel
][
dataVector
[
index
].
sampleIndex
]++;
numPoints
[
dataVector
[
index
].
channel
][
dataVector
[
index
].
sampleIndex
]++;
}
}
...
@@ -811,14 +817,31 @@ private boolean [] filterConcave(
...
@@ -811,14 +817,31 @@ private boolean [] filterConcave(
", slope="
+
IJ
.
d2s
(
100
*
point_slope
[
i
],
3
)+
", concave="
+(
nonConcave
[
i
]?
0.0
:
1.0
));
", slope="
+
IJ
.
d2s
(
100
*
point_slope
[
i
],
3
)+
", concave="
+(
nonConcave
[
i
]?
0.0
:
1.0
));
}
}
}
}
// contribute to z0 calculation
for
(
int
i
=
0
;
i
<
thisIndices
.
length
;
i
++)
if
(!
nonConcave
[
i
]){
z0EstData
[
chn
][
sample
][
1
]+=
dataWeights
[
thisIndices
[
i
]];
}
z0EstData
[
chn
][
sample
][
0
]=
point_z
[
minIndex
];
}
}
}
}
if
(
debugLevel
>
0
)
System
.
out
.
println
(
"filterConcave(): removed for too few points "
+
numFilteredInsufficient
+
" samples"
);
if
(
debugLevel
>
0
)
System
.
out
.
println
(
"filterConcave(): removed for too few points "
+
numFilteredInsufficient
+
" samples"
);
if
(
debugLevel
>
0
)
System
.
out
.
println
(
"filterConcave(): removed for non-concave "
+
numFiltered
+
" samples"
);
if
(
debugLevel
>
0
)
System
.
out
.
println
(
"filterConcave(): removed for non-concave "
+
numFiltered
+
" samples"
);
// for (int chn=0;chn<numPoints.length;chn++) for (int sample=0;sample<numPoints[chn].length;sample++){
z0_estimates
=
new
double
[
getNumChannels
()];
for
(
int
chn
=
0
;
chn
<
z0_estimates
.
length
;
chn
++){
double
z
=
0
;
double
w
=
0
;
for
(
int
sample
=
0
;
sample
<
numPoints
[
chn
].
length
;
sample
++){
z
+=
z0EstData
[
chn
][
sample
][
0
]*
z0EstData
[
chn
][
sample
][
1
];
w
+=
z0EstData
[
chn
][
sample
][
1
];
}
z0_estimates
[
chn
]=
(
w
>
0.0
)?
z
/
w:
Double
.
NaN
;
}
return
enable_out
;
return
enable_out
;
}
}
private
boolean
[]
filterTooFar
(
double
ratio
,
boolean
[]
enable_in
){
private
boolean
[]
filterTooFar
(
double
ratio
,
boolean
[]
enable_in
){
if
(
enable_in
==
null
)
{
if
(
enable_in
==
null
)
{
enable_in
=
new
boolean
[
dataVector
.
length
];
enable_in
=
new
boolean
[
dataVector
.
length
];
...
@@ -2707,7 +2730,7 @@ public boolean LevenbergMarquardt(boolean openDialog, int debugLevel){
...
@@ -2707,7 +2730,7 @@ public boolean LevenbergMarquardt(boolean openDialog, int debugLevel){
fieldFitting
.
initSampleCorrVector
(
fieldFitting
.
initSampleCorrVector
(
flattenSampleCoord
(),
//double [][] sampleCoordinates,
flattenSampleCoord
(),
//double [][] sampleCoordinates,
getSeriesWeights
());
//double [][] sampleSeriesWeights);
getSeriesWeights
());
//double [][] sampleSeriesWeights);
fieldFitting
.
setEstimatedZ0
(
z0_estimates
,
false
);
// boolean force)
this
.
savedVector
=
this
.
fieldFitting
.
createParameterVector
(
sagittalMaster
);
this
.
savedVector
=
this
.
fieldFitting
.
createParameterVector
(
sagittalMaster
);
if
(
debugDerivativesFxDxDy
){
if
(
debugDerivativesFxDxDy
){
...
@@ -3774,6 +3797,18 @@ if ((chn==3) && (sampleIndex==23)){
...
@@ -3774,6 +3797,18 @@ if ((chn==3) && (sampleIndex==23)){
}
}
}
}
public
void
setEstimatedZ0
(
double
[]
z0
,
boolean
force
){
if
((
z0
==
null
)||
(
curvatureModel
==
null
))
return
;
// no estimation available
for
(
int
chn
=
0
;
chn
<
curvatureModel
.
length
;
chn
++){
if
(!
Double
.
isNaN
(
z0
[
chn
])
&&
(!
curvatureModel
[
chn
].
z0IsValid
()
||
force
)){
curvatureModel
[
chn
].
set_z0
(
z0
[
chn
]);
if
(
debugLevel
>
1
)
System
.
out
.
println
(
"Setting initial (estimated) best focal position for channel "
+
chn
+
" = "
+
z0
[
chn
]);
}
}
}
/**
/**
* create matrix of weights of the other parameters influence
* create matrix of weights of the other parameters influence
* @param sampleCoordinates [sample number]{x,y} - flattened array of sample coordinates
* @param sampleCoordinates [sample number]{x,y} - flattened array of sample coordinates
...
@@ -3828,29 +3863,6 @@ if ((chn==3) && (sampleIndex==23)){
...
@@ -3828,29 +3863,6 @@ if ((chn==3) && (sampleIndex==23)){
}
}
}
}
getCorrVector
();
getCorrVector
();
/*
sampleCorrChnParIndex=new int [sampleCorrSelect.length][];
int numPars=0;
for (int nChn=0; nChn< sampleCorrCrossWeights.length;nChn++) {
if (sampleCorrCrossWeights[nChn]!=null){
sampleCorrChnParIndex[nChn]=new int [sampleCorrCrossWeights[nChn].length];
for (int nPar=0;nPar< sampleCorrCrossWeights[nChn].length;nPar++) {
if (sampleCorrCrossWeights[nChn][nPar]!=null){
sampleCorrChnParIndex[nChn][nPar]=numPars; // pointer to the first sample
numPars+=sampleCorrCrossWeights[nChn][nPar].length;
} else {
sampleCorrChnParIndex[nChn][nPar]=-1;
}
}
} else {
sampleCorrChnParIndex[nChn]=null;
}
}
// currently all correction parameters are initialized as zeros.
getCorrVector();
if (debugLevel>1) System.out.println("was resetting sampleCorrVector here");
*/
}
}
...
@@ -3888,15 +3900,6 @@ if ((chn==3) && (sampleIndex==23)){
...
@@ -3888,15 +3900,6 @@ if ((chn==3) && (sampleIndex==23)){
return
sampleCoordinates
;
return
sampleCoordinates
;
}
}
public
double
[]
getCorrPar
(
int
chn
,
int
sampleIndex
){
public
double
[]
getCorrPar
(
int
chn
,
int
sampleIndex
){
/* if ((sampleCorrChnParIndex==null) || (sampleCorrChnParIndex[chn]==null)) return null;
double [] corr =new double [sampleCorrChnParIndex[chn].length];
for (int i=0;i<corr.length;i++){
if (sampleCorrChnParIndex[chn][i]<0) corr[i]=0.0;
else corr[i]=sampleCorrVector[sampleCorrChnParIndex[chn][i]+sampleIndex];
}
*/
// System.out.println("used sampleCorrVector here, now correctionParameters");
if
(
correctionParameters
[
chn
]==
null
)
return
null
;
if
(
correctionParameters
[
chn
]==
null
)
return
null
;
double
[]
corr
=
new
double
[
correctionParameters
[
chn
].
length
];
double
[]
corr
=
new
double
[
correctionParameters
[
chn
].
length
];
for
(
int
i
=
0
;
i
<
corr
.
length
;
i
++){
for
(
int
i
=
0
;
i
<
corr
.
length
;
i
++){
...
@@ -4859,8 +4862,8 @@ if ((chn==3) && (sampleIndex==23)){
...
@@ -4859,8 +4862,8 @@ if ((chn==3) && (sampleIndex==23)){
}
}
public
class
CurvatureModel
{
public
class
CurvatureModel
{
private
double
dflt_na
=
0.15
;
// um/um
private
double
dflt_na
=
Math
.
log
(
0.15
)
;
// um/um
private
double
dflt_r0
=
4.0
;
//3.3; // um (1.5 pix)
private
double
dflt_r0
=
Math
.
log
(
4.0
)
;
//3.3; // um (1.5 pix)
private
double
[][]
modelParams
=
null
;
private
double
[][]
modelParams
=
null
;
public
static
final
int
dflt_distanceParametersNumber
=
5
;
public
static
final
int
dflt_distanceParametersNumber
=
5
;
public
static
final
int
dflt_radialParametersNumber
=
4
;
public
static
final
int
dflt_radialParametersNumber
=
4
;
...
@@ -4934,7 +4937,16 @@ if ((chn==3) && (sampleIndex==23)){
...
@@ -4934,7 +4937,16 @@ if ((chn==3) && (sampleIndex==23)){
}
}
this
.
modelParams
[
1
][
0
]=
dflt_na
;
this
.
modelParams
[
1
][
0
]=
dflt_na
;
this
.
modelParams
[
2
][
0
]=
dflt_r0
;
this
.
modelParams
[
2
][
0
]=
dflt_r0
;
this
.
modelParams
[
0
][
0
]=
Double
.
NaN
;
}
public
boolean
z0IsValid
(){
return
!
Double
.
isNaN
(
this
.
modelParams
[
0
][
0
]);
}
}
public
void
set_z0
(
double
z0
){
this
.
modelParams
[
0
][
0
]=
z0
;
}
public
double
[]
getCenterVector
(){
public
double
[]
getCenterVector
(){
double
[]
vector
=
new
double
[
this
.
modelParams
.
length
];
double
[]
vector
=
new
double
[
this
.
modelParams
.
length
];
for
(
int
i
=
0
;
i
<
this
.
modelParams
.
length
;
i
++){
for
(
int
i
=
0
;
i
<
this
.
modelParams
.
length
;
i
++){
...
@@ -5006,7 +5018,7 @@ if ((chn==3) && (sampleIndex==23)){
...
@@ -5006,7 +5018,7 @@ if ((chn==3) && (sampleIndex==23)){
double
z_in
,
double
z_in
,
double
[]
deriv
){
double
[]
deriv
){
/*
/*
f=sqrt((a*(zin-z0))^2 + r0^2)+a0+ a1*(zin-z0)+...aN*(zin-z0)^N
f=sqrt((
a*(zin-z0))^2 + r0^2)+a0+ a1*(zin-z0)+...aN*(zin-z0)^N
each of the z0,z0,a,a[i] is polynomial of even powers of r (r^0, r^2, r^4...)
each of the z0,z0,a,a[i] is polynomial of even powers of r (r^0, r^2, r^4...)
z=z_in-z0
z=z_in-z0
...
@@ -5014,12 +5026,24 @@ modified parameters, r0 - PSF FWHM at z=0, k (instead of a0), so that old r0 now
...
@@ -5014,12 +5026,24 @@ modified parameters, r0 - PSF FWHM at z=0, k (instead of a0), so that old r0 now
r0*exp(-k), old a0= r0*(1-exp(-k)).
r0*exp(-k), old a0= r0*(1-exp(-k)).
f=sqrt((a*(zin-z0))^2 + (r0*(exp(-k))^2)+r0*(1-exp(-k))+ a1*(zin-z0)+...aN*(zin-z0)^N
f=sqrt((a*(zin-z0))^2 + (r0*(exp(-k))^2)+r0*(1-exp(-k))+ a1*(zin-z0)+...aN*(zin-z0)^N
z0 - ar[0]
z0 - ar[0]
a - ar[1]
a - ar[1]
r0 - ar[2]
r0 - ar[2]
k - ar[3]
k - ar[3]
ar1 - ar[4]
ar1 - ar[4]
modified to avoid zero-crossing for a and r0:
a=exp(ln_a)
r0=exp(ln_r0)
z0 - ar[0]
ln_a - ar[1]
ln_r0 - ar[2]
k - ar[3]
ar1 - ar[4]
*/
*/
double
r
=
Double
.
isNaN
(
pY
)?
pX:
Math
.
sqrt
((
pX
-
pX0
)*(
pX
-
pX0
)+(
pY
-
pY0
)*(
pY
-
pY0
))*
PIXEL_SIZE
;
// in mm
double
r
=
Double
.
isNaN
(
pY
)?
pX:
Math
.
sqrt
((
pX
-
pX0
)*(
pX
-
pX0
)+(
pY
-
pY0
)*(
pY
-
pY0
))*
PIXEL_SIZE
;
// in mm
// double r2=r*r;
// double r2=r*r;
...
@@ -5035,20 +5059,19 @@ ar1 - ar[4]
...
@@ -5035,20 +5059,19 @@ ar1 - ar[4]
ar
[
i
]+=
this
.
modelParams
[
i
][
j
]*
rp
;
ar
[
i
]+=
this
.
modelParams
[
i
][
j
]*
rp
;
}
}
}
}
double
exp_a
=
Math
.
exp
(
ar
[
1
]);
double
exp_r
=
Math
.
exp
(
ar
[
2
]);
double
z
=
z_in
-
ar
[
0
];
double
z
=
z_in
-
ar
[
0
];
double
exp
=
Math
.
exp
(-
ar
[
3
]);
double
exp_k
=
Math
.
exp
(-
ar
[
3
]);
double
reff
=
ar
[
2
]*
exp
;
// double reff=ar[2]*exp_k;
// double sqrt=Math.sqrt((ar[1]*z)*(ar[1]*z) + ar[2]*ar[2]);
double
reff
=
exp_r
*
exp_k
;
double
sqrt
=
Math
.
sqrt
((
ar
[
1
]*
z
)*(
ar
[
1
]*
z
)
+
reff
*
reff
);
// double sqrt=Math.sqrt((ar[1]*z)*(ar[1]*z) + reff*reff);
double
sqrt
=
Math
.
sqrt
((
exp_a
*
z
)*(
exp_a
*
z
)
+
reff
*
reff
);
double
f
=
sqrt
+
ar
[
2
]*(
1
-
exp
);
// double f=sqrt+ar[2]*(1-exp_k);
double
f
=
sqrt
+
exp_r
*(
1
-
exp_k
);
double
zp
=
1.0
;
double
zp
=
1.0
;
/*
for (int i=3;i<ar.length;i++){
f+=ar[i]*zp;
zp*=z;
}
*/
for
(
int
i
=
4
;
i
<
ar
.
length
;
i
++){
for
(
int
i
=
4
;
i
<
ar
.
length
;
i
++){
zp
*=
z
;
zp
*=
z
;
f
+=
ar
[
i
]*
zp
;
f
+=
ar
[
i
]*
zp
;
...
@@ -5057,29 +5080,35 @@ ar1 - ar[4]
...
@@ -5057,29 +5080,35 @@ ar1 - ar[4]
if
(
deriv
==
null
)
return
f
;
// only value, no derivatives
if
(
deriv
==
null
)
return
f
;
// only value, no derivatives
double
[]
df_da
=
new
double
[
this
.
modelParams
.
length
];
// last element - derivative for dz
double
[]
df_da
=
new
double
[
this
.
modelParams
.
length
];
// last element - derivative for dz
// derivative for z0 (shift) - ar[0]
// derivative for z0 (shift) - ar[0]
df_da
[
0
]=-
1.0
/
sqrt
*
ar
[
1
]*
ar
[
1
]*
z
;
// df_da[0]=-1.0/sqrt*ar[1]*ar[1]*z;
df_da
[
0
]=-
1.0
/
sqrt
*
exp_a
*
exp_a
*
z
;
zp
=
1.0
;
zp
=
1.0
;
for
(
int
i
=
4
;
i
<
this
.
modelParams
.
length
;
i
++){
for
(
int
i
=
4
;
i
<
this
.
modelParams
.
length
;
i
++){
df_da
[
0
]-=
ar
[
i
]*(
i
-
3
)*
zp
;
// ar[i] calculated coefficients for current radius
df_da
[
0
]-=
ar
[
i
]*(
i
-
3
)*
zp
;
// ar[i] calculated coefficients for current radius
zp
*=
z
;
zp
*=
z
;
}
}
// derivative for a (related to numeric aperture) - ar[1]
// derivative for a (related to numeric aperture) - ar[1]
df_da
[
1
]=
1.0
/
sqrt
*
ar
[
1
]*
z
*
z
;
// df_da[1]=1.0/sqrt*ar[1]*z*z;
df_da
[
1
]=
1.0
/
sqrt
*
exp_a
*
z
*
z
*
exp_a
;
// d(f)/d(exp_a) *exp_a
// derivative for a (related to lowest PSF radius) - ar[2]
// derivative for a (related to lowest PSF radius) - ar[2]
// df_da[2]=1.0/sqrt*ar[2];
// df_da[2]=1.0/sqrt*reff*exp_k + (1-exp_k); // * exp(-k)
df_da
[
2
]=
1.0
/
sqrt
*
reff
*
exp
+
(
1
-
exp
);
// * exp(-k)
df_da
[
2
]=(
1.0
/
sqrt
*
reff
*
exp_k
+
(
1
-
exp_k
))
*
exp_r
;
// d(f)/d(exp_r) *exp_r
// derivative for k (ar[3]
// derivative for k (ar[3]
df_da
[
3
]=
1.0
/
sqrt
*
reff
*
ar
[
2
]*
exp
*(-
1
)
+
ar
[
2
]*
exp
;
// df_da[3]=1.0/sqrt*reff*ar[2]*exp_k*(-1) + ar[2]*exp_k;
df_da
[
3
]=
1.0
/
sqrt
*
reff
*
exp_r
*
exp_k
*(-
1
)
+
exp_r
*
exp_k
;
// derivatives for rest (polynomial) coefficients
// derivatives for rest (polynomial) coefficients
zp
=
1.0
;
zp
=
1.0
;
/*
for (int i=3;i<this.modelParams.length;i++){
df_da[i]=zp;
zp*=z;
}
*/
for
(
int
i
=
4
;
i
<
this
.
modelParams
.
length
;
i
++){
for
(
int
i
=
4
;
i
<
this
.
modelParams
.
length
;
i
++){
zp
*=
z
;
zp
*=
z
;
df_da
[
i
]=
zp
;
df_da
[
i
]=
zp
;
...
@@ -5089,7 +5118,6 @@ ar1 - ar[4]
...
@@ -5089,7 +5118,6 @@ ar1 - ar[4]
double
[]
dar
=
new
double
[
this
.
modelParams
[
0
].
length
];
double
[]
dar
=
new
double
[
this
.
modelParams
[
0
].
length
];
dar
[
0
]=
1
;
dar
[
0
]=
1
;
for
(
int
j
=
1
;
j
<
dar
.
length
;
j
++){
for
(
int
j
=
1
;
j
<
dar
.
length
;
j
++){
// dar[j]=dar[j-1]*r2;
dar
[
j
]=
dar
[
j
-
1
]*
r
;
dar
[
j
]=
dar
[
j
-
1
]*
r
;
if
(
j
==
1
)
dar
[
j
]*=
r
;
// 0,2,3,4,5...
if
(
j
==
1
)
dar
[
j
]*=
r
;
// 0,2,3,4,5...
}
}
...
@@ -5120,14 +5148,16 @@ ar1 - ar[4]
...
@@ -5120,14 +5148,16 @@ ar1 - ar[4]
}
}
public
String
getZName
(
int
i
){
public
String
getZName
(
int
i
){
if
(
i
==
0
)
return
"z0"
;
if
(
i
==
0
)
return
"z0"
;
if
(
i
==
1
)
return
"na"
;
if
(
i
==
1
)
return
"ln(na)"
;
if
(
i
==
2
)
return
"r0"
;
if
(
i
==
2
)
return
"ln(r0)"
;
if
(
i
==
3
)
return
"ln(k)"
;
else
return
"az_"
+(
i
-
3
);
else
return
"az_"
+(
i
-
3
);
}
}
public
String
getZDescription
(
int
i
){
public
String
getZDescription
(
int
i
){
if
(
i
==
0
)
return
"Focal shift"
;
if
(
i
==
0
)
return
"Focal shift"
;
if
(
i
==
1
)
return
"Defocus/focus shift (~NA)"
;
if
(
i
==
1
)
return
"Defocus/focus shift (~NA), ln()"
;
if
(
i
==
2
)
return
"Best PSF radius"
;
if
(
i
==
2
)
return
"Best PSF radius, ln()"
;
if
(
i
==
3
)
return
"cross shift, ln()"
;
else
return
"Polynomial coefficient for z^"
+(
i
-
3
);
else
return
"Polynomial coefficient for z^"
+(
i
-
3
);
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment