-
Notifications
You must be signed in to change notification settings - Fork 2
/
FractControllerAcuityLineByLine.j
157 lines (135 loc) · 6.68 KB
/
FractControllerAcuityLineByLine.j
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/*
This file is part of FrACT10, a vision test battery.
Copyright © 2021 Michael Bach, bach@uni-freiburg.de, <https://michaelbach.de>
FractControllerAcuityLineByLine.j
This module draws a 5-optotype line to aid in refraction.
It's task does not really fit the testing via trial/run, but whatever
Created by mb on 2021-12-21.
*/
@import "FractControllerAcuity.j"
@implementation FractControllerAcuityLineByLine: FractControllerAcuity {
float localLogMAR;
}
- (float) resultValue4Export {return [self acuityResultValue4Export];}
- (CPString) composeExportString {return [self acuityComposeExportString];}
- (id) initWithWindow: (CPWindow) aWindow parent: (HierarchyController) parent {
localLogMAR = 0.3; // we need this method only for this line, starting acuity
self = [super initWithWindow: aWindow parent: parent];
return self;
}
- (float) stimThresholderunitsFromDeviceunits: (float) d {return [self acuityStimThresholderunitsFromDeviceunits: d];}
- (void) trialStart { //console.info("FractControllerAcuityLineByLine>trialStart");
iTrial = 1;
stimStrengthInDeviceunits = [MiscSpace strokePixelsFromDecVA: [MiscSpace decVAfromLogMAR: localLogMAR]];
state = kStateDrawFore;
[[selfWindow contentView] setNeedsDisplay: YES];
}
- (void) drawStimulusInRect: (CGRect) dirtyRect forView: (FractView) fractView { //console.info("FractControllerAcuityLetters>drawStimulusInRect");
const verticalOffset = 150;
[self prepareDrawing];
switch(state) {
case kStateDrawBack: break;
case kStateDrawFore:
const chartmodeNotConstVA = ![Settings lineByLineChartModeConstantVA];
CGContextSaveGState(cgc);
const lineRange = [Settings lineByLineLinesIndex];
if (lineRange > 0) {
if (chartmodeNotConstVA) {
stimStrengthInDeviceunits /= Math.pow(2, 1/10);
}
}
for (let iLine = -lineRange; iLine <= lineRange; iLine++) {
const usedAlternativesArray = [];
let optotypeDistance = 1; // according to ETDRS
if ([Settings testOnLineByLineDistanceType] == 0) {// according to DIN EN ISO 8596
optotypeDistance = 0.4;
const localDecVA = [MiscSpace decVAfromLogMAR: localLogMAR];
if (localDecVA >= 0.06) optotypeDistance = 1;
if (localDecVA >= 0.16) optotypeDistance = 1.5;
if (localDecVA >= 0.4) optotypeDistance = 2;
if (localDecVA >= 1.0) optotypeDistance = 3;
}
optotypeDistance = (1 + optotypeDistance) * stimStrengthInDeviceunits * 5;
if (iLine >= -1) CGContextTranslateCTM(cgc, 0, optotypeDistance);
const iRange = [Settings lineByLineHeadcountIndex];
for (let i = -iRange; i <= iRange; i++) {//iDex 0…3 → 1, 3, 5, 7
const tempX = i * optotypeDistance;
CGContextTranslateCTM(cgc, -tempX, -verticalOffset);
let currentAlternative = [Misc iRandom: nAlternatives];
while (usedAlternativesArray.includes(currentAlternative)) {
currentAlternative = [Misc iRandom: nAlternatives];
}
usedAlternativesArray.push(currentAlternative);
switch([Settings testOnLineByLine]) {
case 1: [optotypes drawLetterWithStriokeInPx: stimStrengthInDeviceunits letterNumber: currentAlternative]; break;
case 2: [optotypes drawLandoltWithStrokeInPx: stimStrengthInDeviceunits landoltDirection: currentAlternative]; break;
default: console.log("Line-by-line: unsupported optotype-id: ", [Settings testOnLineByLine]);
}
CGContextTranslateCTM(cgc, +tempX, verticalOffset);
}
if (chartmodeNotConstVA) {
stimStrengthInDeviceunits /= Math.pow(2, 1/10);
}
}
CGContextRestoreGState(cgc);
CGContextSetFillColor(cgc, [CPColor blueColor]);
CGContextSetTextDrawingMode(cgc, kCGTextFill);
CGContextSelectFont(cgc, "24px sans-serif"); // must be CSS
const s = [Misc stringFromNumber: localLogMAR decimals: 1 localised: YES] + " LogMAR "
let stringWidth = 140, lineHeight = 24;
try {
const tInfo = cgc.measureText(s);
stringWidth = tInfo.width;
//lineHeight = tInfo.emHeightAscent;// + tInfo.emHeightDescent;
} catch(e) {}
CGContextShowTextAtPoint(cgc, viewWidth2 - stringWidth, -viewHeight2 + lineHeight, s);
if (lineRange > 0) {
CGContextShowTextAtPoint(cgc, viewWidth2 - stringWidth, -viewHeight2 + 2 * lineHeight, "(middle line)");
}
CGContextShowTextAtPoint(cgc, -viewWidth2, -viewHeight2 + lineHeight, " Use ↑↓, ⇄");
break;
default: break;
}
if ([Settings enableTouchControls] && (!responseButtonsAdded)) {
const sze = 50, sze2 = sze / 2;
[self buttonCenteredAtX: viewWidth-sze2 y: 0 size: sze title: "6"];
[self buttonCenteredAtX: sze2 y: 0 size: sze title: "4"];
[self buttonCenteredAtX: viewWidth2 y: -viewHeight2 + sze2 size: sze title: "8"];
[self buttonCenteredAtX: viewWidth2 y: viewHeight2 - sze2 size: sze title: "2"];
[self buttonCenteredAtX: viewWidth - sze2 y: viewHeight2 - sze2 size: sze title: "Ø"];
}
CGContextRestoreGState(cgc);
CGContextTranslateCTM(cgc, 0, -verticalOffset);//so crowding is also offset
[super drawStimulusInRect: dirtyRect];
}
- (void) runStart { //console.info("FractControllerAcuityLetters>runStart");
nAlternatives = 10;
switch([Settings testOnLineByLine]) {
case 1: nAlternatives = 10; break;
case 2: nAlternatives = 8; break; // 4 Landolt orientations not supported
}
nTrials = 9999;
[self setCurrentTestResultUnit: "LogMAR"];
[super runStart];
}
- (void) processKeyDownEvent { //console.info("FractControllerAcuityLineByLine>processKeyDownEvent");
switch (responseKeyChar) {
case CPLeftArrowFunctionKey:
case "4":
case CPRightArrowFunctionKey:
case "6":
break; // just reshuffle the optotypes
case CPUpArrowFunctionKey:
case "8":
localLogMAR +=0.1; break;
case CPDownArrowFunctionKey:
case "2":
localLogMAR -=0.1; break;
case "5":
default:
[self runEnd]; return;
}
localLogMAR = [Misc limit: localLogMAR lo: -1 hi: 3];
[self trialStart];
}
@end