evefile 0.1.0rc2__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -146,9 +146,10 @@ import os
146
146
 
147
147
  import pandas as pd
148
148
 
149
+ import evefile.entities.data
149
150
  from evefile.entities.file import File
150
151
  from evefile.boundaries.eveh5 import HDF5File
151
- from evefile.controllers import version_mapping, joining
152
+ from evefile.controllers import version_mapping, joining, timestamp_mapping
152
153
 
153
154
 
154
155
  logger = logging.getLogger(__name__)
@@ -221,6 +222,11 @@ class EveFile(File):
221
222
  The keys of the dictionary are the (guaranteed to be unique) HDF
222
223
  dataset names, not the "given" names usually familiar to the users.
223
224
 
225
+ **Please note:** For monitors, in contrast to data stored in the
226
+ :attr:`data` attribute, names are *not unique*. Hence, the only way to
227
+ address an individual monitor unequivocally is by its ID that is
228
+ identical to the HDF dataset name.
229
+
224
230
  Each item is an instance of
225
231
  :class:`evefile.entities.data.MonitorData`.
226
232
 
@@ -261,7 +267,8 @@ class EveFile(File):
261
267
  def __init__(self, filename="", load=True):
262
268
  super().__init__()
263
269
  self.filename = filename
264
- self._join_factory = joining.JoinFactory(evefile=self)
270
+ self._join_factory = joining.JoinFactory(file=self)
271
+ self._monitor_mapper = timestamp_mapping.Mapper(file=self)
265
272
  if load:
266
273
  if not filename:
267
274
  raise ValueError("No filename given")
@@ -387,14 +394,24 @@ class EveFile(File):
387
394
  if self.metadata.preferred_axis:
388
395
  output[0] = self.data[self.metadata.preferred_axis]
389
396
  if self.metadata.preferred_channel:
390
- output[1] = self.data[self.metadata.preferred_channel]
397
+ if self.metadata.preferred_channel in self.data:
398
+ output[1] = self.data[self.metadata.preferred_channel]
399
+ else:
400
+ logger.warning(
401
+ "Dataset {} not found, possibly an "
402
+ "attribute or option?".format(
403
+ self.metadata.preferred_channel
404
+ )
405
+ )
391
406
  if self.metadata.preferred_normalisation_channel:
392
407
  output[2] = self.data[
393
408
  self.metadata.preferred_normalisation_channel
394
409
  ]
395
410
  return output
396
411
 
397
- def get_joined_data(self, data=None, mode="AxisOrChannelPositions"):
412
+ def get_joined_data(
413
+ self, data=None, mode="AxisOrChannelPositions", include_monitors=False
414
+ ):
398
415
  """
399
416
  Retrieve data objects with commensurate dimensions.
400
417
 
@@ -420,6 +437,15 @@ class EveFile(File):
420
437
 
421
438
  Default: "AxisOrChannelPositions"
422
439
 
440
+ include_monitors : :class:`bool`
441
+ Whether to include (all) monitors in joined data.
442
+
443
+ If set to :obj:`True`, all monitors available will automatically
444
+ be mapped to :obj:`DeviceData <evefile.entities.data.DeviceData>`
445
+ objects and included in the list of joined data.
446
+
447
+ Default: :obj:`False`
448
+
423
449
  Returns
424
450
  -------
425
451
  data : :class:`list`
@@ -431,10 +457,37 @@ class EveFile(File):
431
457
  """
432
458
  if not data:
433
459
  data = list(self.data.values())
460
+ data = [
461
+ (
462
+ self._convert_str_to_data_object(item)
463
+ if isinstance(item, str)
464
+ else item
465
+ )
466
+ for item in data
467
+ ]
468
+ if include_monitors:
469
+ monitors = self.get_monitors()
470
+ if isinstance(monitors, list):
471
+ data.extend(monitors)
472
+ else:
473
+ data.append(monitors)
434
474
  joiner = self._join_factory.get_join(mode=mode)
435
475
  return joiner.join(data)
436
476
 
437
- def get_dataframe(self, data=None, mode="AxisOrChannelPositions"):
477
+ def _convert_str_to_data_object(self, name_or_id=""):
478
+ try:
479
+ result = self.data[name_or_id]
480
+ except KeyError:
481
+ try:
482
+ result = self.get_data(name_or_id)
483
+ except KeyError:
484
+ # Valid situation: monitor
485
+ result = self.get_monitors(name_or_id)
486
+ return result
487
+
488
+ def get_dataframe(
489
+ self, data=None, mode="AxisOrChannelPositions", include_monitors=False
490
+ ):
438
491
  """
439
492
  Retrieve Pandas DataFrame with given data objects as columns.
440
493
 
@@ -445,6 +498,18 @@ class EveFile(File):
445
498
  The names of the columns of the returned DataFrame are the names (not
446
499
  IDs) of the respective datasets.
447
500
 
501
+ .. note::
502
+
503
+ At least for the time being, for each data object involved only
504
+ the ``data`` attribute will be contained in the returned
505
+ DataFrame as a column. This is of particular importance for more
506
+ complicated data types, such as :class:`NormalizedChannelData
507
+ <evefile.entities.data.NormalizedChannelData>`,
508
+ :class:`AverageChannelData
509
+ <evefile.entities.data.AverageChannelData>`,
510
+ and :class:`IntervalChannelData
511
+ <evefile.entities.data.IntervalChannelData>`.
512
+
448
513
  .. important::
449
514
 
450
515
  While working with a Pandas DataFrame may seem convenient,
@@ -457,12 +522,12 @@ class EveFile(File):
457
522
  Parameters
458
523
  ----------
459
524
  data : :class:`list`
460
- (Names/IDs of) data objects whose data should be joined.
525
+ (Names/IDs of) data objects whose data should be included.
461
526
 
462
527
  You can provide either names or IDs or the actual data objects.
463
528
 
464
529
  If no data are given, by default all data available will be
465
- joined.
530
+ included.
466
531
 
467
532
  Default: :obj:`None`
468
533
 
@@ -473,6 +538,19 @@ class EveFile(File):
473
538
 
474
539
  Default: "AxisOrChannelPositions"
475
540
 
541
+ include_monitors : :class:`bool`
542
+ Whether to include (all) monitors in the dataframe.
543
+
544
+ If set to :obj:`True`, all monitors available will automatically
545
+ be mapped to :obj:`DeviceData <evefile.entities.data.DeviceData>`
546
+ objects and included in the dataframe.
547
+
548
+ Note that for mapped monitors, their IDs are used as column
549
+ names rather than the "given" names, as for monitors, names are
550
+ *not unique*.
551
+
552
+ Default: :obj:`False`
553
+
476
554
  Returns
477
555
  -------
478
556
  dataframe : :class:`pandas.DataFrame`
@@ -484,10 +562,17 @@ class EveFile(File):
484
562
  """
485
563
  if not data:
486
564
  data = list(self.data.values())
487
- joined_data = self.get_joined_data(data=data, mode=mode)
565
+ joined_data = self.get_joined_data(
566
+ data=data, mode=mode, include_monitors=include_monitors
567
+ )
568
+ for item in joined_data:
569
+ # Ensure IDs are used for monitors, as names are not unique
570
+ if isinstance(item, evefile.entities.data.DeviceData):
571
+ item.metadata.name = item.metadata.id
488
572
  dataframe = pd.DataFrame(
489
573
  {item.metadata.name: item.data for item in joined_data}
490
574
  )
575
+ dataframe.index = joined_data[0].position_counts
491
576
  dataframe.index.name = "position"
492
577
  return dataframe
493
578
 
@@ -552,3 +637,93 @@ class EveFile(File):
552
637
  print("\nMONITORS")
553
638
  for item in self.monitors.values():
554
639
  print(item)
640
+
641
+ def get_monitors(self, monitors=None):
642
+ """
643
+ Retrieve monitors as mapped device data objects by ID.
644
+
645
+ While you can get the original monitor data objects by accessing the
646
+ :attr:`monitors <evefile.entities.file.File.monitors>` attribute
647
+ directly, there, they are stored as
648
+ :obj:`MonitorData <evefile.entities.data.MonitorData>` objects with
649
+ timestamps instead of position counts. To relate monitors to the
650
+ measured data, the former need to be mapped to position counts.
651
+
652
+ .. note::
653
+
654
+ Monitors, in contrast to data objects stored in the
655
+ :attr:`data <evefile.entities.file.File.data>` attribute,
656
+ have *no unique names*. Hence, the only way to unequivocally
657
+ access a given monitor (or the respective, mapped
658
+ :obj:`DeviceData <evefile.entities.data.DeviceData>` object) is
659
+ by means of its unique ID.
660
+
661
+
662
+ Parameters
663
+ ----------
664
+ monitors : :class:`str` | :class:`list`
665
+ Name or list of names of monitors to retrieve
666
+
667
+ Returns
668
+ -------
669
+ device_data : :class:`evefile.entities.data.Data` | :class:`list`
670
+ Device data object(s) corresponding to the ID(s).
671
+
672
+ In case of a list of data objects, each object is of type
673
+ :class:`evefile.entities.data.DeviceData`.
674
+
675
+ """
676
+ device_data = []
677
+ if not monitors:
678
+ monitors = list(self.monitors)
679
+ if isinstance(monitors, (list, tuple)):
680
+ for item in monitors:
681
+ device_data.append(self._monitor_mapper.map(item))
682
+ else:
683
+ device_data.append(self._monitor_mapper.map(monitors))
684
+ if len(device_data) == 1:
685
+ device_data = device_data[0]
686
+ return device_data
687
+
688
+ def get_snapshots(self):
689
+ """
690
+ Retrieve Pandas DataFrame with snapshots as rows.
691
+
692
+ Snapshots serve generally two functions:
693
+
694
+ #. Provide base values for axes.
695
+
696
+ In case of joining data using :meth:`get_joined_data`, for axes,
697
+ typically the previous values are used for positions no axes
698
+ values have been recorded. Snapshots are used if available.
699
+
700
+ #. Provide telemetry data for the setup the data were recorded with.
701
+
702
+ Snapshots regularly contain many more parameters than motor axes
703
+ used and detector channels recorded. Generally, this provides a
704
+ lot of telemetry data regarding the setup used for recording the
705
+ data.
706
+
707
+ The first function is served by the :meth:`get_joined_data` method
708
+ automatically. The second function can be served by having a look at
709
+ a summary containing all snapshot data. This is the aim of this
710
+ method: returning a Pandas DataFrame containing all snapshots as
711
+ rows and the position counts as columns.
712
+
713
+
714
+ Returns
715
+ -------
716
+ snapshot_dataframe : :class:`pandas.DataFrame`
717
+ Pandas DataFrame containing all snapshots as rows.
718
+
719
+ The indices (names of the rows) are the names (not IDs) of the
720
+ respective snapshot datasets.
721
+
722
+ """
723
+ snapshot_dataframes = []
724
+ for snapshot in self.snapshots.values():
725
+ data = dict(zip(snapshot.position_counts, snapshot.data))
726
+ snapshot_dataframes.append(
727
+ pd.DataFrame(data=data, index=[snapshot.metadata.name])
728
+ )
729
+ return pd.concat(snapshot_dataframes)
@@ -132,7 +132,7 @@ Monitors have not changed by definition
132
132
  ---------------------------------------
133
133
 
134
134
  A monitor is by definition a device you are observing for changes in its
135
- values. Hence, if no chance has been recorded (*i.e.*, no newer value is
135
+ values. Hence, if no change has been recorded (*i.e.*, no newer value is
136
136
  present), the value hasn't changed. Therefore, monitor data can be
137
137
  "filled" with the last known value, and in contrast to axes, we can be
138
138
  certain that this is true. (For an axis, you could always argue that if
@@ -165,8 +165,12 @@ NoFill
165
165
  have values."
166
166
 
167
167
  Actually, not a filling, but mathematically an intersection, or,
168
- in terms of relational databases, an ``SQL INNER JOIN``. In any case,
169
- data are *reduced*.
168
+ in terms of relational databases, an ``SQL INNER JOIN``. Note, however,
169
+ that in contrast to the usual terminology of relational databases,
170
+ no individual "tables" (*i.e.*, datasets) are joined, but only the
171
+ set unions of channel and axes positions, respectively. In any case,
172
+ data are *reduced*. Nevertheless, you will most probably end up with
173
+ ``NaN`` or otherwise missing values. See below for details.
170
174
 
171
175
  LastFill
172
176
  "Use all channel data and fill in the last known position for all axes
@@ -220,7 +224,9 @@ main/standard section yet.
220
224
  .. note::
221
225
 
222
226
  Note that **none of the fill modes guarantees that there are no NaNs**
223
- (or comparable null values) in the resulting data.
227
+ (or comparable null values) in the resulting data. The reason: Not
228
+ individual axes or channel datasets are joined, but always the set union
229
+ of all axes positions and all channel positions, respectively.
224
230
 
225
231
 
226
232
  .. note::
@@ -235,9 +241,13 @@ main/standard section yet.
235
241
 
236
242
  The IDL Cruncher seems to use LastNaNFill combined with applying some
237
243
  "dirty" fixes to account for scans using MPSKIP and those scans
238
- "monitoring" a motor position via a pseudo-detector. The ``EveHDF``
239
- class (DS) uses LastNaNFill as a default as well but does *not* apply
240
- some additional post-processing.
244
+ "monitoring" a motor position via a pseudo-detector.
245
+
246
+ The ``EveHDF`` class uses LastNaNFill as a default as well but
247
+ additionally fills the channel values with the last known value,
248
+ thus making up data. Therefore, EveHDF is considered harmful with
249
+ potentially very serious consequences of your data analysis and cannot
250
+ be recommended for use.
241
251
 
242
252
  Shall fill modes be something to change in a viewer? And which fill
243
253
  modes are used in practice (and do we have any chance to find this out)?
@@ -330,26 +340,20 @@ final joined data array previously determined for all the other datasets
330
340
  (that in turn depend on the join mode, of course).
331
341
 
332
342
 
333
- A few comments for further development
334
- ======================================
335
-
336
- .. important::
337
-
338
- The classes implemented in this module have been copied from the
339
- corresponding module in `evedata`_, and here particularly from the
340
- "measurement" functional layer. However, the needs in ``evefile`` are
341
- different, hence even the basic :class:`Join` class needs to be
342
- redesigned. Further information below. Once this has been done,
343
- this entire section should be removed.
344
-
343
+ How to deal with additional attributes?
344
+ =======================================
345
345
 
346
- * Joining should probably take into account all available attributes,
347
- not only ``data``, but options as well if present. This of course only
348
- applies to ``evefile`` if options of devices are mapped to the
349
- respective data objects.
346
+ Joining should take into account all available attributes, not only
347
+ ``data``, but options as well if present. This of course only applies to
348
+ ``evefile`` if options of devices are mapped to the respective data
349
+ objects.
350
350
 
351
- * Joining should probably take into account monitors that need to be
352
- converted to :class:`DeviceData <evefile.entities.data.DeviceData>` before.
351
+ A similar case already implemented: More advanced channels such as average
352
+ and interval channels that come with additional data per each recorded
353
+ value (see :class:`AverageChannelData
354
+ <evefile.entities.data.AverageChannelData>` and
355
+ :class:`IntervalChannelData <evefile.entities.data.IntervalChannelData>`
356
+ for details) have these attributes handled accordingly.
353
357
 
354
358
 
355
359
  Join modes currently implemented
@@ -498,7 +502,7 @@ class Join:
498
502
 
499
503
  Attributes
500
504
  ----------
501
- evefile : :class:`evefile.boundaries.evefile.EveFile`
505
+ file : :class:`evefile.boundaries.evefile.EveFile`
502
506
  EveFile object the Join should be performed for.
503
507
 
504
508
  Although joining is carried out for a small subset of the
@@ -507,7 +511,7 @@ class Join:
507
511
 
508
512
  Parameters
509
513
  ----------
510
- evefile : :class:`evefile.boundaries.evefile.EveFile`
514
+ file : :class:`evefile.boundaries.evefile.EveFile`
511
515
  EveFile object the join should be performed for.
512
516
 
513
517
 
@@ -522,7 +526,7 @@ class Join:
522
526
 
523
527
  .. code-block::
524
528
 
525
- join = Join(evefile=my_evefile)
529
+ join = Join(file=my_evefile)
526
530
  # Call with data object names
527
531
  joined_data = join.join(["name1", "name2"])
528
532
  # Call with data objects
@@ -532,12 +536,13 @@ class Join:
532
536
 
533
537
  """
534
538
 
535
- def __init__(self, evefile=None):
539
+ def __init__(self, file=None):
536
540
  self._channel_indices = []
537
541
  self._axes = []
538
542
  self._channels = []
543
+ self._devices = []
539
544
  self._result_positions = None
540
- self.evefile = evefile
545
+ self.file = file
541
546
 
542
547
  def join(self, data=None):
543
548
  """
@@ -573,32 +578,18 @@ class Join:
573
578
  Raised if no data are provided
574
579
 
575
580
  """
576
- if not self.evefile:
581
+ if not self.file:
577
582
  raise ValueError("Need an evefile to join data.")
578
583
  if not data:
579
584
  raise ValueError("Need data to join data.")
580
- data = [
581
- (
582
- self._convert_str_to_data_object(item)
583
- if isinstance(item, str)
584
- else item
585
- )
586
- for item in data
587
- ]
588
585
  return self._join(data=data)
589
586
 
590
- def _convert_str_to_data_object(self, name_or_id=""):
591
- try:
592
- result = self.evefile.data[name_or_id]
593
- except KeyError:
594
- result = self.evefile.get_data(name_or_id)
595
- return result
596
-
597
587
  def _join(self, data=None):
598
588
  self._sort_data(data)
599
589
  self._assign_result_positions()
600
590
  self._fill_axes()
601
591
  self._fill_channels()
592
+ self._fill_devices()
602
593
  return self._assign_result()
603
594
 
604
595
  def _sort_data(self, data):
@@ -608,16 +599,18 @@ class Join:
608
599
  self._channel_indices.append(idx)
609
600
  if isinstance(item, evefile.entities.data.AxisData):
610
601
  self._axes.append(copy.copy(item))
602
+ if isinstance(item, evefile.entities.data.DeviceData):
603
+ self._devices.append(copy.copy(item))
611
604
 
612
605
  def _assign_result_positions(self):
613
606
  pass
614
607
 
615
608
  def _fill_axes(self):
616
609
  for axis in self._axes:
617
- if axis.metadata.id in self.evefile.snapshots:
610
+ if axis.metadata.id in self.file.snapshots:
618
611
  axis.join(
619
612
  positions=self._result_positions,
620
- snapshot=self.evefile.snapshots[axis.metadata.id],
613
+ snapshot=self.file.snapshots[axis.metadata.id],
621
614
  )
622
615
  else:
623
616
  axis.join(positions=self._result_positions, fill=True)
@@ -626,10 +619,15 @@ class Join:
626
619
  for channel in self._channels:
627
620
  channel.join(positions=self._result_positions)
628
621
 
622
+ def _fill_devices(self):
623
+ for device in self._devices:
624
+ device.join(positions=self._result_positions)
625
+
629
626
  def _assign_result(self):
630
627
  result = [*self._axes]
631
628
  for idx, item in enumerate(self._channels):
632
629
  result.insert(self._channel_indices[idx], item)
630
+ result.extend(self._devices)
633
631
  return result
634
632
 
635
633
 
@@ -681,7 +679,7 @@ class ChannelPositions(Join):
681
679
 
682
680
  Attributes
683
681
  ----------
684
- evefile : :class:`evefile.boundaries.evefile.EveFile`
682
+ file : :class:`evefile.boundaries.evefile.EveFile`
685
683
  EveFile object the join should be performed for.
686
684
 
687
685
  Although joining may only be carried out for a small subset of the
@@ -691,7 +689,7 @@ class ChannelPositions(Join):
691
689
 
692
690
  Parameters
693
691
  ----------
694
- evefile : :class:`evefile.boundaries.evefile.EveFile`
692
+ file : :class:`evefile.boundaries.evefile.EveFile`
695
693
  EveFile the join should be performed for.
696
694
 
697
695
 
@@ -706,7 +704,7 @@ class ChannelPositions(Join):
706
704
 
707
705
  .. code-block::
708
706
 
709
- join = ChannelPositions(evefile=my_evefile)
707
+ join = ChannelPositions(file=my_evefile)
710
708
  # Call with data object names
711
709
  joined_data = join.join(["name1", "name2"])
712
710
  # Call with data objects
@@ -773,7 +771,7 @@ class AxisPositions(Join):
773
771
 
774
772
  Attributes
775
773
  ----------
776
- evefile : :class:`evefile.boundaries.evefile.EveFile`
774
+ file : :class:`evefile.boundaries.evefile.EveFile`
777
775
  EveFile object the join should be performed for.
778
776
 
779
777
  Although joining may only be carried out for a small subset of the
@@ -783,7 +781,7 @@ class AxisPositions(Join):
783
781
 
784
782
  Parameters
785
783
  ----------
786
- evefile : :class:`evefile.boundaries.evefile.EveFile`
784
+ file : :class:`evefile.boundaries.evefile.EveFile`
787
785
  EveFile the join should be performed for.
788
786
 
789
787
 
@@ -798,7 +796,7 @@ class AxisPositions(Join):
798
796
 
799
797
  .. code-block::
800
798
 
801
- join = AxisPositions(evefile=my_evefile)
799
+ join = AxisPositions(file=my_evefile)
802
800
  # Call with data object names
803
801
  joined_data = join.join(["name1", "name2"])
804
802
  # Call with data objects
@@ -868,7 +866,7 @@ class AxisAndChannelPositions(Join):
868
866
 
869
867
  Attributes
870
868
  ----------
871
- evefile : :class:`evefile.boundaries.evefile.EveFile`
869
+ file : :class:`evefile.boundaries.evefile.EveFile`
872
870
  EveFile object the join should be performed for.
873
871
 
874
872
  Although joining may only be carried out for a small subset of the
@@ -878,7 +876,7 @@ class AxisAndChannelPositions(Join):
878
876
 
879
877
  Parameters
880
878
  ----------
881
- evefile : :class:`evefile.boundaries.evefile.EveFile`
879
+ file : :class:`evefile.boundaries.evefile.EveFile`
882
880
  EveFile the join should be performed for.
883
881
 
884
882
 
@@ -893,7 +891,7 @@ class AxisAndChannelPositions(Join):
893
891
 
894
892
  .. code-block::
895
893
 
896
- join = AxisAndChannelPositions(evefile=my_evefile)
894
+ join = AxisAndChannelPositions(file=my_evefile)
897
895
  # Call with data object names
898
896
  joined_data = join.join(["name1", "name2"])
899
897
  # Call with data objects
@@ -965,7 +963,7 @@ class AxisOrChannelPositions(Join):
965
963
 
966
964
  Attributes
967
965
  ----------
968
- evefile : :class:`evefile.boundaries.evefile.EveFile`
966
+ file : :class:`evefile.boundaries.evefile.EveFile`
969
967
  EveFile object the join should be performed for.
970
968
 
971
969
  Although joining may only be carried out for a small subset of the
@@ -975,7 +973,7 @@ class AxisOrChannelPositions(Join):
975
973
 
976
974
  Parameters
977
975
  ----------
978
- evefile : :class:`evefile.boundaries.evefile.EveFile`
976
+ file : :class:`evefile.boundaries.evefile.EveFile`
979
977
  EveFile the join should be performed for.
980
978
 
981
979
 
@@ -990,7 +988,7 @@ class AxisOrChannelPositions(Join):
990
988
 
991
989
  .. code-block::
992
990
 
993
- join = AxisOrChannelPositions(evefile=my_evefile)
991
+ join = AxisOrChannelPositions(file=my_evefile)
994
992
  # Call with data object names
995
993
  joined_data = join.join(["name1", "name2"])
996
994
  # Call with data objects
@@ -1027,13 +1025,13 @@ class JoinFactory:
1027
1025
 
1028
1026
  Attributes
1029
1027
  ----------
1030
- evefile : :class:`evefile.boundaries.evefile.EveFile`
1028
+ file : :class:`evefile.boundaries.evefile.EveFile`
1031
1029
  EveFile the join should be performed for.
1032
1030
 
1033
1031
 
1034
1032
  Parameters
1035
1033
  ----------
1036
- evefile : :class:`evefile.boundaries.evefile.EveFile`
1034
+ file : :class:`evefile.boundaries.evefile.EveFile`
1037
1035
  EveFile the join should be performed for.
1038
1036
 
1039
1037
 
@@ -1066,8 +1064,8 @@ class JoinFactory:
1066
1064
 
1067
1065
  """
1068
1066
 
1069
- def __init__(self, evefile=None):
1070
- self.evefile = evefile
1067
+ def __init__(self, file=None):
1068
+ self.file = file
1071
1069
 
1072
1070
  def get_join(self, mode="Join"):
1073
1071
  """
@@ -1093,5 +1091,5 @@ class JoinFactory:
1093
1091
  Join instance
1094
1092
 
1095
1093
  """
1096
- instance = globals()[mode](evefile=self.evefile)
1094
+ instance = globals()[mode](file=self.file)
1097
1095
  return instance