Newer
Older
# Contributor(s): Eric Debreuve (since 2019), Morgane Nadal (2020)
#
# eric.debreuve@cnrs.fr
#
# This software is governed by the CeCILL license under French law and
# abiding by the rules of distribution of free software. You can use,
# modify and/ or redistribute the software under the terms of the CeCILL
# license as circulated by CEA, CNRS and INRIA at the following URL
# "http://www.cecill.info".
#
# As a counterpart to the access to the source code and rights to copy,
# modify and redistribute granted by the license, users are provided only
# with a limited warranty and the software's author, the holder of the
# economic rights, and the successive licensors have only limited
# liability.
#
# In this respect, the user's attention is drawn to the risks associated
# with loading, using, modifying and/or developing or reproducing the
# software by the user in light of its specific status of free software,
# that may mean that it is complicated to manipulate, and that also
# therefore means that it is reserved for developers and experienced
# professionals having in-depth computer knowledge. Users are therefore
# encouraged to load and test the software's suitability as regards their
# requirements in conditions enabling the security of their systems and/or
# data to be ensured and, more generally, to use and operate it in the
# same conditions as regards security.
#
# The fact that you are presently reading this means that you have had
# knowledge of the CeCILL license and that you accept its terms.
# Time profiling:
# python -m cProfile -o runtime/profiling.log -s name main.py
# Memory profiling:
# python -m memory_profiler main.py
# or
# mprof run main.py
# mprof plot
NADAL Morgane
committed
# TODO: create a script that run nutrimorph.py automatically for all images in a repository, with or without
# conditions stored in specific repositories (DIO 1H, DIO 3h, CHO 1H, etc.) AND that concatenate the .csv / pandas
# dataframes into one, used for clustering.
import brick.component.connection as cn_
import brick.component.extension as xt_
import brick.component.soma as sm_
import brick.general.feedback as fb_
# import brick.processing.image_verification as iv_
from sklgraph.skl_fgraph import skl_graph_t
from sklgraph.skl_map import skl_map_t
import brick.processing.graph_feat_extraction as ge_
import matplotlib.pyplot as pl_
import numpy as np_
import skimage.io as io_
# from skimage.segmentation import relabel_sequential
import skimage.morphology as mp_
import skimage.measure as ms_
if sy_.argv.__len__() < 2:
print("Missing parameter file argument")
sy_.exit(0)
if not (os_.path.isfile(sy_.argv[1]) and os_.access(sy_.argv[1], os_.R_OK)):
print("Wrong parameter file path or parameter file unreadable")
sy_.exit(0)
data_path = None
channel = None
size_voxel_in_micron = None
soma_low_c = None
soma_high_c = None
soma_selem_micron_c = None
soma_min_area_c = None
ext_low_c = None
ext_high_c = None
ext_selem_micron_c = None
ext_min_area_c = None
max_straight_sq_dist_c = None
max_weighted_length_c = None
scale_range = None
scale_step = None
alpha = None
beta = None
frangi_c = None
diff_mode = None
bright_on_dark = None
method = None
hist_step_length = None
number_of_bins_length = None
hist_bins_borders_length = None
hist_min_curvature = None
hist_step_curvature = None
number_of_bins_curvature = None
hist_bins_borders_curvature = None
with_plot = None
in_parallel = None
exec(open(sy_.argv[1]).read()) # Only with search_parameters.py (stable version)
soma_t = sm_.soma_t
extension_t = xt_.extension_t
print(f"STARTED: {tm_.strftime('%a, %b %d %Y @ %H:%M:%S')}")
start_time = tm_.time()
NADAL Morgane
committed
# Find the name of the file, eq. to the biological tested condition number x
name_file = os_.path.basename(data_path)
print("FILE: ", name_file)
name_file = name_file.replace(".tif", "")
# Find the dimension of the image voxels in micron
NADAL Morgane
committed
# TODO do something more general, here too specific to one microscope metadata
size_voxel_in_micron = in_.FindVoxelDimensionInMicron(data_path, size_voxel_in_micron=size_voxel_in_micron)
NADAL Morgane
committed
NADAL Morgane
committed
# Read the image
# Image size verification - simple version without user interface
image = in_.ImageVerification(image, channel)
# iv_.image_verification(image, channel) # -> PySide2 user interface # TODO: must return the modified image!
# /!\ conflicts between some versions of PySide2 and Python3
NADAL Morgane
committed
# image = image[:, 512:, 512:] -- For development
#
print(f"IMAGE: s.{img_shape} t.{image.dtype} m.{image.min()} M.{image.max()}")
# Intensity relative normalization (between 0 and 1).
NADAL Morgane
committed
# Image for soma normalized for hysteresis
image_for_soma = in_.IntensityNormalizedImage(image)
NADAL Morgane
committed
# Image for extensions not normalized for frangi enhancement
print(f"NRM-IMG: t.{image_for_soma.dtype} m.{image_for_soma.min():.2f} M.{image_for_soma.max():.2f}")
axes = {}
NADAL Morgane
committed
# Change the soma parameters from micron to pixel dimensions, using the previously determined voxel size
soma_min_area_c = in_.ToPixel(soma_min_area_c, size_voxel_in_micron, dimension=(0, 1))
soma_max_area_c = in_.ToPixel(soma_max_area_c, size_voxel_in_micron, dimension=(0, 1))
NADAL Morgane
committed
soma_selem_c = mp_.disk(in_.ToPixel(soma_selem_micron_c, size_voxel_in_micron))
NADAL Morgane
committed
# - Create the maps for enhancing and detecting the soma
# Hysteresis thresholding and closing/opening
som_nfo["map_small"] = soma_t.Map(image_for_soma, soma_low_c, soma_high_c, soma_selem_c)
# Deletion of too small elements
som_nfo["map_small"], som_lmp_small = soma_t.DeleteSmallSomas(som_nfo["map_small"], soma_min_area_c)
# Deletion of too big elements (probably aggregation of cells)
# Separated from the deletion of small elements to avoid extensions to be detected where too big somas were just deleted
som_nfo["map"], som_lmp = soma_t.DeleteBigSomas(som_nfo["map_small"], som_lmp_small, soma_max_area_c)
# Labelling of somas, find the number of somas
som_nfo["lmp"], n_somas = ms_.label(som_nfo["map"], return_num=True)
NADAL Morgane
committed
# Use relabel instead of label to optimize the algorithm. /!\ But BUG.
# som_nfo["lmp"] = relabel_sequential(som_lmp)[0]
# n_somas = som_nfo["lmp"].max()
NADAL Morgane
committed
# Create the distance and influence maps used next for connexions between somas-extensions and extensions-extensions
som_nfo["dist_to_closest"], som_nfo["influence_map"] = soma_t.InfluenceMaps(som_nfo["lmp"])
NADAL Morgane
committed
# po_.MaximumIntensityProjectionZ(som_nfo["influence_map"])
NADAL Morgane
committed
# Create the tuple somas, containing all the objects 'soma'
somas = tuple(soma_t().FromMap(som_nfo["lmp"], uid) for uid in range(1, n_somas + 1))
elapsed_time = tm_.gmtime(tm_.time() - start_time)
print(f"\nElapsed Time={tm_.strftime('%Hh %Mm %Ss', elapsed_time)}")
if with_plot:
fb_.PlotSomas(somas, som_nfo, axes)
NADAL Morgane
committed
# Set to zeros the pixels belonging to the somas.
# Take into account the aggregation of cells detected as one big soma to avoid extension creation there
del_soma = (som_nfo["map_small"] > 0).nonzero()
# Change the extensions parameters from micron to pixel dimensions
ext_min_area_c = in_.ToPixel(ext_min_area_c, size_voxel_in_micron, dimension=(0, 1))
ext_max_length_c = in_.ToPixel(ext_max_length_c, size_voxel_in_micron, dimension=(0,))
if ext_selem_micron_c == 0:
ext_selem_pixel_c = None
else:
ext_selem_pixel_c = mp_.disk(in_.ToPixel(ext_selem_micron_c, size_voxel_in_micron))
# scale_range_pixel = []
# #
# for value in scale_range:
# value_in_pixel = in_.ToPixel(value, size_voxel_in_micron, decimals=1)
# scale_range_pixel.append(value_in_pixel)
# scale_range = tuple(scale_range_pixel)
# scale_step = in_.ToPixel(scale_step, size_voxel_in_micron, decimals=1)
NADAL Morgane
committed
# - Perform frangi feature enhancement (via python or c - faster - implementation)
enhanced_ext, ext_scales = extension_t.EnhancedForDetection(
image_for_ext,
scale_range,
scale_step,
alpha,
beta,
frangi_c,
bright_on_dark,
method,
diff_mode=diff_mode,
in_parallel=in_parallel)
elapsed_time = tm_.gmtime(tm_.time() - start_time)
print(f"Elapsed Time={tm_.strftime('%Hh %Mm %Ss', elapsed_time)}\n")
# po_.MaximumIntensityProjectionZ(enhanced_ext, cmap="OrRd")
NADAL Morgane
committed
# - Creation of the enhanced maps
# enhanced_ext = ex_.adjust_log(enhanced_ext, 1) # Not necessary, log contrast adjustment
NADAL Morgane
committed
# Enhanced the contrast in frangi output
NADAL Morgane
committed
# necessary but not sufficient, histogram equalisation (adaptative)
enhanced_ext = ex_.equalize_adapthist(enhanced_ext)
NADAL Morgane
committed
# necessary but not sufficient, histogram equalisation (global)
enhanced_ext = ex_.equalize_hist(enhanced_ext)
# po_.MaximumIntensityProjectionZ(enhanced_ext, cmap="OrRd")
NADAL Morgane
committed
# Rescale the image by stretching the intensities between 0 and 255, necessary but not sufficient
enhanced_ext = ex_.rescale_intensity(enhanced_ext, out_range=(0, 255))
# Hysteresis thersholding
ext_nfo["coarse_map"] = extension_t.CoarseMap(enhanced_ext, ext_low_c, ext_high_c, ext_selem_pixel_c)
# po_.MaximumIntensityProjectionZ(ext_nfo["coarse_map"])
NADAL Morgane
committed
# Deletion of too small elements
ext_nfo["coarse_map"], ext_lmp = extension_t.FilteredCoarseMap(ext_nfo["coarse_map"], ext_min_area_c)
# po_.MaximumIntensityProjectionZ(ext_nfo["coarse_map"])
NADAL Morgane
committed
# Creation of the extensions skeleton
ext_nfo["map"] = extension_t.FineMapFromCoarseMap(ext_nfo["coarse_map"])
# TODO delete too long extensions
# ext_nfo["map"] = extension_t.FilteredSkeletonMap(ext_nfo["coarse_map"], ext_max_length_c)
NADAL Morgane
committed
# Deletion of extensions found within the somas
NADAL Morgane
committed
# Labelling of extensions and number of extensions determined
ext_nfo["lmp"], n_extensions = ms_.label(ext_nfo["map"], return_num=True)
NADAL Morgane
committed
# Use relabel instead of label to optimize the algorithm. BUT PROBLEM WITH THE NUMBER OF EXTENSIONS DETECTED !
# ext_nfo["lmp"] = relabel_sequential(ext_lmp)[0]
# n_extensions = ext_nfo["lmp"].max()
NADAL Morgane
committed
# Create the tuple extensions, containing all the objects 'extension'
extensions = tuple(
extension_t().FromMap(ext_nfo["lmp"], ext_scales, uid)
for uid in range(1, n_extensions + 1))
NADAL Morgane
committed
elapsed_time = tm_.gmtime(tm_.time() - start_time)
print(f"\nElapsed Time={tm_.strftime('%Hh %Mm %Ss', elapsed_time)}")
if with_plot:
fb_.PlotExtensions(extensions, ext_nfo, img_shape)
NADAL Morgane
committed
# Calculate the costs of each pixel on the image
NADAL Morgane
committed
dijkstra_costs = in_.DijkstraCosts(image, som_nfo["map"], ext_nfo["map"])
# -- Soma-Extention
print("\n--- Soma <-> Extension")
# Change the extensions parameters from micron to pixel dimensions
max_straight_sq_dist_c = in_.ToPixel(max_straight_sq_dist_c, size_voxel_in_micron)
max_weighted_length_c = in_.ToPixel(max_weighted_length_c, size_voxel_in_micron)
NADAL Morgane
committed
# Find the candidate extensions, with their endpoints, for connexion to the somas
candidate_conn_nfo = cn_.CandidateConnections(
somas,
som_nfo["influence_map"],
som_nfo["dist_to_closest"],
extensions,
max_straight_sq_dist_c,
)
# For each candidate, verify that it is valid based on the shortest path and the maximum allowed straight distance
for ep_idx, soma, extension, end_point in candidate_conn_nfo:
NADAL Morgane
committed
# Only try to connect if the extension is not already connected
print(f" Soma.{soma.uid} <-?-> Ext.{extension.uid}({ep_idx})", end="")
NADAL Morgane
committed
# Use of Dijkstra shortest weighted path
max_straight_sq_dist=max_straight_sq_dist_c,
NADAL Morgane
committed
# Keep the connexion only if inferior to the allowed max weighted distance
if length <= max_weighted_length_c:
cn_.ValidateConnection(soma, extension, end_point, path, dijkstra_costs)
# soma.Extend(extensions, som_nfo["dist_to_closest"], dijkstra_costs)
NADAL Morgane
committed
elapsed_time = tm_.gmtime(tm_.time() - start_time)
print(f"\nElapsed Time={tm_.strftime('%Hh %Mm %Ss', elapsed_time)}")
if with_plot:
fb_.PlotSomasWithExtensions(somas, som_nfo, "all")
NADAL Morgane
committed
# Update the soma maps with next Ext-Ext connexions
NADAL Morgane
committed
# Update the final somas + ext map by adding the new extensions and their connexion paths
som_nfo["soma_w_ext_lmp"] = soma_t.SomasLMap(somas)
NADAL Morgane
committed
# Update the influence map
som_nfo["dist_to_closest"], som_nfo["influence_map"] = soma_t.InfluenceMaps(som_nfo["soma_w_ext_lmp"])
# Find the candidate extensions for connexion to the primary extensions
candidate_conn_nfo = cn_.CandidateConnections(
somas,
som_nfo["influence_map"],
som_nfo["dist_to_closest"],
extensions,
max_straight_sq_dist_c,
)
NADAL Morgane
committed
# For each candidate, verify that it is valid based on the shortest path and the maximum allowed straight distance
for ep_idx, soma, extension, end_point in candidate_conn_nfo:
NADAL Morgane
committed
# Only try to connect if the extension is not already connected
print(f" Soma.{soma.uid} <-?-> Ext.{extension.uid}({ep_idx})", end="")
NADAL Morgane
committed
# Use of Dijkstra shortest weighted path
max_straight_sq_dist=max_straight_sq_dist_c,
NADAL Morgane
committed
# Keep the connexion only if inferior to the allowed max weighted distance
if length <= max_weighted_length_c:
NADAL Morgane
committed
# Verify that the last element of the connexion path is contained in the extension to be connected
# If so, returns the extensions containing the path last site.
tgt_extenstion = extension_t.ExtensionContainingSite(extensions, path[-1])
NADAL Morgane
committed
# Validate connexion: update the glial component objects and store more info in their fields
cn_.ValidateConnection(tgt_extenstion, extension, end_point, path, dijkstra_costs)
NADAL Morgane
committed
# TODO delete too long extensions
NADAL Morgane
committed
# Create an extension map containing all the ext + connexions, without the somas.
NADAL Morgane
committed
# Used for the graphs extraction
ext_nfo["lmp_soma"] = som_nfo['soma_w_ext_lmp'] - som_nfo['lmp']
NADAL Morgane
committed
NADAL Morgane
committed
elapsed_time = tm_.gmtime(tm_.time() - start_time)
print(f"\nElapsed Time={tm_.strftime('%Hh %Mm %Ss', elapsed_time)}")
if with_plot:
fb_.PlotSomasWithExtensions(somas, som_nfo, "with_ext_of_ext")
# -- Summary
print("\n")
for soma in somas:
print(soma)
# snapshot = tr_.take_snapshot()
# top_file_stats = snapshot.statistics('lineno')
# print("Memory Profiling: Top 10 FILES")
# for stat in top_file_stats[:10]:
# print(stat)
# top_block_stats = snapshot.statistics('traceback')
# top_block_stats = top_block_stats[0]
# print(f"Memory Profiling: {top_block_stats.count} memory blocks: {top_block_stats.size / 1024:.1f} KiB")
# for line in top_block_stats.traceback.format():
# print(line)
elapsed_time = tm_.gmtime(tm_.time() - start_time)
print(f"\nElapsed Time={tm_.strftime('%Hh %Mm %Ss', elapsed_time)}")
if with_plot:
pl_.show()
po_.MaximumIntensityProjectionZ(som_nfo['soma_w_ext_lmp'])
#, output_image_file_name=f"D:\\MorganeNadal\\Results\\Images\\{name_file}.png")
NADAL Morgane
committed
NADAL Morgane
committed
# --- Extract all the extensions of all somas as a graph
NADAL Morgane
committed
print('\n--- Graph extraction')
NADAL Morgane
committed
# Create the graphs with SKLGraph module (available on Eric Debreuve Gitlab)
NADAL Morgane
committed
for soma in somas:
NADAL Morgane
committed
# Create SKLGraph skeletonized map
ext_map = skl_map_t.FromShapeMap(ext_nfo['lmp_soma'] == soma.uid, store_widths=True, skeletonize=False, do_post_thinning=True)
NADAL Morgane
committed
# do_post_thinning = True, in order to remove pixels that are not breaking connectivity
# Create the graph from the SKLGaph skeletonized map
soma.skl_graph = skl_graph_t.FromSkeleton(ext_map, size_voxel=size_voxel_in_micron)
NADAL Morgane
committed
# --- Find the root of the {ext+conn} graphs.
# Roots are the nodes of degree 1 that are to be linked to the soma
soma.graph_roots = ge_.FindGraphsRootWithNodes(soma)
# Add a node "soma" and link it to the root nodes
soma_node = f"S-{int(soma.centroid[0])}-{int(soma.centroid[1])}-{int(soma.centroid[2])}"
soma.skl_graph.add_node(soma_node, soma=True, soma_nfo=soma)
NADAL Morgane
committed
soma.skl_graph.add_edge(node, soma_node, root=True)
# if with_plot:
# pl_.show(block=True)
#
print(": Done")
elapsed_time = tm_.gmtime(tm_.time() - start_time)
NADAL Morgane
committed
# --- Extract features
print('\n--- Features Extraction\n')
NADAL Morgane
committed
# Parameters
if hist_bins_borders_length is None:
number_of_bins_length = int(number_of_bins_length)
bins_length = np_.linspace(hist_min_length, hist_min_length + hist_step_length * number_of_bins_length, num=number_of_bins_length)
bins_length[-1] = np_.inf
else:
bins_length = np_.array(hist_bins_borders_length)
bins_length[-1] = np_.inf
if hist_bins_borders_curvature is None:
number_of_bins_curvature = int(number_of_bins_curvature)
bins_curvature = np_.linspace(hist_min_curvature, hist_min_curvature + hist_step_curvature * number_of_bins_curvature, num=number_of_bins_curvature)
bins_curvature[-1] = np_.inf
else:
bins_curvature = np_.array(hist_bins_borders_curvature)
bins_curvature[-1] = np_.inf
NADAL Morgane
committed
# Pandas dataframe creation with all the measured features
features_df = ge_.ExtractFeaturesInDF(name_file, somas, size_voxel_in_micron, bins_length, bins_curvature, ext_scales)
# Save the pandas df into .csv
features_df.to_csv(f"D:\\MorganeNadal\\Results\\Features\\{name_file}.csv")
# features_df.to_csv("...\\features_{name_file}.csv")
NADAL Morgane
committed
elapsed_time = tm_.gmtime(tm_.time() - start_time)
print(f"\nElapsed Time={tm_.strftime('%Hh %Mm %Ss', elapsed_time)}")
print(f"DONE: {tm_.strftime('%a, %b %d %Y @ %H:%M:%S')}")