subcellularatlas.py (20020B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\projectdiscovery\client\projects\subcellular\subcellularatlas.py 2 from __future__ import absolute_import 3 import logging 4 import math 5 import blue 6 import carbonui.const as uiconst 7 import localization 8 import uthread 9 from carbonui.primitives.container import Container 10 from carbonui.primitives.sprite import Sprite 11 from carbonui.primitives.transform import Transform 12 from carbonui.uianimations import animations 13 from .categoryselector import CategorySelector 14 from eve.common.lib.appConst import MSEC 15 from eve.client.script.ui.control import eveLabel, themeColored 16 from carbonui.control.buttonIcon import ButtonIcon 17 from carbonui.control.button import Button 18 from carbonui.control.checkbox import Checkbox 19 from eve.client.script.ui.control.eveLoadingWheel import LoadingWheel 20 from eve.client.script.ui.tooltips.tooltipUtil import SetTooltipHeaderAndDescription 21 from gametime import GetWallclockTime, GetTimeDiff 22 from .info import INFO as PROJECT_INFO 23 from projectdiscovery.client import const 24 from projectdiscovery.client.projects.subcellular import nested_categories_from_json 25 from projectdiscovery.client.projects.subcellular.processingview import ProcessingView 26 from projectdiscovery.client.projects.subcellular.resultwindow import ResultWindow 27 from projectdiscovery.client.projects.subcellular.rewardsview import RewardsView 28 from projectdiscovery.client.projects.subcellular.taskimages import TaskImage 29 from projectdiscovery.client.util.dialogue import Dialogue 30 from projectdiscovery.client.util.eventlistener import eventlistener, on_event 31 from projectdiscovery.common.exceptions import MissingKeyError, NoConnectionToAPIError 32 logger = logging.getLogger(__name__) 33 RESULT_TIME_OUT = 10 34 35 @eventlistener() 36 37 class SubcellularAtlas(Container): 38 MAX_CATEGORIES = PROJECT_INFO['resultSchema']['maxItems'] - 1 39 40 def ApplyAttributes(self, attributes): 41 super(SubcellularAtlas, self).ApplyAttributes(attributes) 42 self.task_time = None 43 self.selection = [] 44 self.tutorial = None 45 self.service = sm.RemoteSvc('ProjectDiscovery') 46 self.submitting = False 47 self.playerState = attributes.get('playerState') 48 self.starting_scale = attributes.get('starting_scale') 49 self.bottom_container = self.parent.parent.FindChild('bottom_container') 50 self.clipChildren = True 51 self.result = None 52 self.getting_new_task = False 53 self.setup_layout() 54 55 def setup_layout(self): 56 self.dialogue_container = Container(name='dialogue_container', parent=self.parent, idx=0) 57 self.left_main_container = Container(name='left_main_container', parent=self, align=uiconst.TOLEFT_PROP, width=0.5, clipChildren=True, top=20) 58 self.category_container = Container(name='category_container', parent=self, align=uiconst.TOLEFT_PROP, width=0.5, padTop=45, clipChildren=True) 59 self.loading_container = Container(name='loadingContainer', parent=self.category_container, align=uiconst.CENTER, width=100, height=100, state=uiconst.UI_HIDDEN) 60 self.loading_result_label = themeColored.LabelThemeColored(name='loadingResultLabel', parent=self.loading_container, align=uiconst.CENTERTOP, text=localization.GetByLabel('UI/ProjectDiscovery/LoadingResultLabel'), fontsize=20) 61 self.loading_wheel = LoadingWheel(name='ResultLoadingIndicator', parent=self.loading_container, align=uiconst.CENTER, width=64, height=64) 62 self.error_container = Container(parent=self.category_container, align=uiconst.CENTER, width=100, height=100, state=uiconst.UI_HIDDEN) 63 self.error_label = themeColored.LabelThemeColored(parent=self.error_container, align=uiconst.CENTERTOP, text=localization.GetByLabel('UI/ProjectDiscovery/LoadingResultErrorLabel'), fontsize=20) 64 self.task_label = themeColored.LabelThemeColored(parent=self.left_main_container, align=uiconst.CENTERTOP, text=localization.GetByLabel('UI/ProjectDiscovery/TaskLabel'), top=40, fontsize=28, opacity=1) 65 self.task_image = TaskImage(label=const.Texts.TaskImageLabel, parent=self.left_main_container, align=uiconst.TOPLEFT_PROP, pos=(25, 80, 420, 445), starting_scale=self.starting_scale) 66 self.checkbox_container = Container(name='checkboxContainer', parent=self.task_image, align=uiconst.BOTTOMLEFT, width=110, height=20, left=8, top=10) 67 self.report_checkbox = Checkbox(name='reportCheckbox', parent=self.checkbox_container, text=localization.GetByLabel('UI/ProjectDiscovery/ReportCheckboxLabel')) 68 SetTooltipHeaderAndDescription(self.report_checkbox, headerText=localization.GetByLabel('UI/ProjectDiscovery/AbnormalSampleTooltipHeader'), descriptionText=localization.GetByLabel('UI/ProjectDiscovery/AbnormalSampleTooltipDescription')) 69 self.result_window = ResultWindow(name='ResultWindow', parent=self.parent, align=uiconst.TOALL, opacity=0, isTrainingPhase=False, starting_scale=self.starting_scale, bottom_container=self.bottom_container) 70 self.rewards_view = RewardsView(parent=self.parent.parent, opacity=0, align=uiconst.TOALL, playerState=self.playerState, bottom_container=self.bottom_container, idx=1, state=uiconst.UI_DISABLED) 71 self.processing_view = ProcessingView(parent=self.parent.parent, opacity=0, idx=2, state=uiconst.UI_DISABLED) 72 self.category_selector = CategorySelector(categories=nested_categories_from_json(PROJECT_INFO['info']['classes']), parent=self.category_container, state=uiconst.UI_DISABLED, starting_scale=self.starting_scale) 73 self.main_button_container = Container(name='main_button_container', parent=self.bottom_container, align=uiconst.CENTERBOTTOM, width=355, height=53, bgTexturePath='res:/UI/Texture/classes/ProjectDiscovery/footerBG.png') 74 self.submit_button_container = Container(name='submitButtonContainer', parent=self.main_button_container, width=250, align=uiconst.CENTER, height=40, top=5) 75 self.submit_button = Button(name='SubcellularSubmitButton', parent=self.submit_button_container, align=uiconst.CENTER, label=localization.GetByLabel('UI/ProjectDiscovery/SubmitButtonLabel'), fontsize=18, fixedwidth=170, fixedheight=30, func=lambda x: self.submit_solution()) 76 Sprite(parent=self.submit_button_container, align=uiconst.CENTERLEFT, width=34, height=20, texturePath='res:/UI/Texture/classes/ProjectDiscovery/submitArrow.png', opacity=0.7) 77 Sprite(parent=Transform(parent=self.submit_button_container, align=uiconst.CENTERRIGHT, width=34, height=20, rotation=math.pi), align=uiconst.CENTERRIGHT, width=34, height=20, texturePath='res:/UI/Texture/classes/ProjectDiscovery/submitArrow.png', opacity=0.7) 78 self.task_id = eveLabel.Label(name='TaskID', parent=self.bottom_container, align=uiconst.BOTTOMRIGHT, height=20, opacity=0, left=10) 79 self.new_task_button = ButtonIcon(name='newTaskButton', parent=self.left_main_container, align=uiconst.TOPLEFT, texturePath='res:/UI/Texture/classes/ProjectDiscovery/recycleButtonUp.png', hoverTexture='res:/UI/Texture/classes/ProjectDiscovery/recycleButtonOver.png', downTexture='res:/UI/Texture/classes/ProjectDiscovery/recycleButtonDown.png', iconSize=24, func=lambda : self.reset_and_get_new_task(), idx=0, left=32, top=54) 80 SetTooltipHeaderAndDescription(self.new_task_button, headerText='', descriptionText=localization.GetByLabel('UI/ProjectDiscovery/NewTaskButtonTooltipDescription')) 81 82 def start(self, show_dialogue): 83 if show_dialogue: 84 self.disable_ui() 85 Dialogue(name='finishedTutorialDialogue', parent=self.dialogue_container, align=uiconst.CENTER, width=450, height=330, messageText=localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/FinishedMessage'), messageHeaderText=localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/FinishedHeader'), label=localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/GreetingLabel'), buttonLabel=localization.GetByLabel('UI/ProjectDiscovery/ErrorButton'), toHide=self) 86 uthread.new(self.get_new_task) 87 88 def close(self): 89 self.task_image.reset_image() 90 self.rewards_view.close() 91 self.result_window.kill() 92 self.processing_view.Close() 93 self.main_button_container.Close() 94 self.dialogue_container.Close() 95 self.task_id.Close() 96 self.Close() 97 98 @on_event(const.Events.ResetAndGetNewTask) 99 def reset_and_get_new_task(self): 100 self.category_selector.reset_categories() 101 self.enable_ui() 102 self.category_selector.state = uiconst.UI_DISABLED 103 self.task_image.reset_image() 104 self.get_new_task() 105 106 def get_new_task(self): 107 if self.getting_new_task: 108 return 109 self.getting_new_task = True 110 self.selection = [] 111 try: 112 self.task = self.service.get_new_task() 113 except (MissingKeyError, NoConnectionToAPIError): 114 self.open_task_retrieval_error_dialogue() 115 return 116 117 sm.ScatterEvent(const.Events.NewTask, self.task) 118 self.task_time = GetWallclockTime() 119 self.task_id.SetAlpha(0) 120 self.task_id.SetText(self.task['taskId']) 121 settings.char.ui.Set('UpdateAccInfo', True) 122 123 @on_event(const.Events.MainImageLoaded) 124 def set_getting_new_task_to_false(self): 125 if self.category_selector: 126 self.category_selector.state = uiconst.UI_NORMAL 127 self.getting_new_task = False 128 129 def _update_excluded(self, excluder): 130 excluded = set() 131 for cat in self.selection: 132 if '*' in cat.get('excludes'): 133 excluded.update(cat.get('excludes', [])) 134 elif 12 in cat.get('excludes', []): 135 excludes = list(cat.get('excludes', [])) 136 excludes.append(121) 137 excludes.append(122) 138 excludes.append(123) 139 excluded.update(excludes, []) 140 else: 141 excluded.update(cat.get('excludes', [])) 142 143 sm.ScatterEvent(const.Events.ExcludeCategories, excluder, excluded) 144 145 def open_no_categories_selected_error_dialogue(self): 146 self.disable_ui() 147 self.dialogue = Dialogue(name='ErrorDialogue', parent=self.dialogue_container, align=uiconst.CENTER, width=450, height=215, messageText=localization.GetByLabel('UI/ProjectDiscovery/NoCategorySelectedErrorMessage'), messageHeaderText=localization.GetByLabel('UI/ProjectDiscovery/NoCategorySelectedHeader'), label=localization.GetByLabel('UI/ProjectDiscovery/NotificationHeader'), buttonLabel=localization.GetByLabel('UI/ProjectDiscovery/ErrorButton'), toHide=self) 148 149 def open_too_many_categories_selected_dialogue(self): 150 self.disable_ui() 151 self.dialogue = Dialogue(name='ErrorDialogue', parent=self.dialogue_container, align=uiconst.CENTER, width=450, height=215, messageText=localization.GetByLabel('UI/ProjectDiscovery/TooManyCategoriesMessage'), messageHeaderText=localization.GetByLabel('UI/ProjectDiscovery/TooManyCategoriesHeader'), label=localization.GetByLabel('UI/ProjectDiscovery/NotificationHeader'), buttonLabel=localization.GetByLabel('UI/ProjectDiscovery/ErrorButton'), toHide=self) 152 153 def open_classification_error_dialogue(self): 154 self.disable_ui() 155 self.dialogue = Dialogue(name='ErrorDialogue', parent=self.dialogue_container, align=uiconst.CENTER, width=450, height=215, messageText=localization.GetByLabel('UI/ProjectDiscovery/ClassificationErrorMessage'), messageHeaderText=localization.GetByLabel('UI/ProjectDiscovery/ClassificationErrorHeader'), label=localization.GetByLabel('UI/ProjectDiscovery/NotificationHeader'), buttonLabel=localization.GetByLabel('UI/ProjectDiscovery/ErrorButton'), toHide=self, onCloseEvent=const.Events.RestartWindow) 156 157 def open_task_retrieval_error_dialogue(self): 158 self.disable_ui() 159 self.dialogue = Dialogue(name='ErrorDialogue', parent=self.dialogue_container, align=uiconst.CENTER, width=450, height=215, messageText=localization.GetByLabel('UI/ProjectDiscovery/TaskRetrievalErrorMessage'), messageHeaderText=localization.GetByLabel('UI/ProjectDiscovery/TaskRetrievalErrorHeader'), label=localization.GetByLabel('UI/ProjectDiscovery/NotificationHeader'), buttonLabel=localization.GetByLabel('UI/ProjectDiscovery/ErrorButton'), toHide=self, onCloseEvent=const.Events.ResetAndGetNewTask) 160 161 def disable_ui(self): 162 self.left_main_container.state = uiconst.UI_DISABLED 163 self.category_container.state = uiconst.UI_DISABLED 164 self.main_button_container.state = uiconst.UI_DISABLED 165 166 @on_event(const.Events.EnableUI) 167 def enable_ui(self): 168 self.task_image.state = uiconst.UI_NORMAL 169 self.left_main_container.state = uiconst.UI_NORMAL 170 self.category_container.state = uiconst.UI_NORMAL 171 self.main_button_container.state = uiconst.UI_NORMAL 172 173 def submit_solution(self): 174 if self.submitting: 175 return 176 if not self.selection: 177 self.open_no_categories_selected_error_dialogue() 178 return 179 sm.GetService('audio').SendUIEvent(const.Sounds.MainImageLoopStop) 180 sm.ScatterEvent(const.Events.SubmitSolution) 181 self.disable_ui() 182 self.submitting = True 183 self._animate_out() 184 self.classification = list(set([ cat['id'] for cat in self.selection if not cat['excluded'] ])) 185 duration = self.get_duration() 186 try: 187 self.result = self.service.post_classification(self.task, self.classification, duration, remark=bool(self.report_checkbox.GetValue())) 188 except: 189 self.disable_ui() 190 self.open_classification_error_dialogue() 191 raise 192 193 def get_duration(self): 194 duration = GetTimeDiff(self.task_time, GetWallclockTime()) / MSEC 195 if duration < 0: 196 duration = 0 197 return duration 198 199 def _animate_out(self): 200 animations.FadeOut(self.main_button_container) 201 animations.FadeOut(self.task_image.colorFilterContainer) 202 animations.FadeOut(self.task_label) 203 animations.FadeOut(self.checkbox_container) 204 animations.FadeOut(self.new_task_button) 205 self.new_task_button.SetState(uiconst.UI_DISABLED) 206 uthread.new(self.category_selector.cascade_categories_out) 207 self.category_selector.state = uiconst.UI_DISABLED 208 self.task_image.start_transmission_animation() 209 210 @on_event(const.Events.TransmissionFinished) 211 def prepare_result_screen(self): 212 self.left_main_container.state = uiconst.UI_NORMAL 213 animations.FadeIn(self.task_image.colorFilterContainer) 214 self.result_window.open() 215 uthread.new(self.wait_for_result) 216 217 def wait_for_result(self): 218 seconds = 1 219 while not self.has_result(): 220 self.show_loading() 221 seconds += 1 222 if seconds > RESULT_TIME_OUT: 223 self.respond_to_result_timeout() 224 return 225 blue.synchro.Sleep(1000) 226 227 self.use_result() 228 229 def respond_to_result_timeout(self): 230 logger.warning('ProjectDiscovery::wait_for_result: Result took longer than 10 seconds to arrive, giving up.', exc_info=1) 231 self.show_loading_error() 232 self.result_window.enable_ui() 233 234 def use_result(self): 235 self.result['playerSelection'] = self.classification 236 self.result_window.assign_result(self.result) 237 if settings.char.ui.Get('loadStatisticsAfterSubmission'): 238 sm.ScatterEvent('OnUpdateHeader') 239 settings.char.ui.Set('loadStatisticsAfterSubmission', False) 240 self.enable_ui() 241 self.category_container.state = uiconst.UI_DISABLED 242 self.main_button_container.state = uiconst.UI_DISABLED 243 self.hide_loading() 244 self.result_window.show_result() 245 self.task_id.SetAlpha(0.5) 246 247 def has_result(self): 248 if self.result: 249 return True 250 return False 251 252 def show_loading(self): 253 self.loading_container.SetState(uiconst.UI_NORMAL) 254 255 def hide_loading(self): 256 self.loading_container.SetState(uiconst.UI_HIDDEN) 257 258 def show_loading_error(self): 259 self.hide_loading() 260 self.error_container.SetState(uiconst.UI_NORMAL) 261 262 def hide_loading_error(self): 263 self.error_container.SetState(uiconst.UI_HIDDEN) 264 265 def get_tutorial_tooltip_steps(self): 266 return [{'owner': self.task_image.image_sprite, 267 'header': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/SampleImageHeader'), 268 'text': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/SampleImageText')}, 269 {'owner': self.category_selector, 270 'header': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/CategorySelectionHeader'), 271 'text': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/CategorySelectionText')}, 272 {'owner': self.category_selector.super_categories[0], 273 'header': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/NucleusHeader'), 274 'text': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/NucleusText')}, 275 {'owner': self.category_selector.super_categories[1], 276 'header': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/CytoplashHeader'), 277 'text': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/CytoplashText')}, 278 {'owner': self.category_selector.super_categories[2], 279 'header': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/PeripheryHeader'), 280 'text': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/PeripheryText')}, 281 {'owner': self.training_category_selector.super_categories[3], 282 'header': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/MiscellaneousHeader'), 283 'text': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/MiscellaneousText')}, 284 {'owner': self.training_category_selector.super_categories[4], 285 'header': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/NotIdentifiableHeader'), 286 'text': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/NotIdentifiableText')}] 287 288 @on_event(const.Events.CategoryChanged) 289 def on_category_changed(self, hexagon): 290 if hexagon.category['selected']: 291 if len(self.selection) > self.MAX_CATEGORIES: 292 hexagon.set_unselected() 293 self.open_too_many_categories_selected_dialogue() 294 return 295 self.selection.append(hexagon.category) 296 elif hexagon.category in self.selection: 297 self.selection.remove(hexagon.category) 298 self._update_excluded(hexagon.category['id']) 299 300 @on_event(const.Events.ContinueFromReward) 301 def on_continue_from_reward(self): 302 if not self.result: 303 self.submitting = False 304 self.task_image.state = uiconst.UI_DISABLED 305 self.category_selector.reset_categories() 306 self.category_selector.reset_categories() 307 self.hide_loading() 308 self.hide_loading_error() 309 self.result = None 310 uthread.new(self.get_new_task) 311 self.report_checkbox.SetValue(0) 312 self._animate_in() 313 self.enable_ui() 314 315 def _animate_in(self): 316 animations.FadeIn(self, duration=1) 317 animations.FadeIn(self.main_button_container) 318 animations.FadeIn(self.task_image.colorFilterContainer) 319 animations.FadeIn(self.task_label) 320 animations.FadeIn(self.checkbox_container) 321 animations.FadeIn(self.new_task_button) 322 self.new_task_button.SetState(uiconst.UI_NORMAL) 323 self.category_selector.cascade_categories_in() 324 325 @on_event(const.Events.ContinueFromResult) 326 def reset_task_image(self): 327 self.submitting = False 328 self.task_image.state = uiconst.UI_DISABLED 329 animations.FadeOut(self, duration=0.4, callback=self.task_image.reset_image)