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
« 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
7from gevent import lock
8from flask import send_file, g
9from marshmallow import fields
10from PIL import Image
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)
64import logging
66logger = logging.getLogger(__name__)
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
353class SampleTagTypeEnum(enum.Enum):
354 sample = "sample"
355 subsample = "subsample"
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
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
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
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
402 if scale and scale != 1:
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
413 return image_response(worker(resize), max_age=10000)
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
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
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
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
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
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
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
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
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
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
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"]]
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
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
599 if kwargs.get("thumb"):
600 ext = os.path.splitext(path)[1][1:].strip()
601 path = path.replace(f".{ext}", f"t.{ext}")
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
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
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
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
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
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
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
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)
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)
726 else:
727 return {"error": "No such map"}, 404
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
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
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
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_)
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 }
802 return {"error": "Invalid point"}, 400
804 else:
805 return {"error": "No such map"}, 404
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)
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
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
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
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
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)
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
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
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
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
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 )
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 )
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"]]
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
1013class MetaData:
1014 def __init__(self, config, *args, **kwargs):
1015 kwargs["config"] = config
1017 self._meta = loader(
1018 "daiquiri.core.metadata",
1019 "MetaDataHandler",
1020 config["meta_type"],
1021 *args,
1022 **kwargs,
1023 )
1025 def init(self):
1026 return self._meta
1029class MetaDataHandler(CoreBase):
1030 _require_session = True
1031 _require_blsession = True
1032 _namespace = "metadata"
1033 _base_url = "metadata"
1035 _cache = {}
1037 def setup(self):
1038 self._cache_locks = {}
1040 self.register_route(UserResource, "/users/current")
1041 self.register_route(UserCacheResource, "/users/cache")
1043 self.register_route(ProposalsResource, "/proposals")
1044 self.register_route(ProposalResource, "/proposals/<string:proposal>")
1046 self.register_route(SessionsResource, "/sessions")
1047 self.register_route(SessionResource, "/sessions/<string:session>")
1048 self.register_route(SelectSessionResource, "/sessions/select")
1050 self.register_route(ComponentsResource, "/components")
1051 self.register_route(ComponentResource, "/components/<int:componentid>")
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")
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 )
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 )
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>")
1100 self.register_route(XRFCompositesMapResource, "/xrf/maps/composite")
1101 self.register_route(
1102 XRFCompositeMapResource, "/xrf/maps/composite/<int:compositeid>"
1103 )
1105 self.register_route(XRFMapROIsResource, "/xrf/maps/rois")
1106 self.register_route(XRFMapROIResource, "/xrf/maps/rois/<int:maproiid>")
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")
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
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)
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)
1151 def notify(self, fn, **wkwargs):
1152 def wrapper(*args, **kwargs):
1153 ret = fn(*args, **kwargs)
1155 # "id": ret[f"{wkwargs['type']}id"]
1156 self.emit(
1157 "message",
1158 {"type": wkwargs["type"], "action": wkwargs["action"]},
1159 )
1161 return ret
1163 return wrapper
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
1169 The returned dict should provide schema consistent with
1170 daiquiri.core.schema.metadata.UserSchema
1172 Should return an instance of daiquiri.core.metadata.user.User
1173 Pass the user dict to initialize
1174 return User(**user)
1176 Returns:
1177 user (User(dict)): The user as a User object
1178 """
1179 raise NotImplementedError
1181 def verify_session(self, session):
1182 """Verify the user has access to the passed session
1184 Args:
1185 session (str): The session of the form ab1234-1
1187 Returns:
1188 session (dict): The verified session
1189 """
1190 raise NotImplementedError
1192 def set_session(self, session):
1193 if self.verify_session(session):
1194 self._session.update({"blsession": session})
1195 return True
1197 def get_proposals(self, proposal=None, **kwargs):
1198 raise NotImplementedError
1200 def get_session(self, session=None, **kwargs):
1201 raise NotImplementedError
1203 def get_user_cache(self):
1204 """Get the cache for the current user"""
1205 return self._cache.get(g.login, {})
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()
1213 with self._cache_locks[g.login]:
1214 self._cache[g.login] = cache
1215 return self._cache[g.login]
1217 # Components
1218 def get_components(self, componentid=None, **kwargs):
1219 raise NotImplementedError
1221 def add_component(self, componentid, **kwargs):
1222 raise NotImplementedError
1224 def update_component(self, componentid, **kwargs):
1225 raise NotImplementedError
1227 # Samples
1228 def get_samples(self, sampleid=None, **kwargs):
1229 raise NotImplementedError
1231 def add_sample(self, **kwargs):
1232 raise NotImplementedError
1234 def update_sample(self, sampleid, **kwargs):
1235 raise NotImplementedError
1237 def remove_sample(self, sampleid, **kwargs):
1238 raise NotImplementedError
1240 def queue_sample(self, sampleid, **kwargs):
1241 raise NotImplementedError
1243 def unqueue_sample(self, sampleid, **kwargs):
1244 raise NotImplementedError
1246 # Subsamples
1247 def get_subsamples(self, subsampleid=None, **kwargs):
1248 raise NotImplementedError
1250 def add_subsample(self, **kwargs):
1251 raise NotImplementedError
1253 def update_subsample(self, subsampeleid, **kwargs):
1254 raise NotImplementedError
1256 def remove_subsample(self, subsampleid, **kwargs):
1257 raise NotImplementedError
1259 def queue_subsample(self, subsampleid, **kwargs):
1260 raise NotImplementedError
1262 def unqueue_subsample(self, subsampleid, **kwargs):
1263 raise NotImplementedError
1265 def get_sample_tags(self, **kwargs):
1266 raise NotImplementedError
1268 # Sample Images
1269 def get_sampleimages(self, sampleimageid=None, **kwargs):
1270 raise NotImplementedError
1272 def add_sampleimage(self, **kwargs):
1273 raise NotImplementedError
1275 def update_sampleimage(self, **kwargs):
1276 raise NotImplementedError
1278 def get_sampleimage(self, sampleimageid, **kwargs):
1279 sampleimage = self.get_sampleimages(sampleimageid)
1280 if sampleimage:
1281 return sampleimage["file"]
1283 def get_sampleactions(self, sampleactionid=None, **kwargs):
1284 raise NotImplementedError
1286 def get_sampleaction_positions(self, **kwargs):
1287 raise NotImplementedError
1289 def add_sampleaction_position(self, **kwargs):
1290 raise NotImplementedError
1292 def remove_sampleaction_position(self, positionid, **kwargs):
1293 raise NotImplementedError
1295 # DC
1296 def get_datacollections(self, datacollectionid=None, **kwargs):
1297 raise NotImplementedError
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
1314 dc["snapshots"] = sns
1315 return dc
1317 def add_datacollection(self, **kwargs):
1318 raise NotImplementedError
1320 def update_datacollection(self, datacollectionid, **kwargs):
1321 raise NotImplementedError
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}"]
1330 # DC Plan
1331 def get_datacollectionplans(self, datacollectionplanid=None, **kwargs):
1332 raise NotImplementedError
1334 def add_datacollectionplan(self, **kwargs):
1335 raise NotImplementedError
1337 def update_datacollectionplan(self, datacollectionplanid, **kwargs):
1338 raise NotImplementedError
1340 def remove_datacollectionplan(self, datacollectionplanid, **kwargs):
1341 raise NotImplementedError
1343 # DC Attachments
1344 def get_datacollection_attachments(self, datacollectionattachmentid=None, **kwargs):
1345 raise NotImplementedError
1347 def add_datacollection_attachment(self, **kwargs):
1348 raise NotImplementedError
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
1358 # Scan Quality Indicators
1359 def get_scanqualityindicators(self, datacollectionid=None, **kwargs):
1360 raise NotImplementedError
1362 def add_scanqualityindicators(self, **kwargs):
1363 raise NotImplementedError
1365 # XRF Maps
1366 def get_xrf_maps(self, mapid=None, **kwargs):
1367 raise NotImplementedError
1369 def get_xrf_map_image(self, mapid, **kwargs):
1370 map_ = self.get_xrf_maps(mapid, data=True, **kwargs)
1372 if map_:
1373 return generate_map_image(map_)
1375 def add_xrf_map(self, **kwargs):
1376 raise NotImplementedError
1378 def update_xrf_map(self, mapid, **kwargs):
1379 raise NotImplementedError
1381 def remove_xrf_map(self, mapid):
1382 raise NotImplementedError
1384 # XRF Composites
1385 def get_xrf_composites(self, compositeid=None, **kwargs):
1386 raise NotImplementedError
1388 def get_xrf_composite_image(self, compositeid, **kwargs):
1389 comp = self.get_xrf_composites(compositeid=compositeid, **kwargs)
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)
1398 def add_xrf_composite(self, **kwargs):
1399 raise NotImplementedError
1401 def update_xrf_composite(self, compositeid, **kwargs):
1402 raise NotImplementedError
1404 def remove_xrf_composite(self, compositeid):
1405 raise NotImplementedError
1407 # XRF ROIs
1408 def get_xrf_map_rois(self, maproiid=None, **kwargs):
1409 raise NotImplementedError
1411 def add_xrf_map_roi(self, **kwargs):
1412 raise NotImplementedError
1414 def update_xrf_map_roi(self, maproiid, **kwargs):
1415 raise NotImplementedError
1417 def remove_xrf_map_roi(self, maproiid):
1418 raise NotImplementedError
1420 # Auto Processing
1421 def get_autoprocprograms(self, **kwargs):
1422 raise NotImplementedError
1424 def get_autoprocprogram_attachments(self, **kwargs):
1425 raise NotImplementedError
1427 def get_autoprocprogram_messages(self, **kwargs):
1428 raise NotImplementedError
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"]