source: extensions/net.sf.basedb.reggie/branches/ticket-489/resources/sampleproc/histology_score.jsp @ 2123

Last change on this file since 2123 was 2123, checked in by Nicklas Nordborg, 8 years ago

References #489: Histology scoring wizard

Information about the next set of HE glass is now retreived and a GUI is created. There is functionality for selecting which sample to score, enter scores and setting the GoodStain annotation. Some validation has been implemented, but it is not possible to save data back to BASE. Manually selecting a different HE glass is not implemented.

File size: 19.4 KB
Line 
1<%@ page
2  pageEncoding="UTF-8"
3  session="false"
4  import="net.sf.basedb.core.User"
5  import="net.sf.basedb.core.DbControl"
6  import="net.sf.basedb.core.SessionControl"
7  import="net.sf.basedb.core.Application"
8  import="net.sf.basedb.clients.web.Base" 
9  import="net.sf.basedb.clients.web.extensions.ExtensionsControl"
10  import="java.text.SimpleDateFormat"
11  import="java.util.Date"
12%>
13<%@ taglib prefix="base" uri="/WEB-INF/base.tld" %>
14<%@ taglib prefix="p" uri="/WEB-INF/path.tld" %>
15<%@ taglib prefix="tbl" uri="/WEB-INF/table.tld" %>
16<%@ taglib prefix="t" uri="/WEB-INF/tab.tld" %>
17<%
18final SessionControl sc = Base.getExistingSessionControl(request, true);
19final String ID = sc.getId();
20final float scale = Base.getScale(sc);
21final String home = ExtensionsControl.getHomeUrl("net.sf.basedb.reggie");
22DbControl dc = null;
23try
24{
25  dc = sc.newDbControl();
26  final User user = User.getById(dc, sc.getLoggedInUserId());
27  final String today = new SimpleDateFormat("yyyyMMdd").format(new Date());
28%>
29<base:page type="default" >
30<base:head scripts="ajax.js,tabcontrol.js" styles="path.css,toolbar.css,tabcontrol.css">
31  <link rel="stylesheet" type="text/css" href="../css/reggie.css">
32  <script language="JavaScript" src="../reggie.js" type="text/javascript" charset="UTF-8"></script>
33
34<script language="JavaScript">
35var debug = 1;
36var currentStep = 1;
37var selectedSample;
38var heGlass;
39
40function init()
41{
42  var frm = document.forms['reggie'];
43  heGlass = getHeGlassForScoring();
44 
45  if (heGlass != null && heGlass.length > 0)
46  {
47    // Generate HE glass HTML
48    var html = '';
49    for (var glassNo = 0; glassNo < heGlass.length; glassNo++)
50    {
51      var glass = heGlass[glassNo];
52      html += createHeGlassSection(glass);
53    }
54    setInnerHTML('he-glass-container', html);
55    Main.show('he-glass-container');
56   
57    // Fill form with know information
58    var firstGoodSample;
59    for (var glassNo = 0; glassNo < heGlass.length; glassNo++)
60    {
61      var glass = heGlass[glassNo];
62      for (var sampleNo = 0; sampleNo < glass.samples.length; sampleNo++)
63      {
64        var sample = glass.samples[sampleNo];
65       
66        // Save ID of HE glass since we need that later
67        sample.heGlassId = glass.id;
68       
69        // Save the first GoodStain sample so we can select it later
70        if (sample.GoodStain && firstGoodSample == null)
71        {
72          firstGoodSample = sample;
73        }
74       
75        // Calculate sum of scores
76        var total = calculateTotalScore(sample)
77       
78        // Generate text inside table cell
79        var html = sample.name.replace(/\.he\d+$/, '');
80        html += '<div class="progress" id="'+sample.id+'.progress">' + total + '%</div>';
81       
82        var td = document.getElementById(sample.heGlassId+'-'+sample.well.location);
83        td.innerHTML = html;
84        td.sample = sample;
85        setSampleClassName(sample);
86       
87        td.addEventListener('click', sampleOnClick, false);
88      }
89    }
90   
91    if (firstGoodSample != null)
92    {
93      selectSample(firstGoodSample);
94      Main.show('score-container');
95    }
96    Main.show('gocreate');
97  }
98  else
99  {
100    var msg = 'No HE glass available for scoring.';
101    msg += ' Specify HE glass name above to re-score.';
102    setInnerHTML('he-glass-container', '<div class="messagecontainer note">'+msg+'</div>');
103    Main.show('he-glass-container');
104  }
105}
106
107function getHeGlassForScoring()
108{
109  var frm = document.forms['reggie'];
110 
111  var request = Ajax.getXmlHttpRequest();
112  try
113  {
114    showLoadingAnimation('Loading next HE glass for scoring...');
115    var url = '../Histology.servlet?ID=<%=ID%>&cmd=GetHeGlassToScore';   
116    request.open("GET", url, false); 
117    request.send(null);
118  }
119  finally
120  {
121    hideLoadingAnimation();
122  }
123 
124  if (debug) Main.debug(request.responseText);
125  var response = JSON.parse(request.responseText); 
126  if (response.status != 'ok')
127  {
128    setFatalError(response.message);
129    return false;
130  }
131  return response.heGlasses;
132}
133
134function createHeGlassSection(glass)
135{
136  var html = '';
137  html += '<div class="he-glass">';
138 
139  html += '<div class="he-name">' + Main.encodeTags(glass.name || '') + '</div>';
140 
141  html += '<div class="he-info">';
142  html += '<span class="he-tray">' +  Main.encodeTags(glass.tray || '') + '</span>; <span class="he-position">' + Main.encodeTags(glass.position || '') + '</span>';
143  html += '<div class="he-comment">' + Main.encodeTags(glass.comments || '') + '</div>';
144  html += '</div>';
145 
146  html += '<table class="he-table">';
147  html += '<tr>';
148  html += '<td class="not-used" id="'+glass.id+'-A1"></td>';
149  html += '<td class="not-used" id="'+glass.id+'-A2"></td>';
150  html += '</tr>';
151  html += '<tr>';
152  html += '<td class="not-used" id="'+glass.id+'-B1"></td>';
153  html += '<td class="not-used" id="'+glass.id+'-B2"></td>';
154  html += '</tr>';
155  html += '<tr>';
156  html += '<td class="not-used" id="'+glass.id+'-C1"></td>';
157  html += '<td class="not-used" id="'+glass.id+'-C2"></td>';
158  html += '</tr>';
159  html += '</table>';
160  html += '</div>';
161
162  return html;
163 
164}
165
166function setSampleClassName(sample)
167{
168  var className = 'used';
169  if (sample.GoodStain)
170  {
171    className += ' good-stain';
172  }
173 
174  if (sample.ScoreTotal == 0)
175  {
176    className += ' score-none';
177  }
178  else if (sample.ScoreTotal != 100 || !sample.ScoreComplete)
179  {
180    className += ' score-incomplete';
181  }
182  else
183  {
184    className += ' score-complete';
185  }
186 
187  if (selectedSample && selectedSample.id == sample.id)
188  {
189    className += ' selected';
190  }
191 
192  var td = document.getElementById(sample.heGlassId+'-'+sample.well.location);
193  td.className = className;
194}
195
196function selectSample(sample)
197{
198  var frm = document.forms['reggie'];
199 
200  // Sample name
201  setInnerHTML('sample.name', sample.name);
202 
203  // Scores
204  frm.score_invasive_cancer.value = sample.ScoreInvasiveCancer;
205  frm.score_insitu_cancer.value = sample.ScoreInsituCancer;
206  frm.score_lymphocytes.value = sample.ScoreLymphocytes;
207  frm.score_normal.value = sample.ScoreNormal;
208  frm.score_stroma.value = sample.ScoreStroma;
209  frm.score_fat.value = sample.ScoreFat;
210 
211  // Total score + score complete
212  var total = calculateTotalScore(sample);
213  setInnerHTML('score_total', total);
214  if (total == 100)
215  {
216    frm.score_complete.disabled = false;
217    frm.score_complete.checked = sample.ScoreComplete;
218    setInputStatus('score_total', '', 'valid');
219  }
220  else
221  {
222    frm.score_complete.disabled = true;
223    frm.score_complete.checked = false;
224    setInputStatus('score_total', 'Not 100%', 'warning');
225  }
226 
227  // Good stain
228  if (sample.GoodStain)
229  {
230    frm.good_stain.checked = true;
231    frm.good_stain.disabled = true;
232  }
233  else
234  {
235    frm.good_stain.checked = false;
236    frm.good_stain.disabled = false;
237  }
238 
239  // Comments
240  frm.comments.value = sample.comments;
241 
242  // Class name of selected table cell
243  if (selectedSample != null)
244  {
245    var td = document.getElementById(selectedSample.heGlassId+'-'+selectedSample.well.location);
246    Main.removeClass(td, ' selected');
247  }
248 
249  selectedSample = sample;
250  setSampleClassName(sample);
251 
252  frm.score_invasive_cancer.focus();
253}
254
255function sampleOnClick(event)
256{
257  var td = event.currentTarget;
258  selectSample(td.sample);
259}
260
261function checkScores()
262{
263  var frm = document.forms['reggie'];
264
265  selectedSample.ScoreInvasiveCancer = toScore(frm.score_invasive_cancer.value);
266  selectedSample.ScoreInsituCancer = toScore(frm.score_insitu_cancer.value);
267  selectedSample.ScoreLymphocytes = toScore(frm.score_lymphocytes.value);
268  selectedSample.ScoreNormal = toScore(frm.score_normal.value);
269  selectedSample.ScoreStroma = toScore(frm.score_stroma.value);
270  selectedSample.ScoreFat = toScore(frm.score_fat.value);
271 
272  var oldTotal = selectedSample.ScoreTotal;
273  var total = calculateTotalScore(selectedSample);
274 
275  setInnerHTML('score_total', total);
276  setInnerHTML(selectedSample.id+'.progress', total + '%');
277 
278  if (total == 100)
279  {
280    frm.score_complete.disabled = false;
281    if (oldTotal != total)
282    {
283      // Only check the 'Score complete' if value has changed
284      frm.score_complete.checked = true;
285      selectedSample.ScoreComplete = true;
286    }
287    setInputStatus('score_total', '', 'valid');
288  }
289  else
290  {
291    frm.score_complete.disabled = true;
292    frm.score_complete.checked = false;
293    selectedSample.ScoreComplete = false;
294    setInputStatus('score_total', 'Not 100%', 'warning');
295  }
296
297  setSampleClassName(selectedSample);
298}
299
300function calculateTotalScore(sample)
301{
302  var total = sumScore(sample.ScoreInvasiveCancer);
303  total += sumScore(sample.ScoreInsituCancer);
304  total += sumScore(sample.ScoreLymphocytes);
305  total += sumScore(sample.ScoreNormal);
306  total += sumScore(sample.ScoreStroma);
307  total += sumScore(sample.ScoreFat);
308  sample.ScoreTotal = total;
309  return total;
310}
311
312function toScore(value)
313{
314  var score = parseInt(value);
315  return isNaN(score) ? null : score;
316}
317
318function sumScore(score)
319{
320  return score || 0;
321}
322
323function scoreCompleteOnClick()
324{
325  var frm = document.forms['reggie'];
326  selectedSample.ScoreComplete = frm.score_complete.checked;
327  setSampleClassName(selectedSample);
328}
329
330function goodStainOnClick()
331{
332  var frm = document.forms['reggie'];
333 
334  // Reset GoodStain on other samples on same location
335  for (var glassNo = 0; glassNo < heGlass.length; glassNo++)
336  {
337    var glass = heGlass[glassNo];
338   
339    for (var sampleNo = 0; sampleNo < glass.samples.length; sampleNo++)
340    {
341      var sample = glass.samples[sampleNo];
342      if (sample.well.location == selectedSample.well.location)
343      {
344        sample.GoodStain = false;
345        var td = document.getElementById(glass.id+'-'+sample.well.location);
346        Main.removeClass(td, 'good-stain');
347      }
348    }
349  }
350 
351  selectedSample.GoodStain = frm.good_stain.checked;
352  frm.good_stain.disabled = true;
353  setSampleClassName(selectedSample);
354}
355
356function commentsOnBlur()
357{
358  var frm = document.forms['reggie'];
359  selectedSample.comments = frm.comments.value;
360}
361
362function selectHeGlass()
363{
364  var url = getRoot() + 'biomaterials/bioplates/index.jsp?ID=<%=ID%>&cmd=UpdateContext&mode=selectone';
365  url += '&resetTemporary=1';
366  url += '&filter:STRING:name=HE%'; 
367  Main.openPopup(url, 'SelectHeGlass', 1050, 700);
368}
369</script>
370<style>
371
372/* Less wide to make more space for help text */
373.stepfields .input
374{
375  width: 200px;
376}
377
378input.score
379{
380  width: 6em;
381  text-align: right;
382  padding-right: 4px;
383}
384
385#he-glass-container
386{
387  white-space: nowrap;
388  overflow: auto;
389}
390
391.he-glass
392{
393  display: inline-block;
394  border: 1px solid #A0A0A0;
395  margin: 1em;
396  width: 20em;
397  background: #E8E8E8;
398}
399
400.he-name
401{
402  font-weight: bold;
403  background-color: #E0E0E0;
404  border-bottom: 1px solid #A0A0A0;
405  padding: 0.25em;
406}
407
408.he-info
409{
410  background-color: #FFFFFF;
411  border-bottom: 1px solid #A0A0A0;
412  padding: 0.25em;
413  height: 5em;
414  overflow: auto;
415  white-space: normal;
416}
417
418.he-tray:before
419{
420  content: 'Tray: ';
421}
422
423.he-position:before
424{
425  content: 'Position: ';
426}
427
428.he-comment
429{
430  font-style: italic;
431}
432
433
434#score-container
435{
436  clear: both;
437}
438
439.he-table
440{
441  margin-top: 1em;
442  margin-bottom: 1em;
443  margin-left: auto;
444  margin-right: auto;
445  border: 1px solid #A0A0A0;
446}
447
448.he-table td
449{
450  width: 10em;
451  height: 10em;
452  border: 1px solid #A0A0A0;
453  padding: 2px;
454  background: #FFFFFF;
455  background-position: center center;
456  background-repeat: no-repeat;
457  vertical-align: top;
458  text-align: center;
459  font-size: 75%;
460  color: #666666;
461  cursor: pointer;
462}
463
464.he-table td.used:hover
465{
466  border: 2px solid #2288AA;
467  border-radius: 4px;
468  padding: 1px;
469}
470
471.he-table td.not-used
472{
473  background-color: #D0D0D0;
474  background-image: url('../images/not-used.png');
475  cursor: default;
476}
477
478.he-table td.selected
479{
480  background-color: #D8FFFF;
481  border: 2px solid #2288AA;
482  border-radius: 4px;
483  padding: 1px;
484}
485
486.he-table td.good-stain
487{
488  font-weight: bold;
489  color: #000000;
490}
491
492.he-table td.good-stain.score-none
493{
494  background-image: url('../images/score-none.png');
495}
496
497.he-table td.good-stain.score-incomplete
498{
499  background-image: url('../images/score-incomplete.png');
500}
501
502.he-table td.good-stain.score-complete
503{
504  background-image: url('../images/score-complete.png');
505}
506
507/* Hide progress by default */
508.progress
509{
510  display: none;
511}
512
513/* Show progress for GoodStain or total score > 0 */
514td.good-stain .progress, td.score-incomplete .progress, td.score-complete .progress
515{
516  display: block;
517}
518
519td.score-incomplete .progress:before
520{
521  content: '(';
522}
523td.score-incomplete .progress:after
524{
525  content: ')';
526}
527
528.score-align
529{
530  width: 6em;
531  display: inline-block;
532  padding: 3px;
533  text-align: right;
534}
535
536#score_total
537{
538  background-color: #E8E8E8;
539  font-weight: bold;
540  padding-right: 6px;
541}
542
543</style>
544
545</base:head>
546<base:body onload="init()">
547
548  <p:path><p:pathelement 
549    title="Reggie" href="<%="../index.jsp?ID="+ID%>" 
550    /><p:pathelement title="Register HE glass score" 
551    /></p:path>
552
553  <div class="content">
554  <%
555  if (sc.getActiveProjectId() == 0)
556  {
557    %>
558    <div class="messagecontainer note" style="width: 950px; margin-left: 20px; margin-bottom: 20px; margin-right: 0px; font-weight: bold; color: #cc0000;">
559      No project has been selected. You may proceed with the registration but
560      created items will not be shared.
561    </div>
562    <%
563  }
564  %>
565  <form name="reggie" onsubmit="return false;">
566 
567  <table class="stepform">
568  <tr>
569    <td rowspan="3" class="stepno">1</td>
570    <td class="steptitle">HE glass to score</td>
571  </tr>
572  <tr>
573    <td class="stepfields">
574      <tbl:toolbar subclass="bottomborder">
575        <tbl:label>
576        <input type="text" style="width: 12em;" placeholder="Find HE glass" tooltip="Enter a HE glass name">
577        <img src="../images/gonext.png">
578        </tbl:label>
579       
580        <tbl:button
581          id="uncompletedHeGlass"
582          title="4 uncompleted HE glass"
583          image="mini_scroll_down.png"
584        />
585     
586        <tbl:button 
587          title="Select HE glass&hellip;" 
588          image="<%=home + "/images/microscope.png"%>" 
589          onclick="selectHeGlass()"
590          tooltip="Manually select a different HE glass"
591        />
592      </tbl:toolbar>
593     
594      <div id="he-glass-container" style="display: none;">
595       
596      </div>
597
598      <div id="score-container" style="display: none;">
599        <table style="border-collapse: collapse; width: 100%;">
600        <tr valign="top" style="background-color: #E8E8E8; border-top: 1px solid #A0A0A0; border-bottom: 1px solid #A0A0A0;">
601          <td class="prompt" style="padding: 0.25em;">Sample</td>
602          <td class="input" id="sample.name" style="padding: 0.25em; font-weight: bold;"></td>
603          <td class="status"></td>
604          <td class="help"></td>
605        </tr>
606       
607        <tr>
608          <td class="prompt">GoodStain</td>
609          <td class="input"><span class="score-align"><input type="checkbox" name="good_stain" onclick="goodStainOnClick()"
610            title="Mark this checkbox to overtake the GoodStain annotation"
611            ></span></td>
612          <td class="status" id="good_stain.status"></td>
613          <td class="help" rowspan="2">
614            Check to overtake the GoodStain annotation.
615            Disabled if this sample is already annotated.
616          </td>
617        </tr>
618       
619        <tr>
620          <td class="prompt">Scores</td>
621          <td class="input"></td>
622          <td class="status"></td>
623        </tr>
624        <tr>
625          <td class="subprompt">Invasive cancer</td>
626          <td class="input"><input type="text" class="score" name="score_invasive_cancer"
627            onkeypress="focusOnEnter(event, 'score_insitu_cancer'); return Numbers.integerOnly(event)"
628            onblur="checkScores()"
629            >%</td>
630          <td class="status" id="score_invasive_cancer.status"></td>
631          <td class="help" rowspan="2">
632            Enter the percentage for each cell type.
633            They must sum up to 100% before scoring can be compeleted.
634          </td>
635        </tr>   
636
637        <tr>
638          <td class="subprompt">In situ cancer</td>
639          <td class="input"><input type="text" class="score" name="score_insitu_cancer"
640            onkeypress="focusOnEnter(event, 'score_lymphocytes'); return Numbers.integerOnly(event)"
641            onblur="checkScores()"
642            >%</td>
643          <td class="status" id="score_insitu_cancer.status"></td>
644        </tr>   
645        <tr>
646          <td class="subprompt">Lymphocytes</td>
647          <td class="input"><input type="text" class="score" name="score_lymphocytes"
648            onkeypress="focusOnEnter(event, 'score_normal'); return Numbers.integerOnly(event)"
649            onblur="checkScores()"
650            >%</td>
651          <td class="status" id="score_lymphocytes.status"></td>
652          <td class="help"></td>
653        </tr>   
654        <tr>
655          <td class="subprompt">Normal</td>
656          <td class="input"><input type="text" class="score" name="score_normal"
657            onkeypress="focusOnEnter(event, 'score_stroma'); return Numbers.integerOnly(event)"
658            onblur="checkScores()"
659            >%</td>
660          <td class="status" id="score_normal.status"></td>
661          <td class="help"></td>
662        </tr>   
663        <tr>
664          <td class="subprompt">Stroma</td>
665          <td class="input"><input type="text" class="score" name="score_stroma"
666            onkeypress="focusOnEnter(event, 'score_fat'); return Numbers.integerOnly(event)"
667            onblur="checkScores()"
668            >%</td>
669          <td class="status" id="score_stroma.status"></td>
670          <td class="help"></td>
671        </tr>   
672        <tr>
673          <td class="subprompt">Fat</td>
674          <td class="input"><input type="text" class="score" name="score_fat"
675            onkeypress="focusOnEnter(event, 'comments'); return Numbers.integerOnly(event)"
676            onblur="checkScores()"
677            >%</td>
678          <td class="status" id="score_fat.status"></td>
679          <td class="help"></td>
680        </tr>
681        <tr>
682          <td class="subprompt" style="font-weight: bold; background-color: #E8E8E8;">Total</td>
683          <td class="input" style="padding: 0;"><span class="score-align" id="score_total"></span>%</td>
684          <td class="status" id="score_total.status"></td>
685          <td class="help"><span class="message" id="score_total.message"></span></td>
686        </tr>
687       
688        <tr>
689          <td class="subprompt">Score complete</td>
690          <td class="input"><span class="score-align"><input type="checkbox" name="score_complete" 
691            onclick="scoreCompleteOnClick()"
692            title="Uncheck if scoring is not complete"></span></td>
693          <td class="status" id="score_complete.status"></td>
694          <td class="help">Uncheck
695            if scoring is not complete. Disabled if the total score is not 100%.
696          </td>
697        </tr>
698       
699        <tr>
700          <td class="prompt" colspan="3">Comments<br>
701            <textarea rows="3" style="width: 97%;" name="comments" onblur="commentsOnBlur()"></textarea>
702          </td>
703          <td class="help"></td>
704        </tr>
705        </table>
706      </div>   
707
708    </td>
709  </tr>
710  </table>
711 
712
713  <div class="loading" id="loading" style="display: none;"><table><tr><td><img src="../images/loading.gif"></td><td id="loading.msg">Please wait...</td></tr></table></div>
714 
715  <div class="messagecontainer error" id="errorMessage" style="display: none; width: 950px; margin-left: 20px; margin-bottom: 0px;"></div>
716 
717  <div id="done" class="success" style="display: none; width: 950px; margin-left: 20px; margin-top: 20px;"></div>
718 
719  <table style="margin-left: 20px; margin-top: 10px;" class="navigation" id="navigation">
720    <tr>
721      <td><base:button id="gocancel" title="Cancel" onclick="goRestart(false)" /></td>
722      <td><base:button id="gonext" title="Next" image="<%=home+"/images/gonext.png"%>" onclick="goNext(true)" style="display: none;" /></td>
723      <td><base:button id="gocreate" title="Save" image="<%=home+"/images/gonext.png"%>" onclick="goCreate()" style="display: none;" /></td>
724      <td><base:button id="gorestart" title="Restart" image="<%=home+"/images/goback.png"%>" onclick="goRestart(true)" style="display: none;"/></td>
725      <td id="gonext.message" class="message"></td>
726    </tr>
727  </table>
728  </form>
729  </div>
730 
731</base:body>
732</base:page>
733<%
734}
735finally
736{
737  if (dc != null) dc.close();
738}
739%>
Note: See TracBrowser for help on using the repository browser.