1111import datetime
1212from io import BytesIO
1313import operator
14- import os .path as op
15- import re
1614
1715import numpy as np
1816from scipy import linalg
2927 write_coord_trans , write_ch_info , write_name_list ,
3028 write_julian , write_float_matrix , write_id , DATE_NONE )
3129from .proc_history import _read_proc_history , _write_proc_history
32- from ..transforms import _to_const , invert_transform
33- from ..utils import (logger , verbose , warn , object_diff , _validate_type ,
34- _check_option )
35- from .. import __version__
30+ from ..transforms import invert_transform
31+ from ..utils import logger , verbose , warn , object_diff , _validate_type
3632from ..digitization .base import _format_dig_points
3733from ..digitization import Digitization
3834from .compensator import get_current_comp
3935
4036# XXX: most probably the functions needing this, should go somewhere else
4137from ..digitization .base import _dig_kind_proper , _dig_kind_rev , _dig_kind_ints
38+ from ..digitization ._utils import _read_dig_fif
4239
4340b = bytes # alias
4441
@@ -661,7 +658,8 @@ def write_fiducials(fname, pts, coord_frame=FIFF.FIFFV_COORD_UNKNOWN,
661658 mne.io.constants.FIFF.FIFFV_COORD_...).
662659 %(verbose)s
663660 """
664- write_dig (fname , pts , coord_frame )
661+ from ..digitization ._utils import write_dig as ff
662+ ff (fname , pts , coord_frame )
665663
666664
667665def write_dig (fname , pts , coord_frame = None ):
@@ -679,214 +677,8 @@ def write_dig(fname, pts, coord_frame=None):
679677 here. Can be None (default) if the points could have varying
680678 coordinate frames.
681679 """
682- if coord_frame is not None :
683- coord_frame = _to_const (coord_frame )
684- pts_frames = {pt .get ('coord_frame' , coord_frame ) for pt in pts }
685- bad_frames = pts_frames - {coord_frame }
686- if len (bad_frames ) > 0 :
687- raise ValueError (
688- 'Points have coord_frame entries that are incompatible with '
689- 'coord_frame=%i: %s.' % (coord_frame , str (tuple (bad_frames ))))
690-
691- with start_file (fname ) as fid :
692- write_dig_points (fid , pts , block = True , coord_frame = coord_frame )
693- end_file (fid )
694-
695-
696- def _read_dig_fif (fid , meas_info ):
697- """Read digitizer data from a FIFF file."""
698- isotrak = dir_tree_find (meas_info , FIFF .FIFFB_ISOTRAK )
699- dig = None
700- if len (isotrak ) == 0 :
701- logger .info ('Isotrak not found' )
702- elif len (isotrak ) > 1 :
703- warn ('Multiple Isotrak found' )
704- else :
705- isotrak = isotrak [0 ]
706- dig = []
707- for k in range (isotrak ['nent' ]):
708- kind = isotrak ['directory' ][k ].kind
709- pos = isotrak ['directory' ][k ].pos
710- if kind == FIFF .FIFF_DIG_POINT :
711- tag = read_tag (fid , pos )
712- dig .append (tag .data )
713- dig [- 1 ]['coord_frame' ] = FIFF .FIFFV_COORD_HEAD
714- return _format_dig_points (dig )
715-
716-
717- def _read_dig_points (fname , comments = '%' , unit = 'auto' ):
718- """Read digitizer data from a text file.
719-
720- If fname ends in .hsp or .esp, the function assumes digitizer files in [m],
721- otherwise it assumes space-delimited text files in [mm].
722-
723- Parameters
724- ----------
725- fname : str
726- The filepath of space delimited file with points, or a .mat file
727- (Polhemus FastTrak format).
728- comments : str
729- The character used to indicate the start of a comment;
730- Default: '%'.
731- unit : 'auto' | 'm' | 'cm' | 'mm'
732- Unit of the digitizer files (hsp and elp). If not 'm', coordinates will
733- be rescaled to 'm'. Default is 'auto', which assumes 'm' for *.hsp and
734- *.elp files and 'mm' for *.txt files, corresponding to the known
735- Polhemus export formats.
736-
737- Returns
738- -------
739- dig_points : np.ndarray, shape (n_points, 3)
740- Array of dig points in [m].
741- """
742- _check_option ('unit' , unit , ['auto' , 'm' , 'mm' , 'cm' ])
743-
744- _ , ext = op .splitext (fname )
745- if ext == '.elp' or ext == '.hsp' :
746- with open (fname ) as fid :
747- file_str = fid .read ()
748- value_pattern = r"\-?\d+\.?\d*e?\-?\d*"
749- coord_pattern = r"({0})\s+({0})\s+({0})\s*$" .format (value_pattern )
750- if ext == '.hsp' :
751- coord_pattern = '^' + coord_pattern
752- points_str = [m .groups () for m in re .finditer (coord_pattern , file_str ,
753- re .MULTILINE )]
754- dig_points = np .array (points_str , dtype = float )
755- elif ext == '.mat' : # like FastScan II
756- from scipy .io import loadmat
757- dig_points = loadmat (fname )['Points' ].T
758- else :
759- dig_points = np .loadtxt (fname , comments = comments , ndmin = 2 )
760- if unit == 'auto' :
761- unit = 'mm'
762- if dig_points .shape [1 ] > 3 :
763- warn ('Found %d columns instead of 3, using first 3 for XYZ '
764- 'coordinates' % (dig_points .shape [1 ],))
765- dig_points = dig_points [:, :3 ]
766-
767- if dig_points .shape [- 1 ] != 3 :
768- err = 'Data must be (n, 3) instead of %s' % (dig_points .shape ,)
769- raise ValueError (err )
770-
771- if unit == 'mm' :
772- dig_points /= 1000.
773- elif unit == 'cm' :
774- dig_points /= 100.
775-
776- return dig_points
777-
778-
779- def _write_dig_points (fname , dig_points ):
780- """Write points to text file.
781-
782- Parameters
783- ----------
784- fname : str
785- Path to the file to write. The kind of file to write is determined
786- based on the extension: '.txt' for tab separated text file.
787- dig_points : numpy.ndarray, shape (n_points, 3)
788- Points.
789- """
790- _ , ext = op .splitext (fname )
791- dig_points = np .asarray (dig_points )
792- if (dig_points .ndim != 2 ) or (dig_points .shape [1 ] != 3 ):
793- err = ("Points must be of shape (n_points, 3), "
794- "not %s" % (dig_points .shape ,))
795- raise ValueError (err )
796-
797- if ext == '.txt' :
798- with open (fname , 'wb' ) as fid :
799- version = __version__
800- now = datetime .datetime .now ().strftime ("%I:%M%p on %B %d, %Y" )
801- fid .write (b'%% Ascii 3D points file created by mne-python version'
802- b' %s at %s\n ' % (version .encode (), now .encode ()))
803- fid .write (b'%% %d 3D points, x y z per line\n ' % len (dig_points ))
804- np .savetxt (fid , dig_points , delimiter = '\t ' , newline = '\n ' )
805- else :
806- msg = "Unrecognized extension: %r. Need '.txt'." % ext
807- raise ValueError (msg )
808-
809-
810- # XXX: all points are supposed to be in FIFFV_COORD_HEAD
811- def _make_dig_points (nasion = None , lpa = None , rpa = None , hpi = None ,
812- extra_points = None , dig_ch_pos = None ):
813- """Construct digitizer info for the info.
814-
815- Parameters
816- ----------
817- nasion : array-like | numpy.ndarray, shape (3,) | None
818- Point designated as the nasion point.
819- lpa : array-like | numpy.ndarray, shape (3,) | None
820- Point designated as the left auricular point.
821- rpa : array-like | numpy.ndarray, shape (3,) | None
822- Point designated as the right auricular point.
823- hpi : array-like | numpy.ndarray, shape (n_points, 3) | None
824- Points designated as head position indicator points.
825- extra_points : array-like | numpy.ndarray, shape (n_points, 3)
826- Points designed as the headshape points.
827- dig_ch_pos : dict
828- Dict of EEG channel positions.
829-
830- Returns
831- -------
832- dig : Digitization
833- A container of DigPoints to be added to the info['dig'].
834- """
835- dig = []
836- if lpa is not None :
837- lpa = np .asarray (lpa )
838- if lpa .shape != (3 ,):
839- raise ValueError ('LPA should have the shape (3,) instead of %s'
840- % (lpa .shape ,))
841- dig .append ({'r' : lpa , 'ident' : FIFF .FIFFV_POINT_LPA ,
842- 'kind' : FIFF .FIFFV_POINT_CARDINAL ,
843- 'coord_frame' : FIFF .FIFFV_COORD_HEAD })
844- if nasion is not None :
845- nasion = np .asarray (nasion )
846- if nasion .shape != (3 ,):
847- raise ValueError ('Nasion should have the shape (3,) instead of %s'
848- % (nasion .shape ,))
849- dig .append ({'r' : nasion , 'ident' : FIFF .FIFFV_POINT_NASION ,
850- 'kind' : FIFF .FIFFV_POINT_CARDINAL ,
851- 'coord_frame' : FIFF .FIFFV_COORD_HEAD })
852- if rpa is not None :
853- rpa = np .asarray (rpa )
854- if rpa .shape != (3 ,):
855- raise ValueError ('RPA should have the shape (3,) instead of %s'
856- % (rpa .shape ,))
857- dig .append ({'r' : rpa , 'ident' : FIFF .FIFFV_POINT_RPA ,
858- 'kind' : FIFF .FIFFV_POINT_CARDINAL ,
859- 'coord_frame' : FIFF .FIFFV_COORD_HEAD })
860- if hpi is not None :
861- hpi = np .asarray (hpi )
862- if hpi .ndim != 2 or hpi .shape [1 ] != 3 :
863- raise ValueError ('HPI should have the shape (n_points, 3) instead '
864- 'of %s' % (hpi .shape ,))
865- for idx , point in enumerate (hpi ):
866- dig .append ({'r' : point , 'ident' : idx + 1 ,
867- 'kind' : FIFF .FIFFV_POINT_HPI ,
868- 'coord_frame' : FIFF .FIFFV_COORD_HEAD })
869- if extra_points is not None :
870- extra_points = np .asarray (extra_points )
871- if extra_points .shape [1 ] != 3 :
872- raise ValueError ('Points should have the shape (n_points, 3) '
873- 'instead of %s' % (extra_points .shape ,))
874- for idx , point in enumerate (extra_points ):
875- dig .append ({'r' : point , 'ident' : idx + 1 ,
876- 'kind' : FIFF .FIFFV_POINT_EXTRA ,
877- 'coord_frame' : FIFF .FIFFV_COORD_HEAD })
878- if dig_ch_pos is not None :
879- keys = sorted (dig_ch_pos .keys ())
880- try : # use the last 3 as int if possible (e.g., EEG001->1)
881- idents = [int (key [- 3 :]) for key in keys ]
882- except ValueError : # and if any conversion fails, simply use arange
883- idents = np .arange (1 , len (keys ) + 1 )
884- for key , ident in zip (keys , idents ):
885- dig .append ({'r' : dig_ch_pos [key ], 'ident' : ident ,
886- 'kind' : FIFF .FIFFV_POINT_EEG ,
887- 'coord_frame' : FIFF .FIFFV_COORD_HEAD })
888-
889- return Digitization (_format_dig_points (dig ))
680+ from ..digitization ._utils import write_dig as ff
681+ return ff (fname , pts , coord_frame = None )
890682
891683
892684@verbose
0 commit comments