Advertisement
Guest User

Untitled

a guest
Mar 26th, 2019
76
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 49.70 KB | None | 0 0
  1. import os
  2. import operator
  3. import string
  4. import copy
  5. import itertools
  6. import numpy as np
  7. import networkx as nx
  8. import scipy.ndimage as nd
  9. import image_ops
  10. from from_fits import create_image_from_fits_file
  11. from skimage.morphology import medial_axis
  12. from scipy import nanmean
  13. import matplotlib.pyplot as plt
  14.  
  15.  
  16.  
  17. # Create 4 to 8-connected elements to use with binary hit-or-miss
  18. struct1 = np.array([[1, 0, 0],
  19. [0, 1, 1],
  20. [0, 0, 0]])
  21.  
  22. struct2 = np.array([[0, 0, 1],
  23. [1, 1, 0],
  24. [0, 0, 0]])
  25.  
  26. # Next check the three elements which will be double counted
  27. check1 = np.array([[1, 1, 0, 0],
  28. [0, 0, 1, 1]])
  29.  
  30. check2 = np.array([[0, 0, 1, 1],
  31. [1, 1, 0, 0]])
  32.  
  33. check3 = np.array([[1, 1, 0],
  34. [0, 0, 1],
  35. [0, 0, 1]])
  36.  
  37.  
  38. def eight_con():
  39. return np.ones((3, 3))
  40.  
  41.  
  42. def _fix_small_holes(mask_array, rel_size=0.1):
  43. '''
  44. Helper function to remove only small holes within a masked region.
  45.  
  46. Parameters
  47. ----------
  48. mask_array : numpy.ndarray
  49. Array containing the masked region.
  50.  
  51. rel_size : float, optional
  52. If < 1.0, sets the minimum size a hole must be relative to the area
  53. of the mask. Otherwise, this is the maximum number of pixels the hole
  54. must have to be deleted.
  55.  
  56. Returns
  57. -------
  58. mask_array : numpy.ndarray
  59. Altered array.
  60. '''
  61.  
  62. if rel_size <= 0.0:
  63. raise ValueError("rel_size must be positive.")
  64. elif rel_size > 1.0:
  65. pixel_flag = True
  66. else:
  67. pixel_flag = False
  68.  
  69. # Find the region area
  70. reg_area = len(np.where(mask_array == 1)[0])
  71.  
  72. # Label the holes
  73. holes = np.logical_not(mask_array).astype(float)
  74. lab_holes, n_holes = nd.label(holes, eight_con())
  75.  
  76. # If no holes, return
  77. if n_holes == 1:
  78. return mask_array
  79.  
  80. # Ignore area outside of the region.
  81. out_label = lab_holes[0, 0]
  82. # Set size to be just larger than the region. Thus it can never be
  83. # deleted.
  84. holes[np.where(lab_holes == out_label)] = reg_area + 1.
  85.  
  86. # Sum up the regions and find holes smaller than the threshold.
  87. sums = nd.sum(holes, lab_holes, range(1, n_holes + 1))
  88. if pixel_flag: # Use number of pixels
  89. delete_holes = np.where(sums < rel_size)[0]
  90. else: # Use relative size of holes.
  91. delete_holes = np.where(sums / reg_area < rel_size)[0]
  92.  
  93. # Return if there is nothing to delete.
  94. if delete_holes == []:
  95. return mask_array
  96.  
  97. # Add one to take into account 0 in list if object label 1.
  98. delete_holes += 1
  99. for label in delete_holes:
  100. mask_array[np.where(lab_holes == label)] = 1
  101.  
  102. return mask_array
  103.  
  104.  
  105. def isolateregions(binary_array, size_threshold=0, pad_size=5,
  106. fill_hole=False, rel_size=0.1, morph_smooth=False):
  107. '''
  108.  
  109. Labels regions in a boolean array and returns individual arrays for each
  110. region. Regions below a threshold can optionlly be removed. Small holes
  111. may also be filled in.
  112.  
  113. Parameters
  114. ----------
  115. binary_array : numpy.ndarray
  116. A binary array of regions.
  117. size_threshold : int, optional
  118. Sets the pixel size on the size of regions.
  119. pad_size : int, optional
  120. Padding to be added to the individual arrays.
  121. fill_hole : int, optional
  122. Enables hole filling.
  123. rel_size : float or int, optional
  124. If < 1.0, sets the minimum size a hole must be relative to the area
  125. of the mask. Otherwise, this is the maximum number of pixels the hole
  126. must have to be deleted.
  127. morph_smooth : bool, optional
  128. Morphologically smooth the image using a binar opening and closing.
  129.  
  130. Returns
  131. -------
  132. output_arrays : list
  133. Regions separated into individual arrays.
  134. num : int
  135. Number of filaments
  136. corners : list
  137. Contains the indices where each skeleton array was taken from
  138. the original.
  139.  
  140. '''
  141.  
  142. output_arrays = []
  143. corners = []
  144.  
  145. # Label skeletons
  146. labels, num = nd.label(binary_array, eight_con())
  147.  
  148. # Remove skeletons which have fewer pixels than the threshold.
  149. if size_threshold != 0:
  150. sums = nd.sum(binary_array, labels, range(1, num + 1))
  151. remove_fils = np.where(sums <= size_threshold)[0]
  152. for lab in remove_fils:
  153. binary_array[np.where(labels == lab + 1)] = 0
  154.  
  155. # Relabel after deleting short skeletons.
  156. labels, num = nd.label(binary_array, eight_con())
  157.  
  158. # Split each skeleton into its own array.
  159. for n in range(1, num + 1):
  160. x, y = np.where(labels == n)
  161. # Make an array shaped to the skeletons size and padded on each edge
  162. # the +1 is because, e.g., range(0, 5) only has 5 elements, but the
  163. # indices we're using are range(0, 6)
  164. shapes = (x.max() - x.min() + 2 * pad_size,
  165. y.max() - y.min() + 2 * pad_size)
  166. eachfil = np.zeros(shapes)
  167. eachfil[x - x.min() + pad_size, y - y.min() + pad_size] = 1
  168. # Fill in small holes
  169. if fill_hole:
  170. eachfil = _fix_small_holes(eachfil, rel_size=rel_size)
  171. if morph_smooth:
  172. eachfil = nd.binary_opening(eachfil, np.ones((3, 3)))
  173. eachfil = nd.binary_closing(eachfil, np.ones((3, 3)))
  174. output_arrays.append(eachfil)
  175. # Keep the coordinates from the original image
  176. lower = (x.min() - pad_size, y.min() - pad_size)
  177. upper = (x.max() + pad_size + 1, y.max() + pad_size + 1)
  178. corners.append([lower, upper])
  179.  
  180. return output_arrays, num, corners
  181.  
  182.  
  183. def shifter(l, n):
  184. return l[n:] + l[:n]
  185.  
  186.  
  187. def distance(x, x1, y, y1):
  188. return np.sqrt((x - x1) ** 2.0 + (y - y1) ** 2.0)
  189.  
  190.  
  191. def find_filpix(branches, labelfil, final=True):
  192. '''
  193.  
  194. Identifies the types of pixels in the given skeletons. Identification is
  195. based on the connectivity of the pixel.
  196.  
  197. Parameters
  198. ----------
  199. branches : list
  200. Contains the number of branches in each skeleton.
  201. labelfil : list
  202. Contains the arrays of each skeleton.
  203. final : bool, optional
  204. If true, corner points, intersections, and body points are all
  205. labeled as a body point for use when the skeletons have already
  206. been cleaned.
  207.  
  208. Returns
  209. -------
  210. fila_pts : list
  211. All points on the body of each skeleton.
  212. inters : list
  213. All points associated with an intersection in each skeleton.
  214. labelfil : list
  215. Contains the arrays of each skeleton where all intersections
  216. have been removed.
  217. endpts_return : list
  218. The end points of each branch of each skeleton.
  219. '''
  220.  
  221. initslices = []
  222. initlist = []
  223. shiftlist = []
  224. sublist = []
  225. endpts = []
  226. blockpts = []
  227. bodypts = []
  228. slices = []
  229. vallist = []
  230. shiftvallist = []
  231. cornerpts = []
  232. subvallist = []
  233. subslist = []
  234. pix = []
  235. filpix = []
  236. intertemps = []
  237. fila_pts = []
  238. inters = []
  239. repeat = []
  240. temp_group = []
  241. all_pts = []
  242. pairs = []
  243. endpts_return = []
  244.  
  245. for k in range(1, branches + 1):
  246. x, y = np.where(labelfil == k)
  247. # pixel_slices = np.empty((len(x)+1,8))
  248. for i in range(len(x)):
  249. if x[i] < labelfil.shape[0] - 1 and y[i] < labelfil.shape[1] - 1:
  250. pix.append((x[i], y[i]))
  251. initslices.append(np.array([[labelfil[x[i] - 1, y[i] + 1],
  252. labelfil[x[i], y[i] + 1],
  253. labelfil[x[i] + 1, y[i] + 1]],
  254. [labelfil[x[i] - 1, y[i]], 0,
  255. labelfil[x[i] + 1, y[i]]],
  256. [labelfil[x[i] - 1, y[i] - 1],
  257. labelfil[x[i], y[i] - 1],
  258. labelfil[x[i] + 1, y[i] - 1]]]))
  259.  
  260. filpix.append(pix)
  261. slices.append(initslices)
  262. initslices = []
  263. pix = []
  264.  
  265. for i in range(len(slices)):
  266. for k in range(len(slices[i])):
  267. initlist.append([slices[i][k][0, 0],
  268. slices[i][k][0, 1],
  269. slices[i][k][0, 2],
  270. slices[i][k][1, 2],
  271. slices[i][k][2, 2],
  272. slices[i][k][2, 1],
  273. slices[i][k][2, 0],
  274. slices[i][k][1, 0]])
  275. vallist.append(initlist)
  276. initlist = []
  277.  
  278. for i in range(len(slices)):
  279. for k in range(len(slices[i])):
  280. shiftlist.append(shifter(vallist[i][k], 1))
  281. shiftvallist.append(shiftlist)
  282. shiftlist = []
  283.  
  284. for k in range(len(slices)):
  285. for i in range(len(vallist[k])):
  286. for j in range(8):
  287. sublist.append(
  288. int(vallist[k][i][j]) - int(shiftvallist[k][i][j]))
  289. subslist.append(sublist)
  290. sublist = []
  291. subvallist.append(subslist)
  292. subslist = []
  293.  
  294. # x represents the subtracted list (step-ups) and y is the values of the
  295. # surrounding pixels. The categories of pixels are ENDPTS (x<=1),
  296. # BODYPTS (x=2,y=2),CORNERPTS (x=2,y=3),BLOCKPTS (x=3,y>=4), and
  297. # INTERPTS (x>=3).
  298. # A cornerpt is [*,0,0] (*s) associated with an intersection,
  299. # but their exclusion from
  300. # [1,*,0] the intersection keeps eight-connectivity, they are included
  301. # [0,1,0] intersections for this reason.
  302. # A blockpt is [1,0,1] They are typically found in a group of four,
  303. # where all four
  304. # [0,*,*] constitute a single intersection.
  305. # [1,*,*]
  306. # The "final" designation is used when finding the final branch lengths.
  307. # At this point, blockpts and cornerpts should be eliminated.
  308. for k in range(branches):
  309. for l in range(len(filpix[k])):
  310. x = [j for j, y in enumerate(subvallist[k][l]) if y == k + 1]
  311. y = [j for j, z in enumerate(vallist[k][l]) if z == k + 1]
  312.  
  313. if len(x) <= 1:
  314. endpts.append(filpix[k][l])
  315. endpts_return.append(filpix[k][l])
  316. elif len(x) == 2:
  317. if final:
  318. bodypts.append(filpix[k][l])
  319. else:
  320. if len(y) == 2:
  321. bodypts.append(filpix[k][l])
  322. elif len(y) == 3:
  323. cornerpts.append(filpix[k][l])
  324. elif len(y) >= 4:
  325. blockpts.append(filpix[k][l])
  326. elif len(x) >= 3:
  327. intertemps.append(filpix[k][l])
  328. endpts = list(set(endpts))
  329. bodypts = list(set(bodypts))
  330. dups = set(endpts) & set(bodypts)
  331. if len(dups) > 0:
  332. for i in dups:
  333. bodypts.remove(i)
  334. # Cornerpts without a partner diagonally attached can be included as a
  335. # bodypt.
  336. if len(cornerpts) > 0:
  337. deleted_cornerpts = []
  338. for i, j in zip(cornerpts, cornerpts):
  339. if i != j:
  340. if distance(i[0], j[0], i[1], j[1]) == np.sqrt(2.0):
  341. proximity = [(i[0], i[1] - 1),
  342. (i[0], i[1] + 1),
  343. (i[0] - 1, i[1]),
  344. (i[0] + 1, i[1]),
  345. (i[0] - 1, i[1] + 1),
  346. (i[0] + 1, i[1] + 1),
  347. (i[0] - 1, i[1] - 1),
  348. (i[0] + 1, i[1] - 1)]
  349. match = set(intertemps) & set(proximity)
  350. if len(match) == 1:
  351. pairs.append([i, j])
  352. deleted_cornerpts.append(i)
  353. deleted_cornerpts.append(j)
  354. cornerpts = list(set(cornerpts).difference(set(deleted_cornerpts)))
  355.  
  356. if len(cornerpts) > 0:
  357. for l in cornerpts:
  358. proximity = [(l[0], l[1] - 1),
  359. (l[0], l[1] + 1),
  360. (l[0] - 1, l[1]),
  361. (l[0] + 1, l[1]),
  362. (l[0] - 1, l[1] + 1),
  363. (l[0] + 1, l[1] + 1),
  364. (l[0] - 1, l[1] - 1),
  365. (l[0] + 1, l[1] - 1)]
  366. match = set(intertemps) & set(proximity)
  367. if len(match) == 1:
  368. intertemps.append(l)
  369. fila_pts.append(endpts + bodypts)
  370. else:
  371. fila_pts.append(endpts + bodypts + [l])
  372. # cornerpts.remove(l)
  373. else:
  374. fila_pts.append(endpts + bodypts)
  375.  
  376. # Reset lists
  377. cornerpts = []
  378. endpts = []
  379. bodypts = []
  380.  
  381. if len(pairs) > 0:
  382. for i in range(len(pairs)):
  383. for j in pairs[i]:
  384. all_pts.append(j)
  385. if len(blockpts) > 0:
  386. for i in blockpts:
  387. all_pts.append(i)
  388. if len(intertemps) > 0:
  389. for i in intertemps:
  390. all_pts.append(i)
  391. # Pairs of cornerpts, blockpts, and interpts are combined into an
  392. # array. If there is eight connectivity between them, they are labelled
  393. # as a single intersection.
  394. arr = np.zeros((labelfil.shape))
  395. for z in all_pts:
  396. labelfil[z[0], z[1]] = 0
  397. arr[z[0], z[1]] = 1
  398. lab, nums = nd.label(arr, eight_con())
  399. for k in range(1, nums + 1):
  400. objs_pix = np.where(lab == k)
  401. for l in range(len(objs_pix[0])):
  402. temp_group.append((objs_pix[0][l], objs_pix[1][l]))
  403. inters.append(temp_group)
  404. temp_group = []
  405. for i in range(len(inters) - 1):
  406. if inters[i] == inters[i + 1]:
  407. repeat.append(inters[i])
  408. for i in repeat:
  409. inters.remove(i)
  410.  
  411. return fila_pts, inters, labelfil, endpts_return
  412.  
  413.  
  414. def pix_identify(isolatefilarr, num):
  415. '''
  416. This function is essentially a wrapper on find_filpix. It returns the
  417. outputs of find_filpix in the form that are used during the analysis.
  418.  
  419. Parameters
  420. ----------
  421. isolatefilarr : list
  422. Contains individual arrays of each skeleton.
  423. num : int
  424. The number of skeletons.
  425.  
  426. Returns
  427. -------
  428. interpts : list
  429. Contains lists of all intersections points in each skeleton.
  430. hubs : list
  431. Contains the number of intersections in each filament. This is
  432. useful for identifying those with no intersections as their analysis
  433. is straight-forward.
  434. ends : list
  435. Contains the positions of all end points in each skeleton.
  436. filbranches : list
  437. Contains the number of branches in each skeleton.
  438. labelisofil : list
  439. Contains individual arrays for each skeleton where the
  440. branches are labeled and the intersections have been removed.
  441. '''
  442.  
  443. interpts = []
  444. hubs = []
  445. ends = []
  446. filbranches = []
  447. labelisofil = []
  448.  
  449. for n in range(num):
  450. funcreturn = find_filpix(1, isolatefilarr[n], final=False)
  451. interpts.append(funcreturn[1])
  452. hubs.append(len(funcreturn[1]))
  453. isolatefilarr.pop(n)
  454. isolatefilarr.insert(n, funcreturn[2])
  455. ends.append(funcreturn[3])
  456.  
  457. label_branch, num_branch = nd.label(isolatefilarr[n], eight_con())
  458. filbranches.append(num_branch)
  459. labelisofil.append(label_branch)
  460.  
  461. return interpts, hubs, ends, filbranches, labelisofil
  462.  
  463.  
  464. def skeleton_length(skeleton):
  465. '''
  466. Length finding via morphological operators. We use the differences in
  467. connectivity between 4 and 8-connected to split regions. Connections
  468. between 4 and 8-connected regions are found using a series of hit-miss
  469. operators.
  470.  
  471. The inputted skeleton MUST have no intersections otherwise the returned
  472. length will not be correct!
  473.  
  474. Parameters
  475. ----------
  476. skeleton : numpy.ndarray
  477. Array containing the skeleton.
  478.  
  479. Returns
  480. -------
  481. length : float
  482. Length of the skeleton.
  483.  
  484. '''
  485.  
  486. # 4-connected labels
  487. four_labels = nd.label(skeleton)[0]
  488.  
  489. four_sizes = nd.sum(skeleton, four_labels, range(np.max(four_labels) + 1))
  490.  
  491. # Lengths is the number of pixels minus number of objects with more
  492. # than 1 pixel.
  493. four_length = np.sum(
  494. four_sizes[four_sizes > 1]) - len(four_sizes[four_sizes > 1])
  495.  
  496. # Find pixels which a 4-connected and subtract them off the skeleton
  497.  
  498. four_objects = np.where(four_sizes > 1)[0]
  499.  
  500. skel_copy = copy.copy(skeleton)
  501. for val in four_objects:
  502. skel_copy[np.where(four_labels == val)] = 0
  503.  
  504. # Remaining pixels are only 8-connected
  505. # Lengths is same as before, multiplied by sqrt(2)
  506.  
  507. eight_labels = nd.label(skel_copy, eight_con())[0]
  508.  
  509. eight_sizes = nd.sum(
  510. skel_copy, eight_labels, range(np.max(eight_labels) + 1))
  511.  
  512. eight_length = (
  513. np.sum(eight_sizes) - np.max(eight_labels)) * np.sqrt(2)
  514.  
  515. # If there are no 4-connected pixels, we don't need the hit-miss portion.
  516. if four_length == 0.0:
  517. conn_length = 0.0
  518.  
  519. else:
  520.  
  521. store = np.zeros(skeleton.shape)
  522.  
  523. # Loop through the 4 rotations of the structuring elements
  524. for k in range(0, 4):
  525. hm1 = nd.binary_hit_or_miss(
  526. skeleton, structure1=np.rot90(struct1, k=k))
  527. store += hm1
  528.  
  529. hm2 = nd.binary_hit_or_miss(
  530. skeleton, structure1=np.rot90(struct2, k=k))
  531. store += hm2
  532.  
  533. hm_check3 = nd.binary_hit_or_miss(
  534. skeleton, structure1=np.rot90(check3, k=k))
  535. store -= hm_check3
  536.  
  537. if k <= 1:
  538. hm_check1 = nd.binary_hit_or_miss(
  539. skeleton, structure1=np.rot90(check1, k=k))
  540. store -= hm_check1
  541.  
  542. hm_check2 = nd.binary_hit_or_miss(
  543. skeleton, structure1=np.rot90(check2, k=k))
  544. store -= hm_check2
  545.  
  546. conn_length = np.sqrt(2) * \
  547. np.sum(np.sum(store, axis=1), axis=0) # hits
  548.  
  549. return conn_length + eight_length + four_length
  550.  
  551.  
  552. def init_lengths(labelisofil, filbranches, array_offsets, img):
  553. '''
  554.  
  555. This is a wrapper on fil_length for running on the branches of the
  556. skeletons.
  557.  
  558. Parameters
  559. ----------
  560.  
  561. labelisofil : list
  562. Contains individual arrays for each skeleton where the
  563. branches are labeled and the intersections have been removed.
  564.  
  565. filbranches : list
  566. Contains the number of branches in each skeleton.
  567.  
  568. array_offsets : List
  569. The indices of where each filament array fits in the
  570. original image.
  571.  
  572. img : numpy.ndarray
  573. Original image.
  574.  
  575. Returns
  576. -------
  577.  
  578. branch_properties: dict
  579. Contains the lengths and intensities of the branches.
  580. Keys are *length* and *intensity*.
  581.  
  582. '''
  583. num = len(labelisofil)
  584.  
  585. # Initialize Lists
  586. lengths = []
  587. av_branch_intensity = []
  588.  
  589. for n in range(num):
  590. leng = []
  591. av_intensity = []
  592.  
  593. label_copy = copy.copy(labelisofil[n])
  594. objects = nd.find_objects(label_copy)
  595. for obj in objects:
  596. # Scale the branch array to the branch size
  597. branch_array = label_copy[obj]
  598.  
  599. # Find the skeleton points and set those to 1
  600. branch_pts = np.where(branch_array > 0)
  601. branch_array[branch_pts] = 1
  602.  
  603. # Now find the length on the branch
  604. branch_length = skeleton_length(branch_array)
  605. if branch_length == 0.0:
  606. # For use in longest path algorithm, will be set to zero for
  607. # final analysis
  608. branch_length = 0.5
  609.  
  610. leng.append(branch_length)
  611.  
  612. # Now let's find the average intensity along each branch
  613. # Get the offsets from the original array and
  614. # add on the offset the branch array introduces.
  615. x_offset = obj[0].start + array_offsets[n][0][0]
  616. y_offset = obj[1].start + array_offsets[n][0][1]
  617. av_intensity.append(
  618. nanmean([img[x + x_offset, y + y_offset]
  619. for x, y in zip(*branch_pts)
  620. if np.isfinite(img[x + x_offset, y + y_offset]) and
  621. not img[x + x_offset, y + y_offset] < 0.0]))
  622.  
  623. lengths.append(leng)
  624. av_branch_intensity.append(av_intensity)
  625.  
  626. branch_properties = {
  627. "length": lengths, "intensity": av_branch_intensity}
  628.  
  629. return branch_properties
  630.  
  631.  
  632. def product_gen(n):
  633. for r in itertools.count(1):
  634. for i in itertools.product(n, repeat=r):
  635. yield "".join(i)
  636.  
  637.  
  638. def pre_graph(labelisofil, branch_properties, interpts, ends):
  639. '''
  640.  
  641. This function converts the skeletons into a graph object compatible with
  642. networkx. The graphs have nodes corresponding to end and
  643. intersection points and edges defining the connectivity as the branches
  644. with the weights set to the branch length.
  645.  
  646. Parameters
  647. ----------
  648.  
  649. labelisofil : list
  650. Contains individual arrays for each skeleton where the
  651. branches are labeled and the intersections have been removed.
  652.  
  653. branch_properties : dict
  654. Contains the lengths and intensities of all branches.
  655.  
  656. interpts : list
  657. Contains the pixels which belong to each intersection.
  658.  
  659. ends : list
  660. Contains the end pixels for each skeleton.
  661.  
  662. Returns
  663. -------
  664.  
  665. end_nodes : list
  666. Contains the nodes corresponding to end points.
  667.  
  668. inter_nodes : list
  669. Contains the nodes corresponding to intersection points.
  670.  
  671. edge_list : list
  672. Contains the connectivity information for the graphs.
  673.  
  674. nodes : list
  675. A complete list of all of the nodes. The other nodes lists have
  676. been separated as they are labeled differently.
  677.  
  678. '''
  679.  
  680. num = len(labelisofil)
  681.  
  682. end_nodes = []
  683. inter_nodes = []
  684. nodes = []
  685. edge_list = []
  686.  
  687. def path_weighting(idx, length, intensity, w=0.5):
  688. '''
  689.  
  690. Relative weighting for the shortest path algorithm using the branch
  691. lengths and the average intensity along the branch.
  692.  
  693. '''
  694. if w > 1.0 or w < 0.0:
  695. raise ValueError(
  696. "Relative weighting w must be between 0.0 and 1.0.")
  697. return (1 - w) * (length[idx] / np.sum(length)) + \
  698. w * (intensity[idx] / np.sum(intensity))
  699.  
  700. lengths = branch_properties["length"]
  701. branch_intensity = branch_properties["intensity"]
  702.  
  703. for n in range(num):
  704. inter_nodes_temp = []
  705. # Create end_nodes, which contains lengths, and nodes, which we will
  706. # later add in the intersections
  707. end_nodes.append([(labelisofil[n][i[0], i[1]],
  708. path_weighting(int(labelisofil[n][i[0], i[1]] - 1),
  709. lengths[n],
  710. branch_intensity[n]),
  711. lengths[n][int(labelisofil[n][i[0], i[1]] - 1)],
  712. branch_intensity[n][int(labelisofil[n][i[0], i[1]] - 1)])
  713. for i in ends[n]])
  714. nodes.append([labelisofil[n][i[0], i[1]] for i in ends[n]])
  715.  
  716. # Intersection nodes are given by the intersections points of the filament.
  717. # They are labeled alphabetically (if len(interpts[n])>26,
  718. # subsequent labels are AA,AB,...).
  719. # The branch labels attached to each intersection are included for future
  720. # use.
  721. for intersec in interpts[n]:
  722. uniqs = []
  723. for i in intersec: # Intersections can contain multiple pixels
  724. int_arr = np.array([[labelisofil[n][i[0] - 1, i[1] + 1],
  725. labelisofil[n][i[0], i[1] + 1],
  726. labelisofil[n][i[0] + 1, i[1] + 1]],
  727. [labelisofil[n][i[0] - 1, i[1]], 0,
  728. labelisofil[n][i[0] + 1, i[1]]],
  729. [labelisofil[n][i[0] - 1, i[1] - 1],
  730. labelisofil[n][i[0], i[1] - 1],
  731. labelisofil[n][i[0] + 1, i[1] - 1]]]).astype(int)
  732. for x in np.unique(int_arr[np.nonzero(int_arr)]):
  733. uniqs.append((x,
  734. path_weighting(x - 1, lengths[n],
  735. branch_intensity[n]),
  736. lengths[n][x - 1],
  737. branch_intensity[n][x - 1]))
  738. # Intersections with multiple pixels can give the same branches.
  739. # Get rid of duplicates
  740. uniqs = list(set(uniqs))
  741. inter_nodes_temp.append(uniqs)
  742.  
  743. # Add the intersection labels. Also append those to nodes
  744. inter_nodes.append(
  745. zip(product_gen(string.ascii_uppercase), inter_nodes_temp))
  746. for alpha, node in zip(product_gen(string.ascii_uppercase),
  747. inter_nodes_temp):
  748. nodes[n].append(alpha)
  749. # Edges are created from the information contained in the nodes.
  750. edge_list_temp = []
  751. for i, inters in enumerate(inter_nodes[n]):
  752. end_match = list(set(inters[1]) & set(end_nodes[n]))
  753. for k in end_match:
  754. edge_list_temp.append((inters[0], k[0], k))
  755.  
  756. for j, inters_2 in enumerate(inter_nodes[n]):
  757. if i != j:
  758. match = list(set(inters[1]) & set(inters_2[1]))
  759. new_edge = None
  760. if len(match) == 1:
  761. new_edge = (inters[0], inters_2[0], match[0])
  762. elif len(match) > 1:
  763. multi = [match[l][1] for l in range(len(match))]
  764. keep = multi.index(min(multi))
  765. new_edge = (inters[0], inters_2[0], match[keep])
  766. if new_edge is not None:
  767. if not (new_edge[1], new_edge[0], new_edge[2]) in edge_list_temp \
  768. and new_edge not in edge_list_temp:
  769. edge_list_temp.append(new_edge)
  770.  
  771. # Remove duplicated edges between intersections
  772.  
  773. edge_list.append(edge_list_temp)
  774.  
  775. return edge_list, nodes
  776.  
  777.  
  778. def try_mkdir(name):
  779. '''
  780. Checks if a folder exists, and makes it if it doesn't
  781. '''
  782.  
  783. if not os.path.isdir(os.path.join(os.getcwd(), name)):
  784. os.mkdir(os.path.join(os.getcwd(), name))
  785.  
  786.  
  787. def longest_path(edge_list, nodes, verbose=False,
  788. skeleton_arrays=None, save_png=False, save_name=None):
  789. '''
  790. Takes the output of pre_graph and runs the shortest path algorithm.
  791.  
  792. Parameters
  793. ----------
  794.  
  795. edge_list : list
  796. Contains the connectivity information for the graphs.
  797.  
  798. nodes : list
  799. A complete list of all of the nodes. The other nodes lists have
  800. been separated as they are labeled differently.
  801.  
  802. verbose : bool, optional
  803. If True, enables the plotting of the graph.
  804.  
  805. skeleton_arrays : list, optional
  806. List of the skeleton arrays. Required when verbose=True.
  807.  
  808. save_png : bool, optional
  809. Saves the plot made in verbose mode. Disabled by default.
  810.  
  811. save_name : str, optional
  812. For use when ``save_png`` is enabled.
  813. **MUST be specified when ``save_png`` is enabled.**
  814.  
  815. Returns
  816. -------
  817.  
  818. max_path : list
  819. Contains the paths corresponding to the longest lengths for
  820. each skeleton.
  821.  
  822. extremum : list
  823. Contains the starting and ending points of max_path
  824.  
  825. '''
  826. num = len(nodes)
  827.  
  828. # Initialize lists
  829. max_path = []
  830. extremum = []
  831. graphs = []
  832.  
  833. for n in range(num):
  834. G = nx.Graph()
  835. G.add_nodes_from(nodes[n])
  836. for i in edge_list[n]:
  837. G.add_edge(i[0], i[1], weight=i[2][1])
  838. paths = nx.shortest_path_length(G, weight='weight')
  839.  
  840. # Fix new API
  841. new_paths = dict()
  842. for path in paths:
  843. new_paths[path[0]] = path[1]
  844.  
  845. values = []
  846. node_extrema = []
  847. for i in new_paths.keys():
  848. j = max(new_paths[i].items(), key=operator.itemgetter(1))
  849. node_extrema.append((j[0], i))
  850. values.append(j[1])
  851. start, finish = node_extrema[values.index(max(values))]
  852. extremum.append([start, finish])
  853. max_path.append(nx.shortest_path(G, start, finish))
  854. graphs.append(G)
  855.  
  856. if verbose or save_png:
  857. if not skeleton_arrays:
  858. Warning("Must input skeleton arrays if verbose or save_png is"
  859. " enabled. No plots will be created.")
  860. elif save_png and save_name is None:
  861. Warning("Must give a save_name when save_png is enabled. No"
  862. " plots will be created.")
  863. else:
  864. # Check if skeleton_arrays is a list
  865. assert isinstance(skeleton_arrays, list)
  866. import matplotlib.pyplot as p
  867. if verbose:
  868. print("Filament: %s / %s" % (n + 1, num))
  869. p.subplot(1, 2, 1)
  870. p.imshow(skeleton_arrays[n], interpolation="nearest",
  871. origin="lower")
  872.  
  873. p.subplot(1, 2, 2)
  874. elist = [(u, v) for (u, v, d) in G.edges(data=True)]
  875. pos = nx.spring_layout(G)
  876. nx.draw_networkx_nodes(G, pos, node_size=200)
  877. nx.draw_networkx_edges(G, pos, edgelist=elist, width=2)
  878. nx.draw_networkx_labels(
  879. G, pos, font_size=10, font_family='sans-serif')
  880. p.axis('off')
  881.  
  882. if save_png:
  883. try_mkdir(save_name)
  884. p.savefig(os.path.join(save_name,
  885. save_name + "_longest_path_" + str(n) + ".png"))
  886. if verbose:
  887. p.show()
  888. p.clf()
  889.  
  890. return max_path, extremum, graphs
  891.  
  892.  
  893. def prune_graph(G, nodes, edge_list, max_path, labelisofil, branch_properties,
  894. length_thresh, relintens_thresh=0.2):
  895. '''
  896. Function to remove unnecessary branches, while maintaining connectivity
  897. in the graph. Also updates edge_list, nodes, branch_lengths and
  898. filbranches.
  899.  
  900. Parameters
  901. ----------
  902. G : list
  903. Contains the networkx Graph objects.
  904. nodes : list
  905. A complete list of all of the nodes. The other nodes lists have
  906. been separated as they are labeled differently.
  907. edge_list : list
  908. Contains the connectivity information for the graphs.
  909. max_path : list
  910. Contains the paths corresponding to the longest lengths for
  911. each skeleton.
  912. labelisofil : list
  913. Contains individual arrays for each skeleton where the
  914. branches are labeled and the intersections have been removed.
  915. branch_properties : dict
  916. Contains the lengths and intensities of all branches.
  917. length_thresh : int or float
  918. Minimum length a branch must be to be kept. Can be overridden if the
  919. branch is bright relative to the entire skeleton.
  920. relintens_thresh : float between 0 and 1, optional.
  921. Threshold for how bright the branch must be relative to the entire
  922. skeleton. Can be overridden by length.
  923.  
  924. Returns
  925. -------
  926. labelisofil : list
  927. Updated from input.
  928. edge_list : list
  929. Updated from input.
  930. nodes : list
  931. Updated from input.
  932. branch_properties : dict
  933. Updated from input.
  934. '''
  935.  
  936. num = len(labelisofil)
  937.  
  938. for n in range(num):
  939. degree = G[n].degree()
  940.  
  941. # To make compatible with new iterator API of networkx
  942. new_degree = dict()
  943. for d in degree:
  944. new_degree[d[0]] = d[1]
  945.  
  946. single_connect = [key for key in new_degree.keys() if new_degree[key] == 1]
  947.  
  948. delete_candidate = list(
  949. (set(nodes[n]) - set(max_path[n])) & set(single_connect))
  950.  
  951. if not delete_candidate: # Nothing to delete!
  952. continue
  953.  
  954. edge_candidates = [edge for edge in edge_list[n] if edge[
  955. 0] in delete_candidate or edge[1] in delete_candidate]
  956. intensities = [edge[2][3] for edge in edge_list[n]]
  957. for edge in edge_candidates:
  958. # In the odd case where a loop meets at the same intersection,
  959. # ensure that edge is kept.
  960. if isinstance(edge[0], str) & isinstance(edge[1], str):
  961. continue
  962. # If its too short and relatively not as intense, delete it
  963. length = edge[2][2]
  964. av_intensity = edge[2][3]
  965. if length < length_thresh \
  966. and (av_intensity / np.sum(intensities)) < relintens_thresh:
  967. edge_pts = np.where(labelisofil[n] == edge[2][0])
  968. labelisofil[n][edge_pts] = 0
  969. edge_list[n].remove(edge)
  970. nodes[n].remove(edge[1])
  971. branch_properties["length"][n].remove(length)
  972. branch_properties["intensity"][n].remove(av_intensity)
  973. branch_properties["number"][n] -= 1
  974.  
  975. return labelisofil, edge_list, nodes, branch_properties
  976.  
  977.  
  978. def extremum_pts(labelisofil, extremum, ends):
  979. '''
  980. This function returns the the farthest extents of each filament. This
  981. is useful for determining how well the shortest path algorithm has worked.
  982.  
  983. Parameters
  984. ----------
  985. labelisofil : list
  986. Contains individual arrays for each skeleton.
  987. extremum : list
  988. Contains the extents as determined by the shortest
  989. path algorithm.
  990. ends : list
  991. Contains the positions of each end point in eahch filament.
  992.  
  993. Returns
  994. -------
  995. extrem_pts : list
  996. Contains the indices of the extremum points.
  997. '''
  998.  
  999. num = len(labelisofil)
  1000. extrem_pts = []
  1001.  
  1002. for n in range(num):
  1003. per_fil = []
  1004. for i, j in ends[n]:
  1005. if labelisofil[n][i, j] == extremum[n][0] or labelisofil[n][i, j] == extremum[n][1]:
  1006. per_fil.append([i, j])
  1007. extrem_pts.append(per_fil)
  1008.  
  1009. return extrem_pts
  1010.  
  1011.  
  1012. def main_length(max_path, edge_list, labelisofil, interpts, branch_lengths,
  1013. img_scale, verbose=False, save_png=False, save_name=None):
  1014. '''
  1015. Wraps previous functionality together for all of the skeletons in the
  1016. image. To find the overall length for each skeleton, intersections are
  1017. added back in, and any extraneous pixels they bring with them are deleted.
  1018.  
  1019. Parameters
  1020. ----------
  1021. max_path : list
  1022. Contains the paths corresponding to the longest lengths for
  1023. each skeleton.
  1024. edge_list : list
  1025. Contains the connectivity information for the graphs.
  1026. labelisofil : list
  1027. Contains individual arrays for each skeleton where the
  1028. branches are labeled and the intersections have been removed.
  1029. interpts : list
  1030. Contains the pixels which belong to each intersection.
  1031. branch_lengths : list
  1032. Lengths of individual branches in each skeleton.
  1033. img_scale : float
  1034. Conversion from pixel to physical units.
  1035. verbose : bool, optional
  1036. Returns plots of the longest path skeletons.
  1037. save_png : bool, optional
  1038. Saves the plot made in verbose mode. Disabled by default.
  1039. save_name : str, optional
  1040. For use when ``save_png`` is enabled.
  1041. **MUST be specified when ``save_png`` is enabled.**
  1042.  
  1043. Returns
  1044. -------
  1045. main_lengths : list
  1046. Lengths of the skeletons.
  1047. longpath_arrays : list
  1048. Arrays of the longest paths in the skeletons.
  1049. '''
  1050.  
  1051. main_lengths = []
  1052. longpath_arrays = []
  1053.  
  1054. for num, (path, edges, inters, skel_arr, lengths) in \
  1055. enumerate(zip(max_path, edge_list, interpts, labelisofil,
  1056. branch_lengths)):
  1057.  
  1058. if len(path) == 1:
  1059. main_lengths.append(lengths[0] * img_scale)
  1060. skeleton = skel_arr # for viewing purposes when verbose
  1061. else:
  1062. skeleton = np.zeros(skel_arr.shape)
  1063.  
  1064. # Add edges along longest path
  1065. good_edge_list = [(path[i], path[i + 1])
  1066. for i in range(len(path) - 1)]
  1067. # Find the branches along the longest path.
  1068. for i in good_edge_list:
  1069. for j in edges:
  1070. if (i[0] == j[0] and i[1] == j[1]) or \
  1071. (i[0] == j[1] and i[1] == j[0]):
  1072. label = j[2][0]
  1073. skeleton[np.where(skel_arr == label)] = 1
  1074.  
  1075. # Add intersections along longest path
  1076. intersec_pts = []
  1077. for label in path:
  1078. try:
  1079. label = int(label)
  1080. except ValueError:
  1081. pass
  1082. if not isinstance(label, int):
  1083. k = 1
  1084. while zip(product_gen(string.ascii_uppercase),
  1085. [1] * k)[-1][0] != label:
  1086. k += 1
  1087. intersec_pts.extend(inters[k - 1])
  1088. skeleton[zip(*inters[k - 1])] = 2
  1089.  
  1090. # Remove unnecessary pixels
  1091. count = 0
  1092. while True:
  1093. for pt in intersec_pts:
  1094. # If we have already eliminated the point, continue
  1095. if skeleton[pt] == 0:
  1096. continue
  1097. skeleton[pt] = 0
  1098. lab_try, n = nd.label(skeleton, eight_con())
  1099. if n > 1:
  1100. skeleton[pt] = 1
  1101. else:
  1102. count += 1
  1103. if count == 0:
  1104. break
  1105. count = 0
  1106.  
  1107. main_lengths.append(skeleton_length(skeleton) * img_scale)
  1108.  
  1109. longpath_arrays.append(skeleton.astype(int))
  1110.  
  1111. if verbose or save_png:
  1112. if save_png and save_name is None:
  1113. Warning("Must give a save_name when save_png is enabled. No"
  1114. " plots will be created.")
  1115. import matplotlib.pyplot as p
  1116. if verbose:
  1117. print("Filament: %s / %s" % (num + 1, len(labelisofil)))
  1118.  
  1119. p.subplot(121)
  1120. p.imshow(skeleton, origin='lower', interpolation="nearest")
  1121. p.subplot(122)
  1122. p.imshow(labelisofil[num], origin='lower',
  1123. interpolation="nearest")
  1124.  
  1125. if save_png:
  1126. try_mkdir(save_name)
  1127. p.savefig(os.path.join(save_name,
  1128. save_name + "_main_length_" + str(num) + ".png"))
  1129. if verbose:
  1130. p.show()
  1131. p.clf()
  1132.  
  1133. return main_lengths, longpath_arrays
  1134.  
  1135.  
  1136. def find_extran(branches, labelfil):
  1137. '''
  1138. Identify pixels that are not necessary to keep the connectivity of the
  1139. skeleton. It uses the same labeling process as find_filpix. Extraneous
  1140. pixels tend to be those from former intersections, whose attached branch
  1141. was eliminated in the cleaning process.
  1142.  
  1143. Parameters
  1144. ----------
  1145. branches : list
  1146. Contains the number of branches in each skeleton.
  1147. labelfil : list
  1148. Contains arrays of the labeled versions of each skeleton.
  1149.  
  1150. Returns
  1151. -------
  1152. labelfil : list
  1153. Contains the updated labeled arrays with extraneous pieces
  1154. removed.
  1155. '''
  1156.  
  1157. initslices = []
  1158. initlist = []
  1159. shiftlist = []
  1160. sublist = []
  1161. extran = []
  1162. slices = []
  1163. vallist = []
  1164. shiftvallist = []
  1165. subvallist = []
  1166. subslist = []
  1167. pix = []
  1168. filpix = []
  1169.  
  1170. for k in range(1, branches + 1):
  1171. x, y = np.where(labelfil == k)
  1172. for i in range(len(x)):
  1173. if x[i] < labelfil.shape[0] - 1 and y[i] < labelfil.shape[1] - 1:
  1174. pix.append((x[i], y[i]))
  1175. initslices.append(np.array([[labelfil[x[i] - 1, y[i] + 1],
  1176. labelfil[x[i], y[i] + 1],
  1177. labelfil[x[i] + 1, y[i] + 1]],
  1178. [labelfil[x[i] - 1, y[i]], 0,
  1179. labelfil[x[i] + 1, y[i]]],
  1180. [labelfil[x[i] - 1, y[i] - 1],
  1181. labelfil[x[i], y[i] - 1],
  1182. labelfil[x[i] + 1, y[i] - 1]]]))
  1183.  
  1184. filpix.append(pix)
  1185. slices.append(initslices)
  1186. initslices = []
  1187. pix = []
  1188.  
  1189. for i in range(len(slices)):
  1190. for k in range(len(slices[i])):
  1191. initlist.append([slices[i][k][0, 0],
  1192. slices[i][k][0, 1],
  1193. slices[i][k][0, 2],
  1194. slices[i][k][1, 2],
  1195. slices[i][k][2, 2],
  1196. slices[i][k][2, 1],
  1197. slices[i][k][2, 0],
  1198. slices[i][k][1, 0]])
  1199. vallist.append(initlist)
  1200. initlist = []
  1201.  
  1202. for i in range(len(slices)):
  1203. for k in range(len(slices[i])):
  1204. shiftlist.append(shifter(vallist[i][k], 1))
  1205. shiftvallist.append(shiftlist)
  1206. shiftlist = []
  1207.  
  1208. for k in range(len(slices)):
  1209. for i in range(len(vallist[k])):
  1210. for j in range(8):
  1211. sublist.append(
  1212. int(vallist[k][i][j]) - int(shiftvallist[k][i][j]))
  1213. subslist.append(sublist)
  1214. sublist = []
  1215. subvallist.append(subslist)
  1216. subslist = []
  1217.  
  1218. for k in range(len(slices)):
  1219. for l in range(len(filpix[k])):
  1220. x = [j for j, y in enumerate(subvallist[k][l]) if y == k + 1]
  1221. y = [j for j, z in enumerate(vallist[k][l]) if z == k + 1]
  1222. if len(x) == 0:
  1223. labelfil[filpix[k][l][0], filpix[k][l][1]] = 0
  1224. if len(x) == 1:
  1225. if len(y) >= 2:
  1226. extran.append(filpix[k][l])
  1227. labelfil[filpix[k][l][0], filpix[k][l][1]] = 0
  1228. # if len(extran) >= 2:
  1229. # for i in extran:
  1230. # for j in extran:
  1231. # if i != j:
  1232. # if distance(i[0], j[0], i[1], j[1]) == np.sqrt(2.0):
  1233. # proximity = [(i[0], i[1] - 1),
  1234. # (i[0], i[1] + 1),
  1235. # (i[0] - 1, i[1]),
  1236. # (i[0] + 1, i[1]),
  1237. # (i[0] - 1, i[1] + 1),
  1238. # (i[0] + 1, i[1] + 1),
  1239. # (i[0] - 1, i[1] - 1),
  1240. # (i[0] + 1, i[1] - 1)]
  1241. # match = set(filpix[k]) & set(proximity)
  1242. # if len(match) > 0:
  1243. # for z in match:
  1244. # labelfil[z[0], z[1]] = 0
  1245. return labelfil
  1246.  
  1247.  
  1248. def in_ipynb():
  1249. try:
  1250. cfg = get_ipython().config
  1251. if cfg['IPKernelApp']['parent_appname'] == 'ipython-notebook':
  1252. return True
  1253. else:
  1254. return False
  1255. except NameError:
  1256. return False
  1257.  
  1258.  
  1259. def make_final_skeletons(labelisofil, inters, verbose=False, save_png=False,
  1260. save_name=None):
  1261. '''
  1262. Creates the final skeletons outputted by the algorithm.
  1263.  
  1264. Parameters
  1265. ----------
  1266. labelisofil : list
  1267. List of labeled skeletons.
  1268. inters : list
  1269. Positions of the intersections in each skeleton.
  1270. verbose : bool, optional
  1271. Enables plotting of the final skeleton.
  1272. save_png : bool, optional
  1273. Saves the plot made in verbose mode. Disabled by default.
  1274. save_name : str, optional
  1275. For use when ``save_png`` is enabled.
  1276. **MUST be specified when ``save_png`` is enabled.**
  1277.  
  1278. Returns
  1279. -------
  1280. filament_arrays : list
  1281. List of the final skeletons.
  1282. '''
  1283.  
  1284. filament_arrays = []
  1285.  
  1286. for n, (skel_array, intersec) in enumerate(zip(labelisofil, inters)):
  1287. copy_array = np.zeros(skel_array.shape, dtype=int)
  1288.  
  1289. for inter in intersec:
  1290. for pts in inter:
  1291. x, y = pts
  1292. copy_array[x, y] = 1
  1293.  
  1294. copy_array[np.where(skel_array >= 1)] = 1
  1295.  
  1296. cleaned_array = find_extran(1, copy_array)
  1297.  
  1298. filament_arrays.append(cleaned_array)
  1299.  
  1300. if verbose or save_png:
  1301. if save_png and save_name is None:
  1302. Warning("Must give a save_name when save_png is enabled. No"
  1303. " plots will be created.")
  1304.  
  1305. plt.clf()
  1306. plt.imshow(cleaned_array, origin='lower', interpolation='nearest')
  1307.  
  1308. if save_png:
  1309. try_mkdir(save_name)
  1310. plt.savefig(os.path.join(save_name,
  1311. save_name+"_final_skeleton_"+str(n)+".png"))
  1312. if verbose:
  1313. plt.show()
  1314. if in_ipynb():
  1315. plt.clf()
  1316.  
  1317. return filament_arrays
  1318.  
  1319.  
  1320. def recombine_skeletons(skeletons, offsets, orig_size, pad_size,
  1321. verbose=False):
  1322. '''
  1323. Takes a list of skeleton arrays and combines them back into
  1324. the original array.
  1325.  
  1326. Parameters
  1327. ----------
  1328. skeletons : list
  1329. Arrays of each skeleton.
  1330. offsets : list
  1331. Coordinates where the skeleton arrays have been sliced from the
  1332. image.
  1333. orig_size : tuple
  1334. Size of the image.
  1335. pad_size : int
  1336. Size of the array padding.
  1337. verbose : bool, optional
  1338. Enables printing when a skeleton array needs to be resized to fit
  1339. into the image.
  1340.  
  1341. Returns
  1342. -------
  1343. master_array : numpy.ndarray
  1344. Contains all skeletons placed in their original positions in the image.
  1345. '''
  1346.  
  1347. num = len(skeletons)
  1348.  
  1349. master_array = np.zeros(orig_size)
  1350. for n in range(num):
  1351. x_off, y_off = offsets[n][0] # These are the coordinates of the bottom
  1352. # left in the master array.
  1353. x_top, y_top = offsets[n][1]
  1354.  
  1355. # Now check if padding will put the array outside of the original array
  1356. # size
  1357. excess_x_top = x_top - orig_size[0]
  1358.  
  1359. excess_y_top = y_top - orig_size[1]
  1360.  
  1361. copy_skeleton = copy.copy(skeletons[n])
  1362.  
  1363. size_change_flag = False
  1364.  
  1365. if excess_x_top > 0:
  1366. copy_skeleton = copy_skeleton[:-excess_x_top, :]
  1367. size_change_flag = True
  1368.  
  1369. if excess_y_top > 0:
  1370. copy_skeleton = copy_skeleton[:, :-excess_y_top]
  1371. size_change_flag = True
  1372.  
  1373. if x_off < 0:
  1374. copy_skeleton = copy_skeleton[-x_off:, :]
  1375. x_off = 0
  1376. size_change_flag = True
  1377.  
  1378. if y_off < 0:
  1379. copy_skeleton = copy_skeleton[:, -y_off:]
  1380. y_off = 0
  1381. size_change_flag = True
  1382.  
  1383. if verbose & size_change_flag:
  1384. print("REDUCED FILAMENT %s/%s TO FIT IN ORIGINAL ARRAY" % (n, num))
  1385.  
  1386. x, y = np.where(copy_skeleton >= 1)
  1387. for i in range(len(x)):
  1388. master_array[x[i] + x_off, y[i] + y_off] = 1
  1389.  
  1390. return master_array
  1391.  
  1392.  
  1393. if __name__ == "__main__":
  1394. # 2D numpy array with image
  1395. image = None
  1396. # rms of the image
  1397. rms = None
  1398. data = image.copy()
  1399. from scipy.ndimage.filters import gaussian_filter
  1400. data = gaussian_filter(data, 5)
  1401. mask = data < 3. * rms
  1402. data[mask] = 0
  1403. data[~mask] = 1
  1404.  
  1405. skel, distance = medial_axis(data, return_distance=True)
  1406. dist_on_skel = distance * skel
  1407.  
  1408. # Plot area and skeleton
  1409. fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4), sharex=True, sharey=True,
  1410. subplot_kw={'adjustable': 'box-forced'})
  1411. ax1.imshow(data, cmap=plt.cm.gray, interpolation='nearest')
  1412. ax1.axis('off')
  1413. ax2.imshow(dist_on_skel, cmap=plt.cm.spectral, interpolation='nearest')
  1414. ax2.contour(data, [0.5], colors='w')
  1415. ax2.axis('off')
  1416. fig.tight_layout()
  1417. plt.show()
  1418. fig.savefig('skeleton_orig.png')
  1419. plt.close()
  1420.  
  1421. isolated_filaments, num, offsets = isolateregions(skel)
  1422.  
  1423. interpts, hubs, ends, filbranches, labeled_fil_arrays =\
  1424. pix_identify(isolated_filaments, num)
  1425.  
  1426. branch_properties = init_lengths(labeled_fil_arrays, filbranches, offsets, data)
  1427. branch_properties["number"] = filbranches
  1428.  
  1429. edge_list, nodes = pre_graph(labeled_fil_arrays, branch_properties, interpts,
  1430. ends)
  1431.  
  1432. max_path, extremum, G = longest_path(edge_list, nodes, verbose=True,
  1433. save_png=False,
  1434. skeleton_arrays=labeled_fil_arrays)
  1435.  
  1436. updated_lists = prune_graph(G, nodes, edge_list, max_path, labeled_fil_arrays,
  1437. branch_properties, length_thresh=20,
  1438. relintens_thresh=0.1)
  1439. labeled_fil_arrays, edge_list, nodes, branch_properties = updated_lists
  1440.  
  1441. filament_extents = extremum_pts(labeled_fil_arrays, extremum, ends)
  1442.  
  1443. length_output = main_length(max_path, edge_list, labeled_fil_arrays, interpts,
  1444. branch_properties["length"], 1, verbose=True)
  1445. filament_arrays = {}
  1446. lengths, filament_arrays["long path"] = length_output
  1447. lengths = np.asarray(lengths)
  1448.  
  1449. filament_arrays["final"] = make_final_skeletons(labeled_fil_arrays, interpts,
  1450. verbose=True)
  1451.  
  1452. skeleton = recombine_skeletons(filament_arrays["final"], offsets, data.shape,
  1453. 0, verbose=True)
  1454. skeleton_longpath = recombine_skeletons(filament_arrays["long path"], offsets,
  1455. data.shape, 1)
  1456. skeleton_longpath_dist = skeleton_longpath * distance
  1457.  
  1458. # Plot area and skeleton
  1459. fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4), sharex=True, sharey=True,
  1460. subplot_kw={'adjustable': 'box-forced'})
  1461. ax1.imshow(data, cmap=plt.cm.gray, interpolation='nearest')
  1462. ax1.axis('off')
  1463. ax2.imshow(skeleton_longpath_dist, cmap=plt.cm.spectral,
  1464. interpolation='nearest')
  1465. ax2.contour(data, [0.5], colors='w')
  1466. ax2.axis('off')
  1467. fig.tight_layout()
  1468. plt.savefig('skeleton.png')
  1469. plt.show()
  1470. plt.close()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement