def get_roc_figure_firsthalf_lasthalf(self,
                                       req: CamcopsRequest,
                                       trialarray: List[ExpDetTrial],
                                       plainroc: bool) -> str:
     if not trialarray or not self.num_blocks:
         return WARNING_INSUFFICIENT_DATA
     figsize = (FULLWIDTH_PLOT_WIDTH, FULLWIDTH_PLOT_WIDTH/2)
     html = ""
     fig = req.create_figure(figsize=figsize)
     warned = False
     for half in range(2):
         ax = fig.add_subplot(1, 2, half+1)
         # ... rows, cols, plotnum (in reading order from 1)
         blocks = list(range(half * self.num_blocks // 2,
                             self.num_blocks // (2 - half)))
         rocinfo = self.get_roc_info(trialarray, blocks, None)
         if rocinfo["rating_out_of_range"]:
             return ERROR_RATING_OUT_OF_RANGE
         if rocinfo["rating_missing"] and not warned:
             html += WARNING_RATING_MISSING
             warned = True
         show_x_label = True
         show_y_label = (half == 0)
         subtitle = "First half" if half == 0 else "Second half"
         self.plot_roc(
             req,
             ax,
             rocinfo["count_stimulus"],
             rocinfo["count_nostimulus"],
             show_x_label,
             show_y_label,
             plainroc,
             subtitle
         )
     title = PLAIN_ROC_TITLE if plainroc else Z_ROC_TITLE
     fontprops = req.fontprops
     fontprops.set_weight("bold")
     fig.suptitle(title, fontproperties=fontprops)
     html += req.get_html_from_pyplot_figure(fig)
     return html
 def get_roc_figure_by_group(self,
                             req: CamcopsRequest,
                             trialarray: List[ExpDetTrial],
                             grouparray: List[ExpDetTrialGroupSpec],
                             plainroc: bool) -> str:
     if not trialarray or not grouparray:
         return WARNING_INSUFFICIENT_DATA
     figsize = (FULLWIDTH_PLOT_WIDTH*2, FULLWIDTH_PLOT_WIDTH)
     html = ""
     fig = req.create_figure(figsize=figsize)
     warned = False
     for groupnum in range(len(grouparray)):
         ax = fig.add_subplot(2, 4, groupnum+1)
         # ... rows, cols, plotnum (in reading order from 1)
         rocinfo = self.get_roc_info(trialarray, [], [groupnum])
         if rocinfo["rating_out_of_range"]:
             return ERROR_RATING_OUT_OF_RANGE
         if rocinfo["rating_missing"] and not warned:
             html += WARNING_RATING_MISSING
             warned = True
         show_x_label = (groupnum > 3)
         show_y_label = (groupnum % 4 == 0)
         subtitle = "Group {} (n = {})".format(groupnum, rocinfo["total_n"])
         self.plot_roc(
             req,
             ax,
             rocinfo["count_stimulus"],
             rocinfo["count_nostimulus"],
             show_x_label,
             show_y_label,
             plainroc,
             subtitle
         )
     title = PLAIN_ROC_TITLE if plainroc else Z_ROC_TITLE
     fontprops = req.fontprops
     fontprops.set_weight("bold")
     fig.suptitle(title, fontproperties=fontprops)
     html += req.get_html_from_pyplot_figure(fig)
     return html
Beispiel #3
0
    def get_task_html(self, req: CamcopsRequest) -> str:
        def percent(score: int, maximum: int) -> str:
            return ws.number_to_dp(100 * score / maximum, PERCENT_DP)

        a = self.attn_score()
        m = self.mem_score()
        f = self.fluency_score()
        lang = self.lang_score()
        v = self.vsp_score()
        t = a + m + f + lang + v
        if self.is_complete():
            figsize = (
                PlotDefaults.FULLWIDTH_PLOT_WIDTH / 3,
                PlotDefaults.FULLWIDTH_PLOT_WIDTH / 4,
            )
            width = 0.9
            fig = req.create_figure(figsize=figsize)
            ax = fig.add_subplot(1, 1, 1)
            scores = numpy.array([a, m, f, lang, v])
            maxima = numpy.array(
                [ATTN_MAX, MEMORY_MAX, FLUENCY_MAX, LANG_MAX, VSP_MAX])
            y = 100 * scores / maxima
            x_labels = ["Attn", "Mem", "Flu", "Lang", "VSp"]
            # noinspection PyTypeChecker
            n = len(y)
            xvar = numpy.arange(n)
            ax.bar(xvar, y, width, color="b")
            ax.set_ylabel("%", fontdict=req.fontdict)
            ax.set_xticks(xvar)
            x_offset = -0.5
            ax.set_xlim(0 + x_offset, len(scores) + x_offset)
            ax.set_xticklabels(x_labels, fontdict=req.fontdict)
            fig.tight_layout()  # or the ylabel drops off the figure
            # fig.autofmt_xdate()
            req.set_figure_font_sizes(ax)
            figurehtml = req.get_html_from_pyplot_figure(fig)
        else:
            figurehtml = "<i>Incomplete; not plotted</i>"
        return (
            self.get_standard_clinician_comments_block(req, self.comments) +
            f"""
                <div class="{CssClass.SUMMARY}">
                    <table class="{CssClass.SUMMARY}">
                        <tr>
                            {self.get_is_complete_td_pair(req)}
                            <td class="{CssClass.FIGURE}"
                                rowspan="7">{figurehtml}</td>
                        </tr>
            """ + tr("Total ACE-III score <sup>[1]</sup>",
                     answer(t) + " / 100") +
            tr(
                "Attention",
                answer(a) + f" / {ATTN_MAX} ({percent(a, ATTN_MAX)}%)",
            ) + tr(
                "Memory",
                answer(m) + f" / {MEMORY_MAX} ({percent(m, MEMORY_MAX)}%)",
            ) + tr(
                "Fluency",
                answer(f) + f" / {FLUENCY_MAX} ({percent(f, FLUENCY_MAX)}%)",
            ) + tr(
                "Language",
                answer(lang) + f" / {LANG_MAX} ({percent(lang, LANG_MAX)}%)",
            ) + tr(
                "Visuospatial",
                answer(v) + f" / {VSP_MAX} ({percent(v, VSP_MAX)}%)",
            ) + f"""
                    </table>
                </div>
                <table class="{CssClass.TASKDETAIL}">
                    <tr>
                        <th width="75%">Question</th>
                        <th width="25%">Answer/score</td>
                    </tr>
            """ + tr_qa(
                "Age on leaving full-time education",
                self.age_at_leaving_full_time_education,
            ) + tr_qa("Occupation", ws.webify(self.occupation)) +
            tr_qa("Handedness", ws.webify(self.handedness)) +
            subheading_spanning_two_columns("Attention") + tr(
                "Day? Date? Month? Year? Season?",
                ", ".join(
                    answer(x) for x in (
                        self.attn_time1,
                        self.attn_time2,
                        self.attn_time3,
                        self.attn_time4,
                        self.attn_time5,
                    )),
            ) + tr(
                "House number/floor? Street/hospital? Town? County? Country?",
                ", ".join(
                    answer(x) for x in (
                        self.attn_place1,
                        self.attn_place2,
                        self.attn_place3,
                        self.attn_place4,
                        self.attn_place5,
                    )),
            ) + tr(
                "Repeat: Lemon? Key? Ball?",
                ", ".join(
                    answer(x) for x in (
                        self.attn_repeat_word1,
                        self.attn_repeat_word2,
                        self.attn_repeat_word3,
                    )),
            ) + tr(
                "Repetition: number of trials <i>(not scored)</i>",
                answer(self.attn_num_registration_trials,
                       formatter_answer=italic),
            ) + tr(
                "Serial subtractions: First correct? Second? Third? Fourth? "
                "Fifth?",
                ", ".join(
                    answer(x) for x in (
                        self.attn_serial7_subtraction1,
                        self.attn_serial7_subtraction2,
                        self.attn_serial7_subtraction3,
                        self.attn_serial7_subtraction4,
                        self.attn_serial7_subtraction5,
                    )),
            ) + subheading_spanning_two_columns("Memory (1)") + tr(
                "Recall: Lemon? Key? Ball?",
                ", ".join(
                    answer(x) for x in (
                        self.mem_recall_word1,
                        self.mem_recall_word2,
                        self.mem_recall_word3,
                    )),
            ) + subheading_spanning_two_columns("Fluency") + tr(
                "Score for words beginning with ‘P’ <i>(≥18 scores 7, 14–17 "
                "scores 6, 11–13 scores 5, 8–10 scores 4, 6–7 scores 3, "
                "4–5 scores 2, 2–3 scores 1, 0–1 scores 0)</i>",
                answer(self.fluency_letters_score) + " / 7",
            ) + tr(
                "Score for animals <i>(≥22 scores 7, 17–21 scores 6, "
                "14–16 scores 5, 11–13 scores 4, 9–10 scores 3, "
                "7–8 scores 2, 5–6 scores 1, &lt;5 scores 0)</i>",
                answer(self.fluency_animals_score) + " / 7",
            ) + subheading_spanning_two_columns("Memory (2)") + tr(
                "Third trial of address registration: Harry? Barnes? 73? "
                "Orchard? Close? Kingsbridge? Devon?",
                ", ".join(
                    answer(x) for x in (
                        self.mem_repeat_address_trial3_1,
                        self.mem_repeat_address_trial3_2,
                        self.mem_repeat_address_trial3_3,
                        self.mem_repeat_address_trial3_4,
                        self.mem_repeat_address_trial3_5,
                        self.mem_repeat_address_trial3_6,
                        self.mem_repeat_address_trial3_7,
                    )),
            ) + tr(
                "Current PM? Woman who was PM? USA president? USA president "
                "assassinated in 1960s?",
                ", ".join(
                    answer(x) for x in (
                        self.mem_famous1,
                        self.mem_famous2,
                        self.mem_famous3,
                        self.mem_famous4,
                    )),
            ) + subheading_spanning_two_columns("Language") + tr(
                "<i>Practice trial (“Pick up the pencil and then the "
                "paper”)</i>",
                answer(self.lang_follow_command_practice,
                       formatter_answer=italic),
            ) + tr_qa(
                "“Place the paper on top of the pencil”",
                self.lang_follow_command1,
            ) + tr_qa(
                "“Pick up the pencil but not the paper”",
                self.lang_follow_command2,
            ) + tr_qa(
                "“Pass me the pencil after touching the paper”",
                self.lang_follow_command3,
            ) + tr(
                "Sentence-writing: point for ≥2 complete sentences about "
                "the one topic? Point for correct grammar and spelling?",
                ", ".join(
                    answer(x) for x in (
                        self.lang_write_sentences_point1,
                        self.lang_write_sentences_point2,
                    )),
            ) + tr(
                "Repeat: caterpillar? eccentricity? unintelligible? "
                "statistician? <i>(score 2 if all correct, 1 if 3 correct, "
                "0 if ≤2 correct)</i>",
                "<i>{}, {}, {}, {}</i> (score <b>{}</b> / 2)".format(
                    answer(self.lang_repeat_word1, formatter_answer=italic),
                    answer(self.lang_repeat_word2, formatter_answer=italic),
                    answer(self.lang_repeat_word3, formatter_answer=italic),
                    answer(self.lang_repeat_word4, formatter_answer=italic),
                    self.get_repeat_word_score(),
                ),
            ) + tr_qa(
                "Repeat: “All that glitters is not gold”?",
                self.lang_repeat_sentence1,
            ) + tr_qa(
                "Repeat: “A stitch in time saves nine”?",
                self.lang_repeat_sentence2,
            ) + tr(
                "Name pictures: spoon, book, kangaroo/wallaby",
                ", ".join(
                    answer(x) for x in (
                        self.lang_name_picture1,
                        self.lang_name_picture2,
                        self.lang_name_picture3,
                    )),
            ) + tr(
                "Name pictures: penguin, anchor, camel/dromedary",
                ", ".join(
                    answer(x) for x in (
                        self.lang_name_picture4,
                        self.lang_name_picture5,
                        self.lang_name_picture6,
                    )),
            ) + tr(
                "Name pictures: harp, rhinoceros/rhino, barrel/keg/tub",
                ", ".join(
                    answer(x) for x in (
                        self.lang_name_picture7,
                        self.lang_name_picture8,
                        self.lang_name_picture9,
                    )),
            ) + tr(
                "Name pictures: crown, alligator/crocodile, "
                "accordion/piano accordion/squeeze box",
                ", ".join(
                    answer(x) for x in (
                        self.lang_name_picture10,
                        self.lang_name_picture11,
                        self.lang_name_picture12,
                    )),
            ) + tr(
                "Identify pictures: monarchy? marsupial? Antarctic? nautical?",
                ", ".join(
                    answer(x) for x in (
                        self.lang_identify_concept1,
                        self.lang_identify_concept2,
                        self.lang_identify_concept3,
                        self.lang_identify_concept4,
                    )),
            ) + tr_qa(
                "Read all successfully: sew, pint, soot, dough, height",
                self.lang_read_words_aloud,
            ) + subheading_spanning_two_columns("Visuospatial") +
            tr("Copy infinity",
               answer(self.vsp_copy_infinity) + " / 1") +
            tr("Copy cube",
               answer(self.vsp_copy_cube) + " / 2") + tr(
                   "Draw clock with numbers and hands at 5:10",
                   answer(self.vsp_draw_clock) + " / 5",
               ) + tr(
                   "Count dots: 8, 10, 7, 9",
                   ", ".join(
                       answer(x) for x in (
                           self.vsp_count_dots1,
                           self.vsp_count_dots2,
                           self.vsp_count_dots3,
                           self.vsp_count_dots4,
                       )),
               ) + tr(
                   "Identify letters: K, M, A, T",
                   ", ".join(
                       answer(x) for x in (
                           self.vsp_identify_letter1,
                           self.vsp_identify_letter2,
                           self.vsp_identify_letter3,
                           self.vsp_identify_letter4,
                       )),
               ) + subheading_spanning_two_columns("Memory (3)") + tr(
                   "Recall address: Harry? Barnes? 73? Orchard? Close? "
                   "Kingsbridge? Devon?",
                   ", ".join(
                       answer(x) for x in (
                           self.mem_recall_address1,
                           self.mem_recall_address2,
                           self.mem_recall_address3,
                           self.mem_recall_address4,
                           self.mem_recall_address5,
                           self.mem_recall_address6,
                           self.mem_recall_address7,
                       )),
               ) +
            tr(
                "Recognize address: Jerry Barnes/Harry Barnes/Harry Bradford?",
                self.get_recog_text(
                    (self.mem_recall_address1 == 1
                     and self.mem_recall_address2 == 1),
                    self.mem_recognize_address1,
                ),
            ) + tr(
                "Recognize address: 37/73/76?",
                self.get_recog_text(
                    (self.mem_recall_address3 == 1),
                    self.mem_recognize_address2,
                ),
            ) + tr(
                "Recognize address: Orchard Place/Oak Close/Orchard "
                "Close?",
                self.get_recog_text(
                    (self.mem_recall_address4 == 1
                     and self.mem_recall_address5 == 1),
                    self.mem_recognize_address3,
                ),
            ) + tr(
                "Recognize address: Oakhampton/Kingsbridge/Dartington?",
                self.get_recog_text(
                    (self.mem_recall_address6 == 1),
                    self.mem_recognize_address4,
                ),
            ) + tr(
                "Recognize address: Devon/Dorset/Somerset?",
                self.get_recog_text(
                    (self.mem_recall_address7 == 1),
                    self.mem_recognize_address5,
                ),
            ) + subheading_spanning_two_columns("Photos of test sheet") +
            tr_span_col(get_blob_img_html(self.picture1),
                        td_class=CssClass.PHOTO) +
            tr_span_col(get_blob_img_html(self.picture2),
                        td_class=CssClass.PHOTO) + f"""
                </table>
                <div class="{CssClass.FOOTNOTES}">
                    [1] In the ACE-R (the predecessor of the ACE-III),
                    scores ≤82 had sensitivity 0.84 and specificity 1.0 for
                    dementia, and scores ≤88 had sensitivity 0.94 and
                    specificity 0.89 for dementia, in a context of patients
                    with AlzD, FTD, LBD, MCI, and controls
                    (Mioshi et al., 2006, PMID 16977673).
                </div>
                <div class="{CssClass.COPYRIGHT}">
                    ACE-III: Copyright © 2012, John Hodges.
                    “The ACE-III is available for free. The copyright is held
                    by Professor John Hodges who is happy for the test to be
                    used in clinical practice and research projects. There is
                    no need to contact us if you wish to use the ACE-III in
                    clinical practice.”
                    (ACE-III FAQ, 7 July 2013, www.neura.edu.au).
                </div>
            """)
    def get_trial_html(self, req: CamcopsRequest) -> str:
        trialarray = self.trials  # type: List[CardinalExpDetThresholdTrial]  # for type hinter!  # noqa
        html = CardinalExpDetThresholdTrial.get_html_table_header()
        for t in trialarray:
            html += t.get_html_table_row()
        html += """</table>"""

        # Don't add figures if we're incomplete
        if not self.is_complete():
            return html

        # Add figures

        figsize = (FULLWIDTH_PLOT_WIDTH / 2, FULLWIDTH_PLOT_WIDTH / 2)
        jitter_step = 0.02
        dp_to_consider_same_for_jitter = 3
        y_extra_space = 0.1
        x_extra_space = 0.02
        trialfig = req.create_figure(figsize=figsize)
        trialax = trialfig.add_subplot(WHOLE_PANEL)
        notcalc_detected_x = []
        notcalc_detected_y = []
        notcalc_missed_x = []
        notcalc_missed_y = []
        calc_detected_x = []
        calc_detected_y = []
        calc_missed_x = []
        calc_missed_y = []
        catch_detected_x = []
        catch_detected_y = []
        catch_missed_x = []
        catch_missed_y = []
        all_x = []
        all_y = []
        for t in trialarray:
            x = t.trial
            y = t.intensity
            all_x.append(x)
            all_y.append(y)
            if t.trial_num_in_calculation_sequence is not None:
                if t.yes:
                    calc_detected_x.append(x)
                    calc_detected_y.append(y)
                else:
                    calc_missed_x.append(x)
                    calc_missed_y.append(y)
            elif t.target_presented:
                if t.yes:
                    notcalc_detected_x.append(x)
                    notcalc_detected_y.append(y)
                else:
                    notcalc_missed_x.append(x)
                    notcalc_missed_y.append(y)
            else:  # catch trial
                if t.yes:
                    catch_detected_x.append(x)
                    catch_detected_y.append(y)
                else:
                    catch_missed_x.append(x)
                    catch_missed_y.append(y)
        trialax.plot(all_x,
                     all_y,
                     marker="",
                     color="0.9",
                     linestyle="-",
                     label=None)
        trialax.plot(notcalc_missed_x,
                     notcalc_missed_y,
                     marker="o",
                     color="k",
                     linestyle="None",
                     label="miss")
        trialax.plot(notcalc_detected_x,
                     notcalc_detected_y,
                     marker="+",
                     color="k",
                     linestyle="None",
                     label="hit")
        trialax.plot(calc_missed_x,
                     calc_missed_y,
                     marker="o",
                     color="r",
                     linestyle="None",
                     label="miss, scored")
        trialax.plot(calc_detected_x,
                     calc_detected_y,
                     marker="+",
                     color="b",
                     linestyle="None",
                     label="hit, scored")
        trialax.plot(catch_missed_x,
                     catch_missed_y,
                     marker="o",
                     color="g",
                     linestyle="None",
                     label="CR")
        trialax.plot(catch_detected_x,
                     catch_detected_y,
                     marker="*",
                     color="g",
                     linestyle="None",
                     label="FA")
        leg = trialax.legend(
            numpoints=1,
            fancybox=True,  # for set_alpha (below)
            loc="best",  # bbox_to_anchor=(0.75, 1.05)
            labelspacing=0,
            handletextpad=0,
            prop=req.fontprops)
        leg.get_frame().set_alpha(0.5)
        trialax.set_xlabel("Trial number (0-based)", fontdict=req.fontdict)
        trialax.set_ylabel("Intensity", fontdict=req.fontdict)
        trialax.set_ylim(0 - y_extra_space, 1 + y_extra_space)
        trialax.set_xlim(-0.5, len(trialarray) - 0.5)
        req.set_figure_font_sizes(trialax)

        fitfig = None
        if self.k is not None and self.theta is not None:
            fitfig = req.create_figure(figsize=figsize)
            fitax = fitfig.add_subplot(WHOLE_PANEL)
            detected_x = []
            detected_x_approx = []
            detected_y = []
            missed_x = []
            missed_x_approx = []
            missed_y = []
            all_x = []
            for t in trialarray:
                if t.trial_num_in_calculation_sequence is not None:
                    all_x.append(t.intensity)
                    approx_x = "{0:.{precision}f}".format(
                        t.intensity, precision=dp_to_consider_same_for_jitter)
                    if t.yes:
                        detected_y.append(1 -
                                          detected_x_approx.count(approx_x) *
                                          jitter_step)
                        detected_x.append(t.intensity)
                        detected_x_approx.append(approx_x)
                    else:
                        missed_y.append(0 + missed_x_approx.count(approx_x) *
                                        jitter_step)
                        missed_x.append(t.intensity)
                        missed_x_approx.append(approx_x)
            fit_x = np.arange(0.0 - x_extra_space, 1.0 + x_extra_space, 0.001)
            fit_y = logistic(fit_x, self.k, self.theta)
            fitax.plot(fit_x, fit_y, color="g", linestyle="-")
            fitax.plot(missed_x,
                       missed_y,
                       marker="o",
                       color="r",
                       linestyle="None")
            fitax.plot(detected_x,
                       detected_y,
                       marker="+",
                       color="b",
                       linestyle="None")
            fitax.set_ylim(0 - y_extra_space, 1 + y_extra_space)
            fitax.set_xlim(
                np.amin(all_x) - x_extra_space,
                np.amax(all_x) + x_extra_space)
            marker_points = []
            for y in (LOWER_MARKER, 0.5, UPPER_MARKER):
                x = inv_logistic(y, self.k, self.theta)
                marker_points.append((x, y))
            for p in marker_points:
                fitax.plot([p[0], p[0]], [-1, p[1]],
                           color="0.5",
                           linestyle=":")
                fitax.plot([-1, p[0]], [p[1], p[1]],
                           color="0.5",
                           linestyle=":")
            fitax.set_xlabel("Intensity", fontdict=req.fontdict)
            fitax.set_ylabel("Detected? (0=no, 1=yes; jittered)",
                             fontdict=req.fontdict)
            req.set_figure_font_sizes(fitax)

        html += """
            <table class="{CssClass.NOBORDER}">
                <tr>
                    <td class="{CssClass.NOBORDERPHOTO}">{trialfig}</td>
                    <td class="{CssClass.NOBORDERPHOTO}">{fitfig}</td>
                </tr>
            </table>
        """.format(CssClass=CssClass,
                   trialfig=req.get_html_from_pyplot_figure(trialfig),
                   fitfig=req.get_html_from_pyplot_figure(fitfig))

        return html
    def _get_figures(self,
                     req: CamcopsRequest) -> Tuple[Figure, Optional[Figure]]:
        """
        Create and return figures. Returns ``trialfig, fitfig``.
        """
        trialarray = self.trials

        # Constants
        jitter_step = 0.02
        dp_to_consider_same_for_jitter = 3
        y_extra_space = 0.1
        x_extra_space = 0.02
        figsize = (
            PlotDefaults.FULLWIDTH_PLOT_WIDTH / 2,
            PlotDefaults.FULLWIDTH_PLOT_WIDTH / 2,
        )

        # Figure and axes
        trialfig = req.create_figure(figsize=figsize)
        trialax = trialfig.add_subplot(MatplotlibConstants.WHOLE_PANEL)
        fitfig = None  # type: Optional[Figure]

        # Anything to do?
        if not trialarray:
            return trialfig, fitfig

        # Data
        notcalc_detected_x = []
        notcalc_detected_y = []
        notcalc_missed_x = []
        notcalc_missed_y = []
        calc_detected_x = []
        calc_detected_y = []
        calc_missed_x = []
        calc_missed_y = []
        catch_detected_x = []
        catch_detected_y = []
        catch_missed_x = []
        catch_missed_y = []
        all_x = []
        all_y = []
        for t in trialarray:
            x = t.trial
            y = t.intensity
            all_x.append(x)
            all_y.append(y)
            if t.trial_num_in_calculation_sequence is not None:
                if t.yes:
                    calc_detected_x.append(x)
                    calc_detected_y.append(y)
                else:
                    calc_missed_x.append(x)
                    calc_missed_y.append(y)
            elif t.target_presented:
                if t.yes:
                    notcalc_detected_x.append(x)
                    notcalc_detected_y.append(y)
                else:
                    notcalc_missed_x.append(x)
                    notcalc_missed_y.append(y)
            else:  # catch trial
                if t.yes:
                    catch_detected_x.append(x)
                    catch_detected_y.append(y)
                else:
                    catch_missed_x.append(x)
                    catch_missed_y.append(y)

        # Create trialfig plots
        trialax.plot(
            all_x,
            all_y,
            marker=MatplotlibConstants.MARKER_NONE,
            color=MatplotlibConstants.COLOUR_GREY_50,
            linestyle=MatplotlibConstants.LINESTYLE_SOLID,
            label=None,
        )
        trialax.plot(
            notcalc_missed_x,
            notcalc_missed_y,
            marker=MatplotlibConstants.MARKER_CIRCLE,
            color=MatplotlibConstants.COLOUR_BLACK,
            linestyle=MatplotlibConstants.LINESTYLE_NONE,
            label="miss",
        )
        trialax.plot(
            notcalc_detected_x,
            notcalc_detected_y,
            marker=MatplotlibConstants.MARKER_PLUS,
            color=MatplotlibConstants.COLOUR_BLACK,
            linestyle=MatplotlibConstants.LINESTYLE_NONE,
            label="hit",
        )
        trialax.plot(
            calc_missed_x,
            calc_missed_y,
            marker=MatplotlibConstants.MARKER_CIRCLE,
            color=MatplotlibConstants.COLOUR_RED,
            linestyle=MatplotlibConstants.LINESTYLE_NONE,
            label="miss, scored",
        )
        trialax.plot(
            calc_detected_x,
            calc_detected_y,
            marker=MatplotlibConstants.MARKER_PLUS,
            color=MatplotlibConstants.COLOUR_BLUE,
            linestyle=MatplotlibConstants.LINESTYLE_NONE,
            label="hit, scored",
        )
        trialax.plot(
            catch_missed_x,
            catch_missed_y,
            marker=MatplotlibConstants.MARKER_CIRCLE,
            color=MatplotlibConstants.COLOUR_GREEN,
            linestyle=MatplotlibConstants.LINESTYLE_NONE,
            label="CR",
        )
        trialax.plot(
            catch_detected_x,
            catch_detected_y,
            marker=MatplotlibConstants.MARKER_STAR,
            color=MatplotlibConstants.COLOUR_GREEN,
            linestyle=MatplotlibConstants.LINESTYLE_NONE,
            label="FA",
        )
        leg = trialax.legend(
            numpoints=1,
            fancybox=True,  # for set_alpha (below)
            loc="best",  # bbox_to_anchor=(0.75, 1.05)
            labelspacing=0,
            handletextpad=0,
            prop=req.fontprops,
        )
        leg.get_frame().set_alpha(0.5)
        trialax.set_xlabel("Trial number (0-based)", fontdict=req.fontdict)
        trialax.set_ylabel("Intensity", fontdict=req.fontdict)
        trialax.set_ylim(0 - y_extra_space, 1 + y_extra_space)
        trialax.set_xlim(-0.5, len(trialarray) - 0.5)
        req.set_figure_font_sizes(trialax)

        # Anything to do for fitfig?
        if self.k is None or self.theta is None:
            return trialfig, fitfig

        # Create fitfig
        fitfig = req.create_figure(figsize=figsize)
        fitax = fitfig.add_subplot(MatplotlibConstants.WHOLE_PANEL)
        detected_x = []
        detected_x_approx = []
        detected_y = []
        missed_x = []
        missed_x_approx = []
        missed_y = []
        all_x = []
        for t in trialarray:
            if t.trial_num_in_calculation_sequence is not None:
                all_x.append(t.intensity)
                approx_x = f"{t.intensity:.{dp_to_consider_same_for_jitter}f}"
                if t.yes:
                    detected_y.append(1 - detected_x_approx.count(approx_x) *
                                      jitter_step)
                    detected_x.append(t.intensity)
                    detected_x_approx.append(approx_x)
                else:
                    missed_y.append(0 + missed_x_approx.count(approx_x) *
                                    jitter_step)
                    missed_x.append(t.intensity)
                    missed_x_approx.append(approx_x)

        # Again, anything to do for fitfig?
        if not all_x:
            return trialfig, fitfig

        fit_x = np.arange(0.0 - x_extra_space, 1.0 + x_extra_space, 0.001)
        fit_y = logistic(fit_x, self.k, self.theta)
        fitax.plot(
            fit_x,
            fit_y,
            color=MatplotlibConstants.COLOUR_GREEN,
            linestyle=MatplotlibConstants.LINESTYLE_SOLID,
        )
        fitax.plot(
            missed_x,
            missed_y,
            marker=MatplotlibConstants.MARKER_CIRCLE,
            color=MatplotlibConstants.COLOUR_RED,
            linestyle=MatplotlibConstants.LINESTYLE_NONE,
        )
        fitax.plot(
            detected_x,
            detected_y,
            marker=MatplotlibConstants.MARKER_PLUS,
            color=MatplotlibConstants.COLOUR_BLUE,
            linestyle=MatplotlibConstants.LINESTYLE_NONE,
        )
        fitax.set_ylim(0 - y_extra_space, 1 + y_extra_space)
        fitax.set_xlim(
            np.amin(all_x) - x_extra_space,
            np.amax(all_x) + x_extra_space)
        marker_points = []
        for y in (LOWER_MARKER, 0.5, UPPER_MARKER):
            x = inv_logistic(y, self.k, self.theta)
            marker_points.append((x, y))
        for p in marker_points:
            fitax.plot(
                [p[0], p[0]],  # x
                [-1, p[1]],  # y
                color=MatplotlibConstants.COLOUR_GREY_50,
                linestyle=MatplotlibConstants.LINESTYLE_DOTTED,
            )
            fitax.plot(
                [-1, p[0]],  # x
                [p[1], p[1]],  # y
                color=MatplotlibConstants.COLOUR_GREY_50,
                linestyle=MatplotlibConstants.LINESTYLE_DOTTED,
            )
        fitax.set_xlabel("Intensity", fontdict=req.fontdict)
        fitax.set_ylabel("Detected? (0=no, 1=yes; jittered)",
                         fontdict=req.fontdict)
        req.set_figure_font_sizes(fitax)

        # Done
        return trialfig, fitfig