working version of equivalency module
Browse files- CHRIS.py +1 -1
- equivalency_module/equivalency.py +39 -28
- equivalency_module/templates/equivalency_index.html +21 -17
CHRIS.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
from flask import Flask, render_template
|
| 2 |
|
| 3 |
app = Flask(__name__)
|
| 4 |
-
app.debug =
|
| 5 |
|
| 6 |
from color_module import blueprint
|
| 7 |
app.register_blueprint(blueprint)
|
|
|
|
| 1 |
from flask import Flask, render_template
|
| 2 |
|
| 3 |
app = Flask(__name__)
|
| 4 |
+
app.debug = False
|
| 5 |
|
| 6 |
from color_module import blueprint
|
| 7 |
app.register_blueprint(blueprint)
|
equivalency_module/equivalency.py
CHANGED
|
@@ -5,42 +5,44 @@ from scipy.special import erf
|
|
| 5 |
from scipy import integrate
|
| 6 |
from lmfit import minimize, Parameters, printfuncs, fit_report
|
| 7 |
|
| 8 |
-
def
|
|
|
|
| 9 |
|
| 10 |
-
return
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
-
|
| 13 |
|
| 14 |
-
|
| 15 |
-
-(-mu + x) ** 2. / (2. * sigma ** 2.)) / sqrt(pi) / sigma
|
| 16 |
-
|
| 17 |
-
def max_PDFCDF(y, lamb, mu_x, mu_y, sigma_x, sigma_y, N):
|
| 18 |
-
|
| 19 |
-
return max_PDF(y, mu_y, sigma_y, N) * max_CDF(y * lamb, mu_x, sigma_x, N)
|
| 20 |
-
|
| 21 |
-
def alpha_residual(params, prob, sigma, N):
|
| 22 |
|
| 23 |
alpha = params['alpha'].value
|
| 24 |
-
tmp
|
|
|
|
| 25 |
|
| 26 |
-
return prob -
|
| 27 |
|
| 28 |
-
def
|
| 29 |
|
| 30 |
beta = params['beta'].value
|
| 31 |
-
tmp
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
-
|
| 34 |
|
| 35 |
-
|
| 36 |
|
|
|
|
| 37 |
alpha_params = Parameters()
|
| 38 |
alpha_params.add('alpha', value=1.)
|
| 39 |
-
alpha_out = minimize(
|
| 40 |
|
| 41 |
beta_params = Parameters()
|
| 42 |
-
beta_params.add('beta', value=
|
| 43 |
-
beta_out = minimize(
|
| 44 |
|
| 45 |
return alpha_out.params['alpha'].value, beta_out.params['beta'].value
|
| 46 |
|
|
@@ -52,18 +54,27 @@ def equivalency():
|
|
| 52 |
|
| 53 |
alpha = 0.
|
| 54 |
beta = 0.
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
if request.method == 'POST':
|
| 61 |
-
|
| 62 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
FPR = float(request.form["FPR"])
|
| 64 |
FNR = float(request.form["FNR"])
|
| 65 |
|
| 66 |
-
alpha, beta = FitParams(FPR, FNR,
|
| 67 |
|
| 68 |
|
| 69 |
-
return render_template('equivalency_index.html',
|
|
|
|
|
|
| 5 |
from scipy import integrate
|
| 6 |
from lmfit import minimize, Parameters, printfuncs, fit_report
|
| 7 |
|
| 8 |
+
def MeanIntegrad(y, RSD, BN, CN, alpha, beta):
|
| 9 |
+
result = exp(-BN * (1. - y) ** 2. / 2. / RSD ** 2.) * (1. + erf(sqrt(CN) * (alpha * y - beta) / sqrt(2) / RSD / beta))
|
| 10 |
|
| 11 |
+
return result
|
| 12 |
+
def Means(RSD, BN, CN, alpha, beta):
|
| 13 |
+
tmp, err = integrate.quad(MeanIntegrad, -inf, inf, args=(RSD, BN, CN, alpha, beta))
|
| 14 |
+
prob = 1. - tmp * sqrt(BN) / sqrt(2. * pi) / RSD / 2.
|
| 15 |
|
| 16 |
+
return prob
|
| 17 |
|
| 18 |
+
def FPR_alpha_residual(params, prob, RSD, BN, CN, beta):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
alpha = params['alpha'].value
|
| 21 |
+
tmp = Means(RSD, BN, CN, alpha, beta)
|
| 22 |
+
FPR = tmp
|
| 23 |
|
| 24 |
+
return prob - FPR
|
| 25 |
|
| 26 |
+
def FNR_beta_residual(params, prob, RSD, BN, CN, alpha):
|
| 27 |
|
| 28 |
beta = params['beta'].value
|
| 29 |
+
tmp = Means(RSD, BN, CN, alpha, beta)
|
| 30 |
+
FNR = 1. - tmp
|
| 31 |
+
|
| 32 |
+
return prob - FNR
|
| 33 |
|
| 34 |
+
def FitParams(FPR, FNR, Bmean, Bstd, BN, Cmean, Cstd, CN):
|
| 35 |
|
| 36 |
+
RSD = max(Bstd/Bmean,Cstd/Cmean)
|
| 37 |
|
| 38 |
+
beta = 1.
|
| 39 |
alpha_params = Parameters()
|
| 40 |
alpha_params.add('alpha', value=1.)
|
| 41 |
+
alpha_out = minimize(FPR_alpha_residual, alpha_params, args=(FPR/100., RSD, BN, CN, beta))
|
| 42 |
|
| 43 |
beta_params = Parameters()
|
| 44 |
+
beta_params.add('beta', value=2.)
|
| 45 |
+
beta_out = minimize(FNR_beta_residual, beta_params, args=(FNR/100., RSD, BN, CN, alpha_out.params['alpha'].value))
|
| 46 |
|
| 47 |
return alpha_out.params['alpha'].value, beta_out.params['beta'].value
|
| 48 |
|
|
|
|
| 54 |
|
| 55 |
alpha = 0.
|
| 56 |
beta = 0.
|
| 57 |
+
Bmean = 100.
|
| 58 |
+
Cmean = 100.
|
| 59 |
+
Bstd = 20.
|
| 60 |
+
Cstd = 20.
|
| 61 |
+
BN = 3
|
| 62 |
+
CN = 3
|
| 63 |
+
FPR = 5.0
|
| 64 |
+
FNR = 5.0
|
| 65 |
|
| 66 |
if request.method == 'POST':
|
| 67 |
+
Bmean = float(request.form["Bmean"])
|
| 68 |
+
Cmean = float(request.form["Cmean"])
|
| 69 |
+
Bstd = float(request.form["Bstd"])
|
| 70 |
+
Cstd = float(request.form["Cstd"])
|
| 71 |
+
BN = int(request.form["BN"])
|
| 72 |
+
CN = int(request.form["CN"])
|
| 73 |
FPR = float(request.form["FPR"])
|
| 74 |
FNR = float(request.form["FNR"])
|
| 75 |
|
| 76 |
+
alpha, beta = FitParams(FPR, FNR, Bmean, Bstd, BN, Cmean, Cstd, CN)
|
| 77 |
|
| 78 |
|
| 79 |
+
return render_template('equivalency_index.html', Bmean=Bmean, Cmean=Cmean, Bstd=Bstd, Cstd=Cstd, \
|
| 80 |
+
BN=BN, CN=CN, FPR=FPR, FNR=FNR, alpha=alpha, beta=beta)
|
equivalency_module/templates/equivalency_index.html
CHANGED
|
@@ -8,13 +8,6 @@
|
|
| 8 |
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.min.js" integrity="sha384-VHvPCCyXqtD5DqJeNxl2dtTyhF78xXNXdkwX1CZeRusQfRKp+tA7hAShOK/B/fQ2" crossorigin="anonymous"></script>
|
| 9 |
<link rel= "stylesheet" type= "text/css" href= "{{ url_for('static',filename='styles.css') }}">
|
| 10 |
|
| 11 |
-
<style>
|
| 12 |
-
table, th, td {
|
| 13 |
-
border: 1px solid black;
|
| 14 |
-
border-collapse: collapse;
|
| 15 |
-
}
|
| 16 |
-
</style>
|
| 17 |
-
|
| 18 |
</head>
|
| 19 |
|
| 20 |
<img src="{{ url_for('static',filename='images/FDAlogo.png') }}" style="float: left;" height="100"/>
|
|
@@ -37,19 +30,30 @@ table, th, td {
|
|
| 37 |
|
| 38 |
<form method="POST" id="theForm" align="left">
|
| 39 |
|
| 40 |
-
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
FPR (%): <input name="FPR" id="FPR" value="{{FPR}}" step="any" min="0.01" type="number" required> <br>
|
| 43 |
-
FNR (%): <input name="FNR" id="FNR" value="{{FNR}}" step="any" min="0.01" type="number" required> <br>
|
| 44 |
|
| 45 |
-
Acceptance criterion factor:
|
| 46 |
{% if alpha != 0 %}
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
{
|
| 52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
|
| 54 |
<br> <button type="submit">Calculate</button>
|
| 55 |
|
|
|
|
| 8 |
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.min.js" integrity="sha384-VHvPCCyXqtD5DqJeNxl2dtTyhF78xXNXdkwX1CZeRusQfRKp+tA7hAShOK/B/fQ2" crossorigin="anonymous"></script>
|
| 9 |
<link rel= "stylesheet" type= "text/css" href= "{{ url_for('static',filename='styles.css') }}">
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
</head>
|
| 12 |
|
| 13 |
<img src="{{ url_for('static',filename='images/FDAlogo.png') }}" style="float: left;" height="100"/>
|
|
|
|
| 30 |
|
| 31 |
<form method="POST" id="theForm" align="left">
|
| 32 |
|
| 33 |
+
<h3> Baseline </h3>
|
| 34 |
+
Mean: <input name="Bmean" id="Bmean" value="{{Bmean}}" step="any" min="0.01" type="number" required> <br>
|
| 35 |
+
Stdev: <input name="Bstd" id="Bstd" value="{{Bstd}}" step="any" min="0.01" type="number" required> <br>
|
| 36 |
+
N: <input name="BN" id="BN" value="{{BN}}" step="1" min="1" type="number" onchange="this.value = parseInt(this.value);" required> <br>
|
| 37 |
+
<h3> Comparator </h3>
|
| 38 |
+
Mean: <input name="Cmean" id="Cmean" value="{{Cmean}}" step="any" min="0.01" type="number" required> <br>
|
| 39 |
+
Stdev: <input name="Cstd" id="Cstd" value="{{Cstd}}" step="any" min="0.01" type="number" required> <br>
|
| 40 |
+
N: <input name="CN" id="CN" value="{{CN}}" step="1" min="1" type="number" onchange="this.value = parseInt(this.value);" required> <br>
|
| 41 |
+
<h3> Statistical limits </h3>
|
| 42 |
FPR (%): <input name="FPR" id="FPR" value="{{FPR}}" step="any" min="0.01" type="number" required> <br>
|
| 43 |
+
FNR (%): <input name="FNR" id="FNR" value="{{FNR}}" step="any" min="0.01" type="number" required> <br>
|
| 44 |
|
|
|
|
| 45 |
{% if alpha != 0 %}
|
| 46 |
+
<h3> Result: </h3>
|
| 47 |
+
{% set accept = alpha*Bmean %}
|
| 48 |
+
The acceptance criterion factor is {{'%0.2f'%alpha|float}}. Therefore, your mean comparator results must be less than
|
| 49 |
+
{{'%0.2f'%accept|float}}. <br>
|
| 50 |
+
{% if accept >= Cmean %}
|
| 51 |
+
The acceptance criteria has been met. However, this only applies if the analyte of interest has a margin of safety (MOS)
|
| 52 |
+
greater than {{'%0.1f'%beta|float}}. <br>
|
| 53 |
+
{% else %}
|
| 54 |
+
The acceptance criteria has not been met. Additional justification and/or analyses will be required. <br>
|
| 55 |
+
{% endif %}
|
| 56 |
+
{% endif %}
|
| 57 |
|
| 58 |
<br> <button type="submit">Calculate</button>
|
| 59 |
|