class_docstr: Convenience class to encapsulate TRK file format.
  
  Notes
  -----
  TrackVis (so its file format: TRK) considers the streamline coordinate
  (0,0,0) to be in the corner of the voxel whereas NiBabel's streamlines
  internal representation (Voxel space) assumes (0,0,0) to be in the
  center of the voxel.
  
  Thus, streamlines are shifted by half a voxel on load and are shifted
  back on save.
docstr: Save tractogram to a filename or file-like object using TRK format.
  
  Parameters
  ----------
  fileobj : string or file-like object
      If string, a filename; otherwise an open file-like object
      pointing to TRK file (and ready to write from the beginning
      of the TRK header data).
docstr_tok: ["Save","tractogram","to","a","filename","or","file-like","object","using","TRK","format",".","Parameters","--","--","--","--","--","fileobj",":","string","or","file-like","object","If","string",",","a","filename",";","otherwise","an","open","file-like","object","pointing","to","TRK","file","(","and","ready","to","write","from","the","beginning","of","the","TRK","header","data",")","."]
code: def save(self, fileobj):
      """Save tractogram to a filename or file-like object using TRK format.
  
      Parameters
      ----------
      fileobj : string or file-like object
          If string, a filename; otherwise an open file-like object
          pointing to TRK file (and ready to write from the beginning
          of the TRK header data).
      """
      # Enforce little-endian byte order for header
      header = self._default_structarr(endianness="little")
  
      # Override hdr's fields by those contained in `header`.
      for k, v in self.header.items():
          if k in header_2_dtype.fields.keys():
              header[k] = v
  
      # By default, the voxel order is LPS.
      # http://trackvis.org/blog/forum/diffusion-toolkit-usage/interpretation-of-track-point-coordinates
      if header[Field.VOXEL_ORDER] == b"":
          header[Field.VOXEL_ORDER] = b"LPS"
  
      # Keep counts for correcting incoherent fields or warn.
      nb_streamlines = 0
      nb_points = 0
      nb_scalars = 0
      nb_properties = 0
  
      with Opener(fileobj, mode="wb") as f:
          # Keep track of the beginning of the header.
          beginning = f.tell()
  
          # Write temporary header that we will update at the end
          f.write(header.tobytes())
  
          i4_dtype = np.dtype("<i4")  # Always save in little-endian.
          f4_dtype = np.dtype("<f4")  # Always save in little-endian.
  
          # Since the TRK format requires the streamlines to be saved in
          # voxmm, we first transform them accordingly. The transformation
          # is performed lazily since `self.tractogram` might be a
          # LazyTractogram object, which means we might be able to loop
          # over the streamlines only once.
          tractogram = self.tractogram.to_world(lazy=True)
          affine_to_trackvis = get_affine_rasmm_to_trackvis(header)
          tractogram = tractogram.apply_affine(
              affine_to_trackvis, lazy=True
          )
  
          # Create the iterator we'll be using for the rest of the function.
          tractogram = iter(tractogram)
  
          try:
              # Use the first element to check
              #  1) the tractogram is not empty;
              #  2) quantity of information saved along each streamline.
              first_item, tractogram = peek_next(tractogram)
          except StopIteration:
              # Empty tractogram
              header[Field.NB_STREAMLINES] = 0
              header[Field.NB_SCALARS_PER_POINT] = 0
              header[Field.NB_PROPERTIES_PER_STREAMLINE] = 0
              # Overwrite header with updated one.
              f.seek(beginning, os.SEEK_SET)
              f.write(header.tobytes())
              return
  
          # Update field 'property_name' using 'data_per_streamline'.
          data_for_streamline = first_item.data_for_streamline
          if (
              len(data_for_streamline)
              > MAX_NB_NAMED_PROPERTIES_PER_STREAMLINE
          ):
              msg = (
                  f"Can only store {MAX_NB_NAMED_SCALARS_PER_POINT} named "
                  "data_per_streamline (also known as 'properties' in the "
                  "TRK format)."
              )
              raise ValueError(msg)
  
          data_for_streamline_keys = sorted(data_for_streamline.keys())
          property_name = np.zeros(
              MAX_NB_NAMED_PROPERTIES_PER_STREAMLINE, dtype="S20"
          )
          for i, name in enumerate(data_for_streamline_keys):
              # Append number of values as ascii to zero-terminated name
              # to encode number of values into trackvis name.
              nb_values = data_for_streamline[name].shape[-1]
              property_name[i] = encode_value_in_name(nb_values, name)
          header["property_name"][:] = property_name
  
          # Update field 'scalar_name' using 'tractogram.data_per_point'.
          data_for_points = first_item.data_for_points
          if len(data_for_points) > MAX_NB_NAMED_SCALARS_PER_POINT:
              msg = (
                  f"Can only store {MAX_NB_NAMED_SCALARS_PER_POINT} "
                  "named data_per_point (also known as 'scalars' in "
                  "the TRK format)."
              )
              raise ValueError(msg)
  
          data_for_points_keys = sorted(data_for_points.keys())
          scalar_name = np.zeros(
              MAX_NB_NAMED_SCALARS_PER_POINT, dtype="S20"
          )
          for i, name in enumerate(data_for_points_keys):
              # Append number of values as ascii to zero-terminated name
              # to encode number of values into trackvis name.
              nb_values = data_for_points[name].shape[-1]
              scalar_name[i] = encode_value_in_name(nb_values, name)
          header["scalar_name"][:] = scalar_name
  
          for t in tractogram:
              if any(
                  len(d) != len(t.streamline)
                  for d in t.data_for_points.values()
              ):
                  raise DataError("Missing scalars for some points!")
  
              points = np.asarray(t.streamline)
              scalars = [
                  np.asarray(t.data_for_points[k])
                  for k in data_for_points_keys
              ]
              scalars = np.concatenate(
                  [np.ndarray((len(points), 0))] + scalars, axis=1
              )
              properties = [
                  np.asarray(t.data_for_streamline[k])
                  for k in data_for_streamline_keys
              ]
              properties = np.concatenate(
                  [np.array([])] + properties
              ).astype(f4_dtype)
  
              data = struct.pack(i4_dtype.str[:-1], len(points))
              pts_scalars = np.concatenate(
                  [points, scalars], axis=1
              ).astype(f4_dtype)
              data += pts_scalars.tobytes()
              data += properties.tobytes()
              f.write(data)
  
              nb_streamlines += 1
              nb_points += len(points)
              nb_scalars += scalars.size
              nb_properties += len(properties)
  
          # Use those values to update the header.
          nb_scalars_per_point = nb_scalars / nb_points
          nb_properties_per_streamline = nb_properties / nb_streamlines
  
          # Check for errors
          if nb_scalars_per_point != int(nb_scalars_per_point):
              msg = "Nb. of scalars differs from one point to another!"
              raise DataError(msg)
  
          if nb_properties_per_streamline != int(
              nb_properties_per_streamline
          ):
              msg = "Nb. of properties differs from one streamline to another!"
              raise DataError(msg)
  
          header[Field.NB_STREAMLINES] = nb_streamlines
          header[Field.NB_SCALARS_PER_POINT] = nb_scalars_per_point
          header[
              Field.NB_PROPERTIES_PER_STREAMLINE
          ] = nb_properties_per_streamline
  
          # Overwrite header with updated one.
          f.seek(beginning, os.SEEK_SET)
          f.write(header.tobytes())
  
imports: import os
  import string
  import struct
  import warnings
  import numpy
  import nibabel
  from nibabel.openers import Opener
  from nibabel.orientations import aff2axcodes, axcodes2ornt
  from nibabel.volumeutils import endian_codes, native_code, swapped_code
  from .array_sequence import create_arraysequences_from_generator
  from .header import Field
  from .tractogram import LazyTractogram, Tractogram, TractogramItem
  from .tractogram_file import DataError, HeaderError, HeaderWarning, TractogramFile
  from .utils import peek_next
label_desc: Use image node_id 6 for calling the TrkFile obj's underlying member method code with example usage: obj.save(fileobj) without return types