.. _Tutorial:

Tutorial
===============

Reading data
-------------

Let's start by reading some data:

.. code-block:: python

    from imagedata.series import Series
    a = Series('in_dir')

The basic assumption when reading data from files is that a folder (directory)
contains one image series. In the example above, the 'in_dir' folder will be
searched for image files which will constitute the 'a' variable. Imagedata will
not sort multiple series for you.

When reading a Series, the library will automatically detect the image format,
whether it is DICOM, Nifti, or one of the other formats supported.

All files found will be sorted according to your 'input_order' criteria.
The default input_order is 'none', which will assume that the data
can be sorted into a 3D volume. When there is one slice only, the data will
be stored as a 2D slice. To see how the data has been sorted, look at the shape
and axes properties of 'a':

.. code-block:: python

    a.shape
    >>> (40, 192, 152)
    a.axes
    >>> [UniformLengthAxis(slice,209.07554320292,40,3.0),
    >>>  UniformLengthAxis(row,-29.985630130569,192,2.0833332538605),
    >>>  UniformLengthAxis(column,-199.72427744686,152,2.0833332538605)]


When reading a 4D dataset, you need to specify the input_order criteria.
At present, the following input_orders can be specified:

* 'none': 2D/3D dataset, sorted on slice position
* 'time': 4D time-dependent dataset, sorted on time
* 'b': 4D MRI diffusion-weighted dataset, sorted on diffusion b value
* 'fa': 4D MRI flip-angle dataset, sorted on flip angle
* 'te': 4D MRI echo time dataset, sorted on echo time (TE)

An example reading a time-dependent dataset:

.. code-block:: python

    dynamic = Series('dynamic_dir', 'time')
    dynamic.shape
    >>> (10, 40, 192, 152)
    dynamic.axes
    >>> [VariableAxis(time,array([52531.6625, 52534.65  , 52537.6375, 52540.625 , 52543.6125,
    >>>     52546.6025, 52549.59  , 52552.5775, 52555.565 , 52558.5525])),
    >>>  UniformLengthAxis(slice,209.07554320292,40,3.0),
    >>>  UniformLengthAxis(row,-29.985630130569,192,2.0833332538605),
    >>>  UniformLengthAxis(column,-199.72427744686,152,2.0833332538605)]
    dynamic.timeline
    >>> array([ 0.    ,  2.9875,  5.975 ,  8.9625, 11.95  , 14.94  , 17.9275,
    >>>        20.915 , 23.9025, 26.89  ])

Notice how the time axis is given in seconds from midnight (DICOM style),
while the timeline property give the time as seconds from the first time step.

Displaying data
---------------

.. code-block:: python

    a.show()

The show() method will use matplotlib to display your dataset.
The following controls can be used to manipulate the viewer:

* Mouse scroll wheel: scroll through the slices of a volume
* Array up/down: scroll through the slices of a volume
* Array left/right: step through the 4th dimension of a 4D dataset
* PgUp/PgDown: Page through datasets when there are more datasets than views
* Left mouse key pressed: adjust window/level of display
* Mouse over: will display 2D coordinate and signal intensity at mouse position
* 'q': Quit. Will end the show() method.

.. code-block:: python

    dynamic.show(a)

The show() method can display multiple series. The example above will setup
a viewport of two series, where each series can be manipulated independently.
When you want to display additional datasets, specify them in a list:

.. code-block:: python

    dynamic.show([a, b, c])

Additionally, you can draw a region of interest (ROI):

.. code-block:: python

    roi = a.get_roi()

The returned 'roi' variable will be a new 3D Series instance, where
voxels are one inside the ROI, and zero elsewhere.

For dynamic data, it is possible to draw ROI for each time step:

.. code-block:: python

    roi = dynamic.get_roi(follow=True)

Draw a ROI for the first time step, then move to next time step using right array key.
For each time step, the ROI polygon can be adjusted using the mouse:

* Move a polygon vertex using left mouse key
* Move the polygon outline using shift key and left mouse key

The returned ROI will be a 4D ROI Series.

Saving data
-----------
.. code-block:: python

    a.write('my_dir')

The write() method will save the given series in a new folder.
With no additional information, the series will be saved in the
same format (DICOM, Nifti, ...) as the input data.
You can specify a different image format, e.g.:

.. code-block:: python

    a.write('my_itk_dir', 'formats=['itk'])

or even multiple formats, where '%p' is replaced be the format name:

.. code-block:: python

    a.write('my_dirs/%p', 'formats=['nifti', 'mat'])

This will save the data in Nifti format in 'my_dirs/nifti', and
in Matlab format in 'my_dirs/mat'.

Add DICOM template to other image formats
-------------------------------------------

.. code-block:: python

    b = Series('my_dirs/mat', template=a, geometry=a)

The above example will read a series from a Matlab formatted file, then
add DICOM headers and geometry from existing Series instance 'a'.

Alternatively, the template can be given as a URL:

.. code-block:: python

    b = Series('my_dirs/mat', template='in_dir', geometry='in_dir')

Add DICOM template to numpy array
---------------------------------

When processing image data using e.g. NumPy or SciPy, you may end up
with numpy arrays with no imagedata header. The DICOM header from an
existing dataset can be added to the numpy array:

.. code-block:: python

    # eye is numpy array
    eye = np.eye(128)
    c = Series(eye, template=a, geometry=a)
    c.seriesNumber = 50
    c.seriesDescription = 'eye
    c.axes
    >>> [UniformLengthAxis(row,-29.985630130569,192,2.0833332538605),
    >>>  UniformLengthAxis(column,-199.72427744686,152,2.0833332538605)]
    print(c)
    >>> Patient: 19.02.07-14:04:17-STD-1.3.12.2.1107.5.2.43.66035 PHANTOM^T1
    >>> Study  Time: 20190207 140516.555000
    >>> Series Time: 20190207 143634.267000
    >>> Series #50: eye
    >>> Shape: 128x128, dtype: float64, input order: none