#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
TubeMap for axons
=======

This script is the main pipeline to generate annotated graphs from axon tracing data 
lightsheet data. It uses TubeMap [Kirst2020]_ and TrailMap [Friedmann2020]_.

See the :ref:`TubeMap tutorial </TubeMap.ipynb>` for a tutorial and usage.

.. image:: ../Static/cell_abstract_2020.jpg
   :target: https://doi.org/10.1016/j.cell.2016.05.007 
   :width: 300  

References
----------
.. [Kirst2020] `Mapping the Fine-Scale Organization and Plasticity of the Brain Vasculature. Kirst, C., Skriabine, S., Vieites-Prado, A., Topilko, T., Bertin, P., Gerschenfeld, G., Verny, F., Topilko, P., Michalski, N., Tessier-Lavigne, M. and Renier, N., Cell, 180(4):780-795 <https://doi.org/10.1016/j.cell.2020.01.028>`_
.. [Friedmann2020] Mapping mesoscale axonal projections in the mouse brain using a 3D convolutional network. Drew Friedmann, Albert Pun, Eliza L Adams, Jan H Lui, Justus M Kebschull, Sophie M Grutzner, Caitlin Castagnola, Marc Tessier-Lavigne, Liqun Luo Proceedings of the National Academy of Sciences 2020 https://dx.doi.org/10.1073/pnas.1918465117
"""
__author__    = 'Nicolas Renier'
__webpage__   = 'http://idisco.info'
__download__  = 'http://www.github.com/ChristophKirst/ClearMap2'

if __name__ == '__main__':

  ### Initialization 
  ###############################################################################
  
  #%% Initialize workspace
  
  from ClearMap.Environment import *  #analysis:ignore
  from TrailMap.inference import segment_brain
  from TrailMap.models import model

  #directories and files
  directory = '/YOURPATHHERE/BRAINNUMBER' #main sample folder
  
  projections_directory = '/YOURPATHHERE/BRAINNUMBER/IMAGESEQUENCE'  #Input image files with regular expression
  
  expression_raw      = 'IMAGESEQUENCE/Z<Z,4>.tif'
  raw = io.join(directory,expression_raw)
  expression_auto     = 'AUTOIMAGES/Z<Z,4>.ome.tif' #imqge sequence for autofluorescence with regular expression
  auto = io.join(directory, expression_auto)

  resources_directory = settings.resources_path
  
  ws = wsp.Workspace('TubeMap', directory=directory);
  ws.update(raw=expression_raw, autofluorescence=expression_auto)
  ws.info()
  
  
  #%% Initialize alignment 
  
  #init atals and reference files
  annotation_file, reference_file, distance_file=ano.prepare_annotation_files(
      slicing=(slice(None),slice(None),slice(None)), orientation=(-3,2,1),
      overwrite=False, verbose=True);
  
  #alignment parameter files    
  align_channels_affine_file   = io.join(resources_directory, 'Alignment/align_affine.txt')
  align_reference_affine_file  = io.join(resources_directory, 'Alignment/align_affine.txt')
  align_reference_bspline_file = io.join(resources_directory, 'Alignment/align_bspline.txt')
  
  
  
  #%%############################################################################
  ### Resampling and atlas alignment 
  ###############################################################################
        
  #%% Resample 
             
  resample_parameter = {
      "source_resolution" : (5.9,5.9,3),
      "sink_resolution"   : (25,25,25),
      "processes" : None,
      "verbose" : True,             
      };
  
  io.delete_file(ws.filename('resampled'));
  
  res.resample(raw, sink=ws.filename('resampled'), **resample_parameter)
  
  #%% Resample autofluorescence
      
  resample_parameter_auto = {
      "source_resolution" : (5.9,5.9,6),
      "sink_resolution"   : (25,25,25),
      "processes" : None,
      "verbose" : True,                
      };    
  
  io.delete_file(ws.filename('resampled', postfix='autofluorescence'));
  
  res.resample(auto, sink=ws.filename('resampled', postfix='autofluorescence'), **resample_parameter_auto)
  
  #p3d.plot([ws.filename('resampled'), ws.filename('resampled', postfix='autofluorescence')])
  
  #%% Aignment - resampled to autofluorescence
  
  # align the two channels
  align_channels_parameter = {            
      #moving and reference images
      "moving_image" : ws.filename('resampled', postfix='autofluorescence'),
      "fixed_image"  : ws.filename('resampled'),
      
      #elastix parameter files for alignment
      "affine_parameter_file"  : align_channels_affine_file,
      "bspline_parameter_file" : None,
      
      #directory of the alig'/home/nicolas.renier/Documents/ClearMap_Ressources/Par0000affine.txt',nment result
      "result_directory" :  ws.filename('resampled_to_auto')
      }; 
  
  elx.align(**align_channels_parameter);
  
  #%% Alignment - autoflourescence to reference
  
  # align autofluorescence to reference
  align_reference_parameter = {            
      #moving and reference images
      "moving_image" : reference_file,
      "fixed_image"  : ws.filename('resampled', postfix='autofluorescence'),
      
      #elastix parameter files for alignment
      "affine_parameter_file"  :  align_reference_affine_file,
      "bspline_parameter_file" :  align_reference_bspline_file,
      #directory of the alignment result
      "result_directory" :  ws.filename('auto_to_reference')
      };
  
  elx.align(**align_reference_parameter);
  
#%% RUN TRAILMAP MODELS


  trailmap_model = model.get_net()
  trailmap_model.load_weights('/modelpath/model.hdf5')
  segment_brain(projections_directory, directory, trailmap_model)


  #%%############################################################################
  ### Graph construction and measurements
  ###############################################################################

  modelbinary = io.read('/PATH TO MODEL RESULT/model.tif') #the output of the model is converted to a single 8bit thresholded tif file with fiji
  boolean = modelbinary > 0 #conversion of the thresholded tif file to a binary
  #%% Skeletonize

  skeleton = ws.filename('skeleton')
  
  skl.skeletonize(boolean, sink=skeleton, delete_border=True, verbose=True);
  
  #%% Graph from skeleton
  
  graph_raw = gp.graph_from_skeleton(ws.filename('skeleton'), verbose=True)
  #graph_raw.save(ws.filename('graph', postfix='raw'))

  #%% Measure radii
  
  coordinates = graph_raw.vertex_coordinates();   
  radii, indices = mr.measure_radius(boolean, coordinates,
                                     value=0, fraction=None, max_radius=150, 
  #                                   value=None, fraction=0.8, max_radius=150,
                                     return_indices=True, default=-1, verbose=True);  
  graph_raw.set_vertex_radii(radii)

    #%% Save raw graph
  
  graph_raw.save(ws.filename('graph', postfix='raw'))
  #graph_raw = grp.load(ws.filename('graph', postfix='raw'))
  
  
  #%%############################################################################
  ### Graph cleaning and reduction
  ###############################################################################
  
  #%% Graph cleaning 
  graph_cleaned = gp.clean_graph(graph_raw, 
                                 vertex_mappings = {'coordinates'   : gp.mean_vertex_coordinates, 
                                                    'radii'         : np.max,
                                                    'artery_binary' : np.max,
                                                    'artery_raw'    : np.max},                    
                                 verbose=True)  
  
  #%% Save cleaned graph
  
  graph_cleaned.save(ws.filename('graph', postfix='cleaned'))
  #graph_cleaned = grp.load(ws.filename('graph', postfix='cleaned'));
  
  
  #%% Graph reduction
  
  def vote(expression):
    return np.sum(expression) >= len(expression) / 1.5;
  
  graph_reduced = gp.reduce_graph(graph_cleaned, edge_length=True,
                            edge_to_edge_mappings = {'length' : np.sum},
                            vertex_to_edge_mappings={'artery_binary' : vote,
                                                     'artery_raw'    : np.max,
                                                     'radii'         : np.max},  
                            edge_geometry_vertex_properties=['coordinates', 'radii', 'artery_binary', 'artery_raw'],
                            edge_geometry_edge_properties=None,                        
                            return_maps=False, verbose=True)
  
  #%% Save reduced graph
  
  graph_reduced.save(ws.filename('graph', postfix='reduced'))
  #graph_reduced = grp.load(ws.filename('graph', postfix='reduced'));
  

  
  #%%############################################################################
  ### Atlas registration and annotation
  ###############################################################################
  
     #%% Graph atlas registration
  
  def transformation(coordinates):
    coordinates = res.resample_points(
                    coordinates, sink=None, orientation=None, 
                    source_shape=io.shape(ws.filename('skeleton')),
                    sink_shape=io.shape(ws.filename('resampled')));
    
    coordinates = elx.transform_points(
                    coordinates, sink=None, 
                    transform_directory=ws.filename('resampled_to_auto'), 
                    binary=True, indices=False);
    
    coordinates = elx.transform_points(
                    coordinates, sink=None, 
                    transform_directory=ws.filename('auto_to_reference'),
                    binary=True, indices=False);
        
    return coordinates;
  
  graph_reduced.transform_properties(transformation=transformation, 
                             vertex_properties = {'coordinates' : 'coordinates_atlas'},
                             edge_geometry_properties = {'coordinates' : 'coordinates_atlas'},
                             verbose=True);
  
  
  def scaling(radii):
    resample_factor = res.resample_factor(
                        source_shape=io.shape(ws.filename('skeleton')),
                        sink_shape=io.shape(ws.filename('resampled')))
    return radii * np.mean(resample_factor);
  
  
  graph_reduced.transform_properties(transformation=scaling,
                             vertex_properties = {'radii' : 'radii_atlas'},
                             edge_properties   = {'radii' : 'radii_atlas'},
                             edge_geometry_properties = {'radii' : 'radii_atlas'})
  
  #%% Graph atlas annotation
  
  ano.set_annotation_file(annotation_file)
  def annotation(coordinates):
    label = ano.label_points(coordinates, key='order');
    return label;
  
  graph_reduced.annotate_properties(annotation, 
                            vertex_properties = {'coordinates_atlas' : 'annotation'},
                            edge_geometry_properties = {'coordinates_atlas' : 'annotation'});
  

  
  #%% Save annotated graph
  graph = graph_reduced.copy()
  graph.save(ws.filename('graph', postfix='annotated'))


  graph = grp.load(ws.filename('graph', postfix='annotated'));


#%% *2 in x and y + plot: correction of the anisotropic coordinates for visualization only
  
  coordinates=graph.vertex_property('coordinates')
  coordinates[:, 0]=coordinates[:, 0]*1.97
  coordinates[:, 1]=coordinates[:, 1]*1.97
  
  graph.set_vertex_property('coordinates', coordinates)

#%% plot #3D visualization of the results

  label = graph.vertex_annotation();
  label_leveled = ano.convert_label(label, key='order', value='order', level=1)
  vertex_filter = label_leveled == 1;
  graph = graph.sub_graph(vertex_filter=vertex_filter);

  vertex_colors = ano.convert_label(graph.vertex_annotation(), key='order', value='rgba');
  p = p3d.plot_graph_line(graph, color=vertex_colors)
  
 # p = p3d.plot_graph_mesh(graph, default_radius=0.15, vertex_colors=vertex_colors, n_tube_points=3)

  #%% Voxelization #Voxel heatmap of axon densities

  voxelize_branch_parameter = {
      "method": 'sphere',
      "radius": (15, 15, 15),
      "weights": None,
      "shape": io.shape(reference_file),
      "verbose": True
  };

  vertices = graph.vertex_property('coordinates_atlas');

  branch_density = vox.voxelize(vertices, sink=ws.filename('density', postfix='branches'), dtype='float32',
                                **voxelize_branch_parameter);



