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
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, <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