B
    -,c'              	   @   s
  d Z ddlmZmZmZmZ ddlZddlZddlZ	ddl
mZ dd Zdd	 Zed
dddddddZdd Ze	je	je	je	je	je	jdZdd Zdd Zdd ZededdZd+ddZdd Zd d! Zd"d# Z d$ej! ej" ej# Z$d%d& Z%d'd( Z&d)d* Z'dS ),a  
utils
-----

Utility functions used by the other modules in the mrcfile package.

Functions
---------

* :func:`data_dtype_from_header`: Work out the data :class:`dtype
  <numpy.dtype>` from an MRC header.
* :func:`data_shape_from_header`: Work out the data array shape from an MRC
  header
* :func:`mode_from_dtype`: Convert a :class:`numpy dtype <numpy.dtype>` to an
  MRC mode number.
* :func:`dtype_from_mode`: Convert an MRC mode number to a :class:`numpy dtype
  <numpy.dtype>`.
* :func:`pretty_machine_stamp`: Get a nicely-formatted string from a machine
  stamp.
* :func:`machine_stamp_from_byte_order`: Get a machine stamp from a byte order
  indicator.
* :func:`byte_orders_equal`: Compare two byte order indicators for equal
  endianness.
* :func:`normalise_byte_order`: Convert a byte order indicator to ``<`` or
  ``>``.
* :func:`spacegroup_is_volume_stack`: Identify if a space group number
  represents a volume stack.

    )absolute_importdivisionprint_functionunicode_literalsN   )IMAGE_STACK_SPACEGROUPc             C   s   | j }t||jjS )a_  Return the data dtype indicated by the given header.
    
    This function calls :func:`dtype_from_mode` to get the basic dtype, and
    then makes sure that the byte order of the new dtype matches the byte order
    of the header's ``mode`` field.
    
    Args:
        header: An MRC header as a :class:`numpy record array
            <numpy.recarray>`.
    
    Returns:
        The :class:`numpy dtype <numpy.dtype>` object for the data array
        corresponding to the given header.
    
    Raises:
        :exc:`ValueError`: If there is no corresponding dtype for the given
            mode.
    )modedtype_from_modeZnewbyteorderdtype	byteorder)headerr    r   ,lib/python3.7/site-packages/mrcfile/utils.pydata_dtype_from_header.   s    r   c             C   sn   t | j}t | j}t | j}t | j}t| jrD|| |||f}n&| jtkr`|dkr`||f}n
|||f}|S )a  Return the data shape indicated by the given header.
    
    Args:
        header: An MRC header as a :class:`numpy record array
            <numpy.recarray>`.
    
    Returns:
        The shape tuple for the data array corresponding to the given header.
    r   )intnxnynzmzspacegroup_is_volume_stackispgr   )r   r   r   r   r   shaper   r   r   data_shape_from_headerE   s    







r               )f2Zf4Zi1Zi2Zu1Zu2Zc8c             C   s2   | j t| j }|tkr t| S td| dS )a  Return the MRC mode number corresponding to the given :class:`numpy
    dtype <numpy.dtype>`.
    
    The conversion is as follows:
    
    * float16   -> mode 12
    * float32   -> mode 2
    * int8      -> mode 0
    * int16     -> mode 1
    * uint8     -> mode 6 (data will be widened to 16 bits in the file)
    * uint16    -> mode 6
    * complex64 -> mode 4
    
    Note that there is no numpy dtype which corresponds to MRC mode 3.
    
    Args:
        dtype: A :class:`numpy dtype <numpy.dtype>` object.
    
    Returns:
        The MRC mode number.
    
    Raises:
        :exc:`ValueError`: If there is no corresponding MRC mode for the given
            dtype.
    z3dtype '{0}' cannot be converted to an MRC file modeN)kindstritemsize_dtype_to_mode
ValueErrorformat)r
   Zkind_and_sizer   r   r   mode_from_dtypea   s
    r$   )r   r   r   r   r   r   c             C   s0   t | } | tkrtt|  S td| dS )a  Return the :class:`numpy dtype <numpy.dtype>` corresponding to the given
    MRC mode number.
    
    The mode parameter may be given as a Python scalar, numpy scalar or
    single-item numpy array.
    
    The conversion is as follows:
    
    * mode 0 -> int8
    * mode 1 -> int16
    * mode 2 -> float32
    * mode 4 -> complex64
    * mode 6 -> uint16
    * mode 12 -> float16
    
    Note that modes 3 and 101 are not supported as there is no matching numpy dtype.
    
    Args:
        mode: The MRC mode number. This may be given as any type which can be
            converted to an int, for example a Python scalar (``int`` or
            ``float``), a numpy scalar or a single-item numpy array.
    
    Returns:
        The :class:`numpy dtype <numpy.dtype>` object corresponding to the
        given mode.
    
    Raises:
        :exc:`ValueError`: If there is no corresponding dtype for the given
            mode.
    zUnrecognised mode '{0}'N)r   _mode_to_dtypenpr
   r"   r#   )r   r   r   r   r	      s    r	   c             C   s   d dd | D S )z7Return a human-readable hex string for a machine stamp. c             s   s   | ]}d  |V  qdS )z0x{:02x}N)r#   ).0Zbyter   r   r   	<genexpr>   s    z'pretty_machine_stamp.<locals>.<genexpr>)join)machstr   r   r   pretty_machine_stamp   s    r,   c             C   sP   | d dkr| d dkrdS | d dkr8| d dkr8dS t | }td| d	S )
a  Return the byte order corresponding to the given machine stamp.
    
    Args:
        machst: The machine stamp, as a :class:`bytearray` or a :class:`numpy
            array <numpy.ndarray>` of bytes.
    
    Returns:
        ``<`` if the machine stamp represents little-endian data, or ``>`` if
        it represents big-endian.
    
    Raises:
        :exc:`ValueError`: If the machine stamp is invalid.
    r   D   r   )r-   A   <   >zUnrecognised machine stamp: N)r,   r"   )r+   Zpretty_bytesr   r   r   byte_order_from_machine_stamp   s    r2   )r-   r-   r   r   )r0   r0   r   r   )r/   r1   =c             C   s   t | } t|  S )ax  Return the machine stamp corresponding to the given byte order
    indicator.
    
    Args:
        byte_order: The byte order indicator: one of ``=``, ``<`` or ``>``, as
            defined and used by numpy dtype objects.
    
    Returns:
        The machine stamp which corresponds to the given byte order, as a
        :class:`bytearray`. This will be either ``(0x44, 0x44, 0, 0)`` for
        little-endian or ``(0x11, 0x11, 0, 0)`` for big-endian. If the given
        byte order indicator is ``=``, the native byte order is used.
    
    Raises:
        :exc:`ValueError`: If the byte order indicator is unrecognised.
    )normalise_byte_order_byte_order_to_machine_stamp)
byte_orderr   r   r   machine_stamp_from_byte_order   s    r7   c             C   s   t | t |kS )a  Work out if the byte order indicators represent the same endianness.
    
    Args:
        a: The first byte order indicator: one of ``=``, ``<`` or ``>``, as
            defined and used by :class:`numpy dtype <numpy.dtype>` objects.
        b: The second byte order indicator.
    
    Returns:
        :data:`True` if the byte order indicators represent the same
        endianness.
    
    Raises:
        :exc:`ValueError`: If the byte order indicator is not recognised.
    )r4   )abr   r   r   byte_orders_equal   s    r:   c             C   s4   | dkrt d| | dkr0tjdkr,dS dS | S )a  Convert a numpy byte order indicator to one of ``<`` or ``>``.
    
    Args:
        byte_order: One of ``=``, ``<`` or ``>``.
    
    Returns:
        ``<`` if the byte order indicator represents little-endian data, or
        ``>`` if it represents big-endian. Therefore on a little-endian
        machine, ``=`` will be converted to ``<``, but on a big-endian machine
        it will be converted to ``>``.
    
    Raises:
        :exc:`ValueError`: If ``byte_order`` is not one of ``=``, ``<`` or
            ``>``.
    )r/   r1   r3   z'Unrecognised byte order indicator '{0}'r3   littler/   r1   )r"   r#   sysr   )r6   r   r   r   r4      s    r4   c             C   s   d|   kodkS   S )a   Identify if the given space group number represents a volume stack.
    
    Args:
        ispg: The space group number, as an integer, numpy scalar or single-
            element numpy array.
    
    Returns:
        :data:`True` if the space group number is in the range 401--630.
    i  iv  r   )r   r   r   r   r     s    
r   r'   c             C   sV   yt | ot | S  tk
rP   ytdd | D S  tk
rJ   dS X Y nX dS )zECheck if a string is entirely composed of printable ASCII characters.c             s   s   | ]}|t kV  qd S )N)printable_chars)r(   charr   r   r   r)   #  s    z%is_printable_ascii.<locals>.<genexpr>FN)r   isprintableisasciiAttributeErrorallUnicodeDecodeError)string_r   r   r   is_printable_ascii  s    rE   c             C   s4   t j| ddd}t|s0dtdd |D }|S )zVConvert bytes into a printable ASCII string by removing non-printable characters.
    asciiignore)encodingerrors c             s   s   | ]}t |r|V  qd S )N)rE   )r(   sr   r   r   r)   -  s    z.printable_string_from_bytes.<locals>.<genexpr>)bytesdecoderE   r*   list)Zbytes_rD   r   r   r   printable_string_from_bytes(  s    rO   c             C   s   t jt | dddS )a7  Convert a string to bytes.

    Even though this is a one-liner, the details are tricky to get right so things work
    properly in both Python 2 and 3. It's broken out as a separate function so it can be
    thoroughly tested.

    Raises:
        UnicodeError: If the input contains non-ASCII characters.
    rF   strict)rH   rI   )r   encode)rD   r   r   r   bytes_from_string1  s    
rR   )r3   )(__doc__Z
__future__r   r   r   r   stringr<   Znumpyr&   Z	constantsr   r   r   dictr!   r$   Zint8Zint16Zfloat32Z	complex64Zuint16Zfloat16r%   r	   r,   r2   	bytearrayr5   r7   r:   r4   r   Zascii_lettersdigitsZpunctuationr=   rE   rO   rR   r   r   r   r   <module>    s8   !
&
	