5 \filetitle{Specimen classification (Animation)}
7 \noindent{\verb{!%W% %G%!}
10 > module Main (main) where
11 > import GoferPreludeBits
19 \end{verbatim}\end{vb}
21 Here we give the Gofer specification of the Cytoline/ILDAS
22 specimen classifier. This is based on the
23 description given in appendix C of the software requirements
24 specification. Note however that the scheme here
25 is modified to admit that even normal specimens contain
26 abnormal objects. Thus, beta distributions are allowed
27 for both the normal and abnormal specimens.
30 The purpose of the specimen classifier is to integrate all
31 evidence from the low and high resolution passes (and in
32 Cytoline, from interactive review), to deliver a decision
34 \footnote{We will consistently adopt the order Abnormal, Normal
35 Leukocyte, Junk for object classes, which we
36 will abbreviate to {\tt A, N, L, J}, and Abnormal, Normal for
37 specimen classifications, which we will abbreviate as {\tt Abn, Nrm}}
39 \begin{Dec}{SpecDecision}
40 The specimen level decision.
43 > data SpecDecision = SignOut | Review
45 \end{verbatim}\end{vb}\end{Dec}
48 \sectionH{Specimen level evidence}
50 \begin{Dec}{SpecEvidence}
51 We assume that all evidence is expressed as likelihoods of observed
52 features given each the four object classes, Normal, Abnormal, Leukocyte,
53 Junk. A posterior probability is assumed to exist for every object seen,
54 based (only) on the most refined evidence available.
58 > type SpecEvidence = [(Int, ObjProbVec)]
59 > type ObjProbVec = (Prob, Prob, Prob, Prob)
61 \end{verbatim}\end{vb}\end{Dec}
64 \begin{Def}{composeEvidence}
66 Extract object level evidence from the low and high resolution
67 target structures. The evidence those objects which were not rescaned
68 is fabricated by replicating the mean PP within the object class,
69 according to the count in that object class.
72 > type TargetList = ()
73 > type TargetRecord = ()
75 > composeEvidence :: TargetList -> TargetList -> SpecEvidence
76 > composeEvidence high low
77 > = extractEvidence low ++ extractEvidence high
80 > = [(1, getPPs t) | t <- ts] ++
81 > [(countA', meanPPA),
87 > countA' = countA - length ts -- avoid double count
89 The extraction functions should come from the TargetList ADT,
90 but we leave this as yet unresolved.
92 > getPPs :: TargetRecord -> ObjProbVec
95 > countA, countN, countL, countJ,
96 > meanPPA, meanPPN, meanPPL, meanPPJ)
97 > = extractFromStruct ts
98 > extractFromStruct ts = undefined
101 \end{verbatim}\end{vb}\end{Def}
103 \sectionH{Specimen level model}
105 \begin{Dec}{SpecModel}
106 It is assumed that the following model parameters are known:
108 \item {\tt specPriorSM} --- Prior probability for specimen classification
109 \item {\tt objProportionsSM} --- Mean proportions of objects, by class, on normal and
111 \item {\tt cvAbnObjSM} --- Coefficient of variation in proportion of abnormal cells
112 on normal and abnormal specimens.
113 \item {\tt fnrSM} --- Acceptable false negative rate.
115 There is facility to read parameters from a file.
118 > type SpecModel = SpecModelImp
123 > specPriorSM, abnPropsSM, qLqJSM, cvAbnObjSM, betaParamsSM, fnrSM,
124 > calibFuncSM, readSpecModelSM, testSpecModelSM
128 > specPriorSM :: SpecModel -> (Double, Double)
129 > abnPropsSM :: SpecModel -> (Double, Double)
130 > qLqJSM :: SpecModel -> (Double, Double)
131 > cvAbnObjSM :: SpecModel -> (Double, Double)
132 > betaParamsSM :: SpecModel -> (BetaParams, BetaParams)
133 > fnrSM :: SpecModel -> Double
134 > calibFuncSM :: SpecModel -> (Double -> Double)
135 > readSpecModelSM :: FileName -> Job s s SpecModel
136 > testSpecModelSM :: SpecModel
138 > type FileName = String
139 > type BetaParams = (Double, Double, Lognum)
141 > type SpecModelImp = ((Double, Double), (Double, Double), (Double, Double),
142 > (Double, Double), (BetaParams, BetaParams), Double)
144 > readSpecModelSM = undefined
146 > specPriorSM (a,b,c,d,e,f) = a
147 > abnPropsSM (a,b,c,d,e,f) = b
148 > qLqJSM (a,b,c,d,e,f) = c
149 > cvAbnObjSM (a,b,c,d,e,f) = d
150 > betaParamsSM (a,b,c,d,e,f) = e
151 > fnrSM (a,b,c,d,e,f) = f
152 > calibFuncSM (a,b,c,d,e,f) = id
154 > calcBetaParams :: SpecModel -> (BetaParams, BetaParams)
156 > = (betaAbn, betaNrm)
158 > betaAbn = cv qAbnA cvAbn
159 > betaNrm = cv qNrmA cvNrm
160 > (qAbnA, qNrmA) = abnPropsSM sm
161 > (cvAbn, cvNrm) = cvAbnObjSM sm
163 > betaAbnDist sm = beta p
165 > (_,p) = betaParamsSM sm
169 \end{verbatim}\end{vb}\end{Dec}
172 \sectionH{Specimen classifier decision}
174 \begin{Def}{SpecClassifier}
175 Make a Sign-out / Review decision on the basis of evidence
176 from all objects seen. The decision is a simple threshold
177 --- is the predicted false negative rate (fnr) acceptable
178 in the light of the posterior probability of abnormality
183 > type SpecClass a = (a,a) -> a
187 > specClassifier :: SpecModel -> SpecEvidence -> SpecDecision
188 > specClassifier sm evidence
190 > = if (truePostProb / pAbn) < fnr then
196 > truePostProb = calibFunc (pAbnGvnEv sm evidence)
197 > (pAbn, _) = specPriorSM sm
198 > calibFunc = calibFuncSM sm
201 \end{verbatim}\end{vb}\end{Def}
203 \sectionH{Specimen posterior probability}
205 \begin{Def}{pAbnGvnEv}
206 Compute the posterior probability $P(S=\mbox{Abn} | {\bf x})$.
209 > type LikelyHood = Prob
210 > type LikeVec = [LikelyHood]
212 > pAbnGvnEv :: SpecModel -> SpecEvidence -> Prob
213 > pAbnGvnEv sm evidence
216 > (pEvGvnAbn * pAbnL) /
217 > (pEvGvnAbn * pAbnL + pEvGvnNrm * pNrmL)) -- Bayes theorem
220 > pEvGvnAbn = lklf sm betaAbn likelihoods
221 > pEvGvnNrm = lklf sm betaNrm likelihoods
223 Convert the evidence, which is in the form of posterior probably vectors
224 on objects, into likelyhood vectors on objects.
226 > likelihoods :: SpecEvidence
227 > likelihoods = [(c,bayes ppvec) | (c,ppvec) <- evidence]
229 > bayes (ppA,ppN,ppL,ppJ) =
230 > (ppA/pA, ppN/pN, ppL/pL, ppJ/pJ)
232 Form the marginal object priors from the specimen-conditional priors.
235 > pA = let it = qAbnA * pAbn + qNrmA * pNrm in it
236 > pN = let it = 1.0 - (pA + pL + pJ) in it
238 Extract model parameters.
240 > (pAbnL, pNrmL) = (toLognum pAbn, toLognum pNrm)
241 > (pAbn, pNrm) = specPriorSM sm
242 > (pL, pJ) = qLqJSM sm
243 > (betaAbn, betaNrm) = let it = betaParamsSM sm in it
244 > (qAbnA, qNrmA) = abnPropsSM sm
246 \end{verbatim}\end{vb}\end{Def}
249 Computes the likelyhood $p({\bf x} | S=s)$. The specimen class, $s$,
250 (Normal or Abnormal) is passed by the within
251 specimen class objectPriors ({\tt priors}) and coefficient of
252 variation of abnormal objects {\tt v}). ${\bf x}$, representing
253 feature data for all observed objects, is passed as the list
254 of likelyhood vectors for for each object. In fact, these likelihoods
255 are are required only to proportionality.
258 > lklf :: SpecModel -> BetaParams -> SpecEvidence -> Lognum
259 > lklf sm betaParams evi =
260 > integratePowN 6 (fromRational 0.000001, fromRational 0.999)
262 > let r' = toDouble r in
263 > beta betaParams r *
264 > product [toLognum (let y =
265 > lA * r' * (1.0 - q) +
266 > lN * (1.0 - r')*(1.0 - q) +
271 >-- ("y="++show y ++ " r=" ++ show r ++
272 >-- " r'="++ show r' ++ " lA=" ++ show lA)
274 > | (c,(lA,lN,lL,lJ)) <- evi]
277 > (qL, qJ) = qLqJSM sm
280 > pow :: (Real b) => Lognum -> b -> Lognum
282 > (LN x) `pow` n = LN (x* toDouble n)
284 \end{verbatim}\end{vb}\end{Def}
289 \sectionH{Function integration}
291 (These definitions more properly belong in a library module).
293 \begin{Def}{integrate, limitFromAbove}~~~
297 \verb@integrate (a,b) f@ = $ \int_a^b f (x) dx $
299 \verb@limitFromAbove a f@ = $ \lim_{x \rightarrow a} f (x) $
304 > integrate :: (Enum a, Fractional a) =>
305 > (a, a) -> (a -> a) -> a
306 > integrate (a, b) f =
307 >-- limitFromAbove zero
308 >-- (\dx -> sum [ (f x) * dx | x <- [a, a+dx ..b]])
311 > integratePowN :: (Enum a, Fractional a, Floating a) =>
312 > Int -> (a, a) -> (a -> a) -> a
313 > integratePowN n (a,b) f
314 > = integrate (invufunc a, invufunc b) (\u -> (f . ufunc) u * dxdu u)
316 > invufunc x = x ** (fromInt 1 / fromInt n)
318 > dxdu u = (u ^ (n-1)) * fromInt n
320 > simpsons :: (Enum a, Fractional a) =>
321 > (a, a) -> (a -> a) -> a
324 > (f a + f b + v4 * odds + v2 * evens)
326 > h = ((b-a)/fromInt(n-1))
327 > odds = (sum) [f x | x <- (take ((n-1)`div`2) [a+h, a+v3*h..])]
328 > evens = (sum) [f x | x <- (take ((n-3)`div`2) [a+v2*h, a+v4*h..])]
330 > [v1,v2,v3,v4] = map fromInt [1..4]
333 \end{verbatim}\end{vb}\end{Def}
335 \sectionH{The beta distribution}
337 A beta distribution is used to model the proportion of abnormal objects
338 on normal and abnormal specimens.
341 This is a distribution of a continuous variable in the range 0 to 1.
344 > beta :: BetaParams -> Lognum -> Lognum
348 > f x = (x `pow` (a-1.0)) * (((one - x) `pow` (b-1.0)))
351 \end{verbatim}\end{vb}\end{Def}
360 > sm = ((0.5, 0.5), -- P(S)
361 > (0.005, 0.0002), -- (P(O=A|S=Abn), P(O=A|S=Nrm))
362 > (0.2,0.2), -- qL, qN
363 > (0.7, 0.7), -- cv - (Abn, Nrm)
364 > calcBetaParams sm, -- beta dist parmaeters
368 > = unlines [s "P(S)=" ++ (show . specPriorSM) sm,
369 > s "(P(O=A|S=Abn), P(O=A|S=Nrm))=" ++ (show . abnPropsSM) sm,
370 > s "(qL, qN)=" ++ (show . qLqJSM) sm,
371 > s "cvAbnObj=" ++ (show . cvAbnObjSM) sm]
372 > where s = (ljustify 33 . show)
374 > nrm = (0.0, 1.0, 0.0, 0.0)
375 > abn = (1.0, 0.0, 0.0, 0.0)
378 > pAbnGvnEv testSpecModelSM [(nAbn, abn), (nNrm, nrm)]
381 > cv :: Double -> Double -> BetaParams
384 > a = (1.0 - m) / v^2 - m
386 > n = integratePowN 6
387 > (toLognum 0.000001, toLognum 0.999)
388 > (\x-> beta (a,b, toLognum 1) x)
392 > (putLine . showSpecModel) testSpecModelSM >>
393 > putLine (tabulate2D 5
402 tabulate2D :: (Show{-was:Text-} a, Show{-was:Text-} b, Show{-was:Text-} c) => Int -> [a] -> [b] -> (a->b->c) -> String
404 > tabulate2D w alist blist f
405 > = (jshow2' " ") ++ " " ++ concat (map jshow alist) ++ "\n\n" ++
406 > unlines [jshow2 b ++ " : " ++ concat [jshow (f a b) | a <- alist ]
409 > jshow x = (ljustify w (take w (showFixed x))) ++ " "
410 > jshow2 x = (ljustify (w*2) (take w (showFixed x))) ++ " "
411 > jshow2' x = (ljustify (w*2) (take w (show x))) ++ " "
414 > tabulate1D :: (Show{-was:Text-} a, Show{-was:Text-} b) => Int -> [a] -> (a->b) -> String
415 > tabulate1D w alist f
416 > = unlines [jshow a ++ " " ++ jshow (f a)
419 > jshow x = (ljustify w (show x))
422 Showfixed formats a Real in *non* exponent form.
423 Big hack --- must be a better way !?!
425 > showFixed :: Real a => a -> String
427 > | dx >= 1.0 = show x
428 > | dx < 0.0000001 = show x
429 > | otherwise = showFixed' 0 dx
432 > showFixed' n x = if x > 0.1 then
433 > "0." ++ (take n (repeat '0')) ++ show (round (x*1000000.0))
435 > showFixed' (n+1) (x*fromInt 10)