Belle II Software development
refiners.py
1
8
9import functools
10import numpy as np
11import collections
12import copy
13
14from tracking.validation.plot import ValidationPlot, compose_axis_label
15from tracking.validation.fom import ValidationFiguresOfMerit
16from tracking.validation.classification import ClassificationAnalysis
17from tracking.validation.pull import PullAnalysis
18from tracking.validation.tolerate_missing_key_formatter import TolerateMissingKeyFormatter
19from tracking.root_utils import root_cd, root_save_name
20
21import ROOT
22
23import logging
24
25
26def get_logger():
27 return logging.getLogger(__name__)
28
29
30
31formatter = TolerateMissingKeyFormatter()
32
33
34class Refiner:
35 """Python module to refine a peeled dictionary"""
36
37 def __init__(self, refiner_function=None):
38 """Constructor of the Refiner instance"""
39
40 self.refiner_function = refiner_function
41
42 def __get__(self, harvesting_module, cls=None):
43 """Getter of the Refiner instance"""
44 if harvesting_module is None:
45 # Class access
46 return self
47 else:
48 # Instance access
49 refine = self.refine
50
51 def bound_call(*args, **kwds):
52 return refine(harvesting_module, *args, **kwds)
53 return bound_call
54
55 def __call__(self, harvesting_module, crops=None, *args, **kwds):
56 """implementation of the function-call of the Refiner instance
57 r = Refiner()
58 r(harvester) # decoration
59 r(harvester, crops, args, keywords) # refinement
60 """
61 if crops is None:
62 # Decoration mode
63 harvesting_module.refiners.append(self)
64 return harvesting_module
65 else:
66 # Refining mode
67 return self.refine(harvesting_module, crops, *args, **kwds)
68
69 def refine(self, harvesting_module, *args, **kwds):
70 """Apply the instance's refiner function"""
71 self.refiner_function(harvesting_module, *args, **kwds)
72
73
75 """Refiner for figures of merit"""
76
77 default_name = "{module.id}_figures_of_merit{groupby_key}"
78
79 default_title = "Figures of merit in {module.title}"
80
81 default_contact = "{module.contact}"
82
83 default_description = "Figures of merit are the {aggregation.__name__} of {keys}"
84
85 default_check = "Check for reasonable values"
86
87 default_key = "{aggregation.__name__}_{part_name}"
88
89
90 @staticmethod
91 def mean(xs):
92 return np.nanmean(xs)
93
94
95 default_aggregation = mean
96
97 def __init__(self,
98 name=None,
99 title=None,
100 contact=None,
101 description=None,
102 check=None,
103 key=None,
104 aggregation=None,
105 ):
106 """Constructor for this refiner"""
107
108 super().__init__()
109
110
111 self.name = name
112
113 self.title = title
114
115
116 self.description = description
117
118 self.check = check
119
120 self.contact = contact
121
122
123 self.key = key
124
125 self.aggregation = aggregation
126
127 def refine(self,
128 harvesting_module,
129 crops,
130 tdirectory=None,
131 groupby_part_name=None,
132 groupby_value=None,
133 **kwds):
134 """Process the figures of merit"""
135
136 name = self.name or self.default_name
137 title = self.title or self.default_title
138 contact = self.contact or self.default_contact
139 description = self.description or self.default_description
140 check = self.check or self.default_check
141
142 aggregation = self.aggregation or self.default_aggregation
143
144 replacement_dict = dict(
145 refiner=self,
146 module=harvesting_module,
147 aggregation=aggregation,
148 groupby_key='_' + groupby_part_name + groupby_value if groupby_part_name else "",
149 groupby=groupby_part_name, # deprecated
150 groupby_value=groupby_value, # deprecated
151 )
152
153 name = formatter.format(name, **replacement_dict)
154 title = formatter.format(title, **replacement_dict)
155 contact = formatter.format(contact, **replacement_dict)
156
157 figures_of_merit = ValidationFiguresOfMerit(name,
158 contact=contact,
159 title=title)
160
161 for part_name, parts in iter_items_sorted_for_key(crops):
162 key = self.key or self.default_key
163 key = formatter.format(key, part_name=part_name, **replacement_dict)
164 figures_of_merit[key] = aggregation(parts)
165
166 keys = list(figures_of_merit.keys())
167
168 description = formatter.format(description, keys=keys, **replacement_dict)
169 check = formatter.format(check, keys=keys, **replacement_dict)
170
171 figures_of_merit.description = description
172 figures_of_merit.check = check
173
174 if tdirectory:
175 figures_of_merit.write(tdirectory)
176
177 print(figures_of_merit)
178
179
181 """Refiner for histograms"""
182
183 default_name = "{module.id}_{part_name}_histogram{groupby_key}{stackby_key}"
184
185 default_title = "Histogram of {part_name}{groupby_key}{stackby_key} from {module.title}"
186
187 default_contact = "{module.contact}"
188
189 default_description = "This is a histogram of {part_name}{groupby_key}{stackby_key}."
190
191 default_check = "Check if the distribution is reasonable"
192
193 def __init__(self,
194 name=None,
195 title=None,
196 contact=None,
197 description=None,
198 check=None,
199 lower_bound=None,
200 upper_bound=None,
201 bins=None,
202 outlier_z_score=None,
203 allow_discrete=False,
204 stackby="",
205 fit=None,
206 fit_z_score=None):
207 """Constructor for this refiner"""
208
209 super().__init__()
210
211
212 self.name = name
213
214 self.title = title
215
216
217 self.description = description
218
219 self.check = check
220
221 self.contact = contact
222
223
224 self.lower_bound = lower_bound
225
226 self.upper_bound = upper_bound
227
228 self.bins = bins
229
230
231 self.outlier_z_score = outlier_z_score
232
233 self.allow_discrete = allow_discrete
234
235 self.stackby = stackby
236
237
238 self.fit = fit
239
240 self.fit_z_score = fit_z_score
241
242 def refine(self,
243 harvesting_module,
244 crops,
245 tdirectory=None,
246 groupby_part_name=None,
247 groupby_value=None,
248 **kwds):
249 """Process the histogram"""
250
251 stackby = self.stackby
252 if stackby:
253 stackby_parts = crops[stackby]
254 else:
255 stackby_parts = None
256
257 replacement_dict = dict(
258 refiner=self,
259 module=harvesting_module,
260 stackby_key=' stacked by ' + stackby if stackby else "",
261 groupby_key=' in group ' + groupby_part_name + groupby_value if groupby_part_name else "",
262 )
263
264 contact = self.contact or self.default_contact
265 contact = formatter.format(contact, **replacement_dict)
266
267 for part_name, parts in iter_items_sorted_for_key(crops):
268 name = self.name or self.default_name
269 title = self.title or self.default_title
270 description = self.description or self.default_description
271 check = self.check or self.default_check
272
273 name = formatter.format(name, part_name=part_name, **replacement_dict)
274 title = formatter.format(title, part_name=part_name, **replacement_dict)
275 description = formatter.format(description, part_name=part_name, **replacement_dict)
276 check = formatter.format(check, part_name=part_name, **replacement_dict)
277
278 histogram = ValidationPlot(name)
279 histogram.hist(parts,
280 lower_bound=self.lower_bound,
281 upper_bound=self.upper_bound,
282 bins=self.bins,
283 outlier_z_score=self.outlier_z_score,
284 allow_discrete=self.allow_discrete,
285 stackby=stackby_parts)
286
287 histogram.title = title
288 histogram.contact = contact
289 histogram.description = description
290 histogram.check = check
291
292 histogram.xlabel = compose_axis_label(part_name)
293
294 if self.fit:
295 if self.fit_z_score is None:
296 kwds = dict()
297 else:
298 kwds = dict(z_score=self.fit_z_score)
299
300 fit_method_name = 'fit_' + str(self.fit)
301 try:
302 fit_method = getattr(histogram, fit_method_name)
303 except AttributeError:
304 histogram.fit(str(self.fit), **kwds)
305 else:
306 fit_method(**kwds)
307
308 if tdirectory:
309 histogram.write(tdirectory)
310
311
313 """Refiner for profile histograms and 2D scatterplots"""
314
315 plot_kind = "profile"
316
317 def __init__(self,
318 y,
319 x=None,
320 name=None,
321 title=None,
322 contact=None,
323 description=None,
324 check=None,
325 stackby=None,
326 y_unit=None,
327 y_binary=None,
328 y_log=None,
329 lower_bound=None,
330 upper_bound=None,
331 bins=None,
332 outlier_z_score=None,
333 fit=None,
334 fit_z_score=None,
335 skip_single_valued=False,
336 allow_discrete=False):
337 """Constructor for this refiner"""
338
339 super().__init__()
340
341
342 self.name = name
343
344 self.title = title
345
346
347 self.description = description
348
349 self.check = check
350
351 self.contact = contact
352
353
354 self.x = x
355
356 self.y = y
357
358 self.stackby = stackby
359
360 self.y_unit = y_unit
361
362
363 self.lower_bound = lower_bound
364
365 self.upper_bound = upper_bound
366
367 self.bins = bins
368
369 self.y_binary = y_binary
370
371 self.y_log = y_log
372
373
374 self.outlier_z_score = outlier_z_score
375
376 self.allow_discrete = allow_discrete
377
378
379 self.fit = fit
380
381 self.fit_z_score = fit_z_score
382
383
384 self.skip_single_valued = skip_single_valued
385
386 def refine(self,
387 harvesting_module,
388 crops,
389 tdirectory=None,
390 groupby_part_name=None,
391 groupby_value=None,
392 **kwds):
393 """Process the profile histogram / scatterplot"""
394
395 stackby = self.stackby
396 if stackby:
397 stackby_parts = crops[stackby]
398 else:
399 stackby_parts = None
400
401 replacement_dict = dict(
402 refiner=self,
403 module=harvesting_module,
404 stackby_key=' stacked by ' + stackby if stackby else "",
405 groupby_key=' in group ' + groupby_part_name + groupby_value if groupby_part_name else "",
406 )
407
408 contact = self.contact or self.default_contact
409 contact = formatter.format(contact, **replacement_dict)
410
411 y_crops = select_crop_parts(crops, select=self.y)
412 x_crops = select_crop_parts(crops, select=self.x, exclude=self.y)
413
414 for y_part_name, y_parts in iter_items_sorted_for_key(y_crops):
415 for x_part_name, x_parts in iter_items_sorted_for_key(x_crops):
416
417 if self.skip_single_valued and not self.has_more_than_one_value(x_parts):
418 get_logger().info('Skipping "%s" by "%s" profile because x has only a single value "%s"',
419 y_part_name,
420 x_part_name,
421 x_parts[0])
422 continue
423
424 if self.skip_single_valued and not self.has_more_than_one_value(y_parts):
425 get_logger().info('Skipping "%s" by "%s" profile because y has only a single value "%s"',
426 y_part_name,
427 x_part_name,
428 y_parts[0])
429 continue
430
431 name = self.name or self.default_name
432 title = self.title or self.default_title
433 description = self.description or self.default_description
434 check = self.check or self.default_check
435
436 name = formatter.format(name,
437 x_part_name=x_part_name,
438 y_part_name=y_part_name,
439 **replacement_dict)
440
441 title = formatter.format(title,
442 x_part_name=x_part_name,
443 y_part_name=y_part_name,
444 **replacement_dict)
445
446 description = formatter.format(description,
447 x_part_name=x_part_name,
448 y_part_name=y_part_name,
449 **replacement_dict)
450
451 check = formatter.format(check,
452 x_part_name=x_part_name,
453 y_part_name=y_part_name,
454 **replacement_dict)
455
456 profile_plot = ValidationPlot(name)
457
458 plot_kind = self.plot_kind
459 if plot_kind == "profile":
460 profile_plot.profile(x_parts,
461 y_parts,
462 lower_bound=self.lower_bound,
463 upper_bound=self.upper_bound,
464 bins=self.bins,
465 y_binary=self.y_binary,
466 y_log=self.y_log,
467 outlier_z_score=self.outlier_z_score,
468 allow_discrete=self.allow_discrete,
469 stackby=stackby_parts)
470
471 if self.fit:
472 if self.fit_z_score is None:
473 kwds = dict()
474 else:
475 kwds = dict(z_score=self.fit_z_score)
476
477 fit_method_name = 'fit_' + str(self.fit)
478 try:
479 fit_method = getattr(profile_plot, fit_method_name)
480 except BaseException:
481 profile_plot.fit(str(self.fit), **kwds)
482 else:
483 fit_method(**kwds)
484
485 elif plot_kind == "scatter":
486 profile_plot.scatter(x_parts,
487 y_parts,
488 lower_bound=self.lower_bound,
489 upper_bound=self.upper_bound,
490 outlier_z_score=self.outlier_z_score,
491 stackby=stackby_parts)
492
493 profile_plot.title = title
494 profile_plot.contact = contact
495 profile_plot.description = description
496 profile_plot.check = check
497
498 profile_plot.xlabel = compose_axis_label(x_part_name)
499 profile_plot.ylabel = compose_axis_label(y_part_name, self.y_unit)
500
501 if tdirectory:
502 profile_plot.write(tdirectory)
503
504 @staticmethod
506 """check if a list has at least two unique values"""
507 first_x = xs[0]
508 for x in xs:
509 if x != first_x:
510 return True
511 else:
512 return False
513
514
516 """Refiner for profile histograms"""
517
518 default_name = "{module.id}_{y_part_name}_by_{x_part_name}_profile{groupby_key}{stackby_key}"
519
520 default_title = "Profile of {y_part_name} by {x_part_name} from {module.title}"
521
522 default_contact = "{module.contact}"
523
524 default_description = "This is a profile of {y_part_name} over {x_part_name}."
525
526 default_check = "Check if the trend line is reasonable."
527
528
529 plot_kind = "profile"
530
531
533 """Refiner for 2D scatterplots"""
534
535 default_name = "{module.id}_{y_part_name}_by_{x_part_name}_scatter{groupby_key}{stackby_key}"
536
537 default_title = "Scatter of {y_part_name} by {x_part_name} from {module.title}"
538
539 default_contact = "{module.contact}"
540
541 default_description = "This is a scatter of {y_part_name} over {x_part_name}."
542
543 default_check = "Check if the distributions is reasonable."
544
545
546 plot_kind = "scatter"
547
548
550 """Refiner for truth-classification analyses"""
551
552
553 default_contact = "{module.contact}"
554
555
556 default_truth_name = "{part_name}_truth"
557
558 default_estimate_name = "{part_name}_estimate"
559
560 def __init__(self,
561 part_name=None,
562 contact=None,
563 estimate_name=None,
564 truth_name=None,
565 cut_direction=None,
566 cut=None,
567 lower_bound=None,
568 upper_bound=None,
569 outlier_z_score=None,
570 allow_discrete=False,
571 unit=None):
572 """Constructor for this refiner"""
573
574
575 self.part_name = part_name
576
577 self.contact = contact
578
579 self.estimate_name = estimate_name
580
581 self.truth_name = truth_name
582
583
584 self.cut = cut
585
586 self.cut_direction = cut_direction
587
588
589 self.lower_bound = lower_bound
590
591 self.upper_bound = upper_bound
592
593 self.outlier_z_score = outlier_z_score
594
595 self.allow_discrete = allow_discrete
596
597 self.unit = unit
598
599 def refine(self,
600 harvesting_module,
601 crops,
602 tdirectory=None,
603 groupby_part_name=None,
604 groupby_value=None,
605 **kwds):
606 """Process the truth-classification analysis"""
607
608 replacement_dict = dict(
609 refiner=self,
610 module=harvesting_module,
611 groupby_key='_' + groupby_part_name + groupby_value if groupby_part_name else "",
612 groupby=groupby_part_name, # deprecated
613 groupby_value=groupby_value, # deprecated
614 )
615
616 contact = self.contact or self.default_contact
617 contact = formatter.format(contact, **replacement_dict)
618
619 if self.truth_name is not None:
620 truth_name = self.truth_name
621 else:
622 truth_name = self.default_truth_name
623
624 truth_name = formatter.format(truth_name, part_name=self.part_name)
625 truths = crops[truth_name]
626
627 if self.estimate_name is not None:
628 estimate_name = self.estimate_name
629 else:
630 estimate_name = self.default_estimate_name
631
632 if isinstance(estimate_name, str):
633 estimate_names = [estimate_name, ]
634 else:
635 estimate_names = estimate_name
636
637 for estimate_name in estimate_names:
638 estimate_name = formatter.format(estimate_name, part_name=self.part_name)
639 estimates = crops[estimate_name]
640
641 classification_analysis = ClassificationAnalysis(quantity_name=estimate_name,
642 contact=contact,
643 cut_direction=self.cut_direction,
644 cut=self.cut,
645 lower_bound=self.lower_bound,
646 upper_bound=self.upper_bound,
647 outlier_z_score=self.outlier_z_score,
648 allow_discrete=self.allow_discrete,
649 unit=self.unit)
650
651 classification_analysis.analyse(estimates, truths)
652
653 if tdirectory:
654 classification_analysis.write(tdirectory)
655
656
658 """Refiner for pull analyses"""
659
660
661 default_name = "{module.id}_{quantity_name}"
662
663 default_contact = "{module.contact}"
664
665 default_title_postfix = " from {module.title}"
666
667
668 default_truth_name = "{part_name}_truth"
669
670 default_estimate_name = "{part_name}_estimate"
671
672 default_variance_name = "{part_name}_variance"
673
674 def __init__(self,
675 name=None,
676 contact=None,
677 title_postfix=None,
678 part_name=None,
679 part_names=None,
680 truth_name=None,
681 estimate_name=None,
682 variance_name=None,
683 quantity_name=None,
684 aux_names=None,
685 unit=None,
686 outlier_z_score=None,
687 absolute=False,
688 which_plots=None):
689 """Constructor for this refiner"""
690 if aux_names is None:
691 aux_names = []
692
693 self.name = name
694
695 self.contact = contact
696
697 self.title_postfix = title_postfix
698
699
700 self.part_names = []
701 if part_names is not None:
702 self.part_names = part_names
703
704 if part_name is not None:
705 self.part_names.append(part_name)
706
707
708 self.truth_name = truth_name
709
710 self.estimate_name = estimate_name
711
712 self.variance_name = variance_name
713
714
715 self.quantity_name = quantity_name
716
717 self.unit = unit
718
719
720 self.aux_names = aux_names
721
722
723 self.outlier_z_score = outlier_z_score
724
725 self.absolute = absolute
726
727 self.which_plots = which_plots
728
729 def refine(self,
730 harvesting_module,
731 crops,
732 tdirectory=None,
733 groupby_part_name=None,
734 groupby_value=None,
735 **kwds):
736 """Process the pull analysis"""
737
738 replacement_dict = dict(
739 refiner=self,
740 module=harvesting_module,
741 # stackby_key='_' + stackby if stackby else "",
742 groupby_key='_' + groupby_part_name + groupby_value if groupby_part_name else "",
743 groupby=groupby_part_name, # deprecated
744 groupby_value=groupby_value, # deprecated
745 )
746
747 contact = self.contact or self.default_contact
748 contact = formatter.format(contact, **replacement_dict)
749
750 name = self.name or self.default_name
751
752 if self.aux_names:
753 auxiliaries = select_crop_parts(crops, self.aux_names)
754 else:
755 auxiliaries = {}
756
757 for part_name in self.part_names:
758 name = formatter.format(name, part_name=part_name, **replacement_dict)
759 plot_name = name + "_{subplot_name}"
760
761 title_postfix = self.title_postfix
762 if title_postfix is None:
763 title_postfix = self.default_title_postfix
764
765 title_postfix = formatter.format(title_postfix, part_name=part_name, **replacement_dict)
766 plot_title = "{subplot_title} of {quantity_name}" + title_postfix
767
768 if self.truth_name is not None:
769 truth_name = self.truth_name
770 else:
771 truth_name = self.default_truth_name
772
773 if self.estimate_name is not None:
774 estimate_name = self.estimate_name
775 else:
776 estimate_name = self.default_estimate_name
777
778 if self.variance_name is not None:
779 variance_name = self.variance_name
780 else:
781 variance_name = self.default_variance_name
782
783 truth_name = formatter.format(truth_name, part_name=part_name)
784 estimate_name = formatter.format(estimate_name, part_name=part_name)
785 variance_name = formatter.format(variance_name, part_name=part_name)
786
787 truths = crops[truth_name]
788 estimates = crops[estimate_name]
789 try:
790 variances = crops[variance_name]
791 except KeyError:
792 variances = None
793
794 quantity_name = self.quantity_name or part_name
795
796 which_plots = self.which_plots
797
798 pull_analysis = PullAnalysis(quantity_name,
799 unit=self.unit,
800 absolute=self.absolute,
801 outlier_z_score=self.outlier_z_score,
802 plot_name=plot_name,
803 plot_title=plot_title)
804
805 pull_analysis.analyse(truths,
806 estimates,
807 variances,
808 auxiliaries=auxiliaries,
809 which_plots=which_plots)
810
811 pull_analysis.contact = contact
812
813 if tdirectory:
814 pull_analysis.write(tdirectory)
815
816
818 """Refiner for ROOT TTrees"""
819
820
821 default_name = "{module.id}_tree"
822
823 default_title = "Tree of {module.id}"
824
825 def __init__(self,
826 name=None,
827 title=None):
828 """Constructor for this refiner"""
829 super().__init__()
830
831
832 self.name = name
833
834 self.title = title
835
836 def refine(self,
837 harvesting_module,
838 crops,
839 tdirectory=None,
840 groupby_part_name=None,
841 groupby_value=None,
842 **kwds):
843 """Process the TTree"""
844
845 replacement_dict = dict(
846 refiner=self,
847 module=harvesting_module,
848 groupby_key='_' + groupby_part_name + groupby_value if groupby_part_name else "",
849 groupby=groupby_part_name, # deprecated
850 groupby_value=groupby_value, # deprecated
851 )
852
853 with root_cd(tdirectory):
854 name = self.name or self.default_name
855 title = self.title or self.default_title
856
857 name = formatter.format(name, **replacement_dict)
858 title = formatter.format(title, **replacement_dict)
859
860 output_ttree = ROOT.TTree(root_save_name(name), title)
861 for part_name, parts in iter_items_sorted_for_key(crops):
862 self.add_branch(output_ttree, part_name, parts)
863
864 output_ttree.FlushBaskets()
865 output_ttree.Write()
866
867 def add_branch(self, output_ttree, part_name, parts):
868 """Add a TBranch to the TTree"""
869 input_value = np.zeros(1, dtype=float)
870
871 branch_type_spec = f'{part_name}/D'
872 tbranch = output_ttree.Branch(part_name, input_value, branch_type_spec)
873
874 if output_ttree.GetNbranches() == 1:
875 # On filling of the first branch we need to use the fill method of the TTree
876 # For all other branches we can use the one of the branch
877 # #justrootthings
878 for value in parts:
879 input_value[0] = value
880 output_ttree.Fill()
881
882 else:
883 for value in parts:
884 input_value[0] = value
885 tbranch.Fill()
886
887 output_ttree.GetEntry(0)
888 output_ttree.ResetBranchAddress(tbranch)
889 also_subbranches = True # No subbranches here but we drop the buffers just in case.
890 output_ttree.DropBranchFromCache(tbranch, also_subbranches)
891
892
894 """Refiner for filters"""
895
896 def __init__(self, wrapped_refiner, filter=None, on=None):
897 """Constructor for this refiner"""
898
899
900 self.wrapped_refiner = wrapped_refiner
901
902 if filter is None:
903
904 self.filter = np.nonzero
905 else:
906 self.filter = filter
907
908
909 self.on = on
910
911 def refine(self, harvesting_module, crops, *args, **kwds):
912 """Process this filter"""
913 filtered_crops = filter_crops(crops, self.filter, part_name=self.on)
914 self.wrapped_refiner(harvesting_module, filtered_crops, *args, **kwds)
915
916
918 """Refiner for selection"""
919
920 def __init__(self, wrapped_refiner, select=None, exclude=None):
921 """Constructor for this refiner"""
922 if select is None:
923 select = []
924 if exclude is None:
925 exclude = []
926
927 self.wrapped_refiner = wrapped_refiner
928
929 self.select = select
930
931 self.exclude = exclude
932
933 def refine(self, harvesting_module, crops, *args, **kwds):
934 """Process this selection"""
935 selected_crops = select_crop_parts(crops, select=self.select, exclude=self.exclude)
936 self.wrapped_refiner(harvesting_module, selected_crops, *args, **kwds)
937
938
940 """Refiner for grouping"""
941
942
943 default_exclude_by = True
944
945 def __init__(self,
946 wrapped_refiner,
947 by=None,
948 exclude_by=None):
949 """Constructor for this refiner"""
950 if by is None:
951 by = []
952
953 self.wrapped_refiner = wrapped_refiner
954
955 self.by = by
956
957 self.exclude_by = exclude_by if exclude_by is not None else self.default_exclude_by
958
959 def refine(self,
960 harvesting_module,
961 crops,
962 groupby_part_name=None,
963 groupby_value=None,
964 *args,
965 **kwds):
966 """Process this grouping"""
967
968 by = self.by
969
970 # A single name to do the group by
971 if isinstance(by, str) or by is None:
972 part_name = by
973 # Wrap it into a list an continue with the general case
974 by = [part_name, ]
975
976 for groupby_spec in by:
977 if groupby_spec is None:
978 # Using empty string as groupby_value to indicate that all values have been selected
979 value = None
980 self.wrapped_refiner(harvesting_module,
981 crops,
982 groupby_part_name=None,
983 groupby_value=value,
984 *args,
985 **kwds)
986 continue
987
988 elif isinstance(groupby_spec, str):
989 part_name = groupby_spec
990 groupby_parts = crops[part_name]
991 unique_values, index_of_values = np.unique(groupby_parts, return_inverse=True)
992 groupby_values = [f" = {value}]" for value in unique_values]
993
994 elif isinstance(groupby_spec, tuple):
995 part_name = groupby_spec[0]
996 cuts = groupby_spec[1]
997
998 groupby_parts = crops[part_name]
999
1000 # Take care of nans
1001 digitization_cuts = list(np.sort(cuts))
1002 if digitization_cuts[-1] != np.inf:
1003 digitization_cuts.append(np.inf)
1004 index_of_values = np.digitize(groupby_parts, digitization_cuts, right=True)
1005
1006 groupby_values = [f"below {digitization_cuts[0]}"]
1007 bin_bounds = list(zip(digitization_cuts[0:], digitization_cuts[1:]))
1008 for lower_bound, upper_bound in bin_bounds:
1009 if lower_bound == upper_bound:
1010 # degenerated bin case
1011 groupby_values.append(f"= {lower_bound}")
1012 elif upper_bound == np.inf:
1013 groupby_values.append(f"above {lower_bound}")
1014 else:
1015 groupby_values.append(f"between {lower_bound} and {upper_bound}")
1016 groupby_values.append("is nan")
1017 assert len(groupby_values) == len(digitization_cuts) + 1
1018
1019 else:
1020 raise ValueError(f"Unknown groupby specification {groupby_spec}")
1021
1022 # Exclude the groupby variable if desired
1023 selected_crops = select_crop_parts(crops, exclude=part_name if self.exclude_by else None)
1024 for index_of_value, groupby_value in enumerate(groupby_values):
1025 indices_for_value = index_of_values == index_of_value
1026 if not np.any(indices_for_value):
1027 continue
1028
1029 filtered_crops = filter_crops(selected_crops, indices_for_value)
1030
1031 self.wrapped_refiner(harvesting_module,
1032 filtered_crops,
1033 groupby_part_name=part_name,
1034 groupby_value=groupby_value,
1035 *args,
1036 **kwds)
1037
1038
1040 """Refiner for change-directory"""
1041
1042
1043 default_folder_name = ""
1044
1045 default_groupby_addition = "_groupby_{groupby}_{groupby_value}"
1046
1047 def __init__(self,
1048 wrapped_refiner,
1049 folder_name=None,
1050 groupby_addition=None):
1051 """Constructor for this refiner"""
1052
1053
1054 self.wrapped_refiner = wrapped_refiner
1055
1056 self.folder_name = folder_name
1057
1058 self.groupby_addition = groupby_addition
1059
1060 def refine(self,
1061 harvesting_module,
1062 crops,
1063 tdirectory=None,
1064 groupby_part_name=None,
1065 groupby_value=None,
1066 *args,
1067 **kwds):
1068 """Process the change-directory"""
1069
1070 folder_name = self.folder_name
1071 if folder_name is None:
1072 if groupby_value is not None:
1073 folder_name = "{groupby_addition}"
1074 else:
1075 folder_name = self.default_folder_name
1076
1077 groupby_addition = self.groupby_addition
1078
1079 if groupby_addition is None:
1080 groupby_addition = self.default_groupby_addition
1081
1082 if groupby_part_name is None and groupby_value is None:
1083 groupby_addition = ""
1084 else:
1085 groupby_addition = formatter.format(groupby_addition,
1086 groupby=groupby_part_name,
1087 groupby_value=groupby_value)
1088
1089 folder_name = formatter.format(folder_name,
1090 groupby_addition=groupby_addition,
1091 groupby=groupby_part_name,
1092 groupby_value=groupby_value)
1093
1094 folder_name = '/'.join(root_save_name(name) for name in folder_name.split('/'))
1095
1096 with root_cd(tdirectory):
1097 with root_cd(folder_name) as tdirectory:
1098 self.wrapped_refiner(harvesting_module,
1099 crops,
1100 tdirectory=tdirectory,
1101 groupby_part_name=groupby_part_name,
1102 groupby_value=groupby_value,
1103 *args,
1104 **kwds)
1105
1106
1108 """Refiner for expert-level categorization"""
1109
1110 def __init__(self, wrapped_refiner, above_expert_level=None, below_expert_level=None):
1111 """Constructor for this refiner"""
1112
1113
1114 self.wrapped_refiner = wrapped_refiner
1115
1116 self.above_expert_level = above_expert_level
1117
1118 self.below_expert_level = below_expert_level
1119
1120 def refine(self, harvesting_module, crops, *args, **kwds):
1121 """Process the expert-level categorization"""
1122
1123 above_expert_level = self.above_expert_level
1124 below_expert_level = self.below_expert_level
1125
1126 proceed = True
1127 if above_expert_level is not None:
1128 proceed = proceed and harvesting_module.expert_level > above_expert_level
1129
1130 if below_expert_level is not None:
1131 proceed = proceed and harvesting_module.expert_level < below_expert_level
1132
1133 if proceed:
1134 self.wrapped_refiner(harvesting_module, crops, *args, **kwds)
1135
1136
1137# Meta refiner decorators
1138def groupby(refiner=None, **kwds):
1139 def group_decorator(wrapped_refiner):
1140 return GroupByRefiner(wrapped_refiner, **kwds)
1141 if refiner is None:
1142 return group_decorator
1143 else:
1144 return group_decorator(refiner)
1145
1146
1147def select(refiner=None, **kwds):
1148 def select_decorator(wrapped_refiner):
1149 return SelectRefiner(wrapped_refiner, **kwds)
1150 if refiner is None:
1151 return select_decorator
1152 else:
1153 return select_decorator(refiner)
1154
1155
1156def filter(refiner=None, **kwds):
1157 def filter_decorator(wrapped_refiner):
1158 return FilterRefiner(wrapped_refiner, **kwds)
1159 if refiner is None:
1160 return filter_decorator
1161 else:
1162 return filter_decorator(refiner)
1163
1164
1165def cd(refiner=None, **kwds):
1166 def cd_decorator(wrapped_refiner):
1167 return CdRefiner(wrapped_refiner, **kwds)
1168 if refiner is None:
1169 return cd_decorator
1170 else:
1171 return cd_decorator(refiner)
1172
1173
1174def context(refiner=None,
1175 above_expert_level=None, below_expert_level=None,
1176 folder_name=None, folder_groupby_addition=None,
1177 filter=None, filter_on=None,
1178 groupby=None, exclude_groupby=None,
1179 select=None, exclude=None):
1180
1181 def context_decorator(wrapped_refiner):
1182 # Apply meta refiners in the reverse order that they shall be executed
1183 if exclude is not None or select is not None:
1184 wrapped_refiner = SelectRefiner(wrapped_refiner,
1185 select=select, exclude=exclude)
1186
1187 if folder_name is not None or groupby is not None or folder_groupby_addition is not None:
1188 wrapped_refiner = CdRefiner(wrapped_refiner,
1189 folder_name=folder_name,
1190 groupby_addition=folder_groupby_addition)
1191
1192 if groupby is not None:
1193 wrapped_refiner = GroupByRefiner(wrapped_refiner,
1194 by=groupby,
1195 exclude_by=exclude_groupby)
1196
1197 if filter is not None or filter_on is not None:
1198 wrapped_refiner = FilterRefiner(wrapped_refiner,
1199 filter=filter,
1200 on=filter_on)
1201
1202 if above_expert_level is not None or below_expert_level is not None:
1203 wrapped_refiner = ExpertLevelRefiner(wrapped_refiner,
1204 above_expert_level=above_expert_level,
1205 below_expert_level=below_expert_level)
1206
1207 if not isinstance(wrapped_refiner, Refiner):
1208 wrapped_refiner = Refiner(wrapped_refiner)
1209
1210 return wrapped_refiner
1211
1212 if refiner is None:
1213 return context_decorator
1214 else:
1215 return functools.wraps(refiner)(context_decorator(refiner))
1216
1217
1218def refiner_with_context(refiner_factory):
1219 @functools.wraps(refiner_factory)
1220 def module_decorator_with_context(above_expert_level=None, below_expert_level=None,
1221 folder_name=None, folder_groupby_addition=None,
1222 filter=None, filter_on=None,
1223 groupby=None, exclude_groupby=None,
1224 select=None, exclude=None,
1225 **kwds_for_refiner_factory):
1226
1227 refiner = refiner_factory(**kwds_for_refiner_factory)
1228
1229 return context(refiner,
1230 above_expert_level=above_expert_level, below_expert_level=below_expert_level,
1231 folder_name=folder_name, folder_groupby_addition=folder_groupby_addition,
1232 filter=filter, filter_on=filter_on,
1233 groupby=groupby, exclude_groupby=exclude_groupby,
1234 select=select, exclude=exclude)
1235
1236 return module_decorator_with_context
1237
1238
1239@refiner_with_context
1240def save_fom(**kwds):
1241 return SaveFiguresOfMeritRefiner(**kwds)
1242
1243
1244@refiner_with_context
1245def save_histograms(**kwds):
1246 return SaveHistogramsRefiner(**kwds)
1247
1248
1249@refiner_with_context
1250def save_profiles(**kwds):
1251 return SaveProfilesRefiner(**kwds)
1252
1253
1254@refiner_with_context
1255def save_scatters(**kwds):
1256 return SaveScatterRefiner(**kwds)
1257
1258
1259@refiner_with_context
1260def save_classification_analysis(**kwds):
1262
1263
1264@refiner_with_context
1265def save_pull_analysis(**kwds):
1266 return SavePullAnalysisRefiner(**kwds)
1267
1268
1269@refiner_with_context
1270def save_tree(**kwds):
1271 return SaveTreeRefiner(**kwds)
1272
1273
1274def select_crop_parts(crops, select=None, exclude=None):
1275 if select is None:
1276 select = []
1277 if exclude is None:
1278 exclude = []
1279
1280 if isinstance(select, str):
1281 select = [select, ]
1282
1283 if isinstance(exclude, str):
1284 exclude = [exclude, ]
1285
1286 if isinstance(crops, collections.abc.MutableMapping):
1287 part_names = list(crops.keys())
1288
1289 if not select and not exclude:
1290 return crops
1291
1292 if select:
1293 not_selected_part_names = [name for name in part_names if name not in select]
1294
1295 # if the selection item is a callable function do not count it as not selectable yet
1296 select_not_in_part_names = [name for name in select
1297 if not isinstance(name, collections.abc.Callable) and name not in part_names]
1298 if select_not_in_part_names:
1299 get_logger().warning("Cannot select %s, because they are not in crop part names %s",
1300 select_not_in_part_names, sorted(part_names))
1301 else:
1302 not_selected_part_names = []
1303
1304 if exclude:
1305 excluded_part_names = [name for name in part_names if name in exclude]
1306 else:
1307 excluded_part_names = []
1308
1309 excluded_part_names.extend(not_selected_part_names)
1310
1311 # Make a shallow copy
1312 selected_crops = copy.copy(crops)
1313 for part_name in set(excluded_part_names):
1314 del selected_crops[part_name]
1315
1316 if isinstance(select, collections.abc.Mapping):
1317 # select is a rename mapping
1318 for part_name, new_part_name in list(select.items()):
1319 if isinstance(part_name, collections.abc.Callable):
1320 selected_crops[new_part_name] = part_name(**crops)
1321 elif part_name in selected_crops:
1322 parts = selected_crops[part_name]
1323 del selected_crops[part_name]
1324 selected_crops[new_part_name] = parts
1325
1326 return selected_crops
1327
1328 else:
1329 raise ValueError(f"Unrecognised crop {crops} of type {type(crops)}")
1330
1331
1332def filter_crops(crops, filter_function, part_name=None):
1333 if isinstance(filter_function, np.ndarray):
1334 filter_indices = filter_function
1335 else:
1336 parts = crops[part_name]
1337 filter_indices = filter_function(parts)
1338
1339 if isinstance(crops, np.ndarray):
1340 return crops[filter_indices]
1341
1342 elif isinstance(crops, collections.abc.MutableMapping):
1343 # Make a shallow copy
1344 filtered_crops = copy.copy(crops)
1345 for part_name, parts in list(crops.items()):
1346 filtered_crops[part_name] = parts[filter_indices]
1347 return filtered_crops
1348
1349 else:
1350 raise ValueError(f"Unrecognised crop {crops} of type {type(crops)}")
1351
1352
1353def iter_items_sorted_for_key(crops):
1354 # is the type of crops is a dictionary assume, that it should be sorted
1355 # in all other cases the users class has to take care of the sorting
1356 if isinstance(crops, dict):
1357 keys = sorted(crops.keys())
1358 return ((key, crops[key]) for key in keys)
1359 else:
1360 return list(crops.items())
str default_folder_name
Folder name to be used if a groupby selection is active.
Definition: refiners.py:1043
def __init__(self, wrapped_refiner, folder_name=None, groupby_addition=None)
Definition: refiners.py:1050
folder_name
cached value of the folder name
Definition: refiners.py:1056
def refine(self, harvesting_module, crops, tdirectory=None, groupby_part_name=None, groupby_value=None, *args, **kwds)
Definition: refiners.py:1067
wrapped_refiner
cached value of the wrapped refiner
Definition: refiners.py:1054
groupby_addition
cached value of the suffix for a groupby selection
Definition: refiners.py:1058
str default_groupby_addition
Default suffix for a groupby selection.
Definition: refiners.py:1045
below_expert_level
cached value of the lower range of the expert level
Definition: refiners.py:1118
wrapped_refiner
cached value of the wrapped refiner
Definition: refiners.py:1114
def refine(self, harvesting_module, crops, *args, **kwds)
Definition: refiners.py:1120
def __init__(self, wrapped_refiner, above_expert_level=None, below_expert_level=None)
Definition: refiners.py:1110
above_expert_level
cached value of the upper range of the expert level
Definition: refiners.py:1116
on
cached value of the part name to filter on
Definition: refiners.py:909
wrapped_refiner
cached value of the wrapped refiner
Definition: refiners.py:900
def refine(self, harvesting_module, crops, *args, **kwds)
Definition: refiners.py:911
filter
cached value of the filter
Definition: refiners.py:904
def __init__(self, wrapped_refiner, filter=None, on=None)
Definition: refiners.py:896
by
cached value of the group-by classifier
Definition: refiners.py:955
def __init__(self, wrapped_refiner, by=None, exclude_by=None)
Definition: refiners.py:948
wrapped_refiner
cached value of the wrapped refiner
Definition: refiners.py:953
exclude_by
cached value of the exclude-by classifier
Definition: refiners.py:957
bool default_exclude_by
default value of the exclude-by classifier
Definition: refiners.py:943
def refine(self, harvesting_module, crops, groupby_part_name=None, groupby_value=None, *args, **kwds)
Definition: refiners.py:965
title
cached user-defined title for this profile histogram / scatterplot
Definition: refiners.py:344
y_log
cached flag for logarithmic y axis for this profile histogram / scatterplot
Definition: refiners.py:371
description
cached user-defined description for this profile histogram / scatterplot
Definition: refiners.py:347
contact
cached user-defined contact person for this profile histogram / scatterplot
Definition: refiners.py:351
fit
cached fit for this profile histogram / scatterplot
Definition: refiners.py:379
upper_bound
cached upper bound for this profile histogram / scatterplot
Definition: refiners.py:365
outlier_z_score
cached Z-score (for outlier detection) for this profile histogram / scatterplot
Definition: refiners.py:374
y_binary
cached flag for probability y axis (range 0.0 .
Definition: refiners.py:369
skip_single_valued
cached flag to skip single-valued bins for this profile histogram / scatterplot
Definition: refiners.py:384
allow_discrete
cached flag to allow discrete values for this profile histogram / scatterplot
Definition: refiners.py:376
str plot_kind
by default, this refiner is for profile histograms
Definition: refiners.py:315
fit_z_score
cached fit Z-score (for outlier detection) for this profile histogram / scatterplot
Definition: refiners.py:381
stackby
cached stacking selection for this profile histogram / scatterplot
Definition: refiners.py:358
y_unit
cached measurement unit for ordinate
Definition: refiners.py:360
def __init__(self, y, x=None, name=None, title=None, contact=None, description=None, check=None, stackby=None, y_unit=None, y_binary=None, y_log=None, lower_bound=None, upper_bound=None, bins=None, outlier_z_score=None, fit=None, fit_z_score=None, skip_single_valued=False, allow_discrete=False)
Definition: refiners.py:336
name
cached user-defined name for this profile histogram / scatterplot
Definition: refiners.py:342
bins
cached number of bins for this profile histogram / scatterplot
Definition: refiners.py:367
def refine(self, harvesting_module, crops, tdirectory=None, groupby_part_name=None, groupby_value=None, **kwds)
Definition: refiners.py:392
lower_bound
cached lower bound for this profile histogram / scatterplot
Definition: refiners.py:363
check
cached user-defined user-check action for this profile histogram / scatterplot
Definition: refiners.py:349
refiner_function
cached copy of the instance's refiner function
Definition: refiners.py:40
def __init__(self, refiner_function=None)
Definition: refiners.py:37
def refine(self, harvesting_module, *args, **kwds)
Definition: refiners.py:69
def __get__(self, harvesting_module, cls=None)
Definition: refiners.py:42
def __call__(self, harvesting_module, crops=None, *args, **kwds)
Definition: refiners.py:55
truth_name
cached truth-values-collection name for this truth-classification analysis
Definition: refiners.py:581
cut
cached threshold of estimates for this truth-classification analysis
Definition: refiners.py:584
contact
cached contact person for this truth-classification analysis
Definition: refiners.py:577
upper_bound
cached upper bound of estimates for this truth-classification analysis
Definition: refiners.py:591
outlier_z_score
cached Z-score (for outlier detection) of estimates for this truth-classification analysis
Definition: refiners.py:593
allow_discrete
cached discrete-value flag of estimates for this truth-classification analysis
Definition: refiners.py:595
unit
cached measurement unit of estimates for this truth-classification analysis
Definition: refiners.py:597
estimate_name
cached estimates-collection name for this truth-classification analysis
Definition: refiners.py:579
cut_direction
cached cut direction (> or <) of estimates for this truth-classification analysis
Definition: refiners.py:586
def __init__(self, part_name=None, contact=None, estimate_name=None, truth_name=None, cut_direction=None, cut=None, lower_bound=None, upper_bound=None, outlier_z_score=None, allow_discrete=False, unit=None)
Definition: refiners.py:571
str default_estimate_name
default name for the truth-classification analysis estimates collection
Definition: refiners.py:558
part_name
cached part name for this truth-classification analysis
Definition: refiners.py:575
str default_truth_name
default name for the truth-classification analysis truth-values collection
Definition: refiners.py:556
def refine(self, harvesting_module, crops, tdirectory=None, groupby_part_name=None, groupby_value=None, **kwds)
Definition: refiners.py:605
lower_bound
cached lower bound of estimates for this truth-classification analysis
Definition: refiners.py:589
str default_contact
default contact person for this truth-classification analysis
Definition: refiners.py:553
title
cached title of the figure of merit
Definition: refiners.py:113
description
cached description of the figure of merit
Definition: refiners.py:116
contact
cached contact person of the figure of merit
Definition: refiners.py:120
def __init__(self, name=None, title=None, contact=None, description=None, check=None, key=None, aggregation=None)
Definition: refiners.py:105
str default_name
default name for this refiner
Definition: refiners.py:77
def mean(xs)
return the mean of the parts, ignoring NaNs
Definition: refiners.py:91
str default_description
default description for this refiner
Definition: refiners.py:83
def default_aggregation
default aggregation is the mean of the parts
Definition: refiners.py:95
str default_key
default key name for this refiner
Definition: refiners.py:87
str default_check
default user-check action for this refiner
Definition: refiners.py:85
aggregation
cached copy of the crops-aggregation method
Definition: refiners.py:125
str default_title
default title for this refiner
Definition: refiners.py:79
key
cached copy of the figures-of-merit key
Definition: refiners.py:123
name
cached name of the figure of merit
Definition: refiners.py:111
def refine(self, harvesting_module, crops, tdirectory=None, groupby_part_name=None, groupby_value=None, **kwds)
Definition: refiners.py:133
check
cached user-check action of the figure of merit
Definition: refiners.py:118
str default_contact
default contact person for this refiner
Definition: refiners.py:81
title
cached user-defined title for this histogram
Definition: refiners.py:214
description
cached user-defined description for this histogram
Definition: refiners.py:217
contact
cached user-defined contact person for this histogram
Definition: refiners.py:221
fit
cached fit for this histogram
Definition: refiners.py:238
upper_bound
cached upper bound for this histogram
Definition: refiners.py:226
outlier_z_score
cached Z-score (for outlier detection) for this histogram
Definition: refiners.py:231
str default_name
default name for this histogram
Definition: refiners.py:183
allow_discrete
cached flag to allow discrete values for this histogram
Definition: refiners.py:233
def __init__(self, name=None, title=None, contact=None, description=None, check=None, lower_bound=None, upper_bound=None, bins=None, outlier_z_score=None, allow_discrete=False, stackby="", fit=None, fit_z_score=None)
Definition: refiners.py:206
fit_z_score
cached fit Z-score (for outlier detection) for this histogram
Definition: refiners.py:240
stackby
cached stacking selection for this histogram
Definition: refiners.py:235
str default_description
default description for this histogram
Definition: refiners.py:189
str default_check
default user-check action for this histogram
Definition: refiners.py:191
str default_title
default title for this histogram
Definition: refiners.py:185
name
cached user-defined name for this histogram
Definition: refiners.py:212
bins
cached number of bins for this histogram
Definition: refiners.py:228
def refine(self, harvesting_module, crops, tdirectory=None, groupby_part_name=None, groupby_value=None, **kwds)
Definition: refiners.py:248
lower_bound
cached lower bound for this histogram
Definition: refiners.py:224
check
cached user-defined user-check action for this histogram
Definition: refiners.py:219
str default_contact
default contact person for this histogram
Definition: refiners.py:187
variance_name
cached name for the pull analysis variances collection
Definition: refiners.py:712
truth_name
cached name for the pull analysis truth-values collection
Definition: refiners.py:708
str default_variance_name
default name for the pull analysis variances collection
Definition: refiners.py:672
quantity_name
cached name of the quantity for the pull analysis
Definition: refiners.py:715
contact
cached contact person for this pull analysis
Definition: refiners.py:695
outlier_z_score
cached Z-score (for outlier detection) for the pull analysis
Definition: refiners.py:723
str default_name
default name for this pull analysis
Definition: refiners.py:661
part_names
cached array of part names for this pull analysis
Definition: refiners.py:700
unit
cached measurement unit for the pull analysis
Definition: refiners.py:717
aux_names
cached auxiliary names for the pull analysis
Definition: refiners.py:720
estimate_name
cached name for the pull analysis estimates collection
Definition: refiners.py:710
title_postfix
cached suffix for the title of this pull analysis
Definition: refiners.py:697
def __init__(self, name=None, contact=None, title_postfix=None, part_name=None, part_names=None, truth_name=None, estimate_name=None, variance_name=None, quantity_name=None, aux_names=None, unit=None, outlier_z_score=None, absolute=False, which_plots=None)
Definition: refiners.py:688
str default_title_postfix
default suffix for the title of this pull analysis
Definition: refiners.py:665
str default_estimate_name
default name for the pull analysis estimates collection
Definition: refiners.py:670
name
cached name for this pull analysis
Definition: refiners.py:693
str default_truth_name
default name for the pull analysis truth-values collection
Definition: refiners.py:668
def refine(self, harvesting_module, crops, tdirectory=None, groupby_part_name=None, groupby_value=None, **kwds)
Definition: refiners.py:735
which_plots
cached list of plots produced by the pull analysis
Definition: refiners.py:727
absolute
cached absolute-value-comparison flag for the pull analysis
Definition: refiners.py:725
str default_contact
default contact person for this pull analysis
Definition: refiners.py:663
title
cached title for this TTree
Definition: refiners.py:834
str default_name
default name for this TTree
Definition: refiners.py:821
def __init__(self, name=None, title=None)
Definition: refiners.py:827
def add_branch(self, output_ttree, part_name, parts)
Definition: refiners.py:867
str default_title
default title for this TTree
Definition: refiners.py:823
name
cached name for this TTree
Definition: refiners.py:832
def refine(self, harvesting_module, crops, tdirectory=None, groupby_part_name=None, groupby_value=None, **kwds)
Definition: refiners.py:842
def __init__(self, wrapped_refiner, select=None, exclude=None)
Definition: refiners.py:920
wrapped_refiner
cached value of the wrapped refiner
Definition: refiners.py:927
def refine(self, harvesting_module, crops, *args, **kwds)
Definition: refiners.py:933
exclude
cached value of the exclusion flag
Definition: refiners.py:931
select
cached value of the selector
Definition: refiners.py:929