Coverage for /opt/conda/envs/apienv/lib/python3.10/site-packages/daiquiri/core/metadata/__init__.py: 65%

637 statements  

« prev     ^ index     » next       coverage.py v7.6.5, created at 2024-11-15 02:12 +0000

1#!/usr/bin/env python 

2# -*- coding: utf-8 -*- 

3import enum 

4import os 

5from functools import wraps 

6 

7from gevent import lock 

8from flask import send_file, g 

9from marshmallow import fields 

10from PIL import Image 

11 

12 

13from daiquiri.core import ( 

14 CoreBase, 

15 CoreResource, 

16 marshal, 

17 no_blsession_required, 

18 require_control, 

19) 

20from daiquiri.core.utils import loader, worker 

21from daiquiri.core.responses import image_response 

22from daiquiri.core.metadata.xrf import ( 

23 generate_map_image, 

24 generate_composite_image, 

25 shape_map, 

26) 

27from daiquiri.core.schema.validators import OneOf 

28from daiquiri.core.schema import ErrorSchema, MessageSchema 

29from daiquiri.core.schema.metadata import ( 

30 paginated, 

31 UserSchema, 

32 UserCacheSchema, 

33 ProposalSchema, 

34 SessionSchema, 

35 NewComponentSchema, 

36 ComponentSchema, 

37 NewSampleSchema, 

38 SampleSchema, 

39 NewSubSampleSchema, 

40 SubSampleSchema, 

41 NewSampleImageSchema, 

42 SampleTags, 

43 SampleImageSchema, 

44 SampleActionSchema, 

45 NewSampleActionPositionSchema, 

46 SampleActionPositionSchema, 

47 DataCollectionSchema, 

48 DataCollectionAttachmentSchema, 

49 NewDataCollectionPlanSchema, 

50 DataCollectionPlanSchema, 

51 ScanQualityIndicatorsSchema, 

52 XRFMapSchema, 

53 XRFMapHistogramSchema, 

54 XRFMapValueSchema, 

55 NewXRFCompositeMapSchema, 

56 XRFCompositeMapSchema, 

57 NewXRFMapROISchema, 

58 XRFMapROISchema, 

59 AutoProcProgramSchema, 

60 AutoProcProgramAttachmentSchema, 

61 AutoProcProgramMessageSchema, 

62) 

63 

64import logging 

65 

66logger = logging.getLogger(__name__) 

67 

68 

69class UserResource(CoreResource): 

70 @no_blsession_required 

71 @marshal(out=[[200, UserSchema(), "The current user"]]) 

72 def get(self): 

73 """Get the current user""" 

74 return self._parent.get_user(), 200 

75 

76 

77class UserCacheResource(CoreResource): 

78 @no_blsession_required 

79 @marshal(out=[[200, UserCacheSchema(), "The cache for the current user"]]) 

80 def get(self): 

81 """Get the current user cache""" 

82 return {"cache": self._parent.get_user_cache()}, 200 

83 

84 @marshal( 

85 inp=UserCacheSchema, 

86 out=[ 

87 [200, UserCacheSchema(), "The updated cache for the current user"], 

88 [400, ErrorSchema(), "Could not update the cache for the current user"], 

89 ], 

90 ) 

91 def patch(self, **kwargs): 

92 """Update the user cache""" 

93 return {"cache": self._parent.update_user_cache(kwargs["cache"])}, 200 

94 

95 

96class ProposalsResource(CoreResource): 

97 @marshal(out=[[200, paginated(ProposalSchema), "List of proposals"]]) 

98 def get(self): 

99 """Get a list of proposals""" 

100 return self._parent.get_proposals(), 200 

101 

102 

103class ProposalResource(CoreResource): 

104 @marshal( 

105 out=[ 

106 [200, ProposalSchema(), "List of proposals"], 

107 [404, ErrorSchema(), "No such proposal"], 

108 ] 

109 ) 

110 def get(self, proposal): 

111 """Get selected proposal""" 

112 proposal = self._parent.get_proposals(proposal) 

113 if proposal: 

114 return proposal, 200 

115 else: 

116 return {"error": "No such proposal"}, 404 

117 

118 

119class SessionsResource(CoreResource): 

120 @no_blsession_required 

121 @marshal( 

122 inp={"current": fields.Bool()}, 

123 out=[[200, paginated(SessionSchema), "List of sessions"]], 

124 ) 

125 def get(self, **kwargs): 

126 """Get a list of session""" 

127 return self._parent.get_sessions(**kwargs), 200 

128 

129 

130class SessionResource(CoreResource): 

131 @marshal( 

132 out=[ 

133 [200, SessionSchema(), "Get selected session"], 

134 [404, ErrorSchema(), "No such session"], 

135 ] 

136 ) 

137 def get(self, session): 

138 """Get selected session""" 

139 session = self._parent.get_sessions(session) 

140 if session: 

141 return session, 200 

142 else: 

143 return {"error": "No such session"}, 404 

144 

145 

146class SelectSessionResource(CoreResource): 

147 @no_blsession_required 

148 @marshal( 

149 inp={"session": fields.Str()}, 

150 out=[ 

151 [200, SessionSchema(), "The selected session"], 

152 [400, ErrorSchema(), "Could not select session"], 

153 ], 

154 ) 

155 def post(self, **kwargs): 

156 """Set the session for the current user""" 

157 if self._parent.set_session(kwargs["session"]): 

158 return self._parent.get_sessions(kwargs["session"]), 200 

159 else: 

160 return {"error": "Could not select session"}, 400 

161 

162 

163class ComponentsResource(CoreResource): 

164 @marshal(out=[[200, paginated(ComponentSchema), "List of components"]]) 

165 def get(self, **kwargs): 

166 """Get a list of components""" 

167 return self._parent.get_components(**kwargs), 200 

168 

169 @marshal( 

170 inp=NewComponentSchema, 

171 out=[ 

172 [200, SampleSchema(), "New Component"], 

173 [400, ErrorSchema(), "Could not create component"], 

174 ], 

175 ) 

176 def post(self, **kwargs): 

177 """Create a component""" 

178 component = self._parent.add_component(**kwargs) 

179 if component: 

180 return component, 201 

181 else: 

182 return {"error": "Could not create component"}, 400 

183 

184 

185class ComponentResource(CoreResource): 

186 @marshal( 

187 out=[ 

188 [200, ComponentSchema(), "Get selected component"], 

189 [404, ErrorSchema(), "No such component"], 

190 ] 

191 ) 

192 def get(self, componentid): 

193 """Get selected component""" 

194 component = self._parent.get_components(componentid) 

195 if component: 

196 return component, 200 

197 else: 

198 return {"error": "No such component"}, 404 

199 

200 @marshal( 

201 inp=ComponentSchema, 

202 out=[ 

203 [200, ComponentSchema(), "Updated component"], 

204 [404, ErrorSchema(), "No such component"], 

205 ], 

206 ) 

207 def patch(self, componentid, **kwargs): 

208 """Update a component""" 

209 component = self._parent.update_component(componentid, **kwargs) 

210 if component: 

211 return component, 200 

212 else: 

213 return {"error": "No such component"}, 404 

214 

215 

216class SamplesResource(CoreResource): 

217 @marshal(out=[[200, paginated(SampleSchema), "List of samples"]]) 

218 def get(self, **kwargs): 

219 """Get a list of samples""" 

220 return self._parent.get_samples(**kwargs), 200 

221 

222 @marshal( 

223 inp=NewSampleSchema, 

224 out=[ 

225 [200, SampleSchema(), "New Sample"], 

226 [400, ErrorSchema(), "Could not create sample"], 

227 ], 

228 ) 

229 def post(self, **kwargs): 

230 """Create a sample""" 

231 sample = self._parent.add_sample(**kwargs) 

232 if sample: 

233 return sample, 201 

234 else: 

235 return {"error": "Could not create sample"}, 400 

236 

237 

238class SampleResource(CoreResource): 

239 @marshal( 

240 out=[ 

241 [200, SampleSchema(), "Get selected samples"], 

242 [404, ErrorSchema(), "No such sample"], 

243 ] 

244 ) 

245 def get(self, sampleid): 

246 """Get selected sample""" 

247 sample = self._parent.get_samples(sampleid) 

248 if sample: 

249 return sample, 200 

250 else: 

251 return {"error": "No such sample"}, 404 

252 

253 @marshal( 

254 inp=SampleSchema, 

255 out=[ 

256 [200, SampleSchema(), "Updated sample"], 

257 [404, ErrorSchema(), "No such sample"], 

258 ], 

259 ) 

260 def patch(self, sampleid, **kwargs): 

261 """Update a sample""" 

262 sample = self._parent.update_sample(sampleid, **kwargs) 

263 if sample: 

264 return sample, 200 

265 else: 

266 return {"error": "No such sample"}, 404 

267 

268 @marshal( 

269 out=[ 

270 [200, MessageSchema(), "Sample deleted"], 

271 [404, ErrorSchema(), "No such sample"], 

272 ] 

273 ) 

274 def delete(self, sampleid, **kwargs): 

275 """Delete a sample""" 

276 subsample = self._parent.remove_sample(sampleid, **kwargs) 

277 if subsample: 

278 return {"message": "Subsample deleted"}, 200 

279 else: 

280 return {"error": "No such subsample"}, 404 

281 

282 

283class SubSamplesResource(CoreResource): 

284 @marshal( 

285 inp={"sampleid": fields.Int()}, 

286 out=[[200, paginated(SubSampleSchema), "List of sub samples"]], 

287 ) 

288 def get(self, **kwargs): 

289 """Get a list of subsamples""" 

290 return self._parent.get_subsamples(**kwargs), 200 

291 

292 @marshal( 

293 inp=NewSubSampleSchema, 

294 out=[ 

295 [200, SubSampleSchema(), "New Subsample"], 

296 [400, ErrorSchema(), "Could not create subsample"], 

297 ], 

298 ) 

299 def post(self, **kwargs): 

300 """Create a subsample""" 

301 subsample = self._parent.add_subsample(**kwargs) 

302 if subsample: 

303 return subsample, 201 

304 else: 

305 return {"error": "Could not create subsample"}, 400 

306 

307 

308class SubSampleResource(CoreResource): 

309 @marshal( 

310 out=[ 

311 [200, SubSampleSchema(), "Get selected subsample"], 

312 [404, ErrorSchema(), "No such subsample"], 

313 ] 

314 ) 

315 def get(self, subsampleid): 

316 """Get selected subsample""" 

317 subsample = self._parent.get_subsamples(subsampleid) 

318 if subsample: 

319 return subsample, 200 

320 else: 

321 return {"error": "No such subsample"}, 404 

322 

323 @marshal( 

324 inp=SubSampleSchema, 

325 out=[ 

326 [200, SubSampleSchema(), "Updated subsample"], 

327 [404, ErrorSchema(), "No such subsample"], 

328 ], 

329 ) 

330 def patch(self, subsampleid, **kwargs): 

331 """Update a sub sample""" 

332 subsample = self._parent.update_subsample(subsampleid, **kwargs) 

333 if subsample: 

334 return subsample, 200 

335 else: 

336 return {"error": "No such subsample"}, 404 

337 

338 @marshal( 

339 out=[ 

340 [200, MessageSchema(), "Subsample deleted"], 

341 [404, ErrorSchema(), "No such subsample"], 

342 ] 

343 ) 

344 def delete(self, subsampleid, **kwargs): 

345 """Delete a subsample""" 

346 subsample = self._parent.remove_subsample(subsampleid, **kwargs) 

347 if subsample: 

348 return {"message": "Subsample deleted"}, 200 

349 else: 

350 return {"error": "No such subsample"}, 404 

351 

352 

353class SampleTagTypeEnum(enum.Enum): 

354 sample = "sample" 

355 subsample = "subsample" 

356 

357 

358class SampleTagsResource(CoreResource): 

359 @marshal( 

360 inp={"type": fields.Enum(SampleTagTypeEnum)}, 

361 out=[[200, SampleTags, "List of sub/sample tags"]], 

362 ) 

363 def get(self, type=None, **kwargs): 

364 """Get a list of sub/sample tags""" 

365 return self._parent.get_sample_tags(type=type, **kwargs), 200 

366 

367 

368class SampleImagesResource(CoreResource): 

369 @marshal( 

370 inp={"proposal": fields.Str(), "sampleid": fields.Int()}, 

371 out=[[200, paginated(SampleImageSchema), "List of sample images"]], 

372 ) 

373 def get(self, **kwargs): 

374 """Get a list of sample images""" 

375 return self._parent.get_sampleimages(**kwargs), 200 

376 

377 @marshal(inp=NewSampleImageSchema, out=[[200, SampleImageSchema(), "Sample image"]]) 

378 def post(self, **kwargs): 

379 """Create a sample image""" 

380 return self._parent.add_sampleimage(**kwargs), 201 

381 

382 

383class SampleImageResource(CoreResource): 

384 @marshal( 

385 inp={ 

386 "no_cache": fields.Int(metadata={"description": "Send no cache header"}), 

387 "scale": fields.Float( 

388 metadata={"description": "Scale images by this factor"}, dump_default=1 

389 ), 

390 } 

391 ) 

392 def get( 

393 self, sampleimageid: int, scale: float = 1, no_cache: bool = None, **kwargs 

394 ): 

395 """Get a sample image""" 

396 path = self._parent.get_sampleimage(sampleimageid, **kwargs) 

397 if path: 

398 extra_args = {} 

399 if no_cache: 

400 extra_args["max_age"] = -1 

401 

402 if scale and scale != 1: 

403 

404 def resize(): 

405 img = Image.open(path) 

406 if img.height < img.width: 

407 size = (int(img.width * scale), int(img.width * scale)) 

408 else: 

409 size = (int(img.height * scale), int(img.height * scale)) 

410 img.thumbnail(size, Image.LANCZOS) 

411 return img 

412 

413 return image_response(worker(resize), max_age=10000) 

414 

415 if os.path.exists(path): 

416 return send_file(path, **extra_args) 

417 else: 

418 return {"error": "No such image"}, 404 

419 else: 

420 return {"error": "No such image"}, 404 

421 

422 

423class SampleActionsResource(CoreResource): 

424 @marshal( 

425 inp={"actiontype": fields.Str(), "sampleid": fields.Int()}, 

426 out=[[200, paginated(SampleActionSchema), "List of sample actions"]], 

427 ) 

428 def get(self, **kwargs): 

429 """Get a list of sample actions""" 

430 return self._parent.get_sampleactions(**kwargs), 200 

431 

432 

433class SampleActionResource(CoreResource): 

434 @marshal( 

435 out=[[200, SampleActionSchema(), "A sample actions"]], 

436 ) 

437 def get(self, sampleactionid, **kwargs): 

438 """Get a sample action""" 

439 sampleaction = self._parent.get_sampleactions( 

440 sampleactionid=sampleactionid, **kwargs 

441 ) 

442 if sampleaction: 

443 return sampleaction, 200 

444 else: 

445 return {"error": "No such sample action"}, 404 

446 

447 

448class SampleActionPositionsResource(CoreResource): 

449 @marshal( 

450 inp={"sampleactionid": fields.Int(), "type": OneOf(["reference", "real"])}, 

451 out=[ 

452 [ 

453 200, 

454 paginated(SampleActionPositionSchema), 

455 "List of sample action positions", 

456 ] 

457 ], 

458 ) 

459 def get(self, **kwargs): 

460 """Get a list of sample action positions""" 

461 return self._parent.get_sampleaction_positions(**kwargs), 200 

462 

463 @marshal( 

464 inp=NewSampleActionPositionSchema, 

465 out=[ 

466 [201, SampleActionPositionSchema(), "Sample action position"], 

467 [400, ErrorSchema(), "Could not add sample action position"], 

468 ], 

469 ) 

470 def post(self, **kwargs): 

471 """Create a sample action position""" 

472 sampleactionposition = self._parent.add_sampleaction_position(**kwargs) 

473 if sampleactionposition: 

474 return sampleactionposition, 201 

475 else: 

476 return {"error": "Could not create sample action position"}, 400 

477 

478 

479class SampleActionPositionResource(CoreResource): 

480 @marshal( 

481 out=[ 

482 [200, MessageSchema(), "Sample action position deleted"], 

483 [404, ErrorSchema(), "No such sample action position"], 

484 ] 

485 ) 

486 def delete(self, positionid, **kwargs): 

487 """Delete a sample action position""" 

488 sampleactionposition = self._parent.remove_sampleaction_position( 

489 positionid, **kwargs 

490 ) 

491 if sampleactionposition: 

492 return "", 204 

493 else: 

494 return {"error": "No such sample action position"}, 404 

495 

496 

497class DataCollectionsResource(CoreResource): 

498 @marshal( 

499 inp={ 

500 "session": fields.Str(), 

501 "datacollectiongroupid": fields.Int(), 

502 "subsampleid": fields.Int(), 

503 "sampleid": fields.Int(), 

504 "status": fields.Str(), 

505 "ungroup": fields.Int(), 

506 }, 

507 out=[[200, paginated(DataCollectionSchema), "List of datacollections"]], 

508 paged=True, 

509 ordered=True, 

510 ) 

511 def get(self, **kwargs): 

512 """Get a list of datacollections""" 

513 return self._parent.get_datacollections(**kwargs), 200 

514 

515 

516class DataCollectionResource(CoreResource): 

517 @marshal( 

518 inp=DataCollectionSchema, 

519 out=[ 

520 [200, DataCollectionSchema(), "Updated datacollection"], 

521 [404, ErrorSchema(), "No such datacollection"], 

522 ], 

523 ) 

524 def patch(self, datacollectionid, **kwargs): 

525 """Update a datacollection 

526 

527 Only allows updating the comments field 

528 """ 

529 dc = self._parent.update_datacollection( 

530 datacollectionid, comments=kwargs.get("comments") 

531 ) 

532 if dc: 

533 return dc, 200 

534 else: 

535 return {"error": "No such datacollection"}, 404 

536 

537 

538class DataCollectionAttachmentsResource(CoreResource): 

539 @marshal( 

540 inp={"datacollectionid": fields.Int(), "filetype": fields.Str()}, 

541 out=[ 

542 [ 

543 200, 

544 paginated(DataCollectionAttachmentSchema), 

545 "List of datacollection file attachments", 

546 ] 

547 ], 

548 ) 

549 def get(self, **kwargs): 

550 """Get a list of datacollection file attachments""" 

551 return self._parent.get_datacollection_attachments(**kwargs), 200 

552 

553 

554class DataCollectionAttachmentResource(CoreResource): 

555 def get(self, datacollectionattachmentid, **kwargs): 

556 """Get a datacollection attachment""" 

557 att = self._parent.get_datacollection_attachment( 

558 datacollectionattachmentid, **kwargs 

559 ) 

560 if att: 

561 mime_args = {} 

562 # TODO: have to override flasks defaults here, why? 

563 mimes = { 

564 "json": "application/json", 

565 "txt": "text/plain", 

566 "log": "text/plain", 

567 } 

568 if att["extension"] in mimes: 

569 mime_args["mimetype"] = mimes[att["extension"]] 

570 

571 if os.path.exists(att["filefullpath"]): 

572 return send_file( 

573 att["filefullpath"], 

574 max_age=-1, 

575 as_attachment=True, 

576 **mime_args, 

577 ) 

578 else: 

579 return {"error": "No such datacollection attachment"}, 404 

580 else: 

581 return {"error": "No such datacollection attachment"}, 404 

582 

583 

584class DataCollectionSnapshotResource(CoreResource): 

585 @marshal( 

586 inp={ 

587 "thumb": fields.Int(metadata={"description": "Return thumbnail image"}), 

588 "no_cache": fields.Int(metadata={"description": "Send no cache header"}), 

589 } 

590 ) 

591 def get(self, datacollectionid, snapshotid, **kwargs): 

592 """Get a datacollection snapshot""" 

593 path = self._parent.get_snapshot(datacollectionid, snapshotid, **kwargs) 

594 if path: 

595 extra_args = {} 

596 if kwargs.get("no_cache"): 

597 extra_args["max_age"] = -1 

598 

599 if kwargs.get("thumb"): 

600 ext = os.path.splitext(path)[1][1:].strip() 

601 path = path.replace(f".{ext}", f"t.{ext}") 

602 

603 if os.path.exists(path): 

604 return send_file(path, **extra_args) 

605 else: 

606 return {"error": "No such snapshot"}, 404 

607 else: 

608 return {"error": "No such snapshot"}, 404 

609 

610 

611class DataCollectionPlansResource(CoreResource): 

612 @marshal( 

613 inp={ 

614 "session": fields.Str(), 

615 "subsampleid": fields.Int(), 

616 "sampleid": fields.Int(), 

617 "status": OneOf(["executed", "pending"]), 

618 "queued": fields.Bool(), 

619 }, 

620 ordered=True, 

621 out=[ 

622 [200, paginated(DataCollectionPlanSchema), "List of datacollection plans"] 

623 ], 

624 ) 

625 def get(self, **kwargs): 

626 """Get a list of datacollection plans""" 

627 return self._parent.get_datacollectionplans(**kwargs), 200 

628 

629 @marshal( 

630 inp=NewDataCollectionPlanSchema, 

631 out=[ 

632 [200, DataCollectionPlanSchema(), "New Datacollection plan"], 

633 [400, ErrorSchema(), "Could not create datacollection plan"], 

634 ], 

635 ) 

636 def post(self, **kwargs): 

637 """Create a datacollection plan""" 

638 dcplan = self._parent.add_datacollectionplan(**kwargs) 

639 if dcplan: 

640 return dcplan, 201 

641 else: 

642 return {"error": "Could not create datacollection plan"}, 400 

643 

644 

645class DataCollectionPlanResource(CoreResource): 

646 @marshal( 

647 out=[ 

648 [200, DataCollectionPlanSchema(), "Get selected datacollection plan"], 

649 [404, ErrorSchema(), "No such datacollection plan"], 

650 ] 

651 ) 

652 def get(self, datacollectionplanid): 

653 """Get selected datacollection plan""" 

654 dcplan = self._parent.get_datacollectionplans(datacollectionplanid) 

655 if dcplan: 

656 return dcplan, 200 

657 else: 

658 return {"error": "No such datacollection plan"}, 404 

659 

660 @marshal( 

661 inp=DataCollectionPlanSchema, 

662 out=[ 

663 [200, DataCollectionPlanSchema(), "Updated datacollection plan"], 

664 [404, ErrorSchema(), "No such datacollection plan"], 

665 ], 

666 ) 

667 def patch(self, datacollectionplanid, **kwargs): 

668 """Update a datacollection plan""" 

669 dcplan = self._parent.update_datacollectionplan(datacollectionplanid, **kwargs) 

670 if dcplan: 

671 return dcplan, 200 

672 else: 

673 return {"error": "No such datacollection plan"}, 404 

674 

675 @marshal( 

676 out=[ 

677 [200, MessageSchema(), "Datacollection plan deleted"], 

678 [404, ErrorSchema(), "No such datacollection plan"], 

679 [400, ErrorSchema(), "Could not remove datacollection plan"], 

680 ] 

681 ) 

682 def delete(self, datacollectionplanid, **kwargs): 

683 """Delete a datacollection plan""" 

684 try: 

685 dcplan = self._parent.remove_datacollectionplan( 

686 datacollectionplanid, **kwargs 

687 ) 

688 if dcplan: 

689 return {"message": "Datacollection plan deleted"}, 200 

690 else: 

691 return {"error": "No such datacollection plan"}, 404 

692 except RuntimeError as e: 

693 return {"error": e.args[0]}, 400 

694 

695 

696class ScanQualityIndicatorsResource(CoreResource): 

697 @marshal(out=[[200, ScanQualityIndicatorsSchema(), "Get scan quality indicators"]]) 

698 def get(self, datacollectionid): 

699 """Get scan quality indicators""" 

700 sqis = self._parent.get_scanqualityindicators(datacollectionid=datacollectionid) 

701 return sqis, 200 

702 

703 

704class XRFMapsResource(CoreResource): 

705 @marshal( 

706 inp={ 

707 "datacollectiongroupid": fields.Int(), 

708 "sampleid": fields.Int(metadata={"description": "The sampleid"}), 

709 "subsampleid": fields.Int(metadata={"description": "The subsampleid"}), 

710 }, 

711 out=[[200, paginated(XRFMapSchema), "A list of XRF maps"]], 

712 ) 

713 def get(self, **kwargs): 

714 """Get a list of XRF maps""" 

715 return self._parent.get_xrf_maps(**kwargs) 

716 

717 

718class XRFMapResource(CoreResource): 

719 @marshal(out=[[404, ErrorSchema(), "XRF map not found"]]) 

720 def get(self, mapid, **kwargs): 

721 """Get an image of an XRF map""" 

722 img = self._parent.get_xrf_map_image(mapid=mapid, **kwargs) 

723 if img is not None: 

724 return image_response(img) 

725 

726 else: 

727 return {"error": "No such map"}, 404 

728 

729 @marshal( 

730 inp=XRFMapSchema, 

731 out=[ 

732 [200, XRFMapSchema(), "The updated XRF map"], 

733 [400, ErrorSchema(), "Could not update XRF map"], 

734 ], 

735 ) 

736 def patch(self, mapid, **kwargs): 

737 """Update an XRF map""" 

738 m = self._parent.update_xrf_map(mapid, **kwargs) 

739 if m: 

740 return m, 200 

741 else: 

742 return {"error": "Could not update XRF map"}, 400 

743 

744 @marshal( 

745 out=[ 

746 [200, MessageSchema(), "XRF map deleted"], 

747 [400, ErrorSchema(), "Could not delete XRF map"], 

748 ] 

749 ) 

750 def delete(self, mapid, **kwargs): 

751 """Delete an XRF map""" 

752 _map = self._parent.remove_xrf_map(mapid, **kwargs) 

753 if _map: 

754 return {"message": "XRF Map deleted"}, 200 

755 else: 

756 return {"error": "No such XRF map"}, 404 

757 

758 

759class XRFMapHistogramResource(CoreResource): 

760 @marshal( 

761 inp={ 

762 "autoscale": fields.Bool(), 

763 }, 

764 out=[ 

765 [200, XRFMapHistogramSchema(), "The XRF map histogram"], 

766 [400, ErrorSchema(), "XRF map not found"], 

767 ], 

768 ) 

769 def get(self, mapid, **kwargs): 

770 """Get a histogram of values for an XRF map""" 

771 hist = self._parent.get_xrf_maps(mapid=mapid, histogram=True, **kwargs) 

772 if hist is not None: 

773 return hist 

774 else: 

775 return {"error": "No such map"}, 404 

776 

777 

778class XRFMapValueResource(CoreResource): 

779 @marshal( 

780 inp={"x": fields.Int(required=True), "y": fields.Int(required=True)}, 

781 out=[ 

782 [200, XRFMapValueSchema(), "XRF map value"], 

783 [400, ErrorSchema(), "Invalid point"], 

784 [404, ErrorSchema(), "XRF map not found"], 

785 ], 

786 ) 

787 def get(self, mapid, **kwargs): 

788 """Get a value at an x,y point from an XRF map""" 

789 map_ = self._parent.get_xrf_maps(mapid=mapid, data=True, **kwargs) 

790 if map_ is not None: 

791 shaped = shape_map(map_) 

792 

793 if kwargs["y"] < len(shaped): 

794 if kwargs["x"] < len(shaped[kwargs["y"]]): 

795 return { 

796 "mapid": mapid, 

797 "x": kwargs["x"], 

798 "y": kwargs["y"], 

799 "value": shaped[kwargs["y"]][kwargs["x"]], 

800 } 

801 

802 return {"error": "Invalid point"}, 400 

803 

804 else: 

805 return {"error": "No such map"}, 404 

806 

807 

808class XRFCompositesMapResource(CoreResource): 

809 @marshal( 

810 inp={"sampleid": fields.Int(metadata={"description": "The sampleid"})}, 

811 out=[[200, paginated(XRFCompositeMapSchema), "A list of XRF composite maps"]], 

812 ) 

813 def get(self, **kwargs): 

814 """Get a list of XRF composite maps""" 

815 return self._parent.get_xrf_composites(**kwargs) 

816 

817 @marshal( 

818 inp=NewXRFCompositeMapSchema, 

819 out=[ 

820 [200, XRFCompositeMapSchema(), "New XRF composite map"], 

821 [400, ErrorSchema(), "Could not add XRF composite map"], 

822 ], 

823 ) 

824 def post(self, **kwargs): 

825 """Add a new XRF composite map""" 

826 comp = self._parent.add_xrf_composite(**kwargs) 

827 if comp: 

828 return comp, 201 

829 else: 

830 return {"error": "Could not add XRF composite map"}, 400 

831 

832 

833class XRFCompositeMapResource(CoreResource): 

834 @marshal(out=[[404, ErrorSchema(), "XRF composite map not found"]]) 

835 def get(self, compositeid, **kwargs): 

836 """Get an image of an XRF composite map""" 

837 img = self._parent.get_xrf_composite_image(compositeid=compositeid, **kwargs) 

838 if img is not None: 

839 return image_response(img) 

840 else: 

841 return {"error": "No such XRF composite map"}, 404 

842 

843 @marshal( 

844 inp=XRFCompositeMapSchema, 

845 out=[ 

846 [200, XRFCompositeMapSchema(), "The updated XRF composite map"], 

847 [400, ErrorSchema(), "Could not update XRF composite map"], 

848 ], 

849 ) 

850 def patch(self, compositeid, **kwargs): 

851 """Update an XRF composite map""" 

852 c = self._parent.update_xrf_composite(compositeid, **kwargs) 

853 if c: 

854 return c, 200 

855 else: 

856 return {"error": "Could not update XRF composite map"}, 400 

857 

858 @marshal( 

859 out=[ 

860 [200, MessageSchema(), "XRF composite map deleted"], 

861 [404, ErrorSchema(), "No such XRF composite map"], 

862 ] 

863 ) 

864 def delete(self, compositeid, **kwargs): 

865 """Delete an XRF composite map""" 

866 comp = self._parent.remove_xrf_composite(compositeid, **kwargs) 

867 if comp: 

868 return {"message": "XRF composite map deleted"}, 200 

869 else: 

870 return {"error": "No such XRF composite map"}, 404 

871 

872 

873class XRFMapROIsResource(CoreResource): 

874 @marshal( 

875 inp={"sampleid": fields.Int()}, 

876 out=[[200, paginated(XRFMapROISchema), "A list of XRF map ROIs"]], 

877 ) 

878 def get(self, **kwargs): 

879 """Get a list of XRF map ROIs""" 

880 return self._parent.get_xrf_map_rois(**kwargs) 

881 

882 @require_control 

883 @marshal( 

884 inp=NewXRFMapROISchema, 

885 out=[ 

886 [200, XRFMapROISchema(), "New XRF map ROI"], 

887 [400, ErrorSchema(), "Could not add XRF map ROI"], 

888 ], 

889 ) 

890 def post(self, **kwargs): 

891 """Add a new XRF map ROI""" 

892 maproi = self._parent.add_xrf_map_roi(**kwargs) 

893 if maproi: 

894 return maproi, 201 

895 else: 

896 return {"error": "Could not add XRF map ROI"}, 400 

897 

898 

899class XRFMapROIResource(CoreResource): 

900 @require_control 

901 @marshal( 

902 inp=XRFMapROISchema, 

903 out=[ 

904 [200, XRFMapROISchema(), "The updated XRF map ROI"], 

905 [400, ErrorSchema(), "Could not update XRF map ROI"], 

906 ], 

907 ) 

908 def patch(self, maproiid, **kwargs): 

909 """Update an XRF map ROI""" 

910 m = self._parent.update_xrf_map_roi(maproiid, **kwargs) 

911 if m: 

912 return m, 200 

913 else: 

914 return {"error": "Could not update XRF map ROI"}, 400 

915 

916 @require_control 

917 @marshal( 

918 out=[ 

919 [200, MessageSchema(), "XRF map ROI deleted"], 

920 [404, ErrorSchema(), "No such XRF map ROI"], 

921 ] 

922 ) 

923 def delete(self, maproiid, **kwargs): 

924 """Delete an XRF map ROI""" 

925 roi = self._parent.remove_xrf_map_roi(maproiid, **kwargs) 

926 if roi: 

927 return {"message": "XRF map roi deleted"}, 200 

928 else: 

929 return {"error": "No such XRF map roi"}, 404 

930 

931 

932class AutoProcProgramsResource(CoreResource): 

933 @marshal( 

934 inp={ 

935 "sampleid": fields.Int(), 

936 "subsampleid": fields.Int(), 

937 "datacollectionid": fields.Int(), 

938 "exclude_sqi": fields.Int(metadata={"description": "Exclude SQI Programs"}), 

939 }, 

940 out=[[200, paginated(AutoProcProgramSchema), "List of auto processings"]], 

941 paged=True, 

942 ordered=True, 

943 ) 

944 def get(self, **kwargs): 

945 """Get a list of auto processings""" 

946 return self._parent.get_autoprocprograms(**kwargs), 200 

947 

948 

949class AutoProcProgramAttachmentsResource(CoreResource): 

950 @marshal( 

951 inp={"autoprocprogramid": fields.Int()}, 

952 out=[ 

953 [ 

954 200, 

955 paginated(AutoProcProgramAttachmentSchema), 

956 "List of auto processing attachments", 

957 ] 

958 ], 

959 ) 

960 def get(self, **kwargs): 

961 """Get a list of auto processing attachments""" 

962 return ( 

963 self._parent.get_autoprocprogram_attachments(**kwargs), 

964 200, 

965 ) 

966 

967 

968class AutoProcProgramMessagesResource(CoreResource): 

969 @marshal( 

970 inp={"autoprocprogramid": fields.Int()}, 

971 out=[ 

972 [ 

973 200, 

974 paginated(AutoProcProgramMessageSchema), 

975 "List of auto processing messages", 

976 ] 

977 ], 

978 ) 

979 def get(self, **kwargs): 

980 """Get a list of auto processing messages""" 

981 return ( 

982 self._parent.get_autoprocprogram_messages(**kwargs), 

983 200, 

984 ) 

985 

986 

987class AutoProcProgramAttachmentResource(CoreResource): 

988 def get(self, autoprocprogramattachmentid, **kwargs): 

989 """Get an auto processing attachment""" 

990 attachment = self._parent.get_autoprocprogram_attachments( 

991 autoprocprogramattachmentid, **kwargs 

992 ) 

993 path = attachment["filefullpath"] 

994 if path: 

995 if os.path.exists(path): 

996 mime_args = {} 

997 # TODO: have to override flasks defaults here, why? 

998 mimes = { 

999 "json": "application/json", 

1000 "txt": "text/plain", 

1001 "log": "text/plain", 

1002 } 

1003 if attachment["extension"] in mimes: 

1004 mime_args["mimetype"] = mimes[attachment["extension"]] 

1005 

1006 return send_file(path, max_age=-1, as_attachment=True, **mime_args) 

1007 else: 

1008 return {"error": "No such attachment"}, 404 

1009 else: 

1010 return {"error": "No such attachment"}, 404 

1011 

1012 

1013class MetaData: 

1014 def __init__(self, config, *args, **kwargs): 

1015 kwargs["config"] = config 

1016 

1017 self._meta = loader( 

1018 "daiquiri.core.metadata", 

1019 "MetaDataHandler", 

1020 config["meta_type"], 

1021 *args, 

1022 **kwargs, 

1023 ) 

1024 

1025 def init(self): 

1026 return self._meta 

1027 

1028 

1029class MetaDataHandler(CoreBase): 

1030 _require_session = True 

1031 _require_blsession = True 

1032 _namespace = "metadata" 

1033 _base_url = "metadata" 

1034 

1035 _cache = {} 

1036 

1037 def setup(self): 

1038 self._cache_locks = {} 

1039 

1040 self.register_route(UserResource, "/users/current") 

1041 self.register_route(UserCacheResource, "/users/cache") 

1042 

1043 self.register_route(ProposalsResource, "/proposals") 

1044 self.register_route(ProposalResource, "/proposals/<string:proposal>") 

1045 

1046 self.register_route(SessionsResource, "/sessions") 

1047 self.register_route(SessionResource, "/sessions/<string:session>") 

1048 self.register_route(SelectSessionResource, "/sessions/select") 

1049 

1050 self.register_route(ComponentsResource, "/components") 

1051 self.register_route(ComponentResource, "/components/<int:componentid>") 

1052 

1053 self.register_route(SamplesResource, "/samples") 

1054 self.register_route(SampleResource, "/samples/<int:sampleid>") 

1055 self.register_route(SubSamplesResource, "/samples/sub") 

1056 self.register_route(SubSampleResource, "/samples/sub/<int:subsampleid>") 

1057 self.register_route(SampleImagesResource, "/samples/images") 

1058 self.register_route(SampleImageResource, "/samples/images/<int:sampleimageid>") 

1059 self.register_route(SampleTagsResource, "/samples/tags") 

1060 

1061 self.register_route(SampleActionsResource, "/samples/actions") 

1062 self.register_route( 

1063 SampleActionResource, "/samples/actions/<int:sampleactionid>" 

1064 ) 

1065 self.register_route(SampleActionPositionsResource, "/samples/actions/positions") 

1066 self.register_route( 

1067 SampleActionPositionResource, "/samples/actions/positions/<int:positionid>" 

1068 ) 

1069 

1070 self.register_route(DataCollectionsResource, "/datacollections") 

1071 self.register_route( 

1072 DataCollectionResource, "/datacollections/<int:datacollectionid>" 

1073 ) 

1074 self.register_route( 

1075 DataCollectionAttachmentsResource, "/datacollections/attachments" 

1076 ) 

1077 self.register_route( 

1078 DataCollectionAttachmentResource, 

1079 "/datacollections/attachments/<int:datacollectionattachmentid>", 

1080 ) 

1081 self.register_route( 

1082 DataCollectionSnapshotResource, 

1083 "/datacollections/snapshots/<int:datacollectionid>/<int:snapshotid>", 

1084 ) 

1085 self.register_route(DataCollectionPlansResource, "/datacollections/plans") 

1086 self.register_route( 

1087 DataCollectionPlanResource, 

1088 "/datacollections/plans/<int:datacollectionplanid>", 

1089 ) 

1090 self.register_route( 

1091 ScanQualityIndicatorsResource, 

1092 "/datacollections/sqis/<int:datacollectionid>", 

1093 ) 

1094 

1095 self.register_route(XRFMapsResource, "/xrf/maps") 

1096 self.register_route(XRFMapResource, "/xrf/maps/<int:mapid>") 

1097 self.register_route(XRFMapValueResource, "/xrf/maps/value/<int:mapid>") 

1098 self.register_route(XRFMapHistogramResource, "/xrf/maps/histogram/<int:mapid>") 

1099 

1100 self.register_route(XRFCompositesMapResource, "/xrf/maps/composite") 

1101 self.register_route( 

1102 XRFCompositeMapResource, "/xrf/maps/composite/<int:compositeid>" 

1103 ) 

1104 

1105 self.register_route(XRFMapROIsResource, "/xrf/maps/rois") 

1106 self.register_route(XRFMapROIResource, "/xrf/maps/rois/<int:maproiid>") 

1107 

1108 self.register_route(AutoProcProgramsResource, "/autoprocs") 

1109 self.register_route( 

1110 AutoProcProgramAttachmentsResource, 

1111 "/autoprocs/attachments", 

1112 ) 

1113 self.register_route( 

1114 AutoProcProgramAttachmentResource, 

1115 "/autoprocs/attachments/<int:autoprocprogramattachmentid>", 

1116 ) 

1117 self.register_route(AutoProcProgramMessagesResource, "/autoprocs/messages") 

1118 

1119 # Inject socketio notifier to subclassed methods 

1120 for t in [ 

1121 "sample", 

1122 "subsample", 

1123 "datacollection", 

1124 "datacollectionplan", 

1125 "sampleimage", 

1126 "sampleaction", 

1127 "sampleaction_position", 

1128 "xrf_map", 

1129 "xrf_composite", 

1130 ]: 

1131 for a in ["add", "update"]: 

1132 if a == "update" and t in ["sampleaction", "sampleaction_position"]: 

1133 continue 

1134 

1135 fn = f"{a}_{t}" 

1136 old = getattr(self, fn) 

1137 if not hasattr(old, "_wrapped"): 

1138 new = wraps(fn)(self.notify(getattr(self, fn), type=t, action=a)) 

1139 new._wrapped = True 

1140 setattr(self, fn, new) 

1141 

1142 for t in ["subsample"]: 

1143 for a in ["queue", "unqueue"]: 

1144 fn = f"{a}_{t}" 

1145 old = getattr(self, fn) 

1146 if not hasattr(old, "_wrapped"): 

1147 new = wraps(fn)(self.notify(getattr(self, fn), type=t, action=a)) 

1148 new._wrapped = True 

1149 setattr(self, fn, new) 

1150 

1151 def notify(self, fn, **wkwargs): 

1152 def wrapper(*args, **kwargs): 

1153 ret = fn(*args, **kwargs) 

1154 

1155 # "id": ret[f"{wkwargs['type']}id"] 

1156 self.emit( 

1157 "message", 

1158 {"type": wkwargs["type"], "action": wkwargs["action"]}, 

1159 ) 

1160 

1161 return ret 

1162 

1163 return wrapper 

1164 

1165 def get_user(self): 

1166 """Get the current user from the metadata handler 

1167 The current login can be retrieved from flask.g.login 

1168 

1169 The returned dict should provide schema consistent with 

1170 daiquiri.core.schema.metadata.UserSchema 

1171 

1172 Should return an instance of daiquiri.core.metadata.user.User 

1173 Pass the user dict to initialize 

1174 return User(**user) 

1175 

1176 Returns: 

1177 user (User(dict)): The user as a User object 

1178 """ 

1179 raise NotImplementedError 

1180 

1181 def verify_session(self, session): 

1182 """Verify the user has access to the passed session 

1183 

1184 Args: 

1185 session (str): The session of the form ab1234-1 

1186 

1187 Returns: 

1188 session (dict): The verified session 

1189 """ 

1190 raise NotImplementedError 

1191 

1192 def set_session(self, session): 

1193 if self.verify_session(session): 

1194 self._session.update({"blsession": session}) 

1195 return True 

1196 

1197 def get_proposals(self, proposal=None, **kwargs): 

1198 raise NotImplementedError 

1199 

1200 def get_session(self, session=None, **kwargs): 

1201 raise NotImplementedError 

1202 

1203 def get_user_cache(self): 

1204 """Get the cache for the current user""" 

1205 return self._cache.get(g.login, {}) 

1206 

1207 def update_user_cache(self, cache): 

1208 """Update the user cache for the current user""" 

1209 if g.login not in self._cache: 

1210 self._cache[g.login] = {} 

1211 self._cache_locks[g.login] = lock.Semaphore() 

1212 

1213 with self._cache_locks[g.login]: 

1214 self._cache[g.login] = cache 

1215 return self._cache[g.login] 

1216 

1217 # Components 

1218 def get_components(self, componentid=None, **kwargs): 

1219 raise NotImplementedError 

1220 

1221 def add_component(self, componentid, **kwargs): 

1222 raise NotImplementedError 

1223 

1224 def update_component(self, componentid, **kwargs): 

1225 raise NotImplementedError 

1226 

1227 # Samples 

1228 def get_samples(self, sampleid=None, **kwargs): 

1229 raise NotImplementedError 

1230 

1231 def add_sample(self, **kwargs): 

1232 raise NotImplementedError 

1233 

1234 def update_sample(self, sampleid, **kwargs): 

1235 raise NotImplementedError 

1236 

1237 def remove_sample(self, sampleid, **kwargs): 

1238 raise NotImplementedError 

1239 

1240 def queue_sample(self, sampleid, **kwargs): 

1241 raise NotImplementedError 

1242 

1243 def unqueue_sample(self, sampleid, **kwargs): 

1244 raise NotImplementedError 

1245 

1246 # Subsamples 

1247 def get_subsamples(self, subsampleid=None, **kwargs): 

1248 raise NotImplementedError 

1249 

1250 def add_subsample(self, **kwargs): 

1251 raise NotImplementedError 

1252 

1253 def update_subsample(self, subsampeleid, **kwargs): 

1254 raise NotImplementedError 

1255 

1256 def remove_subsample(self, subsampleid, **kwargs): 

1257 raise NotImplementedError 

1258 

1259 def queue_subsample(self, subsampleid, **kwargs): 

1260 raise NotImplementedError 

1261 

1262 def unqueue_subsample(self, subsampleid, **kwargs): 

1263 raise NotImplementedError 

1264 

1265 def get_sample_tags(self, **kwargs): 

1266 raise NotImplementedError 

1267 

1268 #  Sample Images 

1269 def get_sampleimages(self, sampleimageid=None, **kwargs): 

1270 raise NotImplementedError 

1271 

1272 def add_sampleimage(self, **kwargs): 

1273 raise NotImplementedError 

1274 

1275 def update_sampleimage(self, **kwargs): 

1276 raise NotImplementedError 

1277 

1278 def get_sampleimage(self, sampleimageid, **kwargs): 

1279 sampleimage = self.get_sampleimages(sampleimageid) 

1280 if sampleimage: 

1281 return sampleimage["file"] 

1282 

1283 def get_sampleactions(self, sampleactionid=None, **kwargs): 

1284 raise NotImplementedError 

1285 

1286 def get_sampleaction_positions(self, **kwargs): 

1287 raise NotImplementedError 

1288 

1289 def add_sampleaction_position(self, **kwargs): 

1290 raise NotImplementedError 

1291 

1292 def remove_sampleaction_position(self, positionid, **kwargs): 

1293 raise NotImplementedError 

1294 

1295 #  DC 

1296 def get_datacollections(self, datacollectionid=None, **kwargs): 

1297 raise NotImplementedError 

1298 

1299 def _check_snapshots(self, dc): 

1300 sns = {} 

1301 for i, sn in enumerate( 

1302 [ 

1303 "xtalsnapshotfullpath1", 

1304 "xtalsnapshotfullpath2", 

1305 "xtalsnapshotfullpath3", 

1306 "xtalsnapshotfullpath4", 

1307 ] 

1308 ): 

1309 if sn in dc: 

1310 sns[i + 1] = os.path.exists(dc[sn]) if dc[sn] is not None else False 

1311 else: 

1312 sns[i + 1] = False 

1313 

1314 dc["snapshots"] = sns 

1315 return dc 

1316 

1317 def add_datacollection(self, **kwargs): 

1318 raise NotImplementedError 

1319 

1320 def update_datacollection(self, datacollectionid, **kwargs): 

1321 raise NotImplementedError 

1322 

1323 def get_snapshot(self, datacollectionid, snapshotid=1, **kwargs): 

1324 dc = self.get_datacollections(datacollectionid=datacollectionid) 

1325 if dc: 

1326 if snapshotid in dc["snapshots"]: 

1327 if os.path.exists(dc[f"xtalsnapshotfullpath{snapshotid}"]): 

1328 return dc[f"xtalsnapshotfullpath{snapshotid}"] 

1329 

1330 # DC Plan 

1331 def get_datacollectionplans(self, datacollectionplanid=None, **kwargs): 

1332 raise NotImplementedError 

1333 

1334 def add_datacollectionplan(self, **kwargs): 

1335 raise NotImplementedError 

1336 

1337 def update_datacollectionplan(self, datacollectionplanid, **kwargs): 

1338 raise NotImplementedError 

1339 

1340 def remove_datacollectionplan(self, datacollectionplanid, **kwargs): 

1341 raise NotImplementedError 

1342 

1343 # DC Attachments 

1344 def get_datacollection_attachments(self, datacollectionattachmentid=None, **kwargs): 

1345 raise NotImplementedError 

1346 

1347 def add_datacollection_attachment(self, **kwargs): 

1348 raise NotImplementedError 

1349 

1350 def get_datacollection_attachment(self, datacollectionattachmentid, **kwargs): 

1351 att = self.get_datacollection_attachments( 

1352 datacollectionattachmentid=datacollectionattachmentid, **kwargs 

1353 ) 

1354 if att: 

1355 if os.path.exists(att["filefullpath"]): 

1356 return att 

1357 

1358 # Scan Quality Indicators 

1359 def get_scanqualityindicators(self, datacollectionid=None, **kwargs): 

1360 raise NotImplementedError 

1361 

1362 def add_scanqualityindicators(self, **kwargs): 

1363 raise NotImplementedError 

1364 

1365 # XRF Maps 

1366 def get_xrf_maps(self, mapid=None, **kwargs): 

1367 raise NotImplementedError 

1368 

1369 def get_xrf_map_image(self, mapid, **kwargs): 

1370 map_ = self.get_xrf_maps(mapid, data=True, **kwargs) 

1371 

1372 if map_: 

1373 return generate_map_image(map_) 

1374 

1375 def add_xrf_map(self, **kwargs): 

1376 raise NotImplementedError 

1377 

1378 def update_xrf_map(self, mapid, **kwargs): 

1379 raise NotImplementedError 

1380 

1381 def remove_xrf_map(self, mapid): 

1382 raise NotImplementedError 

1383 

1384 # XRF Composites 

1385 def get_xrf_composites(self, compositeid=None, **kwargs): 

1386 raise NotImplementedError 

1387 

1388 def get_xrf_composite_image(self, compositeid, **kwargs): 

1389 comp = self.get_xrf_composites(compositeid=compositeid, **kwargs) 

1390 

1391 if comp: 

1392 maps = { 

1393 col: self.get_xrf_maps(mapid=comp[col], data=True) 

1394 for col in ["r", "g", "b"] 

1395 } 

1396 return generate_composite_image(comp, maps) 

1397 

1398 def add_xrf_composite(self, **kwargs): 

1399 raise NotImplementedError 

1400 

1401 def update_xrf_composite(self, compositeid, **kwargs): 

1402 raise NotImplementedError 

1403 

1404 def remove_xrf_composite(self, compositeid): 

1405 raise NotImplementedError 

1406 

1407 #  XRF ROIs 

1408 def get_xrf_map_rois(self, maproiid=None, **kwargs): 

1409 raise NotImplementedError 

1410 

1411 def add_xrf_map_roi(self, **kwargs): 

1412 raise NotImplementedError 

1413 

1414 def update_xrf_map_roi(self, maproiid, **kwargs): 

1415 raise NotImplementedError 

1416 

1417 def remove_xrf_map_roi(self, maproiid): 

1418 raise NotImplementedError 

1419 

1420 # Auto Processing 

1421 def get_autoprocprograms(self, **kwargs): 

1422 raise NotImplementedError 

1423 

1424 def get_autoprocprogram_attachments(self, **kwargs): 

1425 raise NotImplementedError 

1426 

1427 def get_autoprocprogram_messages(self, **kwargs): 

1428 raise NotImplementedError 

1429 

1430 def get_autoprocprogram_attachment(self, autoprocprogramattachmentid, **kwargs): 

1431 attachment = self.get_autoprocprogram_attachments( 

1432 autoprocprogramattachmentid=autoprocprogramattachmentid 

1433 ) 

1434 if attachment: 

1435 if os.path.exists(attachment["filefullpath"]): 

1436 return attachment["filefullpath"]