Coverage for application / tator / routes.py: 20%

122 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-07 06:46 +0000

1""" 

2General endpoints for Tator that are used throughout the application. 

3 

4/tator/login [POST] 

5/tator/token [GET] 

6/tator/logout [GET] 

7/tator/projects [GET] 

8/tator/sections/<project_id> [GET] 

9/tator/deployments/<project_id>/<section_id> [GET] 

10/tator/refresh-sections [GET] 

11/tator/frame/<media_id>/<frame> [GET] 

12/tator/localization-image/<localization_id> [GET] 

13/tator/localization [PATCH] 

14/tator/localization/good-image [PATCH] 

15""" 

16 

17import base64 

18import json 

19 

20import tator 

21import requests 

22from flask import current_app, request, session, Response 

23 

24from . import tator_bp 

25from ..util.constants import TERM_YELLOW, TERM_RED, TERM_NORMAL 

26from ..util.tator_localization_type import TatorLocalizationType 

27 

28 

29# log in to tator (get token from tator) 

30@tator_bp.post('/login') 

31def tator_login(): 

32 res = requests.post( 

33 url=f'{current_app.config.get("TATOR_URL")}/rest/Token', 

34 headers={'Content-Type': 'application/json'}, 

35 data=json.dumps({ 

36 'username': request.values.get('username'), 

37 'password': request.values.get('password'), 

38 'refresh': True, 

39 }), 

40 ) 

41 if res.status_code == 201: 

42 session['tator_token'] = res.json()['token'] 

43 return {'username': request.values.get('username')}, 200 

44 return {}, res.status_code 

45 

46 

47# check if stored tator token is valid 

48@tator_bp.get('/token') 

49def check_tator_token(): 

50 if 'tator_token' not in session.keys(): 

51 return {}, 400 

52 try: 

53 api = tator.get_api( 

54 host=current_app.config.get('TATOR_URL'), 

55 token=session['tator_token'], 

56 ) 

57 print(f'Your Tator token: {session["tator_token"]}') 

58 return {'username': api.whoami().username}, 200 

59 except tator.openapi.tator_openapi.exceptions.ApiException: 

60 return {}, 400 

61 

62 

63# clears stored tator token 

64@tator_bp.get('/logout') 

65def tator_logout(): 

66 session.pop('tator_token', None) 

67 return {}, 200 

68 

69 

70# get a list of sections associated with a project from tator 

71@tator_bp.get('/sections/<project_id>') 

72def tator_sections(project_id): 

73 def should_skip(section_path): 

74 section_path_lower = section_path.lower() 

75 return 'test' in section_path_lower or 'toplevelsectionname' in section_path_lower 

76 

77 try: 

78 sections = {} 

79 section_list = tator.get_api( 

80 host=current_app.config.get('TATOR_URL'), 

81 token=session.get('tator_token'), 

82 ).get_section_list(project_id) 

83 # just doing two passes to simplify the logic 

84 for section in section_list: # first pass - get top-level sections 

85 if should_skip(section.path): 

86 print(f'Skipping section with test path: "{section.path}" and name: "{section.name}"') 

87 continue 

88 path_parts = section.path.split('.') 

89 if len(path_parts) != 1: 

90 continue # not a top-level section 

91 section_path_name = path_parts[0] 

92 if section_path_name == 'None': 

93 # handle case where top-level section is named "None" 🙄 (PNG DOEX010, Solomons DOEX010, etc?) 

94 section_path_name = section.name 

95 if sections.get(section_path_name): 

96 print(f'{TERM_YELLOW}WARNING: duplicate expedition-level section name "{section_path_name}" detected{TERM_NORMAL}') 

97 sections[section_path_name] = { 

98 'id': section.id, 

99 'name': section.name, 

100 'folders': {}, 

101 } 

102 for section in section_list: # second pass - get subsections 

103 if should_skip(section.path): 

104 continue 

105 path_parts = section.path.split('.') 

106 if len(path_parts) == 1: 

107 continue 

108 if len(path_parts) != 3: 

109 if path_parts[1] == 'dscm' or path_parts[1] == 'sub': 

110 continue 

111 print(f'Skipping section with unexpected path format: "{section.path}"') 

112 continue 

113 parent_name, folder_name, _ = path_parts 

114 if sections.get(parent_name) is None: 

115 print(f'{TERM_YELLOW}WARNING: Skipping sub-section "{section.name}" because parent section "{parent_name}" was not found{TERM_NORMAL}') 

116 continue 

117 if sections[parent_name]['folders'].get(folder_name) is None: 

118 sections[parent_name]['folders'][folder_name] = [] 

119 sections[parent_name]['folders'][folder_name].append({ 

120 'id': section.id, 

121 'name': section.name, 

122 }) 

123 return list(sections.values()), 200 

124 except tator.openapi.tator_openapi.exceptions.ApiException as e: 

125 print(f'{TERM_RED}ERROR: Unable to fetch Tator sections:{TERM_NORMAL} {e}') 

126 return {'500': 'Error fetching Tator sections'}, 500 

127 

128 

129# view tator video frame (not cropped) 

130@tator_bp.get('/frame/<media_id>/<frame>') 

131def tator_frame(media_id, frame): 

132 if 'tator_token' in session.keys(): 

133 token = session['tator_token'] 

134 else: 

135 token = request.args.get('token') 

136 url = f'{current_app.config.get("TATOR_URL")}/rest/GetFrame/{media_id}?frames={frame}' 

137 if request.values.get('preview'): 

138 url += '&quality=650' 

139 res = requests.get( 

140 url=url, 

141 headers={'Authorization': f'Token {token}'} 

142 ) 

143 if res.status_code == 200: 

144 base64_image = base64.b64encode(res.content).decode('utf-8') 

145 return Response(base64.b64decode(base64_image), content_type='image/png'), 200 

146 return '', 500 

147 

148 

149# view tator localization image (cropped) 

150@tator_bp.get('/localization-image/<localization_id>') 

151def tator_image(localization_id): 

152 if not session.get('tator_token'): 

153 if not request.values.get('token'): 

154 return {}, 400 

155 token = request.values.get('token') 

156 else: 

157 token = session["tator_token"] 

158 res = requests.get( 

159 url=f'{current_app.config.get("TATOR_URL")}/rest/LocalizationGraphic/{localization_id}', 

160 headers={'Authorization': f'Token {token}'} 

161 ) 

162 if res.status_code == 200: 

163 base64_image = base64.b64encode(res.content).decode('utf-8') 

164 return Response(base64.b64decode(base64_image), content_type='image/png'), 200 

165 return '', 500 

166 

167 

168# update tator localization 

169@tator_bp.patch('/localization') 

170def update_tator_localization(): 

171 localization_id_types = json.loads(request.values.get('localization_id_types')) 

172 attributes = { 

173 'Scientific Name': request.values.get('scientific_name'), 

174 'Qualifier': request.values.get('qualifier'), 

175 'Reason': request.values.get('reason'), 

176 'Tentative ID': request.values.get('tentative_id'), 

177 'IdentificationRemarks': request.values.get('identification_remarks'), 

178 'Morphospecies': request.values.get('morphospecies'), 

179 'Identified By': request.values.get('identified_by'), 

180 'Notes': request.values.get('notes'), 

181 'Attracted': request.values.get('attracted'), 

182 } 

183 try: 

184 for localization in localization_id_types: 

185 this_attributes = attributes.copy() 

186 if localization['type'] == TatorLocalizationType.DOT.value: 

187 this_attributes['Categorical Abundance'] = request.values.get('categorical_abundance') if request.values.get('categorical_abundance') else '--' 

188 api = tator.get_api( 

189 host=current_app.config.get('TATOR_URL'), 

190 token=session.get('tator_token'), 

191 ) 

192 api.update_localization_by_elemental_id( 

193 version=localization['version'], 

194 elemental_id=localization['elemental_id'], 

195 localization_update=tator.models.LocalizationUpdate( 

196 attributes=this_attributes, 

197 ) 

198 ) 

199 except tator.openapi.tator_openapi.exceptions.ApiException: 

200 return {}, 500 

201 return {}, 200 

202 

203 

204# update tator localization 'good image' 

205@tator_bp.patch('/localization/good-image') 

206def update_tator_localization_image(): 

207 localization_elemental_ids = request.values.getlist('localization_elemental_ids') 

208 version = request.values.get('version') 

209 try: 

210 for elemental_id in localization_elemental_ids: 

211 api = tator.get_api( 

212 host=current_app.config.get('TATOR_URL'), 

213 token=session.get('tator_token'), 

214 ) 

215 api.update_localization_by_elemental_id( 

216 version=version, 

217 elemental_id=elemental_id, 

218 localization_update=tator.models.LocalizationUpdate( 

219 attributes={ 

220 'Good Image': True if request.values.get('good_image') == 'true' else False, 

221 }, 

222 ) 

223 ) 

224 except tator.openapi.tator_openapi.exceptions.ApiException: 

225 return {}, 500 

226 return {}, 200